<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_洼则盈</title><subtitle type="text">关注软件开发中的过程、架构、人……</subtitle><id>http://feed.cnblogs.com/blog/u/56770/rss</id><updated>2011-11-26T03:11:41Z</updated><author><name>菜阿彬</name><uri>http://www.cnblogs.com/CaiAbin/</uri></author><generator>feed.cnblogs.com</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/CaiAbin/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/56770/rss"/><entry><id>http://www.cnblogs.com/CaiAbin/archive/2011/11/24/2262323.html</id><title type="text">我的常识——程序员应该“道”“术”双修</title><summary type="text">“道”和“术”分别是什么？设计模式是“道”还是“术”？你写的或者读的博客，有多少是关于“术”的？有多少是关于“道”的？</summary><published>2011-11-24T14:16:00Z</published><updated>2011-11-24T14:16:00Z</updated><author><name>菜阿彬</name><uri>http://www.cnblogs.com/CaiAbin/</uri></author><link rel="alternate" href="http://www.cnblogs.com/CaiAbin/archive/2011/11/24/2262323.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/CaiAbin/archive/2011/11/24/2262323.html"/><content type="html">&lt;h3 style="background-color: #a3bb50;"&gt;&lt;span style="color: #ffffff;"&gt;什么是&amp;ldquo;道&amp;rdquo;和&amp;ldquo;术&amp;rdquo;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;ldquo;术&amp;rdquo;是工具，&amp;ldquo;道&amp;rdquo;是工具背后的理论，想法。举几个例子：&lt;/p&gt;&lt;p&gt;1，Entity Framework，Nhibernate，就是&amp;ldquo;术&amp;rdquo;，这些工具（框架）背后的ORMapping思想的来源，它要解决的问题，Data Access Layer在架构中的地位（怎么实现Business Logic Layer对DAL的解耦），Unit Of Work模式的含义&amp;hellip;&amp;hellip;等等，就是&amp;ldquo;道&amp;rdquo;；&lt;/p&gt;&lt;p&gt;2，Asp.net MVC框架是&amp;ldquo;术&amp;rdquo;，MVC这个Pattern，它跟MVP，MVVM的关系，他们的缘起，来龙去脉，用途与目的，在整个架构中的地位（很多人甚至不知道MVC模式只是展现层的模式）、与其它逻辑层的配合&amp;hellip;&amp;hellip;等等，就是&amp;ldquo;道&amp;rdquo;。&lt;/p&gt;&lt;h3 style="background-color: #a3bb50;"&gt;&lt;span style="color: #ffffff;"&gt;&amp;ldquo;道&amp;rdquo;和&amp;ldquo;术&amp;rdquo;，应该更重视哪个？&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;以道统术，以术得道&amp;mdash;&amp;mdash;用浅显的话来说，理论指导实践，实践反过来又会影响和升华理论。我们程序员在学习的过程中，总是需要在&amp;ldquo;写点代码&amp;mdash;&amp;mdash;领悟一点东西&amp;mdash;&amp;mdash;继续写代码&amp;mdash;&amp;mdash;继续领悟&amp;rdquo;的一个循环中，这也是一个反馈系统。&lt;/p&gt;&lt;p&gt;可问题是：太多的程序员&amp;ldquo;道&amp;rdquo;与&amp;ldquo;术&amp;rdquo;严重偏科，他们热衷于工具的使用，却不知道工具被发明出来是为了解决什么问题的；他们可以用代码解决很多问题，却在解决完之后都不知道真正的问题是什么&amp;mdash;&amp;mdash;这也是此文的目的，我很想对这样的程序员强调&amp;ldquo;道&amp;rdquo;的重要性。&lt;/p&gt;&lt;p&gt;以上面的例子来说，重&amp;ldquo;术&amp;rdquo;不重&amp;ldquo;道&amp;rdquo;的人，会把Entity Framework框架，MVC框架用的很熟练，并且也能用它们完成所有的任务，可是，会用的很不好&amp;mdash;&amp;mdash;代码的耦合很高，设计很混乱，代码可读性差，可维护性差，可扩展性差，需求一改，到处都得改，过几个月，连自己都看不懂自己的代码了，有一天若要维护这样的人写的代码，就想骂娘，同时自己不断生产出让别人想骂娘的代码。&lt;/p&gt;&lt;p&gt;这种人工作5年后出去说自己有5年工作经验，&lt;span style="color: #0000ff;"&gt;其实只是1年工作经验重复了5遍而已&lt;/span&gt;。&amp;nbsp;&lt;/p&gt;&lt;h3 style="background-color: #a3bb50;"&gt;&lt;span style="color: #ffffff;"&gt;&amp;ldquo;术&amp;rdquo;不重要吗？&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;要说明的是，我绝不否认&amp;ldquo;术&amp;rdquo;的重要。甚至对于初学者来说，&amp;ldquo;术&amp;rdquo;更重要，它是我们赖以生存的东西，并且，它能帮助我们对&amp;ldquo;道&amp;rdquo;有更深的理解，或者说，没有经过自己&amp;ldquo;术&amp;rdquo;加以验证的&amp;ldquo;道&amp;rdquo;，很可能只是&amp;ldquo;半瓶子水&amp;rdquo;。古语说&amp;ldquo;纸上得来终觉浅，绝知此事要躬行&amp;rdquo;，说的就是这个理。&lt;/p&gt;&lt;p&gt;&amp;ldquo;术&amp;rdquo;很重要，但不能仅止于&amp;ldquo;术&amp;rdquo;，应该试图掌握工具背后的东西，如果你知道了ORMapping、DAL那一套，哪天到新公司要用NHibernate，或者要用NoSql，都完全没有障碍，所需要的只是一点点的上手时间和google而已。这个时候才是&lt;span style="color: #0000ff;"&gt;&amp;ldquo;技术为我所用&amp;rdquo;，而不是&amp;ldquo;我为技术所累&amp;rdquo;&lt;/span&gt;。&lt;/p&gt;&lt;h3 style="background-color: #a3bb50;"&gt;&lt;span style="color: #ffffff;"&gt;顺便谈谈面试&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我在面试别人时有个基本的原则，除了考察他的学习能力、态度、技术热情、交流能力以外，在技术方面，是比较注重他对&amp;ldquo;道&amp;rdquo;的理解的，尤其是招聘Sr. Developer时。&lt;/p&gt;&lt;p&gt;也许有人会说，如此强调&amp;ldquo;道&amp;rdquo;的作用，是不是太不靠谱了？万一那个人谈起来头头是道，写起代码一无是处，怎么办？其实不会这样，真正对&amp;ldquo;道&amp;rdquo;说起来头头是道的人，他对&amp;ldquo;道&amp;rdquo;的理解都必须是建立在大量的&amp;ldquo;术&amp;rdquo;的经验上的。而且，若要招好的程序员，&lt;span style="color: #0000ff;"&gt;我一直希望在面试时有&amp;ldquo;结对编程&amp;rdquo;的阶段&lt;/span&gt;，但同样，在&amp;ldquo;结对编程&amp;rdquo;时，我会考察他对代码的理解&amp;mdash;&amp;mdash;可测性，设计思想，做事方式，等等，而不是考察他的代码能不能解决问题（即是&amp;ldquo;术&amp;rdquo;）。&lt;/p&gt;&lt;p&gt;也许又有人说，能解决问题就是王道，不管黑猫白猫，能抓到老鼠就是好猫，能解决问题不就行了吗？对这样的见解我只想说：你愿意做个低级码农我不拦着，你也别拦着我朝高级码农的方向努力。&lt;/p&gt;&lt;h3 style="background-color: #a3bb50;"&gt;&lt;span style="color: #ffffff;"&gt;设计模式是&amp;ldquo;道&amp;rdquo;还是&amp;ldquo;术&amp;rdquo;？&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;前面已经说了，道术相辅相成。但有时候，甚至很难区分什么是道，什么是术。&lt;/p&gt;&lt;p&gt;比如，设计模式，这个博客园的长青博文主题，每隔一段时间都会冒出来几篇，它是&amp;ldquo;道&amp;rdquo;，还是&amp;ldquo;术&amp;rdquo;？&lt;/p&gt;&lt;p&gt;大概两年前，我拿这个问题问我所在团队的所有同事，他们大部分回答这是&amp;ldquo;道&amp;rdquo;。&lt;/p&gt;&lt;p&gt;我不能说这个答案是错的，四人帮的《设计模式》一书，已成经典，人人必看。甚至去面试时，你不谈点设计模式的东西，你都不好意思说自己是程序员。我就碰到过几次这样的面试者，说实话水平一般，但也一定要说上一点设计模式的东西，来抬高自己的身价。&lt;/p&gt;&lt;p&gt;现实中也是，很多程序员为了显示自己的&amp;ldquo;设计能力&amp;rdquo;，让他写一个功能时，他先想好这边可以用几个&amp;ldquo;设计模式&amp;rdquo;？这边来个工厂，那边来个单例（面试时被问到&amp;ldquo;设计模式&amp;rdquo;时，最常用的两个必杀答案），再来个访问者模式，成了。&lt;span style="color: #0000ff;"&gt;&amp;mdash;&amp;mdash;这种使用设计模式的方式错了&lt;/span&gt;。&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;在真正好的程序员心中，设计模式是&amp;ldquo;术&amp;rdquo;，设计模式背后的用意才是&amp;ldquo;道&amp;rdquo;。&lt;/span&gt;为什么要用某个模式，是为了解决什么问题？紧耦合？可测性差？扩展性差？他们写代码时，心中已无设计模式，用心设计、简单设计；在可测性、可扩展性和复杂程度之间做巧妙的权衡取舍；当他们写完代码，已经不知不觉合理地使用了若干设计模式。&amp;mdash;&amp;mdash;&lt;span style="color: #0000ff;"&gt;其实我这句话也说错了，代码永远没有&amp;ldquo;写完&amp;rdquo;的一天，他们会不断重构它，重构出设计模式，或者重构掉设计模式。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;回头看，如果把设计模式看成&amp;ldquo;道&amp;rdquo;，我也不反对，但为什么说前面那种使用设计模式的方式还是错了？因为那是在使用&amp;ldquo;术&amp;rdquo;的方式来对待&amp;ldquo;道&amp;rdquo;。&lt;/p&gt;&lt;h3 style="background-color: #a3bb50;"&gt;&lt;span style="color: #ffffff;"&gt;结语&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&amp;ldquo;道&amp;rdquo;&amp;ldquo;术&amp;rdquo;双修。就我观察的程序员现状来看，怎么强调&amp;ldquo;道&amp;rdquo;都不过分。多用我们聪明的脑袋来领悟&amp;ldquo;道&amp;rdquo;吧，把很多&amp;ldquo;术&amp;rdquo;的问题交给google。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/CaiAbin/aggbug/2262323.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2011/11/24/2262323.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/CaiAbin/archive/2011/11/12/2246551.html</id><title type="text">我的常识—— Do it early, do it often, do it automatically</title><summary type="text">1，不要完全依赖人来测试我们的代码——用代码来测试我们的代码。2，不要完全依赖人来验证我们系统的集成——用自动化脚本，用持续集成软件。3，不要依赖人工一次性的劳动来确保软件的正确——用分而治之、迭代开发来“反复确保”。4，不要等软件腐化后再来“重写”软件——时时刻刻“重构”它。5，不要期待设计/架构可以一步到位——时时刻刻进化它。Do it often。</summary><published>2011-11-12T13:02:00Z</published><updated>2011-11-12T13:02:00Z</updated><author><name>菜阿彬</name><uri>http://www.cnblogs.com/CaiAbin/</uri></author><link rel="alternate" href="http://www.cnblogs.com/CaiAbin/archive/2011/11/12/2246551.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/CaiAbin/archive/2011/11/12/2246551.html"/><content type="html">&lt;h3 style="background-color: #a3bb50;"&gt;&lt;span style="color: #ffffff;"&gt;缘起&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;上篇博客转眼已是半年前的了，最近有两个原因，促使我再次写博：&lt;/p&gt;&lt;p&gt;一是深深感到，在软件开发行业，有很多我认为已经是&amp;ldquo;常识性&amp;rdquo;的东西，可是对很多团队却完全没有体会。敏捷软件开发已走过十年时间，可是仍未普及，我觉得一个原因正在于此：很多&amp;ldquo;最佳实践&amp;rdquo;，它背后的原理，仍不被很多团队理解。反过来说，只有行业把这些东西当做&amp;ldquo;常识&amp;rdquo;了，敏捷才会真正的普及开来。&lt;/p&gt;&lt;p&gt;这些&amp;ldquo;常识性&amp;rdquo;的东西，往往很小。很小的实践，背后却隐藏着一种价值观。作为个人来说，如果能跟拥有共同常识、共同价值观的人共事，无疑会让每天的工作更加愉快。可是正因为它&amp;ldquo;小&amp;rdquo;，导致我一直没有动力把它们写下来，直到前几天参加上海的敏捷沙龙，聆听了&lt;a href="http://www.danielteng.com" target="_blank"&gt;Daniel Teng&lt;/a&gt;的演讲《Survival》。&lt;/p&gt;&lt;p&gt;这个主题演讲非常好，讲的是个人和组织怎么在当今信息爆炸的年代生存下来。其中有一个观点：个人在自己的知识更新过程中，要保持&amp;ldquo;habit to ship&amp;rdquo;&amp;mdash;&amp;mdash;如同软件开发中的&amp;ldquo;持续集成&amp;rdquo;/&amp;ldquo;持续交付&amp;rdquo;，&lt;span style="color: #800080;"&gt;&lt;span style="color: #0000ff;"&gt;个人所学的知识也要经常&amp;ldquo;交付出去&lt;/span&gt;&amp;rdquo;&lt;/span&gt;，&amp;ldquo;交付&amp;rdquo;的形式可以是写博客、可以是做演讲&amp;hellip;&amp;hellip;目的是一样的：&lt;span style="color: #0000ff;"&gt;1是寻求反馈；2是小步伐前进，积少成多。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;很有趣的是Daniel的这个演讲所用的PPT，在他演讲时并未完成。他的演讲本身就在实践&amp;ldquo;ship it&amp;rdquo;的思想&amp;mdash;&amp;mdash;不必等自己的知识体系很完整了，才能去给别人讲（知识爆炸的年代，这样太没有效率了），有了想法，就可以&amp;ldquo;交付&amp;rdquo;给社区，从社区获得反馈，再做下一步学习的调整，避免方向上的错误。&lt;/p&gt;&lt;p&gt;这就是促使我再次写博的第二个原因。&amp;nbsp;&lt;/p&gt;&lt;h3 style="background-color: #a3bb50;"&gt;&lt;span style="color: #ffffff;"&gt;说明&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我希望把&amp;ldquo;我的常识&amp;rdquo;写成一个系列，把我认为的在软件开发中的常识一个个写出来，并尽量阐明背后的价值观。有几点想说明：&lt;/p&gt;&lt;p&gt;1， 唯一不变的东西就是变化，&amp;ldquo;常识&amp;rdquo;也会变。我所写的，仅代表我目前的认识水平。&lt;/p&gt;&lt;p&gt;2， 我所写的，不代表全是对的，尤其是不同的团队，有不同的&amp;ldquo;context&amp;rdquo;，没有放之四海而皆准的东西。因为希望能从社区得到反馈的同时，也希望能抛砖引玉。&amp;nbsp;&lt;/p&gt;&lt;h3 style="background-color: #a3bb50;"&gt;&lt;span style="color: #ffffff;"&gt;正文&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这是这个系列的第一篇，其实主题已经在上面的&amp;ldquo;缘起&amp;rdquo;里体现了，这篇的正文只举几个或真实或假想的案例来说明。&lt;/p&gt;&lt;h4 style="background-color: #bbb;"&gt;&lt;span style="color: #ffffff;"&gt;案例一&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;A团队在进行一个预计3个月后上线的小项目，项目经理排的计划是2个月后完成开发，然后半个月Alpha测试，半个月Beta测试。项目很小，需求很简单，似乎没什么风险。2个月后顺利进入Alpha测试，结果发现bug很多，有些细节没有达到客户的要求。半个月很快就要过去，于是大家加班修bug。&lt;/p&gt;&lt;p&gt;相信很多团队都经历过类似的上线前的鸡飞狗跳的日子。问题出在什么地方？&amp;mdash;&amp;mdash;团队把测试放到最后的阶段，并且只有一次。如果回到3个月前，项目经理可以做些什么改变呢？Do it often&amp;mdash;&amp;mdash;团队可以先完成一个小的Feature，然后让QA针对这个Feature进行测试；同时团队继续下一个Feature的开发&amp;hellip;&amp;hellip;如此循环。这其实就是&amp;ldquo;迭代开发&amp;rdquo;的概念了。&lt;/p&gt;&lt;p&gt;总结：如果一个团队需要使用&amp;ldquo;测试阶段&amp;rdquo;来对产品进行质量的保证，那么让这样的&amp;ldquo;测试阶段&amp;rdquo;多一些。&lt;/p&gt;&lt;h4 style="background-color: #bbb;"&gt;&lt;span style="color: #ffffff;"&gt;案例二&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;B团队在重写一个为期一年的中型应用软件，旧的应用软件已经成为遗留系统，维护成本太大，因此公司决定重写。除了要实现老系统的原有功能外，这个项目还有些特殊的需求：1，该系统跟其他几个系统有集成，重写后仍然需要支持。2， 新系统的数据库会有调整，但老系统的数据需要在新系统上线的时候，无缝地Migrate到新系统中。&lt;/p&gt;&lt;p&gt;团队在经历了几个月的开发后，终于完成了所有功能的开发。然后就进入了上线前的准备&amp;mdash;&amp;mdash;1， 把新系统部署到服务器；2， 配置好与其他系统的集成，并且进行测试；3， 把老系统的数据库备份一个出来，然后运行脚本进行数据库的升级，然后进行测试。&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;所有这一切都是手工完成。导致的问题是：上线前有很多繁琐的，让人精神紧绷的事情，完全靠团队里个人的能力和细心程度，稍有差错，就会导致真正上线时的混乱甚至失败。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;这其实不是个新鲜的问题，就是ThoughtWorks公司&lt;a href="http://book.douban.com/subject/4031959/" target="_blank"&gt;《软件开发沉思录》&lt;/a&gt;里提到的&amp;ldquo;最后一英里&amp;rdquo;的问题。&lt;/p&gt;&lt;p&gt;可以做些什么来改善这种状况，来减轻上线前的压力，增加上线的信心和可靠性呢？还是Do it often，如果能在每个月、甚至每天、甚至&amp;ldquo;持续集成&amp;rdquo;里的每次&amp;ldquo;代码check in&amp;rdquo;，都来演练一下上线前要做的所有这些事，那么到真正上线的时候，团队就几乎不会有任何额外的担心和压力了。&lt;/p&gt;&lt;p&gt;当然要做到这一点，&amp;ldquo;自动化&amp;rdquo;是不可或缺的。最理想的状态，一切都应该&amp;ldquo;一键完成&amp;rdquo;&amp;mdash;&amp;mdash;团队需要维护一系列脚本，团队成员只需要按一个键，就能完成&amp;ldquo;代码迁出&amp;mdash;&amp;mdash;build&amp;mdash;&amp;mdash;部署&amp;mdash;&amp;mdash;数据库升级&amp;mdash;&amp;mdash;集成测试&amp;rdquo;等整个过程。或者，至少，可以把最没信心、最复杂的环节单独拿出来做自动化。&lt;/p&gt;&lt;p&gt;总结：&lt;span style="color: #0000ff;"&gt;越是困难的事情，越是要do it early, do it often, do it automatically。&lt;/span&gt;&lt;/p&gt;&lt;h4 style="background-color: #bbb;"&gt;&lt;span style="color: #ffffff;"&gt;案例三&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;C团队在做一个新项目，在团队进入功能代码的编写前，架构师已经把架构搭好了。团队基于这个架构开始进行开发，可是随着代码越来越多，需求越来越复杂，大家发现，架构师的架构已经不能满足需求了。&lt;/p&gt;&lt;p&gt;于是团队向项目经理建议：需要时间来调整、完善框架，这样对开发更有利。项目经理的答案是：时间不允许了，我们需要把功能全部做完，赶紧发布1.0。架构的完善，以后再说。&lt;/p&gt;&lt;p&gt;结果导致什么问题呢：由于架构不够好，代码写的越来越乱，没有可测性，bug越来越多，在发布1.0之前，团队花在修bug的时间大概有4个月。&lt;/p&gt;&lt;p&gt;于是团队总结：&lt;span style="color: #0000ff;"&gt;如果不在前期花2个月的时间来改进代码的质量，那么就等着在后期花4个月的时间来修bug吧。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;可是团队其实还可以做更进一步的总结：&lt;span style="color: #0000ff;"&gt;为什么改善架构需要向项目经理要额外的时间？架构是什么？架构无非是一些大的设计，软件开发中无处没有设计，难道我们开发一个功能时，需要做一个设计，比如这边想用一个单例模式，也要向项目经理额外要时间吗？当然不用，写代码就是设计，架构无非是大一些的设计，架构不是什么特殊的东西。它可以也应该在编写Feature时不断的完善和演化。架构的演化是日常开发的一部分。提前有个架构是好的，然而日常的演化必不可少。这就是&lt;a href="http://lostechies.com/jimmybogard/2010/01/29/evolutionary-architecture/" target="_blank"&gt;&amp;ldquo;进化式架构&amp;rdquo;&lt;/a&gt;的概念。&lt;/span&gt;&lt;/p&gt;&lt;h3 style="background-color: #a3bb50;"&gt;&lt;span style="color: #ffffff;"&gt;结语&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;作为软件工程师，我们编写软件就是因为人脑的&lt;span style="color: #0000ff;"&gt;运算速度、精确程度、可重复性&lt;/span&gt;都不如电脑，因此编写软件，用电脑来帮忙干活。可是在编写软件的时候，为什么很多人会忽略让电脑来帮我们验证我的软件呢？&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;1，不要完全依赖人来测试我们的代码&amp;mdash;&amp;mdash;用代码来测试我们的代码。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;2，不要完全依赖人来验证我们系统的集成&amp;mdash;&amp;mdash;用自动化脚本，用持续集成软件。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;3，不要依赖人工一次性的劳动来确保软件的正确&amp;mdash;&amp;mdash;用分而治之、迭代开发来&amp;ldquo;反复确保&amp;rdquo;。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;4，不要等软件腐化后再来&amp;ldquo;重写&amp;rdquo;软件&amp;mdash;&amp;mdash;时时刻刻&amp;ldquo;重构&amp;rdquo;它。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;5，不要期待设计/架构可以一步到位&amp;mdash;&amp;mdash;时时刻刻进化它。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;do it often。&lt;/p&gt;&lt;h3 style="background-color: #a3bb50;"&gt;&lt;span style="color: #ffffff;"&gt;参考阅读&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Daniel Teng：&lt;a href="http://www.danielteng.com/2011/07/18/iterate/" target="_blank"&gt;我谈迭代&lt;/a&gt;&amp;nbsp;。迭代就是do it again and again。也是do it often的体现。&lt;/p&gt;&lt;p&gt;Matin Fowler：&lt;a href="http://martinfowler.com/bliki/OpportunisticRefactoring.html" target="_blank"&gt;有机会就重构&lt;/a&gt;。大师最新的关于重构的文章，教导我们：refactor it often。&lt;/p&gt;&lt;p&gt;刘未鹏：&lt;a href="http://mindhacks.cn/2009/02/15/why-you-should-start-blogging-now/" target="_blank"&gt;为什么你应该（从现在就开始）写博客&lt;/a&gt;。经常写博客。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/CaiAbin/aggbug/2246551.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2011/11/12/2246551.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/CaiAbin/archive/2011/05/09/2041578.html</id><title type="text">为什么要让我们的“领域模型”裸奔？（下）</title><summary type="text">上篇文章引起不少有价值的回复，我也学到不少东西，谢谢大家。 在此对上篇做下补充说明： 1，因本人毕业以来从事的项目全是业务逻辑复杂的企业应用软件，ERP，SCM，HRP，CRM……，这种系统，如Martin Fowler在PEAA一书中所说，是适合使用Domain Model的，上文和本篇讨论的都是基于这样的场景和前提。 2，正如一哥们回复中说的，天下没有绝对的东西，我们都在写随笔，不是写论文。这两篇文章只是提供一种看待问题的视角，看问题的视角多了，到了具体的项目，就会有更多的选择。 3，写上篇时没想到要分上下篇，导致整个上篇没有说明啥叫“裸奔”，不过从评论看，大部分人都读懂了：就是让“领域.</summary><published>2011-05-09T14:22:00Z</published><updated>2011-05-09T14:22:00Z</updated><author><name>菜阿彬</name><uri>http://www.cnblogs.com/CaiAbin/</uri></author><link rel="alternate" href="http://www.cnblogs.com/CaiAbin/archive/2011/05/09/2041578.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/CaiAbin/archive/2011/05/09/2041578.html"/><content type="html">&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2011/05/08/2040026.html"&gt;上篇文章&lt;/a&gt;引起不少有价值的回复，我也学到不少东西，谢谢大家。&lt;/p&gt;&lt;p&gt;在此对上篇做下补充说明：&lt;/p&gt;&lt;p&gt;1，因本人毕业以来从事的项目全是业务逻辑复杂的企业应用软件，ERP，SCM，HRP，CRM&amp;hellip;&amp;hellip;，这种系统，如Martin Fowler在PEAA一书中所说，是适合使用Domain Model的，上文和本篇讨论的都是基于这样的场景和前提。&lt;/p&gt;&lt;p&gt;2，正如一哥们回复中说的，天下没有绝对的东西，我们都在写随笔，不是写论文。这两篇文章只是提供一种看待问题的视角，看问题的视角多了，到了具体的项目，就会有更多的选择。&lt;/p&gt;&lt;p&gt;3，写上篇时没想到要分上下篇，导致整个上篇没有说明啥叫&amp;ldquo;裸奔&amp;rdquo;，不过从评论看，大部分人都读懂了：就是让&amp;ldquo;领域模型&amp;rdquo;不依赖于其它任何东西（如数据访问层）。&lt;/p&gt;&lt;p&gt;天气热了，实在不想下了班还鼓捣技术，不过想想还是一鼓作气写完拉倒。&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;逻辑依赖与物理依赖&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;上篇留下的问题是：&lt;strong&gt;&lt;span&gt;为什&lt;/span&gt;么&amp;ldquo;业务逻辑&amp;rdquo;要依赖于&amp;ldquo;存储技术&amp;rdquo;？为什么&amp;ldquo;目的&amp;rdquo;要依赖于&amp;ldquo;手段&amp;rdquo;？&lt;/strong&gt;&lt;strong&gt;　&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;其实&amp;ldquo;目的&amp;rdquo;依赖于&amp;ldquo;手段&amp;rdquo;并没有什么问题，但更准确的说法应该是&amp;ldquo;目的&amp;rdquo;受约束于&amp;ldquo;手段&amp;rdquo;，具体说就是&amp;ldquo;业务逻辑层&amp;rdquo;受约束于&amp;ldquo;数据存储层&amp;rdquo;，举个例子，如果使用NHibernate作为ORM框架，设计的&amp;ldquo;领域模型&amp;rdquo;一定是把所有属性都设置为virtual，为了迁就于NHibernate的LazyLoad实现技术。这种迁就或者依赖是无法消除的，然而这里说的是概念上或逻辑上的依赖。&lt;/p&gt;&lt;p&gt;如果到了具体实现上，仍然存在这种依赖，就成了物理上的依赖，简单地说就是BLL这个assembly会对DAL这个assembly有个引用。&lt;strong&gt;&lt;span style="color: #008000;"&gt;物理依赖有什么问题？&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;反馈延迟带来的伤害&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;先离题一下说说反馈。举个例子，我们拿着杯子去饮水机接水，随着水位的上升，我们知道何时应该停止，这就是眼睛看到水位后，大脑给出的反馈。如果反馈延迟（哪怕只延迟2秒）甚至根本没有反馈，会有什么后果？水溢出来了，大脑才反应过来，后果一定是手被烫到。&lt;/p&gt;&lt;p&gt;简单的例子可以说明反馈被延迟带来的危害。&lt;strong&gt;&lt;span style="color: #008000;"&gt;然而在软件开发中，很多团队不断地被延迟的反馈所反复蹂躏伤害&lt;/span&gt;&lt;/strong&gt;。此话怎讲呢？&lt;/p&gt;&lt;p&gt;举个例子吧，&amp;ldquo;代码即设计&amp;rdquo;，如果代码就是我们的设计，那么如何保证我们的设计正确？很多团队最常见的办法是人肉测试。把代码打包成软件，然后丢给测试人员甚至客户。在我经历过的一个瀑布式软件过程里，今天写好的代码，也许要一个月后才会到测试人员手中，半年后到客户手中，也就是说，外界对我们设计（代码）的验证和反馈周期，需要几个月之久。这是多么大的延迟，2秒延迟就会烫伤我们的手，几个月，我们伤的起吗？&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;如何加速反馈&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;这就是&amp;ldquo;迭代开发&amp;rdquo;被引入的一个理由：缩小反馈周期。一个迭代（常见的是2周）内必须把反馈圈给结束掉，也就是2周内完成一个Feature的需求分析、设计、代码、测试等所有环节。从这个角度出发，如果一个迭代里不能getting things done，那不叫迭代，那就叫&amp;ldquo;两周&amp;rdquo;。&lt;/p&gt;&lt;p&gt;对于一个Feature来说，两周的反馈周期是可以接受的，毕竟每两周有个功能点给客户看看，确保我们do the right thing，很不错了。&lt;/p&gt;&lt;p&gt;然而如何保证我们do things right（比如，设计和可维护性等等足够好）呢？还有，这两周做的正确的东西，如何保证随着功能的不断增加而不会在将来被破坏呢（答案：回归测试）？如果每两周都人肉回归以前做过的所有功能，那就需要太多QA了。&lt;/p&gt;&lt;p&gt;答案就是&lt;strong&gt;&lt;span style="color: #008000;"&gt;自动化测试&lt;/span&gt;&lt;/strong&gt;。&lt;strong&gt;&lt;span style="color: #008000;"&gt;Unit Test保证do things right；验收测试/集成测试来保证do right things。&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;自动化测试金字塔&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/61088/2011050921392965.png" /&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;如图，意思是什么呢？如果一个项目的所有自动化测试用例是100，那么最下面的Unit Test应该占80个左右，中间的集成测试占15个左右，上面的UI驱动的验收测试占5个左右。（还有个最上面的人肉测试，那是浮云:)）为啥呢？因为Unit Test的ROI（投资回报率）最高，它上手容易、运行快，UI驱动的验收测试的ROI最低，运行慢、维护成本高（因为UI是很易变的，UI一变，UI测试脚本就得改。）&lt;/p&gt;&lt;p&gt;所以一个团队如果要开始自动化测试，最好从Unit Test开始。而最应该写Unit Test的地方是哪个地方呢？毫无疑问，是我们的&amp;ldquo;目的层&amp;rdquo;&amp;mdash;&amp;mdash;&amp;ldquo;领域模型层&amp;rdquo;。&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;Persistence Ignorance&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;回到我们的问题，&amp;ldquo;领域模型层&amp;rdquo;对&amp;ldquo;数据存储层&amp;rdquo;有物理上的依赖，导致的不好的结果就是，很难写Unit Test。想象一下，有个Customer类，它的AddOrder（）方法里面调用了DAL层的东西，也就是连接了数据库，那我跑我的UT时也一定要连数据库。连数据库的UT那不叫UT。&lt;/p&gt;&lt;p&gt;怎么办呢？&amp;ldquo;依赖反转&amp;rdquo;，Inversion Of Control，IOC。具体做法是：本来BLL依赖于DAL，现在抽一个接口IDAL，让BLL依赖于IDAL，DAL从IDAL继承。从Assembly上来说，BLL和IDAL放到一个Assembly里，DAL放到另一个Assembly，那么DAL这个Assembly现在对BLL那个Assembly有个依赖了。&lt;span style="color: #008000;"&gt;&lt;strong&gt;&amp;mdash;&amp;mdash;这样，就把依赖给反转了&lt;/strong&gt;&lt;/span&gt;。然后通过Dependency Injection，在运行时把DAL作为IDAL的运行时实例，注入到BLL中。这就是IOC和DI的关系，他们其实不是一个东西，只不过很相关，有时就用IOC或DI泛指这项技术了。&lt;/p&gt;&lt;p&gt;BLL对DAL的依赖，从编译期延迟到了运行期，编译期对DAL没有依赖，只对IDAL有依赖，这就是Persistence Ignorance（不知的请google之）。&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;这有多重要？&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;BLL（领域模型）开始裸奔了，它对其它层没有依赖，我们可以为他写丰富的Unit Test，这有多重要？&lt;/p&gt;&lt;p&gt;每个unit test都用其方法名说明了我们的设计意图，甚至小片业务逻辑，比如&lt;span style="color: #008000;"&gt;&lt;strong&gt;有个测试用例，方法名叫&amp;ldquo;should_promote_to_VIP_when_customer_buying_platinum_card&amp;rdquo;，如果让你接手一个别人留下的代码，你不是很清楚里面的业务逻辑，你是愿意去看文档？还是愿意去看他留下的存储过程、或者100行又臭又长的方法？还是愿意看这样的一句话：&amp;ldquo;当客户买了白金卡后，应该把它提升为VIP&amp;rdquo;？&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;unit test的覆盖率足够高时，我们&lt;strong&gt;&lt;span style="color: #008000;"&gt;读完所有的unit test方法名（只是名字），我们已经了解了大部分的业务逻辑。&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;事实上，一个项目的维护成本往往是开发成本的四五倍甚至几十倍（越差的代码，这个比例越高）。另外大家也深有体会：读代码比写代码难。那么为了降低读代码或者维护别人/自己代码的痛苦（当老板的，降低维护成本意味着白花花的银子啊），有啥理由不让我们的&amp;ldquo;领域模型&amp;rdquo;裸奔呢？&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;丰满的领域模型裸奔着向我们呼啸而来&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;下图是敏捷宣言签署者之一的Alistair Cockburn的&lt;a href="http://alistair.cockburn.us/Hexagonal+architecture"&gt;Hexagonal Architecture&lt;/a&gt;，很精彩的图，留作参考资料，大家意会，不解释了。&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/61088/2011050922122833.jpg" /&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/CaiAbin/aggbug/2041578.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2011/05/09/2041578.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/CaiAbin/archive/2011/05/08/2040026.html</id><title type="text">为什么要让我们的“领域模型”裸奔？（上）</title><summary type="text">三层架构里，往往认为中间的业务逻辑层（BLL）依赖于最下面的数据存储层（DAL），可是每个应用系统的“业务逻辑”才是应用系统存在的理由，才是开发它的目的所在。而UI展现、数据库存储、Cache等都是为了实现“业务逻辑”这个目的所提供的手段，都有成熟的框架、模式可用，都可以是雷同的。那么为什么“业务逻辑”要依赖于“存储技术”？为什么“目的”要依赖于“手段”？</summary><published>2011-05-07T16:03:00Z</published><updated>2011-05-07T16:03:00Z</updated><author><name>菜阿彬</name><uri>http://www.cnblogs.com/CaiAbin/</uri></author><link rel="alternate" href="http://www.cnblogs.com/CaiAbin/archive/2011/05/08/2040026.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/CaiAbin/archive/2011/05/08/2040026.html"/><content type="html">&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;做不完的应用软件&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;我爸是个乡村小学教师，对我所从事的软件行业一无所知，但是他对我的工作稳定性表示怀疑：&amp;ldquo;你这做软件的，要是有一天软件做完了，你岂不是要失业了？&amp;rdquo;也许他想起了他作为老师的情况，教完一批学生，下一批又上来了，一茬一茬的。于是又问我：&amp;ldquo;你们是不是一个软件接着一个软件做？&amp;rdquo;我回答他：&amp;ldquo;不是，就一个软件，好几十个人得做好几年呢。&amp;rdquo;解释了很多次仍旧没有消除他的疑问：&amp;ldquo;你们做软件怎么会一直做下去？怎么没有个做完的时候呢？&amp;rdquo;。&lt;/p&gt;&lt;p&gt;如果他在通往张江的地铁上，知道有那么多我伤不起的IT同类们，他也许会更加迷惑。为什么如此庞大的程序员大军，日复一日年复一年地敲着代码，生产出无数的软件，可是他们不用担心失业？为什么需要那么多看上去类似的软件？为什么这些软件永远没有做完的那天？&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;应用软件独一无二的地方&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;答案其实很简单，我们做的每一套软件，都是为了解决某个领域的业务需求。&lt;span style="color: #008000;"&gt;&lt;strong&gt;而业务需求永远没有停止变化的一天，这就是为什么应用软件永远也做不完的原因。&lt;/strong&gt;&lt;/span&gt;我刚开始工作时，在一个小团队里为一家公司定制开发ERP，其实就是一个中小型的管理系统，在知道国内有用友金蝶在做ERP的时候，觉得很奇怪：有那么好的公司在做ERP，我们还有做的必要吗？客户怎么不去买用友金蝶呢？现在想来这想法很幼稚，因为每家公司的业务都是独一无二的，因此每个应用软件，即使都叫ERP，它也是独一无二的。&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;　&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;把独一无二的东西分离出去&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;想想我们为了构建一个应用软件起来，要做哪些事？最基本的三件是：Domain Business Logic、Presentation（UI展现）、Persistence(存数据库)。还有就是Authentication，Authorization，Cache等等。复杂的系统还包括：与其它系统的集成，提供Service（API）等等。&lt;/p&gt;&lt;p&gt;就说最基本的3件吧，稍微思考一下就会发现，&lt;strong&gt;&lt;span style="color: #008000;"&gt;只有Business Logic是独一无二的&lt;/span&gt;。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Presentation层是个重复重复再重复的事情，再怎么不同的应用，我们都可以用同一套工具来实现它：用Grid来展现多条记录；用Combobox来提供选择；用MVC模式来分离数据与展现&amp;hellip;&amp;hellip;框架应运而生。&lt;/p&gt;&lt;p&gt;Persistence也是个重复重复又重复的事情，关系型数据库，ORM框架，NoSql&amp;hellip;&amp;hellip;同样约定俗成。&lt;/p&gt;&lt;p&gt;所有的东西都可以找到框架，这就是为什么写应用软件，跟写游戏，或者写操作系统等比起来，是最没有技术含量的。&lt;/p&gt;&lt;p&gt;然和唯独&amp;ldquo;业务逻辑&amp;rdquo;没有框架。正是这&amp;ldquo;业务逻辑&amp;rdquo;，让每个应用软件区别于其它应用软件。因此&lt;span style="color: #008000;"&gt;&lt;strong&gt;我们决定要做一个应用软件，我们要做的就是实现客户的业务逻辑，这是唯一真正的目的。持久化，UI，Cache&amp;hellip;&amp;hellip;，都是手段。&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color: #0000ff;"&gt;如何实现业务逻辑？&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;span style="color: #000000;"&gt;现在知道了，我们做的每一个系统，都在为客户交付独一无二的业务价值。这就解决了程序员们&amp;ldquo;存在的意义&amp;rdquo;的哲学问题。那么如何实现业务逻辑呢？把客户的业务需求转化为解决方案的过程，就是设计的过程。因此，这个问题可以这样问：通过何种的设计，让客户的业务需求得到满足呢？这个问题很傻，因为显而易见是通过写代码实现啊，这不是常识吗？很可惜这不是常识，因为很多团队把设计写在设计文档里了。&lt;span style="color: #008000;"&gt;&lt;strong&gt;他们把写文档说成设计，把写代码说成实现。大错特错，代码是唯一的设计，MsBuild把代码build成exe才是实现！&lt;/strong&gt;&lt;/span&gt;用Unit Test确保自己的设计（即代码）正确反应了自己脑中的设计意图，用Integration Test来确保自己的设计（即代码）正确地满足客户的需求，有着这种认识和追求的程序员，是我想要共事的程序员。&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;span style="color: #000000;"&gt;想起来，台湾习惯把程序员说成&amp;ldquo;设计师&amp;rdquo;，是更贴切的。把代码当做设计的程序员是幸福的，比较一下大楼的设计人员，当他们设计好的方案（设计图纸）一旦被建筑工人们开始&amp;ldquo;实现&amp;rdquo;的时候，他们的设计几乎就不能再改了，因为&amp;ldquo;实现&amp;rdquo;的成本太昂贵了。而作为软件设计师，我们的&amp;ldquo;建筑工人&amp;rdquo;MsBuild包工头以及它的团队（CPU，RAM等小兵）是多么的廉价和高效，几秒钟就把我们的设计给实现了。这&lt;strong&gt;&lt;span style="color: #008000;"&gt;就是我们能够利用&amp;ldquo;重构&amp;rdquo;技术的理由。&lt;/span&gt;&lt;/strong&gt;（想象一下如果大楼设计人员也这么说：&amp;ldquo;你们先按照这方案盖起来，我看效果，然后再调整（重构）&amp;rdquo;&amp;hellip;&amp;hellip;）&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;span style="color: #000000;"&gt;与传统行业的设计师相比，我们软件设计师能得到的反馈更快更多（因为我们面对的是电脑），这就是我们幸福的地方，也是我们应该利用的地方。准备写个&amp;ldquo;软件开发中的反馈系统&amp;rdquo;系列文章阐述此问题。&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style="color: #0000ff;"&gt;在哪里实现业务逻辑？&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;span style="color: #000000;"&gt;用代码实现业务逻辑，那么，在代码的哪个地方呢？有很多个地方：&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;span style="color: #000000;"&gt;1，前些年很常见如今被人很鄙视的一种是，存储过程。这种曾经非常流行的技术，自然有它产生的原因。存储过程是什么？是数据库里的东西，而且是关系型数据库里的东西。很多人的思维是这样的：当他试图理解一个业务逻辑时，他心里想的是表以及表与表之间的关系，这就是&lt;span style="color: #008000;"&gt;&lt;strong&gt;Database-Driven&lt;/strong&gt;&lt;/span&gt;逻辑，在这种逻辑下，把业务逻辑写在存储过程里，是很自然的事情。如果把思维切换到&lt;span style="color: #008000;"&gt;&lt;strong&gt;Domain-Driven&lt;/strong&gt;&lt;/span&gt;的模式中：业务逻辑是我的核心，持久化只是一个辅助的手段，我可以用关系型数据库，也可以用NoSql，而NoSql根本没有存储过程，如此，你把业务逻辑写在存储过程中让人情何以堪啊？&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;span style="color: #000000;"&gt;2，写在UI里，这就要提到当年的RAD之王Delphi了。并不是说在Delphi里只能这么做，而是说Delphi里很多人就这么做，UI直接绑定DataSet，用户点击了某按钮，直接在IDE里双击该按钮，生成Btn1_Click方法，把业务逻辑通通写在那。这么做有一万种缺点，但有一个优点，就是RAD中的R（Rapid）。哥用这种方式写过好几年的Delphi，不堪回首。&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;span style="color: #000000;"&gt;3，写在MVC的Controller里，其实等同于2。现在Asp.net MVC框架很热，可是很多人不知道MVC本质上是啥东西。虽然它有个&amp;ldquo;M&amp;rdquo;，可是他在分层的架构体系里，只是非核心的Presentation层的一个pattern而已。跟Domain层毫无关系。&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;span style="color: #000000;"&gt;4，最好的方式，当然是写在一个独立的Domain Layer里。别忘了业务逻辑是一个应用系统唯一独一无二的地方。&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style="color: #0000ff;"&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000ff;"&gt;&lt;strong&gt;如何实现Domain L&lt;/strong&gt;&lt;strong&gt;ayer（Business Logic Laye&lt;/strong&gt;&lt;strong&gt;r）？&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;我做过几次技术面试，一般都会有个问题：&amp;ldquo;你能说说你对架构的理解吗？&amp;rdquo;得到的回答，第一句往往是：&amp;ldquo;关于架构，一般是分成3层，Presentation，Business Logic，Persistence&amp;hellip;&amp;hellip;&amp;rdquo;。这句话即使是很Junior的人也能说得上来，可是再往下问就能问出有意思的东西了：3层之间的依赖关系是怎样的？&lt;/p&gt;&lt;p&gt;一般的回答是：Presentation依赖于Business Logic， &lt;span style="color: #008000;"&gt;Business Logic依赖于Persistence。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;可是&lt;strong&gt;&lt;span style="color: #008000;"&gt;既然每个应用系统的&amp;ldquo;业务逻辑&amp;rdquo;才是应用系统存在的理由，才是开发它的目的所在。而UI展现、数据库存储、Cache等都是为了实现&amp;ldquo;业务逻辑&amp;rdquo;这个目的所提供的手段，都有成熟的框架、模式可用，都可以是雷同的。&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style="color: #008000;"&gt;那么&lt;/span&gt;&lt;/strong&gt;&lt;span style="color: #008000;"&gt;&lt;strong&gt;&lt;span style="color: #008000;"&gt;为什&lt;/span&gt;么&amp;ldquo;业务逻辑&amp;rdquo;要依赖于&amp;ldquo;存储技术&amp;rdquo;？为什么&amp;ldquo;目的&amp;rdquo;要依赖于&amp;ldquo;手段&amp;rdquo;？&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #008000;"&gt;&lt;span style="color: #000000;"&gt;待续&amp;hellip;&amp;hellip;&lt;/span&gt;&lt;strong&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #008000;"&gt;&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style="color: #0000ff;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style="color: #0000ff;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/CaiAbin/aggbug/2040026.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2011/05/08/2040026.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/CaiAbin/archive/2011/04/09/2010706.html</id><title type="text">大家是怎么做Code Review的？</title><summary type="text">先说说我们公司现在的做法，一个团队被人为地分为两个阵营：Senior Developers和Junior Developers，比例差不多是1:1，Senior Developers就担负着对Junior Developers的代码进行Review的职责，每天Review一次，对有问题的代码写上comments，然后也check in到代码库中。这种comments有特殊格式（比如//\\CodeReview:blah blah），要求Junior Developers每天下班前一小时去代码库中找这种格式的comments，然后修复自己的有问题的代码，修复时删除Reviewer留下的Comm.</summary><published>2011-04-09T11:25:00Z</published><updated>2011-04-09T11:25:00Z</updated><author><name>菜阿彬</name><uri>http://www.cnblogs.com/CaiAbin/</uri></author><link rel="alternate" href="http://www.cnblogs.com/CaiAbin/archive/2011/04/09/2010706.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/CaiAbin/archive/2011/04/09/2010706.html"/><content type="html">&lt;p&gt;先说说我们公司现在的做法，一个团队被人为地分为两个阵营：Senior Developers和Junior Developers，比例差不多是1:1，Senior Developers就担负着对Junior Developers的代码进行Review的职责，每天Review一次，对有问题的代码写上comments，然后也check in到代码库中。这种comments有特殊格式（比如//\\CodeReview:blah blah），要求Junior Developers每天下班前一小时去代码库中找这种格式的comments，然后修复自己的有问题的代码，修复时删除Reviewer留下的Comments。把Review的Comments也check in到代码库，本意是说让任何东西都有记录。&lt;/p&gt;&lt;p&gt;我对Code Review的理解是，最好的形式是&amp;ldquo;结对编程&amp;rdquo;，特地查了下&lt;a target="_blank" href="http://en.wikipedia.org/wiki/Code_review"&gt;Wiki对Code Review的定义&lt;/a&gt;，明确讲了&amp;ldquo;Pair Programming&amp;rdquo;是Code Review的一种形式。我个人非常喜欢pair，甚至到了在写一些重要代码时，没人跟我pair我都不想写代码的程度。然而现实中，大部分人对pair是排斥的，尤其是那些对软件开发似懂非懂的项目经理们&amp;mdash;&amp;mdash;他们直觉上认为那会让生产力减半。&lt;/p&gt;&lt;p&gt;那就回到现实，在一个不允许进行&amp;ldquo;结对编程&amp;rdquo;的团队里，怎么做Code Review 比较好呢？说说我的想法：&lt;/p&gt;&lt;p&gt;1，Code Review在某种意义上是一种&lt;strong&gt;&amp;ldquo;反馈系统&amp;rdquo;&lt;/strong&gt;。软件开发中存在太多的&amp;ldquo;反馈系统&amp;rdquo;。尤其是在&amp;ldquo;敏捷&amp;rdquo;中，追求更多的&amp;ldquo;反馈&amp;rdquo;，也追求更快的&amp;ldquo;反馈&amp;rdquo;，比如Iteration，Daily meeting，TDD，face-to-face communication&amp;hellip;&amp;hellip;不一而足。&amp;ldquo;反馈周期&amp;rdquo;总是越短越好，&amp;ldquo;结对编程&amp;rdquo;的反馈周期为0，它是&amp;ldquo;即时&amp;rdquo;的反馈系统，这也是我认为它是最好的Code Review的原因之一。那么除开&amp;ldquo;结对编程&amp;rdquo;，怎么让Code Review的&amp;ldquo;反馈周期&amp;rdquo;变短呢？像我们公司一天一次Review的做法，也许已经很短了，但显然还有更快的，比如每次Check in时Review。&lt;/p&gt;&lt;p&gt;2，check in前review本身也不一定就更快，如果有人2天才check in一次代码，那还不如daily的review呢。所以就需要结合另外一个理念：&lt;a href="http://www.codinghorror.com/blog/2008/08/check-in-early-check-in-often.html"&gt;check in as often as possible&lt;/a&gt;。以我自己的经验，在有活干的时候，check in频率在30分钟到2小时是正常的。有时会超过2小时，但那要么是有特殊原因，要么我就感觉自己写代码的方式出了问题。&lt;/p&gt;&lt;p&gt;3，顺便说句题外话：看到不少程序员的一个&amp;ldquo;通病&amp;rdquo;是，完全没法在短时间内check in，他们一旦开工（尤其是做一个比较大的模块时），一发不可收拾，这边写一个类，还没写完，突然又想到要写另一个类，这样写了很多代码，你过去让他check in，他会告诉你再等等，因为他的代码现在还没法通过编译（这跟把没通过编译的代码check in的人比，还是很有人性的）。事实上，一个高手的必备技能之一就是&lt;strong&gt;让自己的代码处于不安全状态的时间越短越好&lt;/strong&gt;。这种不安全包括不能通过编译，不能通过现有的单元测试，不能通过整个应用程序的smoke test&amp;hellip;&amp;hellip;。对有这种&amp;ldquo;通病&amp;rdquo;的程序员来说，TDD是剂良药，我个人也认为他是一个分水岭：这世界上有两种程序员，一种会TDD，另一种不会TDD。个人意见，就不展开叙述了。&lt;/p&gt;&lt;p&gt;4，我们公司的做法还有哪些问题？作为Reviewer，我要去找哪些代码在今天被修改过，这很费时间，如果有个工具帮忙会好点，比如有些系统可以把每次check in产生的diff发送mail给reviewer。但是，很多时候，事情的本质不会通过一个工具得到改变。本质是什么？是这种Code Review的做法是基于&amp;ldquo;push&amp;rdquo;的一个做法&amp;mdash;&amp;mdash;Reviewer被push去review代码，然后把Review的Comments提交到代码库，Junior Devs被push每天去搜索那些comments，然后修复它们。&lt;/p&gt;&lt;p&gt;5，有没有一种基于&amp;ldquo;pull&amp;rdquo;的做法？我以前一家公司，有设置一个简单的check in policy：每次check in时，必须在note里填上reviewer的名字。这样，每个人check in前就会主动找人给他Review，我感觉这有点&amp;ldquo;pull&amp;rdquo;的意思。&lt;/p&gt;&lt;p&gt;6，另外一个问题是：比如某人写了一天的代码，Reviewer在第二天Review后发现他的代码设计有比较大的问题（但是能work），那么在第三天，这个人会去重构他的代码以达到更好的设计吗？基本上是不会的。那这样的review有何意义呢？&lt;/p&gt;&lt;p&gt;7，这里牵涉到Code Review到底要Review什么的问题，我觉得至少可以分三种：一是小的issue（比如命名规范，代码标准）；二是大的issue（比如内存泄露）；&lt;strong&gt;最后是那种非&amp;ldquo;issue&amp;rdquo;，而是设计是否优雅简单，代码是否干净可读的问题&lt;/strong&gt;，这种问题不会导致程序出错，在短期内甚至没有任何问题，只会在一段时间之后引起维护成本，可扩展性之类的问题。我觉得越是一个成熟的团队，Review时花在最后一种情况的时间会越多。可是这种东西，有时没有一个标准，更多的是一种程序员之间的交流和互相学习。而把Reviewer的comments冷冰冰地记录在&amp;ldquo;不良代码&amp;rdquo;的上方，然后被Review的人每天默默地修复那些&amp;ldquo;不良代码&amp;rdquo;的行为，是不是完全忽视了这一点呢？&lt;/p&gt;&lt;p&gt;8，总之，code review要注意达到一些目标：快速反馈，简单有效，知识传播&amp;hellip;&amp;hellip;&lt;/p&gt;&lt;p&gt;一些零散想法，不成体系。不知道大家在公司有没有做Code Review？是怎么做的呢？&lt;/p&gt;&lt;img src="http://www.cnblogs.com/CaiAbin/aggbug/2010706.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2011/04/09/2010706.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/CaiAbin/archive/2010/10/26/Team_Learning_0.html</id><title type="text">团队学习日记（0）——用wiki来记录团队的学习日记</title><summary type="text">准备尝试一个实践：团队作为一个整体，来写“工作日记”，记录一切跟工作相关的、让我们团队成长的知识点，包括写代码、做事的方式方法、敏捷实践、团队建设、团队学习……而这篇文章——“用wiki来记录团队学习日记”，就当做我们团队的第一篇“团队学习日记”吧（传说中的递归？！）。</summary><published>2010-10-26T14:42:00Z</published><updated>2010-10-26T14:42:00Z</updated><author><name>菜阿彬</name><uri>http://www.cnblogs.com/CaiAbin/</uri></author><link rel="alternate" href="http://www.cnblogs.com/CaiAbin/archive/2010/10/26/Team_Learning_0.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/CaiAbin/archive/2010/10/26/Team_Learning_0.html"/><content type="html">&lt;p&gt;先说说&lt;strong&gt;我们团队的背景&lt;/strong&gt;：6个程序员，除了少数（比如我）虚长几岁经验丰富点外，大部分都是年轻人，而我们开发的软件是个遗留系统，里面的代码是非常&amp;ldquo;过程式&amp;rdquo;的，而开发任务又一直比较紧，这些因素导致这些年轻人OO的经验相对薄弱。其它像设计能力、写clean code、重构、TDD等等，也是我们团队非常感兴趣要去提高的地方。&lt;/p&gt;&lt;p&gt;公司虽然开发任务紧，但还是经常有团队学习的时间，比如release一个版本的时候，会有1个月的时间code freeze，QA进行系统测试，这段时间我们dev就比较闲了，可以研究一些技术上的东西。（当然对于一个敏捷团队来说，一个月的code freeze，本身是个issue，但这不是本文重点。）&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;这个月就是这样，我们dev闲下来了，就对一个模块进行重构，一方面为下一阶段做的需求增强做准备，另一方面也是锻炼大家重构、设计、写干净代码的能力。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;这个模块的背景&lt;/strong&gt;：代码大多散布在code-behind文件里，也就是aspx.cs文件，当然还包括一些domain object类；代码最大的特点是可读性非常差，函数长、圈复杂度高、命名不规范，总之不调试进去的话，很多代码都不知道是干嘛用的。我们作为有追求有活力的团队，当然是要改变这个现状。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;重构大致分了两个阶段，第一阶段用pair的方式，把原来比较混乱的代码清理了一遍，清理的动作包括：把长函数抽成小函数；规范化命名；移除不可及代码等等。第二阶段是从昨天开始的，目标是把aspx.cs里属于domain logic的代码，移到domain model类里，让domain model类&amp;ldquo;充血&amp;rdquo;，重要的是，这样就可以对这些代码进行unit test了。一个没有unit test的世界是黑暗的，而我们看到了一丝曙光。&lt;/p&gt;&lt;p&gt;这第二阶段没有用pair了，而是采取的是所有人坐在一起写代码的方式。这是考虑到大家处在学习的过程中，对&amp;ldquo;好的设计&amp;rdquo;的理解还不是很一致，如果pair的话，分为3组，这3组仍旧可能写出很不一致的代码出来，于是干脆大家坐在一起写，有那么点&lt;a href="http://www.cnblogs.com/tengzy/archive/2010/06/02/1750281.html"&gt;coding dojo&lt;/a&gt;的意思。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;话说今天，大家又坐在一起写了一天的代码，而每次在这个过程中，我作为设计、重构和TDD经验相对比较丰富的一个人，都感觉还是有很多东西可以分享给团队的，此其一；其二，他们对代码的思考和疑问，反过来也会促进我的思考。我相信他们也是有和我一样的感觉，那么，这些思想碰撞的过程，对整个团队能力的提升是非常有利的。&lt;/p&gt;&lt;p&gt;可是晚上回来，我还是有点困惑：有些东西，团队学习的很快，但有些东西，也许是经验未到一定的程度的关系，团队比较难记住，需要多次的提醒。&lt;/p&gt;&lt;p&gt;洗澡的时候我就想：如果我是他们中的一个年轻人，我会怎么办？自然而然就想起若干年前，我还年轻的时候（俺也年轻过啊！），我是会写&amp;ldquo;&lt;strong&gt;工作日记&amp;rdquo;&lt;/strong&gt;的，把学到的知识点，简单记下来（不一定每天，但有了就记），当时看好像没什么，但日积月累，一段时间后，把那个日记本重新翻一下，收获是很多的，可以温故知新，又可以提高自信（过去一个月居然学到那么多东西，我真是小强）。想到这里，决定，明天上班向他们推荐这个做法。&lt;/p&gt;&lt;p&gt;这时准备收工，可是灵光一闪，又想：为什么不让&lt;strong&gt;团队作为一个整体，来写这个&amp;ldquo;工作日记&amp;rdquo;&lt;/strong&gt;呢？团队刚好建立了wiki，是写这个日记的绝佳地方。随着这些日记的增多，对团队知识的巩固应该是个帮助，而且还可以分享给公司的其他团队，以及以后新来的员工，甚至我可以把有价值的部分，放到我的blog上来。我觉得是个不错的主意，于是心满意足地收工了。&lt;/p&gt;&lt;p&gt;为啥我的灵感，大多都是在洗澡或睡觉的时候得到呢？&lt;strong&gt;白天为公司工作，晚上回家还继续为公司爆发人品值，我的公司真是赚大了。&lt;/strong&gt;（boss看这句就行了，haha）&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;洗完澡整理了下思路，我们的&amp;ldquo;团队学习日记&amp;rdquo;应该会记录一切跟工作相关的、让我们团队成长的知识点，包括写代码、做事的方式方法、敏捷实践、团队建设、团队学习&amp;hellip;&amp;hellip;嗯，我们是敏捷团队，&lt;strong&gt;团队作为一个整体来做事、来交付软件、来做决定&amp;hellip;&amp;hellip;也应该作为一个整体来学习。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;而这篇文章&amp;mdash;&amp;mdash;&amp;ldquo;用wiki来记录团队学习日记&amp;rdquo;，就当做我们团队的第一篇&amp;ldquo;团队学习日记&amp;rdquo;吧（传说中的递归？！）。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/CaiAbin/aggbug/1861956.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2010/10/26/Team_Learning_0.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/CaiAbin/archive/2010/10/15/1852613.html</id><title type="text">我在敏捷中国Open Space上发起的讨论主题——如何激励，用绩效考核吗？</title><summary type="text">第五届敏捷中国大会刚刚落幕，两天听了14场演讲（因为每天的下午都有两个分会场，有8场演讲没有听到），整体还是不错的，给我印象比较深的是Mary的《Making Change Happen and Making Change Stick》、Jean Tabaka的《敏捷运用之12种成功模式》、阳陆育的《大型企业应用系统的敏捷需求管理》，其它演讲也可圈可点，尤其是我今天听的最后一场Alan Atlas...</summary><published>2010-10-15T14:25:00Z</published><updated>2010-10-15T14:25:00Z</updated><author><name>菜阿彬</name><uri>http://www.cnblogs.com/CaiAbin/</uri></author><link rel="alternate" href="http://www.cnblogs.com/CaiAbin/archive/2010/10/15/1852613.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/CaiAbin/archive/2010/10/15/1852613.html"/><content type="html">&lt;p&gt;第五届敏捷中国大会刚刚落幕，两天听了14场演讲（因为每天的下午都有两个分会场，有8场演讲没有听到），整体还是不错的，给我印象比较深的是Mary的《Making Change Happen and Making Change Stick》、Jean Tabaka的《敏捷运用之12种成功模式》、阳陆育的《大型企业应用系统的敏捷需求管理》，其它演讲也可圈可点，尤其是我今天听的最后一场Alan Atlas的《Amazon敏捷转型案例研究》，听之前还在想最后一场了，大家都疲倦了，Alan这老头看上去又其貌不扬，会不会中途走的人很多，没想到这老头挺风趣幽默，在PPT里还展现了一张他自己的抽烟照，实在太可乐。结果他引起最多的笑声，结束鼓掌时还有人尖叫起哄，以示支持。老头不错。&lt;/p&gt;&lt;p&gt;昨天的演讲流程跟今天一样，只是晚上多了场Open Space，不知道以前的Agile China有没有类似的活动，反正我是第一次见识，感觉效果很好，这里先简单介绍一下。&lt;/p&gt;&lt;p&gt;Open Space据说是Thought Works的经典玩法，活动开始时大家围成一个圈，主持人介绍有7个Market Place&amp;mdash;&amp;mdash;也就是大厅的7个角落，每个Market Place都将有2场自由讨论（分两个时段，1小时1场），所以会有14个主题，而主题还没确定，场地的中央有14张纸条，任何想发起讨论的人，都可以上去拿纸条，然后写上自己想发起讨论的主题，然后挑选一个Market Place。14个主题确认后，大家就可以随意到任何一个Market Place自由讨论去。有几个原则，记的不太全，大致说下：讨论随时可开始，随时可结束，可以跑题，任何人可以随时离开，也可以随时换场地。&lt;/p&gt;&lt;p&gt;由于我一个同事在我离开上海到北京时提了一个问题，让我到会议现场问问大家，他的问题是这样的：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&amp;ldquo;在一个敏捷团队，需要用绩效来激励团队成员吗？如果要，有什么这方面的经验？如果不要，那么有什么其它激励方式，尤其是对团队中积极性比较低的成员？&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我于是就抢到一张纸条，把这个问题写上去，作为第5号Market Place的第一时段的主题。这篇文章主要将回忆并记录一下当时大家对这个主题的讨论情况，由于讨论激励，我也没有现场记录，全靠回忆，所以可能不能记录的太全面。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;我刚把这题目写到5号Market Place的大白板上，一位H先生就过来跟我聊了起来，后来1个小时的讨论中，这位H先生和一位来自ThoughtWorks的W先生是主角，当然其它人也有不少声音。顺便说下，感觉这位H先生在他公司是个不错的头。&lt;/p&gt;&lt;p&gt;H先生一开始就对&amp;ldquo;绩效考核&amp;rdquo;持反面意见。他认为&lt;strong&gt;绩效考核做不到公平，即使manager尽最大努力做到了公平，那些成员也总是会感觉到不公平&lt;/strong&gt;。在得知我来自上海后，他又表示，&lt;strong&gt;上海和北京很像，对于那些工作年限不长，还在为房子努力奋斗的80后，很难用&amp;ldquo;绩效考核&amp;rdquo;来激励他，因为这些人还处在马斯洛需求层次的最低层，他们最在乎的是钱，不管公司用什么好的东西来激励他，只要有别的公司开更高的薪水，他一般都会选择跳槽；相反对于工作年限长、生活较稳定的员工，他们在乎的更多是自我价值实现的成就感。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;说实话，一直以来都是作为开发人员，我很少想这方面的问题，H先生的这番话，把激励跟马斯洛需求层次连接起来，对我来说是很新鲜。&lt;/p&gt;&lt;p&gt;说着这些的时候，Thoughtworks的W先生也加入了，还有些别人的。W先生顺着H先生的思路认为，&lt;strong&gt;关键是公司的期望和个人的期望能不能Match，如果他们的价值观能够有交集，个人就会为了实现自我价值而留下来。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;因为对Thoughtworks公司尤其是它的文化仰慕已久，我就问他：所以这就是个人对公司文化的认同？他点头同意，并进一步阐述：一个公司，它有来自客户的驱动，有来自竞争对手的驱动，在这些驱动下，它为了生存，自然而然需要形成一些特殊的东西，这些东西慢慢就会形成文化。&lt;/p&gt;&lt;p&gt;接下来周围的人就围绕着&amp;ldquo;公司文化&amp;rdquo;而进行讨论，并基本形成一个共识：&lt;strong&gt;公司文化是对团队成员最好的激励。&lt;/strong&gt;因为文化不是虚的，它体现在公司的每个人身上，这些人能成为公司文化的体现者，说明这些人对公司文化或者价值观的认可，那么他们每天的工作在实现公司价值的同时也在实现个人价值&amp;mdash;&amp;mdash;这无疑是最大的激励。&lt;/p&gt;&lt;p&gt;顺着公司文化的议题，H先生又提到了&lt;strong&gt;&amp;ldquo;末位淘汰&amp;rdquo;&lt;/strong&gt;，他认为一个公司形成它的文化之后，那些不认可公司文化的人自然会走。而对于&amp;ldquo;如何build公司文化&amp;rdquo;这个问题，有人（好像是W）提到&lt;strong&gt;&amp;ldquo;阻力最小理论&amp;rdquo;&lt;/strong&gt;， 水总是流向最低的地方、阻力最小的地方，所以领导层要致力于build这样一个环境：让每个人在通往公司价值观的路上遇到的阻力最小。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;前半段的讨论基本是围绕&amp;ldquo;公司文化&amp;rdquo;与&amp;ldquo;激励&amp;rdquo;来进行的。后半段则还是回到了&amp;ldquo;绩效考核&amp;rdquo;的话题上来，毕竟大家认为它不是最好的激励方式，但仍在很多公司都存在着。&lt;strong&gt;一位哥们说到他们公司的做法&lt;/strong&gt;：在每个sprint介绍后，进行内评，也就是360度评分，这种个人在内评中得到的分数，将占据涨薪时30%的权重，而整个团队的表现，将占据团队成员涨薪时70%的权重。&lt;/p&gt;&lt;p&gt;这让我想到当初读到&lt;a href="http://www.scrumalliance.org/articles/123-top-ten-organizational-impediments"&gt;《十大组织性障碍》&lt;/a&gt;这篇文章中的其中一个障碍：Individual Performance Evaluation and Reward时，有点不解其意，为什么对个人的绩效考核会成为Impediment？就去问&lt;strong&gt;Daniel Teng，他的回答是：因为对个人的绩效考核会让很多成员觉得不公平，在Activate某个人的同时DeActivate了其他很多人（这也是昨天讨论时H先生的意见），所以敏捷团队应该针对团队进行绩效考核，然后给这个团队一个涨薪幅度，而具体到每个人，让这个团队自行决定。&lt;/strong&gt;我想那位哥们公司的做法，是不是也是基于跟Daniel同样的理念。我个人是认同Daniel的，而且他的回答也印证了一个原则：Agile中，最小单位是团队。&lt;/p&gt;&lt;p&gt;我把&amp;ldquo;针对团队，而不是个人进行绩效考核&amp;rdquo;的想法说出来，并没有引起太多反响，反而另一位刚加入的哥们，铿锵有力地说：&lt;strong&gt;激励最好的方法就是绩效考核，就是钱！&lt;/strong&gt;引起H和W等人把前半段的一些结论又重复了一遍。另一位来自alibaba的小伙也现身说法反驳他，说他们公司很多人都有种责任感，他们的软件是服务社会的，所以只要给的钱让他觉得公平就行了&lt;strong&gt;，激励他的是文化，是责任感，而不是钱&lt;/strong&gt;。我们都善意地笑他被马先生洗脑了，但&amp;ldquo;公司文化&amp;rdquo;说白了就是洗脑，只不过这里洗脑不再是贬义词。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;后来我还问了ThoughtWorks公司的W先生，他们公司是怎么惩罚人的？他说用360度考核，由于他们咨询师是一个项目接着一个项目，所以360考核发生在每个项目结束，但公司不会因为一个项目中对某人的考核指标差而对他进行惩罚，而是接连考核若干个项目，如果这些项目中，某人的不足的地方一直没有改进，这才是惩罚的时候，所以公司还是很&lt;strong&gt;看重一个人的improvement的&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;好了，记录到此，也许不太准确和详细，但应该也足以引起对此感兴趣的人的思考和进一步讨论了。顺便说下，在第二个时段，我去参加了一个Thoughtworks公司哥们的关于Leadership主题的讨论，受益匪浅，可惜没带相机，没把白板上写的一些东西拍下来。其它12个主题讨论（其中有两个是敏捷游戏）也很不错，期待那些发起人也写博客出来分享。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/CaiAbin/aggbug/1852613.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2010/10/15/1852613.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/CaiAbin/archive/2010/09/25/1834797.html</id><title type="text">我的JavaScript之旅——this到底是啥？</title><summary type="text">下图是在ASP.NET中为button挂上客户端onclick事件的两种办法：图中的2和3/1。结果发现两种方式调用同样一个函数clickMe，this却不一样。如果采用3或1的做法，那么点击button1后将alert出[object DOMWindow]；而采用2的做法，将alert出[object HTMLInputElement]（在chrome下测试。）显然，在1的做法中，t...</summary><published>2010-09-25T14:17:00Z</published><updated>2010-09-25T14:17:00Z</updated><author><name>菜阿彬</name><uri>http://www.cnblogs.com/CaiAbin/</uri></author><link rel="alternate" href="http://www.cnblogs.com/CaiAbin/archive/2010/09/25/1834797.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/CaiAbin/archive/2010/09/25/1834797.html"/><content type="html">&lt;p&gt;下图是在ASP.NET中为button挂上客户端onclick事件的两种办法：图中的2和3/1。&amp;nbsp;结果发现两种方式调用同样一个函数clickMe，this却不一样。&lt;/p&gt;&lt;p&gt;如果采用3或1的做法，那么点击button1后将alert出&lt;strong&gt;[object DOMWindow]&lt;/strong&gt;；而采用2的做法，将alert出&lt;strong&gt;&amp;nbsp;[object HTMLInputElement]&lt;/strong&gt;（在chrome下测试。）&lt;/p&gt;&lt;p&gt;显然，在1的做法中，this指向DOMWindow，也就是全局的object&amp;mdash;&amp;mdash;global；而2的做法中，this指向Button1这个元素。&lt;/p&gt;&lt;p&gt;为什么呢？&lt;/p&gt;&lt;p&gt;(注：对于3这种通过Attribute来声明处理程序的方式，button的onclick不是指向clickMe函数，而是指向一个被自动创建的匿名函数，该匿名函数以Attribute的值（也就是"clickMe();"）作为函数体&amp;mdash;&amp;mdash;也就是等价于1。)&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2010/61088/2010092518372542.png" alt="" /&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;简单的答案&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;/strong&gt;基于类的OO语言（比如C#）中，函数总是声明在一个类中，函数内部的this指向该类的当前实例。而JS中，函数是第一等公民（它不会声明为别的东西的一部分），所以this跟函数是如何声明的无关，跟函数是怎么被调用的有关。&lt;/p&gt;&lt;p&gt;方式1和2对clickMe的调用，不同之处在于：1中clickMe是被button1.onclick所指向的函数调用的，2中clickMe是作为button1.onclick属性直接调用的。因此对于clickMe函数，1中的this指向&amp;ldquo;拥有&amp;rdquo;clickMe函数的对象&amp;mdash;&amp;mdash;global（DOMWindow），而2中的this指向&amp;ldquo;拥有&amp;rdquo;onclick属性的对象&amp;mdash;&amp;mdash;button1。&lt;/p&gt;&lt;p&gt;可惜对像我这种写JS写的不多的人来说，总是记不住这个简单答案，因为它只告诉我what，没有告诉我why。本文试图从ECMAScript官方文档出发，从原理上说明：在不同的场合中，函数的this到底是什么？&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;call和apply&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;首先明确一点，在最正常的情况下，我们这样调用：Func()，这时this是由JavaScript来确定的，这也是本篇要研究的主题。而如果用Func.call(thisArg, &amp;nbsp;arg1, arg2, ...)或者Func.apply(thisArg, [arg1, arg2, ...])来调用时，this是我们自己传进去的（作为call或apply的第一个参数）。如果我们不传this进去，或者传null进去，会怎样？这时this将会是global object。&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2010/61088/2010092519364758.png" alt="" /&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;函数作为构造器时的this&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;先从简单的说起。所谓构造器，就是用new关键字来调用函数，在&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2010/08/25/1808285.html"&gt;这篇关于new关键字的玄机的文章&lt;/a&gt;中有说到。看下面的注释，可以知道，A函数里的this这时就是a。&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;span style="color: #0000ff;"&gt;function&lt;/span&gt;&lt;span style="color: #000000;"&gt; A() {&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;this&lt;/span&gt;&lt;span style="color: #000000;"&gt;.x &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;1&lt;/span&gt;&lt;span style="color: #000000;"&gt;;}&lt;/span&gt;&lt;div&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;var&lt;/span&gt;&lt;span style="color: #000000;"&gt; a &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;new&lt;/span&gt;&lt;span style="color: #000000;"&gt; A();  &lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;这行代码大概等价于下面两行&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="color: #008000;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;var&lt;/span&gt;&lt;span style="color: #000000;"&gt; a &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; {};   &lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt; a指向一个新创建的对象。&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;A.call(a);       &lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;再把a作为第一个参数传给A.call函数。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;普通调用时的this&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;看下图，对于在全局定义的函数A，this就是global。&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2010/61088/2010092521202063.png" alt="" /&gt;&lt;/p&gt;&lt;p&gt;这个好理解，A作为顶级函数，是顶级对象global（也就是window）的属性，所以this === global。但看下图：&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2010/61088/2010092521215895.png" alt="" /&gt;&lt;/p&gt;&lt;p&gt;Outer()返回inner函数，Outer()()就是是对inner的调用，在inner内部，this同样等于global。可是inner函数是执行Outer时，在Outer的context下创建起来的，inner并不是global的属性（见下图）。为什么inner里的this仍旧是global？一个函数被调用时，this究竟是怎么确定的？&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2010/61088/2010092521264653.png" alt="" /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;官方文档对this的解释&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;上面第1个例子，当执行A()时，&lt;a href="http://bclary.com/2004/11/07/#a-11.2.3"&gt;这里说明了函数被调用时发生的事情&lt;/a&gt;，简述如下：&lt;/p&gt;&lt;p&gt;1，&amp;nbsp;&amp;nbsp;得到A这个引用。&lt;/p&gt;&lt;p&gt;2， 求A的值，就是A这个函数对象。&lt;/p&gt;&lt;p&gt;3， A的值必须是object，并且必须有[[Call]]方法。（当然，它是函数型object，函数被创建时都有[[Call]]方法。）&lt;/p&gt;&lt;p&gt;4， 如果A是个引用（是），那么通过GetBase()得到A所在的对象，就是包含有A属性的对象（在我们例子中就是global）&lt;/p&gt;&lt;p&gt;5， 检查上面步骤中A所在的对象。如果是个activation object（就是variable object，scope chain里的对象），则把null作为thisArg传给A.call()；否则就把该对象作为thisArg。&lt;/p&gt;&lt;p&gt;重点就是这第5步（需要对variable object熟悉，否则请先阅读&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2010/09/05/1818493.html"&gt;这篇文章&lt;/a&gt;），执行A()时，global里有A属性，而global是global context下的activation object，所以将会把null作为thisArg参数传给A.call()。而上文说了，如果传的thisArg参数为null，则会把global作为this。所以gloabl === this还不是天生就这样，还转了下弯。&lt;/p&gt;&lt;p&gt;对于第2个例子，Outer()()，也就好解释了：因为Outer()返回的inner函数是在OuterContext中创建为OuterVariableObject的属性，所以第4步得到的对象是OuterVariableObject；那么根据第5步逻辑，仍旧把null作为thisArg传给inner.call()。因此inner里的this也同样是global。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;把函数作为对象的属性值&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;所以，如果直接调用函数，无论这个函数是在哪个执行上下文被创建，this都指向global。除非&amp;ldquo;拥有&amp;rdquo;这个函数的对象不是一个variable object，它才会被当做this。那么很简单，我们把函数作为我们自己创建的对象(非variable object)的属性值，然后再调用，那么this就应该是这个我们创建的对象了。验证如下：&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2010/61088/2010092521551295.png" alt="" /&gt;&lt;/p&gt;&lt;p&gt;回过头看文章开头的问题：&lt;/p&gt;&lt;p&gt;方式1是这样调用的：Button1.onclick(clickMe();)，clickMe是global的属性，因此clickMe里的this指向global。&lt;/p&gt;&lt;p&gt;方式2是这样调用的：Button1.onclick()，onclick是Button1的属性，因此里面的this指向Button1。&lt;/p&gt;&lt;p&gt;简言之，onclick和clickMe都是对真正的函数object的引用，只不过通过GetBase()得到的&amp;ldquo;所属对象&amp;rdquo;不同，this自然也就不同了。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;阶段性结语&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我的JavaScript之旅先告一段落了（快写吐了），5篇文章写下来，至少自己收获不少，下次面人的时候，终于可以问点JS的东西了。&lt;/p&gt;&lt;p&gt;this, scope chain, prototype chain, constructor, closure，这些都是学习JavaScript必须懂的东西，每本JS的书都会对这些概念进行描述，我看过几篇，都是讲what，就是要你死记硬背，或者多写JS，熟能生巧才行。没有看到一篇讲why的。而我把这5篇写why的博文写完，再看那些书籍上的描述，真有一览众山小的感觉，哈哈。&lt;/p&gt;&lt;p&gt;至于对这些概念的运用，就需要多写多看JS代码了，不想写那样的文章了。JS告一段落，要研究别的东西去了。FubuMVC、Ruby on Rails、CQRS&amp;hellip;&amp;hellip;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/CaiAbin/aggbug/1834797.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2010/09/25/1834797.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/CaiAbin/archive/2010/09/18/1830388.html</id><title type="text">QA不是QC，兼谈Lean、Kanban和TDD（下）——对所在团队的反思</title><summary type="text">上篇文章说了QA与QC的区别，以及一种不太好的流程所造成的浪费，这篇对我所在团队的状况进行反思。为什么这么多bug？1个月200多个，按工作日平均下来1天10个，触目惊心。我认为原因很多，列举如下：环境。在进度第一的观念下，代码库中渐渐充斥着设计拙劣、充满陷阱的代码。殊不知慢就是快，而表面上的快实际上严重拖慢了团队的脚步。造成的结果不仅bug多，而且被fix后，不知哪天又冒出来了。程序员习惯。...</summary><published>2010-09-18T13:09:00Z</published><updated>2010-09-18T13:09:00Z</updated><author><name>菜阿彬</name><uri>http://www.cnblogs.com/CaiAbin/</uri></author><link rel="alternate" href="http://www.cnblogs.com/CaiAbin/archive/2010/09/18/1830388.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/CaiAbin/archive/2010/09/18/1830388.html"/><content type="html">&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2010/09/11/1824111.html"&gt;上篇文章&lt;/a&gt;说了QA与QC的区别，以及一种不太好的流程所造成的浪费，这篇对我所在团队的状况进行反思。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;为什么这么多bug？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;1个月200多个，按工作日平均下来1天10个，触目惊心。我认为原因很多，列举如下：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;环境。在进度第一的观念下，代码库中渐渐充斥着设计拙劣、充满陷阱的代码。殊不知慢就是快，而表面上的快实际上严重拖慢了团队的脚步。造成的结果不仅bug多，而且被fix后，不知哪天又冒出来了。&lt;/li&gt;&lt;li&gt;程序员习惯。一个团队中的坏习惯比好习惯更容易传染，如果代码中到处都是不规则的命名、长函数、无用的代码、与代码不一致的注释、&lt;a href="http://www.ndepend.com/Metrics.aspx#CC"&gt;圈复杂度&lt;/a&gt;好几百，那么如果不是有特殊的动力，谁还有心情去写&lt;a href="http://product.dangdang.com/picbox.php?product_id=20750190&amp;amp;name=%B4%FA%C2%EB%D5%FB%BD%E0%D6%AE%B5%C0&amp;amp;type=publish"&gt;干净的代码&lt;/a&gt;？&lt;/li&gt;&lt;li&gt;scrum-but。比如，sprint结束没有demo，没有product backlog，没有user story。关于scrum-but，&lt;a href="http://www.scrumalliance.org/resources/606"&gt;这边&lt;/a&gt;有ppt下载。好现象是，这些状况有很多改善。&lt;/li&gt;&lt;li&gt;没有XP实践。现在有了code review，及其偶尔会有pair，但还非常不够。没有pair过的人是体会不到它带来的代码质量和产量的提升的。也很少有unit test，有不少test都不是&amp;ldquo;unit&amp;rdquo; test，因为他们依赖于数据库，或者外部文件，他们很少被运行。另外有些地方根本没办法写unit test，因为一个类的一个方法做了太多事，不够&lt;a href="http://en.wikipedia.org/wiki/Solid_(object-oriented_design)"&gt;SOLID&lt;/a&gt;。&lt;/li&gt;&lt;li&gt;没有&lt;a href="http://www.cnblogs.com/tengzy/archive/2009/06/14/1503281.html"&gt;持续集成&lt;/a&gt;。只有daily build，没有local build脚本。很多次发生代码库里的代码不能通过编译，却很久之后才被发现的情况。&lt;/li&gt;&lt;li&gt;组织结构的问题。daily build是专门的CM部门维护，CM部门的人不属于我们的Scrum团队。而Scrum应该是one team，一个跨功能的团队。&lt;/li&gt;&lt;li&gt;项目结构的问题。整个项目按模块分成几个solution，而每个solution都包含公用的十几个project。如果按solution来build，势必造成公用project的重复build。CM的daily build脚本是怎么处理的呢？它不build solution，而是依一定顺序，build所有的project。所以如果dev增加了一个project，脚本就必须改。&amp;mdash;&amp;mdash;这样的脚本，实在没有把它拿来当local build脚本的心情。&lt;/li&gt;&lt;li&gt;VSS。我们是分布式团队，VSS服务器在上海，外地团队获取代码的时间要数个小时，所以他们几乎不获取。他们只在每周，由某人获取他们团队正在工作的某个solution（不是全部），然后其他人从他那copy。团队的代码永远都是过时的。这样的情形延续了两三年，在最近终于转向了SVN。SVN不是目的，只是&lt;a href="http://www.cnblogs.com/tengzy/archive/2010/06/23/1764003.html"&gt;Check in dance&lt;/a&gt;的基本前提。&lt;/li&gt;&lt;li&gt;流程。那么多bug，可是我们&lt;a href="http://www.flowfinders.com/biztips/biztip-blame-the-process-not-the-people.html"&gt;应该责怪的不是团队成员，而是流程&lt;/a&gt;。在上篇文章开头提到的流程，真实的发生在我们团队。&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div&gt;&lt;strong&gt;kanban&lt;/strong&gt;&lt;/div&gt;&lt;div&gt;我们以Scrum为框架&amp;ldquo;使用&amp;rdquo;敏捷已经近三年了，但是&amp;ldquo;使用&amp;rdquo;一词只能加上引号。因为很长一段时间，都没有user story；没有&lt;a href="http://www.extremeprogramming.org/rules/collective.html"&gt;代码集体所有&lt;/a&gt;；QA和Dev以及Dev之间交流太少&amp;hellip;&amp;hellip;为了强化团队成员在开发期间的交流，也为了促使PO写出更小的story，我们前一阵做了一个尝试：尝试不再拆task，领到story后直接做，把拆task时的讨论，从plan meeting延后到真正写代码的时候，避免写代码时单兵作战。&amp;mdash;&amp;mdash;其实这时非常适合pair，可惜&amp;hellip;&amp;hellip;。（有奖竞答：在中国的软件团队实行Scrum，最大的障碍是什么？）&lt;/div&gt;&lt;div&gt;没有task，带来一个相关的改变，就是scrum board的改变，我们引入了kanban的一些理念，改变后的白板如下：&lt;/div&gt;&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/CaiAbin/262236/r_kanban1.png" alt="kanban1" width="768" height="452" /&gt;&lt;/div&gt;&lt;div&gt;白板很简单易懂：story就是plan meeting上团队commit的所有story。dev开始写代码时，移到development一列的ongoing，同时QA设计test case。代码完成（一般QA的case也完成）则往右移到done。然后QA开始测试时，把它移到verify中的ongoing，所有test case通过，则往右移到done。最后PO检查一下，如果没有问题，则放到最后一列Live！另外，development和verify都有WIP的限制，分别取得是dev人数+1，QA人数+1。&lt;/div&gt;&lt;div&gt;一切很完美，直到bug的出现：QA在verify的时候，如果有bug怎么办？而且如果这是development列的WIP已经达到限制了，有bug的story怎么办？把development列的1个story往左移，再把有bug的story从verify移回到development列？这是个好的开始。我的意思是，kanban的一个目的达到了&amp;mdash;&amp;mdash;它把潜在的问题可视化了。&lt;/div&gt;&lt;div&gt;既然问题出现，就应该解决。回想上篇文章的内容，解决办法很简单&amp;mdash;&amp;mdash;既然有QA的test case，那么在verify的阶段，为什么要出现bug？dev写完一个story，在没有通过test case检验的时候，就把它扔给QA，同时自己去开始编写下一个story的代码。&amp;mdash;&amp;mdash;这是典型而常见的错误流程。为什么在development的时候，就把&amp;ldquo;代码写完+case通过&amp;rdquo;成为done的条件呢？是的，但就这个改变来说，bug不会少，&lt;strong&gt;但是bug的发现会提前，&lt;/strong&gt;&lt;strong&gt;并且减少了dev和qa之间的往返。&lt;/strong&gt;&lt;/div&gt;&lt;div&gt;因此我的想法是把白板改为如下：&lt;strong&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/div&gt;&lt;div&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/CaiAbin/262236/r_kanban2.png" alt="kanban2" width="768" height="452" /&gt;&lt;/div&gt;&lt;div&gt;当然白板的微调只是表象，需要调整的是流程，是观念，是&lt;strong&gt;test first&lt;/strong&gt;的理念。&lt;/div&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div&gt;&lt;strong&gt;什么是TDD&lt;/strong&gt;&lt;/div&gt;&lt;div&gt;TDD在很多人眼里是可望不可即的，它需要很高的技能，其实这样想的人也许对TDD有些误解，很多人以为TDD的T只是Unit Test。其实不是。TDD是测试驱动，这里的测试包括unit test，也包括验收测试。在scrum中，每个story都有&amp;ldquo;验收条件&amp;rdquo;，QA正是以此为蓝本来设计test case，设计出来的test case其实就是story的&amp;ldquo;验收测试&amp;rdquo;。那么我们以此&amp;ldquo;验收测试&amp;rdquo;来驱动我们的开发，不也是TDD吗？虽然我们还写不出自动化的验收测试，我们不会用FIT或者Story Teller或者Spec Flow&amp;hellip;&amp;hellip;，但是，如果QA在设计test case时即与Dev保持沟通和思想碰撞，用test case来指导我们的代码编写（而不是在代码完成后用来&amp;ldquo;发现&amp;rdquo;bug），我认为，这也是一种TDD，是最廉价却同样有效果的TDD。在练习TDD技能的同时，不要忘了TDD的一种简单定义：&lt;a href="http://www.slideshare.net/baronslideshare/testdriven-development-tdd"&gt;TDD is&amp;nbsp;a technique where teams use short development iterations based on pre-written test cases.&lt;br /&gt;&lt;br /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;怎么样减少bug？&lt;/strong&gt;&lt;/div&gt;&lt;div&gt;文章开头列出的那些问题，如果都解决了，bug是不是就减少了？也许，而且那只是我看到的问题，也许是对，也许是错。&lt;strong&gt;重点是团队养成反省，发现问题和学习的习惯。&lt;/strong&gt;&lt;/div&gt;&lt;img src="http://www.cnblogs.com/CaiAbin/aggbug/1830388.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2010/09/18/1830388.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/CaiAbin/archive/2010/09/14/1826287.html</id><title type="text">我的JavaScript之旅——“闭包”是什么时候创建的</title><summary type="text">[代码]对于这样一个简单的闭包函数，下面两种调用方式有什么不一样的地方？[代码][代码]此篇试图解答这个问题。先复习一下：上篇文章说到，每次执行一个function时，就会进入一个新的“执行上下文”（execution context）。context的几个重要属性：1， 有一个对应的variable object；在global context中就是global ob...</summary><published>2010-09-14T13:43:00Z</published><updated>2010-09-14T13:43:00Z</updated><author><name>菜阿彬</name><uri>http://www.cnblogs.com/CaiAbin/</uri></author><link rel="alternate" href="http://www.cnblogs.com/CaiAbin/archive/2010/09/14/1826287.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/CaiAbin/archive/2010/09/14/1826287.html"/><content type="html">&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;!--&lt;br /&gt;&lt;br /&gt;Code highlighting produced by Actipro CodeHighlighter (freeware)&lt;br /&gt;http://www.CodeHighlighter.com/&lt;br /&gt;&lt;br /&gt;--&gt;&lt;span style="color: #0000ff;"&gt;function&lt;/span&gt;&lt;span style="color: #000000;"&gt; Outer(){&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;var&lt;/span&gt;&lt;span style="color: #000000;"&gt; x &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;1&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;function&lt;/span&gt;&lt;span style="color: #000000;"&gt; Inner(y) {&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; x &lt;/span&gt;&lt;span style="color: #000000;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt; y}; &lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; Inner;&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;对于这样一个简单的闭包函数，下面两种调用方式有什么不一样的地方？&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;!--&lt;br /&gt;&lt;br /&gt;Code highlighting produced by Actipro CodeHighlighter (freeware)&lt;br /&gt;http://www.CodeHighlighter.com/&lt;br /&gt;&lt;br /&gt;--&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;方式1&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;var&lt;/span&gt;&lt;span style="color: #000000;"&gt; inner1 &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; Outer(&lt;/span&gt;&lt;span style="color: #000000;"&gt;);&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;var&lt;/span&gt;&lt;span style="color: #000000;"&gt; result &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; inner1(&lt;/span&gt;&lt;span style="color: #000000;"&gt;2&lt;/span&gt;&lt;span style="color: #000000;"&gt;); &lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;!--&lt;br /&gt;&lt;br /&gt;Code highlighting produced by Actipro CodeHighlighter (freeware)&lt;br /&gt;http://www.CodeHighlighter.com/&lt;br /&gt;&lt;br /&gt;--&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;方式2&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;var&lt;/span&gt;&lt;span style="color: #000000;"&gt; result &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; Outer(&lt;/span&gt;&lt;span style="color: #000000;"&gt;)(&lt;/span&gt;&lt;span style="color: #000000;"&gt;2&lt;/span&gt;&lt;span style="color: #000000;"&gt;); &lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;此篇试图解答这个问题。先复习一下：&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2010/09/05/1818493.html" target="_blank"&gt;上篇文章&lt;/a&gt;说到，每次执行一个function时，就会进入一个新的&amp;ldquo;执行上下文&amp;rdquo;（execution context）。context的几个重要属性：&lt;/p&gt;&lt;p&gt;1， 有一个对应的variable object；在global context中就是global object。&lt;br /&gt;2， 有一个对应的scope chain，这个scope chain的第一个object就是variable object；&lt;br /&gt;3， 有一个不变的this变量。&lt;/p&gt;&lt;p&gt;其中第2条，scope chain是JS实现闭包（closure）的关键所在，这篇对&amp;ldquo;闭包&amp;rdquo;展开描述，以加深印象。后续文章将对this专门探讨。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Variable object的实例化三部曲&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我们已经知道，当JS执行时碰到一个变量，它会到scope chain里递归去找，而scope chain是由variable object和global object组成的一个object chain，global object包含JS预定义好的所有object，function的variable object则会包含函数内声明的所有东西，包括function的参数、内部函数、局部变量。创建Variable object的过程有三步，上篇有写过，&lt;a href="http://bclary.com/2004/11/07/#a-10.1.3"&gt;这边是官方的文档&lt;/a&gt;。简述如下：&lt;/p&gt;&lt;p&gt;1， 为variable object创建与函数参数同名的属性；属性值为传入的参数值。&lt;/p&gt;&lt;p&gt;2， 对于&amp;ldquo;function declaration(见下节)&amp;rdquo;，首先创建函数，然后为variable object创建属性；属性值即为该函数实例。覆盖1的同名属性。&lt;/p&gt;&lt;p&gt;3， 为variable object创建各变量的属性；属性值为undefined。不覆盖1,2的同名属性。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;function declaration vs. function statement&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;下面就是一个function declaration:&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;!--&lt;br /&gt;&lt;br /&gt;Code highlighting produced by Actipro CodeHighlighter (freeware)&lt;br /&gt;http://www.CodeHighlighter.com/&lt;br /&gt;&lt;br /&gt;--&gt;&lt;span style="color: #0000ff;"&gt;function&lt;/span&gt;&lt;span style="color: #000000;"&gt; A(){&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;下面是一个function statement:&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;!--&lt;br /&gt;&lt;br /&gt;Code highlighting produced by Actipro CodeHighlighter (freeware)&lt;br /&gt;http://www.CodeHighlighter.com/&lt;br /&gt;&lt;br /&gt;--&gt;&lt;span style="color: #0000ff;"&gt;var&lt;/span&gt;&lt;span style="color: #000000;"&gt; A &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;function&lt;/span&gt;&lt;span style="color: #000000;"&gt; A(){&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;这是创建函数的三种方式之二，区别在上面的三部曲中就可以看出来：&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;1， 在进入execution context时（还没执行任何代码），function declaration就已经在第2步创建函数实例起来了；而function statement属于第3步，而且初始值是undefined，要到执行这行代码时，函数才会被创建起来。&lt;/p&gt;&lt;p&gt;2， function statement不会覆盖同名的function declaration。&lt;/p&gt;&lt;p&gt;验证一下：&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/img/hongxuan20/201009/2010091420235083.png" alt="" /&gt;&lt;/p&gt;&lt;p&gt;如图：A可以先用再声明；B则不行。B在执行到最后一句之前，都是undefined。但是B这个属性是一开始就在的：&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/img/hongxuan20/201009/2010091420331325.png" alt="" /&gt;&lt;/p&gt;&lt;p&gt;（this是什么？因为上面的代码是执行在global context中，B属性应该创建到global object身上，也就是this。下篇会详述this。）&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;三部曲中说了，function declaration会覆盖同名的1里的参数，所以它是varaible object里的第一等公民：&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/img/hongxuan20/201009/2010091420440267.png" alt="" /&gt;&lt;/p&gt;&lt;p&gt;而function statement不会。&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/img/hongxuan20/201009/2010091420441673.png" alt="" /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;闭包的创建时机&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;对于开头的这段代码：&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;!--&lt;br /&gt;&lt;br /&gt;Code highlighting produced by Actipro CodeHighlighter (freeware)&lt;br /&gt;http://www.CodeHighlighter.com/&lt;br /&gt;&lt;br /&gt;--&gt;&lt;span style="color: #0000ff;"&gt;function&lt;/span&gt;&lt;span style="color: #000000;"&gt; Outer(){&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;var&lt;/span&gt;&lt;span style="color: #000000;"&gt; x &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;1&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;function&lt;/span&gt;&lt;span style="color: #000000;"&gt; Inner(y) {&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; x &lt;/span&gt;&lt;span style="color: #000000;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt; y}; &lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; Inner;&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;然后执行&amp;nbsp;&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;!--&lt;br /&gt;&lt;br /&gt;Code highlighting produced by Actipro CodeHighlighter (freeware)&lt;br /&gt;http://www.CodeHighlighter.com/&lt;br /&gt;&lt;br /&gt;--&gt;&lt;span style="color: #0000ff;"&gt;var&lt;/span&gt;&lt;span style="color: #000000;"&gt; inner1 &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; Outer();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;回忆一下这时会发生什么？&lt;/p&gt;&lt;p&gt;会进入一个新的&amp;ldquo;执行上下文&amp;rdquo;（Outer Context），创建OuterVariableObject（有x和Inner属性），放到OuterScopeChain的最前方。而且会创建Inner函数，&lt;strong&gt;创建时把当前Scope Chain作为Inner函数的[[Scope]]属性&lt;/strong&gt;。这是重点。&lt;/p&gt;&lt;p&gt;创建起来的Inner函数被inner1变量引用，Inner有[[Scope]]属性，引用了OuterScopeChain，即[OuterVariableObject, global object]，而OuterVariableObject又引用了局部变量x。&lt;strong&gt;所以inner1变量就对Outer函数体内的局部变量x有间接的引用。内部函数对外部函数的变量有了引用关系&amp;mdash;&amp;mdash;闭包就是这时产生的。&lt;/strong&gt;&lt;strong&gt;每次对外部函数的调用，都会产生一次闭包。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;garbage collection&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;很多人都听过闭包容易引起内存泄露。为什么呢？因为如上所述，inner1变量对x有间接引用，而inner1是声明在global context下的一个变量，它在global context下随时可以被用，那么JS的垃圾回收器就不会回收它（inner1），当然也就不会回收它所引用的x&amp;mdash;&amp;mdash;直到退出global context，也就是我们关掉网页的时候。&lt;/p&gt;&lt;p&gt;这就是文章开头两种调用方式的区别：方式1，x在关掉网页前一直不能被回收；而方式2，x会被回收。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;后续文章将介绍闭包的用处，和this关键字（比我想象的复杂）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/CaiAbin/aggbug/1826287.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/CaiAbin/archive/2010/09/14/1826287.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry></feed>
