2009年5月18日星期一

鹳·垃圾站

剖宫产有一个好处,就是如果有一天孩子问妈妈:“我是从哪里来的呀?”,妈妈只要在肚子上的那条“遗址”上一指就非常明白了。
估计像我这样年纪的人,小时候问父母这个问题所得到的答案大多都是“拣来的”,不同的只是拣的地方不大一样罢了。我是在拉圾站被拣来的。我有个朋友是在垃圾箱里拣的。据说她们家门口有一年换垃圾箱,她还跟在后面边跑边喊:“别拿走!那是我出生的地方!”
在西方文化中,“白鹳”(White stork)用来像征生孩子这件事。如果有小孩问:"Where did I come from?",她妈妈或许会说: "The stork brought you to us"。刚刚出生的小孩儿在前额附近会有一条红印,在英语里被煞有其事地称为“stork bite”。其实是婴儿的静脉在发育中,通常18个月内就消失啦。
最后插播一则广告:我老婆在淘宝上开店,卖小宝宝的衣服啊什么的,大家多支技她一下吧:)

看上去现在正是一个"stork season",衷心祝愿老婆大人生意兴隆!


2009年3月22日星期日

另类穿越小说·电子生涯

这次不知道是不是火星了。
前几天在查资料的时候偶然间发现了一部很另类的网络小说《电子生涯》。之所以说它另类,是因为它的作者显然是一个程序员,而似乎其读者除了程序员也不大可能会有其它人了。小说描写2004年的一个程序员被雷劈到了1966年的美国,然后开始在计算机行业创业的穿越故事。里面充满了计算机软件、硬件,软件工程等术语,以及计算机及其相关数学的发展史。篇幅很长,真不知道作者写这种东西的动力在哪里。其故事有一定的趣味性,但太专业了,除非你对“计算机革命史”真的有兴趣,否则一定找不到太多阅读的乐趣。呵呵,说到计算机革命史,想当年在学校的宿舍里熄灯以后我也是个布道者啊:-)

我是在google“瀑布开发模型”,“Winston Royce”时偶然发现这部小说的,为了写那篇“倒霉的温斯顿•罗伊斯”。后来我读了小说的那个部分。主人公范含想要根据记忆(他的记忆非常准确,因为他的电脑被雷劈到脑子里去了)组织人力在1967年开发现在很流行的数学计算软件matlab,于是他要找到一种开发模式。小说里依然固执的称罗伊斯为瀑布开发模型的创始人,这也是没办法的事,众口铄金啊。里面还比较客观的分析了各种开发方式的利弊,指出瀑布模型是一种不合理的开发方式。我发现作者的确是行业中的明白人,因为他理所当然的帮小说主人公选择了瀑布模型做为他这个项目的开发方式。这样做是非常合理的,因为:
1.所有的风险都是已知的,范含作为一个现代过去的人,对于matlab用都用过了,对于可能的风险一清二楚;
2.所有的需求都已经非常明确,不会有新的,或者变化的需求了;
3.没有市场压力。虽然是个很有市场的软件,但在那个年代,没人指望马上用上这个东西,也没人在做同样的东西;

不知道在现实世界里有没有能满足上面任何一项要求的项目。如果有,那么不用瀑布模型来开发就是傻子。嘿嘿,如果下次有人强调瀑布模型的好处,我就可以说:“大师,你穿越了”。

2009年3月21日星期六

在线HTML编辑器 - Tabel

很多天没写博客啦,因为这几天一直忙着在写一个在线编辑HTML的小工具,用javascript。好多年没用啦,写起来很吃力。现在总算有了个差不多的版本了。嵌入了这段javascript代码的本地HTML文件可以直接在IE里进行编辑并保存。主要是一些表格操作,因此我给它起名叫“tabel”。项目的地址为:

这个工具对你可能没什么用,除非你使用Robot Framework 。Robot Framework是由诺基亚西门子网络(NSN)发起的开源软件,是一个不错的测试自动化工具。正确的使用robot framework可以写出可读性很强的测试脚本,这些测试脚本甚至可以取代需求文档。可以很方便的使用Python或java语言对其进行扩展,适用于从大型嵌入式、分布式系统的功能测试,到有图形界面的桌面系统的用户测试等很多测试领域。NSN很明智的把robot framework开源。因为软件开发中所使用的工具的成败就在于这个工具的成熟度和比软件工程还要长的生命周期,而想要做到这一点,把工具开源是一个好办法。如果它真的是个好工具那么自然会有很多人用它,并不断的改进。甚至在我自己没事时随便写写程序也会使用这个framework进行一下功能测试。

问题是robot framework的test case文件是用HTML写的,里面都是一些表格。要编辑这些test case其实只用到很少的一些HTML编辑功能,如果用word或者open office有点大材小用,而其它的编辑工具似乎也都不太合手。在开发IDE如Eclipse里目前还没有能用的开源所见即所得的HTML编辑插件。于是我就写了这个东西。在打开html文件以后,直接就可以进行编辑和保存,也不用写什么eclipse插件了。

目前,这个小工具还只支持IE(一部分因为IE安全性差)。接下来我还想实现如copy/paste等功能。

希望这个小东西能对和我有一样需求的人有用。

2009年3月17日星期二

解耦合手段之七:Liskov代换原则

周二,美国计算机协会宣布,来自麻省理工学院的女教授芭芭拉·利斯科夫(Barbara Liskov)获得本年度图灵奖以及25万美元的奖金,她的贡献是让计算机程序更加可靠、安全和易于使用。(新闻
Barbara Liskov 是美国第一位获得计算机博士学位的女性。她的研究为模块化编程和面向对向编程的产生奠定了基础。除此之外,Barbara的另一个为人们所熟知的供献是她定义了Liskov substitution principle ,我们且称之为“Liskov代换原则”。它为子类型化或者说面向对向中的“继承”定义了重要的原则。
Liskov代换原则的原文是挺难看懂的:
Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T.

幸好有Bob大叔做好事,早就写了一篇相对容易理解一点的文章来解释Liskov Substitution Principle:
使用指向基类(超类)的引用的函数,必须能够在不知道具体派生类(子类)对象类型的情况下使用它们。

举一个例子:
有一个基类Rectangle(矩形)。按常理来说,正方形(Square)也是一个矩形,那么Square类可以从Rectangle类继承而来。而事实是这样的么?假设有一个函数:
void g(Rectangle& r)
{
   r.SetWidth(5);
   r.SetHeight(4);
   assert(r.GetWidth() * r.GetHeight()) == 20);
}
很显然,这个函数对于传入的基类Rectangle变量来讲是正确的。然而如果传如入的变量是Square类型就不正确了。因此,Square与Rectangle之间的继承关系违反了Liskov代换原则。
那么为什么会这样呢?从常识(或几何定义)的角度来看,正方形是一个矩形;然而从行为的角度看,正方形不是一个矩形!因为正方形的行为与矩形有所不同。如果一定要让正方形继承于矩形,那么正方形的行为必须修改矩形的行为。可以看出,这同时也违反了“开闭原则 ”。

Liskov代换原则与“契约式设计(Design by Contract)”也有很紧密的联系。在Design by Contract的模式下对于一个类的方法有precondition(先决条件)和postcondition(后置条件)两个概念。Precondition是为了使用这个方法而必须事先满足的条件;Postcondition是这个方法必须保证它被使用以后成立的条件。由Liskov代换原则我们可以得到:
.子类不应要求比基类更高的precondition。
.子类不应弱化基类的postcondition。
例如上面的Rectangle类里SetWidth方法的一个postcondition是矩形的Height不应发生变化。这个postcondition在Square中就不能满足。

发现中文维基中没有Liskov代换原则的条目,把它补上:


2009年3月5日星期四

倒霉的温斯顿•罗伊斯

温斯顿•罗伊斯(Winston Royce, 1929–1995)地下有知,一定会感到非常懊恼。因为他被人们公认为是瀑布开发模型的创使人。

几乎所有的讲软件工程的书或文章中提到瀑布模型时都会加上引用“... waterfall model [Royce70] ...”。这篇Royce写于1970年的文章被称为“瀑布模型的摇篮”:



如果读一读这篇文章,你会发现,这些引用的人并没有真的花点时间去读一读它。因为Royce的这篇文章只是想说明瀑布模型并不能工作。在文章的最后Royce甚至指出应该采用迭代的方式进行软件开发。


瀑布模型的一个简单的例子就是:


需求分析->可行性分析->高层设计->详细设计->编码->底层测试->系统测试->发布->维护


这样的一个顺序开发模式。


瀑布模型的流行不仅因为他看上去很合理,另一个主要的原因是它受到了美国国防部(Department of Defense)的追捧。后者要求它的合同商采用瀑布模型进行开发。美国国防部的指导精神随之影响到了北大西洋公约组织(NATO),瀑布模型想不流行都不行了。其结果,对美国国防部而言是导致了其超过50%项目的失败。然而与Royce同期甚至更早的软件科学家们更多的提倡迭代开发,如冯·诺依曼。


与瀑布模型同出一辙的“V模型”,其实质都是一样的,是一种理想化了的软件开发模型。对于可预测性强的生产制造业,如生产汽车,的确很适合这种生产模式。然而对于具有很强的不可预测性的新产品开发,如生物学研究、新药的研发等,就不合适了。同软件开发一样,这些研发工作都更适合用迭代的模式来“生长”出来。




在中文维基中“瀑布模型”条目中对Royce对这个模型的供献描述并不准确,去修改了一下:

百度百科里仍是把Royce作为瀑布模型的提出者而不加解释,由它去吧。
另,美国国防部已于1994年放弃了瀑布模型。


2009年3月4日星期三

Craig Larman来访小结

Craig Larman这次来我们这里一共三个星期,期间我花了一些时间参与大师Coach研发部门的各项活动,并与大师讨论了一些关于Agile和软件工程的问题。感觉学习到了不少东西。

生活中的大师是非常浪漫的,这不是本文的重点。在工作中的大师是非常严谨的,常常引用一些已有的论文或研究结果,当谈到个人观点时总是很小心。

大师比较强调工程师的软件设计能力。当然,他也反对BDUF(Big Design Up-Front),但他认为,一定的事先Design还是需要的。例如在一个迭代周期伊始,team应该组织一个Design Workshop,来讨论和决定一些Design的大体状况。当然大师也重调不断的refactoring。大师说:“TDD can also make very bad design.”他要说明,仅依靠某种开发方式是不会自然而然的得到好的design的,关键是要有设计的能力。大师甚至引用禅宗的道理来比喻软件设计(http://terry-yinzhe.spaces.live.com/blog/cns!92560BAA05230C71!315.entry)。





大师还讲解了一些需求分析的方法。这些方法多是在他帮不同的team组织backlog grooming与sprint planning I时引入的(backlog grooming与Sprint Planning I是Scrum中的两项活动,这两项活动的一个共同目的是澄清需求)。并且大师给出了切实可行的ATDD方式,包括:
1.如何用user story的方式来描述需求
2.如何把user story拆分成合适的大小
3.如何把user story用use case、表格或UML的方式来详细描述
4.如何把前面需求分析的结果用workflow或者data table的方式用pseudo test script来描述等。
在sprint中间(或者说一个迭代周期内)就以这些test script的通过为目标进行开发。大师强调“需求”与“设计”间的区别,要求需求分析一定要尽量面向最终用户。(http://terry-yinzhe.spaces.live.com/blog/cns!92560BAA05230C71!376.entry

大师强调,瀑布开发是限制软件交付能力的主要因素。但他引用COCOMO模型来说明,在一个项目中人的因素最重要(http://terry-yinzhe.spaces.live.com/blog/cns!92560BAA05230C71!407.entry)。在批评有些人对Scrum中有“所有的人能做所有的事”这种理解时明确的指出,我们要的是specialist,不是generalist,当然最好是generalized specialist(http://terry-yinzhe.spaces.live.com/blog/cns!92560BAA05230C71!355.entry)。


大师还谈到了应用agile的目的。他说,agile的目的不是提高生产力,或者提高质量,agile就是为了agile,“The purpose of adopting agile is to be agile”。Agile的目的不是用更少的人做更多的事,并且做得更好。这些目的都是通过提高人的能力或者其它软件工程手段得到的。Agile的目的是关注客户价值,提高交付能力和灵活应对改变等。从而,使企业有机会及时为客户创造更多价值,并不断改进。因此他说,“be agile”比“do agile”更重要。也正因此,大师也说,与其学会agile的方法,不如学会agile的思想。大师称之为thinking tools,主要包含了:System Thinking、Lean Thinking、Queuing Theory等。这些思想方法全部包含在他与Bas Vodde合著的《Scaling Lean And Agile - Thinking And Organizational Tools》中。

当然,大师的言行不止于此。最后引用他给我在他的《Applying UML and Patterns》这本书上的留言:
“May all your objects be suitably coupled.”
-- Craig Larman


软件成本估算模型COCOMO

这两天研究了一下软件成本估算模型COCOMO,顺便在中文维基里添加了COCOMO条目:

http://zh.wikipedia.org/wiki/COCOMO






完全是从英文的wikipedia里翻译来的。当然,要了解COCOMO恐怕要读完那本《软件成本估算:COCOMO Ⅱ模型方法》才行。不过我并不是真的对这种枯燥而又形式化的东西有兴趣,只是想了解一下COCOMO模型中对影响软件开发的各种因素的评估,因此,我想简单了解了下也就够了。

COCOMO模型(Constructive Cost Model)的中级公式引入了一系列影响开发成本的因子,包括:


  • 产品属性
    • 软件可靠性需求
    • 应用数据库的大小
    • 产品复杂度
  • 硬件属性
    • 运行时的性能约束
    • 内存约束
    • 虚拟机稳定性
    • 回复时间的需求
  • 人员属性
    • 分析能力
    • 软件工程能力
    • 应用经验
    • 虚拟机的经验
    • 编程语言经验
  • 项目属性
    • 采用的软件工具
    • 采用的软件工程手段
    • 对开发时间的要求



把所有的因子相乘就会得到总的“工作量调整因子(EAF)”。我对这些因子的最大值和最小值的比率做了统计。似乎结果说明就总体分类而言,人员因素对软件开发的影响远高于其它因素。

就具体影响因素而言,“产品复杂度”似乎是最大的单一对软件开发影响的因素,其次是人员的分析能力和软件工程能力。

因此似乎可以得到这样的结论:对软件开发影响最大的是人的因素,其中人的分析能力与软件工程能力尤其重要。产品复杂度也是影响软件成本(工作量)的重要因素。 同时,值得注意的是“对开发时间的要求”,即软件项目的工期压力对软件工作量的影响微乎其微。这也说明了,管项目的人催得再紧,对其结果也不会有什么大的改变。
Craig Larman在引用COCOMO模型时也指出,人的因素远大于其它因素,包括排名第二的产品复杂度。并且他指出,这些因子是在同一种软件开发模式下得到的(例如瀑布模式),软件开发模式(例如采用迭代开发)对软件开发成本的影响比这些因素都要大。








2009年2月25日星期三

对底层的需求就是高层的设计么?

“对底层的需求就是高层的设计。”


我把这句话和很多软件方面的专家,系统方面的牛人还有软件开发的经理确认过,几乎所有的人都不约而同的认同这个说法。这句话听起来似乎有道理。如果上层是这样设计的,那么下层就要满足这些设计,这样这些上层的设计就成了下层的需求。


然而我认为,这是个致命的错误


“为领导服务还是为人民服务?”


Requirement和Design是软件开发的两个维度,Design用来承载Requirement的实现。就像体积和重量一样,两者不能互相转换,设计也不能变成需求。需求经过分析,可以被细化,分解。并且需求分析也可以有很多选择。但无论如何细化,如何分解,其结果还是需求。同样的,设计也有高层架构和底层实现,也有很多可选的方案。但不论是多么高层次的设计,它总还是于设计,成不了需求。


之所以说这是个致命的错误,是因为正是因为这个错误的理念,软件的设计者在不断的人为创造“需求”。架构的设计者做了大量的BDUF(Big Design Up-Front,预先的大量设计),然后把它们交给下面的工程师当做需求去实现。这时大部分的工程师工作的目的已经不再是满足客户需求了,而是满足这些人为创造的所谓“需求”。这样一来,一方面,产生了大量无用的代码及其相关工作;另一方面,如此设计并不一定能满足最终需求,往往要等到最后的集成测试才突然发现其实设计没有满足需求,这时往往已经大势晚矣。


在我们动手设计之前,需求分析应该已经完成了,否则设计什么东西?当然,除非是为了设计而设计。在迭代开发的模式下,在进入每一个迭代之前,需求一定已经是清楚的,并且在这一个迭代中要花时间为今后的迭代进行需求分析。现在流行的描述需求的方法是用user story,它有这样的格式:



做为<某用户角色>,我需要<某功能>,因为<某原因>。


这样,需求就明显的和设计区分开了。


设计应该是“进化”式的,随着需求不断的被实现,系统的架构与设计逐渐的浮现出来。而不是先有了架构再向里面加东西。软件设计和建筑设计不同,对于软件这样复杂而充满未知的系统,其设计只能是“长(grow)”出来的,而不是“计划(plan)”出来


诚然,有时系统的设计会影响到需求的细分。举一个例子:一个软件需要从网络自动下载网页进行处理,这个需求比较大,需要把它分解开才能进行迭代开发。那么根据系统架构,可以把需求分解为,1.从模拟接口(stub)下载网页进行处理;2.从真正的网络下载网页进行处理。这样做的好处不单单是让需求变小了,可以分成两次做甚至由不同的team来做。并且,它解开了网页处理和网络之间的依赖关系,方便实现依赖倒置







感谢Craig Larman的耐心指正,让我理清了对需求与设计的混乱概念。总之,需求就是需求,设计就是设计。


2009年2月19日星期四

天山童姥与大蝌蚪

金庸小说《天龙八部》中有一个奇怪的女人,名叫天山童姥。之所以说她奇怪,并不是因为她武功高强性格变态,而是因为她身材永如女童。这并不是一篇讲萝莉的文章,如果你是对那个话题有兴趣再读下去可能会浪费你宝贵的时间。
因为我又要扯到软件设计上来了。
我奇怪的是,当她练到更多的武功,需要更多的feature的时候,这个老萝莉却停止了架构的变化。比方说蝌蚪吧,长成那个样子是为了适应当前的需求——在水中生活。当需求发生变化,需要新的feature时,蝌蚪逐渐的发生了架构上的变化——变成了青蛙——两栖动物。设想蝌蚪仍用原来的架构来应对增加的新需求,像个鼻涕虫一样趴在荷叶上可能没问题,想跳起来就有难度了。
软件当时的架构往往是为了支撑当时的需求的,当不断有新的需求增加进来时架构要随之调整。可是我们的软件模块有时就像老萝莉或者大蝌蚪一样,用一成不变的架构,笨拙的应付着新的功能。
有点意思的是,我很久以前曾经工作过的一个软件模块,是在更早以前用一个当时著名的芬兰美女的名字命名的,她那年20岁。十六年过去了,当年的美女依然美丽动人,而且有了洽当耦合的结果——已是孩子她妈了,相夫教女,非常美满。而那个模块如今却已是臃肿不堪了。早就违反了Single  Responsibility Principle,并且不断的违反着Open/Closed Principle,十六年间和无数其它模块发生了理也理不清的耦合关系。不知道是不是该把美女的名字还给人家,改名叫做天山童姥的好。
Craig Larman对我的大蝌蚪的比喻提出了批评(我当然没办法把天山童姥的事和他说,说了他也不懂)。他说蝌蚪是要注定变成青蛙的,但软件需求的变化却是不可预知的。他更喜欢园丁的比喻——园中的植物不断的生长,就像系统不断的成长一样。园丁们要不断的修剪,不能让花园变成杂乱的荒园。他说,这个比喻将会出现在他的下一本书中。

附上美女的照片,名字就不提了。为了强调本文的重点,把那个模块描写的比较不堪。其实和事实相比有很大的夸大成份。并且一直一来围绕这个模块工作的同仁们都很努力,让我多多少少有点心里不安。



2009年2月18日星期三

Suitably Coupled

“May all your objects be suitably coupled.”
                  -- Craig Larman

这是Craig Larman帮我在他的书上签名时的留言,并说当我读完他这本《Applying UML and Patterns》以后会有更多的体会。看来我要先放下手上的《Head First Design Patterns》了。不过《Head First》图文并茂,放下这么一本书而去读另一本满是小字的大厚书真是件痛苦的事。
不知道Craig怎么知道我最近在研究coupling和cohesion?但我想这只是个巧合吧。“suitably coupled”的确是一个很好的祝愿,不论是对software object还是生活中的方方面面。没有耦合,软件只是一盘散沙,实现不了任何完整的功能。没有耦合,人只是孤零零的个体,失去了生活的意义。而过度的耦合让软件难于维护。过度的耦合让生活陷入一片混乱。
没有耦合就像光棍没老婆,又没工作,没爱好,没亲戚,没朋友,然而。。。。。。
如果有一个老婆,又偷偷的去和N个女人有关系统。。。。。。
如果有一份工作,又在上班的时间炒炒股票、读读小说。。。。。。
如果有几个同事,他们同时又是你的亲戚。。。。。。
如果你的朋友要贷款,又要找你来担保。。。。。。
如朋友找你担保贷款买你们公司的股票,而你老婆的表弟是你的下属一直在掏公司的墙角,还和你的小秘勾勾搭搭,而小秘就是找你担保的那个朋友。。。。。。

也许我对耦合的理解还很有限。所以我要暂停前面一直在写的“解耦合手段”系列,先读读这本书,然后再来继续。

"通才"还是"专才"

在采用繁捷开发的组织当中,一个经常让人困惑的问题是“我们到底需要通才(generalist)还是专才(specialist)?”

传统的软件开发组织模式中(尤其是大型软件产品)通常按照产品的部件来组织的(component team)。Team中所有的人都是该部件的专才。这种组织方式的核心问题是每个team都是围绕组件工作,而不是真正的围绕customer value。从而产生很多问题,例如过份的设计,专门的测试部门,无法解开的依赖关系,资源的浪费等等。
在繁捷开发中主张功能团队(feature team)。Team是多功能的,他们围绕着完整的customer feature做所有需要的工作,不论完成这个feature需要用到哪些component,哪些技能(需求分析、设计、编码、测试)。并且到底做什么feature是由客户的优先级决定的,而不是team本身的能力范围。这样就可以实现迭代开发。很多人,包括很多宣扬繁捷开发的人,会简单的把这种情况描述为“所有的人能做所有的事”。也就是说把所有的人都变成generalist。
然而事实往往远非这样的简单。
软件产品是复杂的(complex),这种复杂性存在于软件各个组件的细节当中,任何对这种复杂的不敬和无知都早晚有一天会反扑回来,使得软件变得混乱(chaotic)(软件的复杂理论)。我的经验是,在一个大型的软件产品当中寥寥几个胆敢声称“所有的人能做所有的事”的人,要么是天才,要么是受了“达克效应”的影响。
并且,在Capers Jones的研究中发现,“专才”的表现远远超越了“通才”。
看来,又是一个需要中庸之道出马的问题。“专才”或是“通才”也许都不是解决问题的好方法,需要一个平衡点。
在Bas Vodde和Craig Larman合著的新书《Scaling Lean & Agile Development》中有专门关于组建feature team的一章,有得下载。其中给出了很多实用的建议。虽然,我认为Bas本身已经有点落入了“所有的人能做所有的事”的魔道,但也不过是离“中庸”左了一点。
书中提到“generalizing specialist”,即承认specialist的重要性,又鼓励扩展范围,同时关注customer value,尽量提高人员的利用率。并且提出在大型的项目中,按照相近的需求能力范围,以requirement area的方式来进行较高层次的组织。这样,尽量的发挥feature team的能力,减少人力空置的浪费,又减少了不断切换知识的浪费。

从管理的角度来讲,这也有很重要的指导意义。要为新人创造成为某一方面specialist的机会,鼓励specialist扩展技能。同时又要引导员工关注customer value。并不是所有的技能都会永远是创造客户价值的热点,而人的时间和精力是有限的。员工往往希望成为某方面的specialist从而获得职业安全感,这并无可厚非,也有积极一面。然而为了创造更多的客户价值往往需要进行取舍。最终还是要把对员工作的要求提高到“craftsman”的水平上来。

这样就又带来了一个新的管理问题:“要不要事先计划人员的能力分布呢?”如果不计划,就会出现对热点需求缺少准备的问题;如果计划了,就违反了Lean Thinking中“减小库存”的要求(把员工事先学好的能力想像成库存)。关于这个问题,Craig Larman认为,这种事先的计划还是必要的,这是一种必要的付出。然而Bas Vodde却对此有很极端的看法,他认为事先学习和用到了再学是一样的。对于Bas的想法很难理解,下次遇到他要再次好好请教一下,然后来专门来写写这个主题。

最后,不一定恰当的引用Craig Larman对“关注客户价值”的一个比喻:
“Watch the baton, not the runners.”
(解释一下:baton:接力棒,runner:接力运动员。在接力比赛中baton就是我们要deliver的value,提高每个runner的利用率对最后的结果并没什么帮助,每个人只跑一百和每个人都跑400是一样的。)





2009年2月12日星期四

色即是空,空即是色,TDD,Software Design

有幸成为Craig Larman大师的host,在大师来访的这三个星期安排大师的行程,聆听大师的教诲。Craig Larman是全球敏捷方法与软件分析设计领域颇有名气的大师。大师言行非常严谨,讲究研究与调查,从不空谈所谓“opinion”。这里几天打算随笔罗列些大师的名言,作为记录。其中自然会隐去一些和公司产品相关的内容,以免不必要的麻烦。

这天和一些在用TDD(Test-Driven Development)方法进行开发的工程师做讨论,说到了软件设计的问题。我们是否应该在开始实现,或者说开始写代码之前做Design?
Craig Larman引用禅宗至理(Zen Buddhism)来比喻软件设计:
“Before enlightenment, mountains are mountains and rivers are rivers. During enlightenment, mountains are no longer mountains and rivers are on longer rivers. After enlightenment, mountains are still mountains and rivers are still rivers.” (高深吧-_-!)

回去查了一下,“enlightenment”即佛家所讲的“悟”,孙悟空的悟。汗一个先。我想那意思就是说:我们刚接触Software Design时,感觉它也就是那么点事情,很自然。可当我们悟出一些软件的道理后,忽然发现Design已经不是Design了,变得非常复杂让我们无能为力,所谓“色即是空”。这时我们不是过份Design就是没了Design。等我们大彻大悟之时,发现,Design其实还是那么点事情,即所谓“空即是色”。这似乎与前几天在电视上见到的刚刚去逝的禅宗大师圣严法师的“本来面目”有异曲同工之妙:

过份的设计,或者叫BDUF(Big Design Up-Front),往往是有害而无宜的。BDUF一方面否定了软件设计是演化而来的,容易产生僵死的框架;另一方面为现在用不到的功能花精力,违反了lean的原则。例如冗长的软件设计文档、长达三个月的软件设计而不写一行代码,等等。都是我们在领悟软件工程的过程中附会到Design上去的东西。
而完全依靠测试驱动的开发方式,或者用户需求驱动的开发方式也不会自然而然的产生好的软件设计。如大师所言:“TDD can also generate really bad design”。因此,依据设计原则进行合理的Design Up Front也是必须的。

所以我想大师是要我们在纷乱的敏捷方法、软件理念之余还原软件设计的本来面目。例如在开始实现一段功能前开个简短的Design workshop,画画图,写一个快速原型或者伪代码等等。

另外,也有人讲“Before enlightenment chop wood and carry water. After enlightenment, chop wood and carry water.”:-)

2009年2月8日星期日

解耦合手段之六:并发






什么?并发也是一种解耦合的手段么?
是的。
一般的程序都是顺序执行的,在这种情况下,程序的执行和执行的时间是相互依赖的或者说是耦合的。当这种耦合成为一种阻碍时我们就需要并发(Concurrency)。
"Concurrency is a decoupling strategy. It helps us decouple what gets done from when it gets done."
 --Clean Code, Robert C. Martin

一个很常见的错误观点是并发总是能够提高性能。这在大多数情况下,尤其是单核系统中往往是不成立的,就像1+1+1+1+1和1*4都等于4一样。
我能想象到的采用并发的理由总结一下有以下几点:
  • 解开what和when的耦合以后,程序的结构更容易被理解。例如用一个大的循环来处理所有用户的聊天请求,就不如用不同的线程来处理每个用户的请求那么清晰,而且也更方便扩展对用户的服务。
  • 需要提高响应输入的速度,或及时的输出中间结果。
  • 需要利用多核,多CPU,多个机算机甚至网络的计算能力。
并发就需要用到多线程或者多进程。而在多线程或多进程编程的时候我们往往又需要或不得不把其它耦合性带回到程序里去。例如对共享数据的操作需要互斥,有些业务逻辑要求有顺序,同时还要避免可能发生的死锁。因此,结果可能是我们引入了更多的耦合性。例如前面提到的聊天程序。因为每个请求都很简单,处理得很快,因此即使是上千用户一起使用单一线程的服务也是感觉不到有什么问题。然而采用多线程处理就要引入很多其它负载,反而可能降低性能或引入更多问题。但如果每个用户的服务内容很复杂,单一线程又会变得很难理解或很难处理。
这个时候,合理的选择串行和并发以及并发的手段就成为降低耦合性的关键。如果我们不得不选择并发,那么提高每个进程的内聚性仍是降低整体耦合性的有效手段。

现在有很多并发语言,例如传统的电信行业中使用的SDL,还有新的语言如Erlang, Stackless Python。它们往往会提供高效的进程调度机制,方便的进程间通信手段以及合理的互斥方法。但无论使用什么语言,在多么先进的框架下工作,并发编程都是非常复杂的,并且和串行的编程所需要用到的设计策略往往是完全不同的。


2009年2月5日星期四

对DRY原则的补充






为了证明代码重复对软件质量的影响,我对一些真实软件中的代码进行了分析。

分析的工具为PMD。PMD是开源软件,主要功能是对Java进行静态分析以找出bug。它的一个功能是CPD(Copy/Paste Detector),也就是找出重复代码,也部分支持C语言。以下是我对一些C语言程序进行分析的结果。由于CPD工具本身的限制,不能很完美的找出C语言中的全部重复,例如对于常量的改动无法识别。因此代码中实际的重复应该比下表中的值要大一些。不过相信做为相对值还是有意义的。
















































































Source Code NLOC LOCdup60 LOCduP30 RATE60 RATE30
SW1(Too many bugs found) 44325 9314 26504 21% 60%
SW2(Too many bugs found) 39164 7264 19010 19% 49%
SW3(Too many bugs found) 109807 18903 49264 17% 45%
SW4(Many bugs found) 20050 2881 6910 14% 34%
SW5(A few bugs found) 12471 270 2141 2% 17%






Python1.5(Core) 19418 1072 3023 6% 16%
Python2.5(Core) 35797 1656 6441 5% 18%
Python3.0(Core) 40737 3460 9076 8% 22%
Apache(server) 18693 1114 2553 6% 14%
其中:
NLOC:净代码行数。
LOCdup60/30:超过60/30个token重复的代码行数。
RATE60/30:重复代码占总代码的比例。

其中的SW1~SW5是来源于同一个产品的5个不同模块(其中SW5的bugs found数目还有待收集)。通过比较可以看出,无论是RATE60还是RATE30都和代码中BUG的数量有一定的相关性。代码重复得越多就越容易产生BUG。当然,BUG的产生可能有很多原因,代码重复只是众多的原因之一,但它显然是很重要的原因。代码的过度重复往往意味着缺少谨慎的设计和及时的重构,这种代码往往是在匆忙之中或压力之下产生的,也可能是写代码的人不够投入。维护过度重复的代码难度很高,而且再次引入新BUG的风险会高到超出100%(这是真的,平均每改一个错误,引入两个错误)。

我同时也对一些流行的开源软件进行了同样的统计。Python和Apache都是相当流行的开源软件,它们的成功必然依赖于其较高的可维护性。这里我只分析了他们的一部分核心代码,一方面是为了方便,另一方面也是为了和前面的软件模块的大小有可比性。可以很容易的看出,Python和Apache的代码重复是相对很低的。
其中Python1.5、Python2.5和我取的Apache源码都是久经考验的成熟版本,他们的RATE60都在6%左右,RATE30也只有16%左右。Python3.0最近才刚刚发布,代码是原来的114%,重复代码却是原来的209%!(这里忽略了Python的另一个主要版本2.6,因为2.6也是刚发布不久,而且主要目的是为了向3.0过渡)可见,Python3.0的质量让人有点小小的担心啊。





2009年2月4日星期三

解耦合手段之五:Dependency Inversion Principle

通常,我们认为上层模块理所当然的依赖于下层模块的服务,例如业务模块对数据库的依赖,通信模块对网络的依赖。按照这种逻辑,如果要设计一款电视的话,也许会是这样:
class TVSet{
   private Program program = LocateProgramFromRadiobBroadcast();
   void Play() {
       Show(Program);
   }
};
 因为电视依赖于节目源,所以电视自己创建了节目源,或者说自己来定位节目源。在上面的例子中是定位一个无线广播节目。但这样的电视有个问题,它只能播广播节目。生产厂商要对电视进行测试也要依赖于电视台的广播。因此,我们的电视通常都不是这样的,而是专门有接口,由使用者提供节目源:
class TVSet{
   void Play(Program program) {
       Show(Program);
   }
};
这样,不论是测试用的简单信号发生器,还是DVD,电脑都可以通过电视播放了。
这种依赖关系的倒置被称为Dependency Inversion,又被称为控制反转(Inversion of Control)。这种原则又被戏称为“好莱坞原则”——“Don't call us, we'll call you.”。Uncle Bob是这样描述DIP(Dependency Inversion Principle)的:
A. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
B. 抽象不应该依赖于细节,细节应该依赖于抽象。

依赖倒置使得系统的耦合性降低,模块的可重用性提高,并能提高系统的可测性和可用性。尤其是对单元测试有至关重要的作用,因为这样就可以用mock object来代替真实的对象。
实现依赖倒置的技术被称为“依赖注入”(Dependency Injection)。通常,当一个模块需要一种服务的时候,它要么直接持有对此服务的索引,要么通过一个服务定位器(ServiceLocator)来获得对此服务的索引。而采用依赖注入的方法,则是从外界传入对此服务的索引。例如通过传入参数,或者回调函数来设置服务。
Martin Fowler把依赖注入的方法分为三类:
  • Interface injection
  • Setter injection
  • Constructor injection

甚至有很多框架工具可以支持我们方便的在代码中实现Dependency Injection,例如google Guice。

2009年1月31日星期六

解耦合手段之四:Law of Demeter

迪米特法则(Law of Demeter),又称“最少知识原则”(Principle of Least Knowledge),也是主要针对面向对象思想的,可以简单的概括为“talk only to your immediate friends”。

具体来说,在面向对象的方法中,一个方法“M”和一个对象“O”只可以调用以下几种对象的方法:
1. O自己
2. M的参数
3. 在M中创建的对象
4. O的直接组件对象

Law of Demeter来源于1987年荷兰大学的一个叫做Demeter的项目。Craig Larman把Law of Demeter又称作“不要和陌生人说话”。在《程序员修炼之道》中讲LoD的那一章叫作“解耦合与迪米特法则”。可以看出迪米特法则是非常流行的解耦合手段之一。关于迪米特法则有一些很形象的比喻: 
如果你想让你的狗狗跑的话,你会对狗狗说还是对四条狗腿说? 
如果你去店里买东西,你会把钱交给店员,还是会把钱包交给店员让他自己拿? 

让店员自己从钱包里拿钱?这听起来有点荒唐,不过在我们的代码里这几乎是见怪不怪的事情了:
class Clerk {
    Store store;
    void SellGoodsTo(Client client)
    {
        money = client.GetWallet().GetMoney();//店员自己从钱包里拿钱了!
        store.ReceiveMoney(money);
    }
};

在《Clean Code》一书中,作者找到了Apache framework中的一段违反了LoD的代码:
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
这么长的一串对其它对象的细节,以及细节的细节,细节的细节的细节......的调用,违反了迪米特法则,增加了耦合,使得代码结构复杂、僵化,难以扩展和维护。

在《重构》一书中的各种“Bad smells of code”中有一种“smell”叫做“Feature Envy”(依恋情结),形象的描述了一种违反了LoC的情况。Feature Envy就是说一个对象对其它对象的内容更有兴趣,也就是说老是羡慕别的对象的成员、结构或者功能,大老远的调用人家的东西。这样的结构显然是不合理的。我们的程序应该写得比较“害羞”。不能像前面例子中的那个不把自己当外人的店员一样,拿过客人的钱包自己把钱拿出来。“害羞”的程序只和自己最近的朋友交谈。这种情况下应该调整程序的结构,让那个对象自己拥有它羡慕的feature,或者使用合理的设计模式(例如Facade和Mediator)。
店员的例子如果是这样就会好一点:
        money = client.GetMoney();//客户自己从钱包里拿钱
或者根本不用那么麻烦:
    void SellGoods(Money money)
    {
        store.ReceiveMoney(money);
    }

这一法则不仅仅局限于计算机领域,在其他领域也同样适用。据说美国人就在航天系统的设计中采用这一法则。

2009年1月29日星期四

解耦合手段之三:Open/Closed Principle

这又是一个面向对向的方法。Open/Closed Principle,可以译成“开/关原则”,“开-闭原则”或者“开放封闭原则”,是说“一个软件实体应当对扩展开放,对修改关闭( Software entities should be open for extension,but closed for modification.)。”这里所说的“实体”可以是函数、类、模块等等。


举个我所见过的比较差的例子吧。我们做的产品是由很多单板组成的分布式系统,为了适应不断提高的数据传输要求,其接口板经常要更新换代,不断升级。每次有新的接口板硬件以后,这块板上的几个程序模块都需要进行修改,甚至其它单板上的程序也要改动很多处。这种改动往往是在函数级别上的。系统的这种高耦合性给我们带来了很多麻烦,即费时又容易出问题。显然,我们的系统设计一定程度上违反了OCP,因为这个系统的模块都对修改开放,对扩展封闭。如果遵守了OCP,如果一段程序自身的功能没有BUG的话是不会被打开修改的,并且如果接口没有发生变化也不会对接口两端都进行改动的。

还是在Uncle Bob的著作《Agile Software Development Principles, Patterns, and Practice》(PPP)中有对于这个原则的详细描述。实现OCP原则,往往要用到继承和抽象接口。这篇文章 是和《PPP》中的内容一样的。

通常如果我们只是为了满足眼前的功能所写出的代码是不会自然而然的满足OCP和前面提到的SRP的,这就需要我们不断的对代码进行重构(Refactoring)。Martin Fowler在他的著作《重构-改善即有代码的设计》(Refactoring Improving the Design of Existing Code)一书中讲到了许多“Bad smells of code”——代码中的坏味道。如果你“闻”到你的代码中有他提到的“发散式变化(divergent change)”或者“散弹式修改(shotgun surgery)”那么你的代码可能违反了SRP或者OCP,需要重构。






2009年1月28日星期三

解耦合手段之二:Single Responsibility Principle

在面向对象的方法中,Robert C. Martin引入了Single Responsibility Principle(SRP),即单一职责原则。就是说:所有的对象都应该有单一的职责,它提供的所有的服务也都仅围绕着这个职责。用Uncle Bob自己的话来说就是:“永远不要让一个类存在多个改变的理由”(There should never be more than one reason for a class to change.

多个职责挤在一个类当中就是耦合。例如一个矩形类即要负责几何计算,又要负责在图形界面中画出这个矩形,这就违反了SRP。为什么这样耦合性就高了呢?因为类内部的“内聚性”差了。如果一个类里面所有的方法都用到了这个类所有的成员变量,我们说这个类的内聚性是最高的。如果是一些方法用到了一半的成员变量,而另一些方法用到了另一半,那么这显然是两个不相干的职责被挤到了一个类里,这个类的内聚性是最差的。遵守SRP原则就能够使我们提高类的内聚性,从而降低系统的耦合性。

那么怎么来定义职责呢?Uncle Bob的定义就是“改变的理由”。如果你能想到一个类存在多个使其改变的原因,那么这个类就存在多个职责。例如上面提到的矩形类,要改变图形界面中矩形的外观要改动它,要改变矩形的几何运算也要改到它。因此这个矩形类要被拆成两个。

这样就会使得每个类都很小,是的,就是要类很小。可能有人会担心这么多很小的类会不会用起来很麻烦。用个Uncle Bob的例子吧:你是希望工具箱里只有几个大抽屉,所有的工具都放在一起,还是希望有很多小抽屉,上面都标好了里面工具的名称?

即使不是面向对象的语言,我们还是可以使用面向对象的思想,把数据和它所对应的操作关联起来。函数,类,模块以至于整个系统,这些不同层面代表了对业务逻辑不同层面的抽象。SRP从职责,或者是“改变的理由”的角度为类的抽象粒度提供的判断的标准。






最后提前说一下,所谓SRP只不过是伟大的Separation of Concerns原则的一个引伸原则,SoC才是一切解耦合的不二法门。等我把SoC的引伸原则一个一个的列举完了,再来谈它自己吧。






解耦合手段之一:DRY原则

一直信誓旦旦的要总结一下软件系统解耦合的手段。等到真的要写了,才发现这可不是件容易的事情。所谓解耦合,也就是解开系统内各模块间不必要或不合理的依赖关系,让各个模块内部结构紧凑,整体架构简化从而易于理解和维护,方便扩展。目标就是“高内聚,低耦合”。软件系统中的耦合,说远了可能是软件开发团队的组织结构和管理造成的,说近了也可能是你键盘上的Ctrl+C、Ctrl+V造成的。我打算想到哪里就写到哪里,一次写一个方法。

首先想到的自然是DRY原则了。DRY原则,全称是Don't Repeat Yourself 原则,有人翻译做“一次且仅此一次”原则,其实就是“不要重复”。这句话也是著名的《程序员修炼之道》中的核心原则之一。
信息不应重复。因为重复的信息给将来的改动带来困难,使得系统更复杂,并且可能导致将来的不一致性。比方说有一段代码我写好了,然后发现另外一个地方也要用,于是我就把它拷贝过去,结果就造成了代码上的重复。等到下次我要改的时候就要改两个地方。也可能下次不是我改了,而改的那个人不知道另一段相同内容、相同目的的代码的存在,只改了一个地方。比这个例子还要糟糕许多的真实情形每天都在我们的软件开发组织里发生着。
比方说我们常听说的所谓“详细设计文档”往往就是对代码的重复。一批又红又专的程序员写了代码,然后又把代码中所写的内容记录在文档里,虽然他们不知道为啥要这样做(当然,他们也可能先写的文档,再写代码,那只会更糟)。后来来了一批不那么红,不那么专的程序员,改了代码没补文档。再后来大家就不知道该信文档还是该信代码了。如果那批又红又专的程序员知道,代码不光是写给编译器看的,也是写给人看的,就不会把精力放在不该放的地方了。听到这个,总会有更红更专的老程序员跳出来大叫:“咄!不做设计就写代码,成何体统!”没人说不让你做设计,且不说万恶的BDUF(Big Design Up-Front),就说简单的设计吧,你把它写在白板上,画在纸上不是也一样么。然后把它用代码清晰的描述出来。最后别忘了把白板擦了,把那张纸吃掉,省得留下来害人。
还有,测试文档往往是对自动化测试脚本的重复;需求分析文档往往是对验收测试的重复。。。。。。
为了发现和剪除代码中的重复,有专门的CPD(Copy-Paste Detector)工具可以用。我用过一个叫PMD的开源软件,很不错,支持Java, C/C++, PHP。把它用在我们的一些代码上,有时你会发现几十处,每处上百行的重复代码。我想,这足够说明问题的了。
几乎软件中所有的问题都和重复有关,而软件设计中用到的各种原则,最后其实也就是用来减少重复的。《Clean Code》里面说:“Duplication may be the root of all evil in software.”这话说得可能大了点,看你如何来理解了。像我那位狡猾的以色列朋友在听到有人夸他们犹太人都很有经济头脑时常说的:
“All generalizations are wrong, including this one.” :-)


2009年1月18日星期日

软件系统中的耦合性对系统可持续发展的影响










我所工作的软件产品线很庞大,历史也很久。即使不考虑它继承的另一个产品线,也有快十年的历史了(否则的话是二十年)。现在仍不断的有新的feature加进来。其中有一些长期困扰我们的问题,我想可能在很多其它软件系统中也是存在的。



  • 维护的工作量很大。一个新人要至少到半年以后才有可能参与有限的模块的维护。两年工作经验的人可能维护的也就是这么几个模块。


  • 相对于优秀的软件工程师,我们似乎更需要的是考古学家和历史学家。因为只有历史学家才懂得系统的原始设计,在没有历史学家的情况下,就只能靠考古学家了。幸好,我们还有几个这样的人。


  • 测试的资源非常紧张,因为



    • 我们生产的设备非常昂贵


    • 大部分的功能都必须在满配的设备上测,否则就不放心。这种测试非常慢,而且很难自动化。



  • 看上去似乎没有经验的新人或team也可以进行新功能的开发,但最后总会发现,没有历史学家和考古学家的参与这永远不可能实现。


  • 系统看上去比实际需要的复杂的多。


  • 一方面,很多人都说部门里的人数看上去比实际需要的多很多,可是另一方面还老感觉缺人。


  • 这么多人的组织又成了头痛的问题。各种team和部门,千奇百怪,花样百出。组织内部沟通成本、管理成本越来越高。各个流派长年争执,很难说服对方。
  • 每个release的周期是一年到两年,几乎没有不延期的release。







这些问题背后的原因可能是瀑布开发模型、代码质量、资源管理、测试水平等等。我觉得可能与所有这些问题相关的一个根本原因是系统的耦合性。所谓耦合性,说白了就是系统模块间的依赖性。我们当然需要这种依赖关系,否则各个模块各行各事,无法完成我们需要的功能。然而过多不必要的依赖关系会给我们带来无尽的麻烦。例如:




  • 新人问题
    。模块间的高耦合性导致模块内部不紧凑(内聚性低),往往模块会很庞大,并且模块间有千丝万缕的联系。这使得理解系统架构和单独的模块都很难。这就是为什么新人要花费这么长时间才能上手工作。这一点在开源软件中会好很多。因为如果开源软件的架构不好理解,单独模块很难入手的话,就会让新加入的人望而却步,从而失去了生存的根本(见我写的Conway's Law)


  • 开发问题
    。模块间的高耦合性使得模块间在漫长的产品开发历史上累积了太多纠缠在一起的复杂关系,这些是任何文档都没有办法完整记录的。所以,只有靠懂得这部分的“历史学家”了。然而历史学家们总是要高升或者离开的,只有善于刨根问底的天才“考古学家”才有可能解开这些迷团。我真想建议他们去搞一门“软件考古学”的学问来(google了一下,还真有人发明了这门学问)。可惜的是,要搞“软件考古学”,不单要是天才,而且还要愿意干才行,而这样的人并不多。



  • 维护问题
    。模块之间的高耦合性使得出问题的几率大大提高。而且出了问题还捉摸不定,很多问题都很难定位,往往在这里改也成,在那里改也说得通。


  • 系统架构设计上的高耦合性也是测试问题的根本原因。



    • 功能模块与系统平台高度依赖
      。“想在本地测试?”做做单元测试还有些可能(但也受耦合性影响),想做功能测试就不可能了。


    • 系统平台又与硬件平台高度依赖
      。“想做个模拟环境?”别提那个贵得吓人,功能有限又慢得要命的第三方模拟平台了,还是拿到真的设备上测吧。



    • 模块之间的依赖性高
      。想用X,必须带上那个Y,而要有那个Y,还得有个Z,然后要想测到这个功能整个系统还必须重启一下,这时候XYZ又乱作一团了。有一个很有经验的测试工程师有一次和我说:“我看到几乎所有的测试人员好像都在测查不多的case,都是在建call”。他们当然做得都是差不多的测试——他们的那部分功能都在里面的,想分也分不出来啊!


    • 测试工具与测试环境依赖性高
      。因为上面的问题,想写个自动化测试是很难很难的一件事。好不容易写了一个,环境略微变化了一点点,又跑不动了!三天两头儿的出毛病,维护TA case的时间不知道是维护真实功能的多少倍!


  • 系统庞大、效率低下的问题。这么高的耦合性起码会使整个系统紧凑起来,大小总会小一点吧?事实并非如此,不但并非如此,就连性能都打了折扣。通常,设计者愿意牺牲一部分性能来换取低的耦合性,然而高的耦合性却往往换不来高的性能。高耦合的设计使得新的功能忙于应付已有的复杂的依赖关系,最终让系统越来越大,越来越慢。我们所做的提高性能的努力无非也就是折解开这些耦合性,然而这很难很难。

  • 人员众多的问题。即然系统已经这么大了,架构已经这么难懂了,测试已经这么难做了,那么我们只好招更多的人了。

  • 组结结构问题。不知道是不是高耦合的组织产生了高耦合的系统,反正这样的产品系统注定了相应的组织结构的高耦合性。在这种情况下,很难有一种组织结构能被人们公认为是有效的。典型的开发组织形式无非就是component team或者feature team。Component team以模块为单位组织team,倾向于屈从于系统内部的依赖关系。而feature team以客户功能为目标组织team,倾向于挑战系统内部的依赖关系。然而在不改变系统高耦合性的现实的情况下,两者都不能很好的工作。(我个人倾向于feature team,component team只会产生更多的耦合性,然而feature team的挑战太大了,最后可能死得很惨。)







因此,我认为系统的耦合性是大型软件产品开发的各种主要问题的根本原因。






按照原本的计划我想写写:





  • 软件系统设计中的耦合性



  • 系统耦合性与可测性的关系


    • 对于这个话题已经在本文中写了一些,我对测试的理解有限,不知道能不能写出更多的东西来了。


  • 解耦合的一些手段
  • 另外还想写写耦合性和组织结构的关系
    • 已经写了一篇Conway's Law
    • 也许将来有了更多的想法再多写一些

















2009年1月17日星期六

人在面对压力与冲突时的反应类型分析





















就算是嚎啕大哭不肯吃奶的婴儿,面对面色焦急,千万百计的母亲最后也总是要屈服的。人不可能永远持续的面对压力和冲突,所以就会产生出逃避或应对冲突的策略,渐渐的这些策略形成了固定的模式,也就变成了人性格的一部分。

前面提到婴儿不爱吃奶,可能是因为她正心情不好,也可能她根本不饿,或者想吃母乳而不是奶瓶;而母亲总是担心孩子吃不饱,或是生病了,一定要她吃一点才放心。婴儿在百般挣扎后慢慢学会“认命”了。慢慢的,她可能会型成一种无责型(Impunitive)的性格。这种性格的表现是“认命”,错不已,也不在人,又不可避免,运气使然。他们逃避的方式是做出屈从的姿态,回避面对事态的严重性。例如一个人心里有意见,但是就是不说出来,因为他知道说出来就要面对冲突。

在有的家庭,大人认为小孩子犯了错误,就会对小孩子说:“你自己去反醒一下吧!”其实七岁以前的孩子思维方式尚未成熟,不会反向思维,就算是自己错了也会认为是别人不对。然而在这种环境下,慢慢的,他就会习惯把冲突的责任归咎到自己身上。这种性格叫做内责型(Intropunitive),面对冲突与压力的表现为懊悔、负罪感、自责等。最极端的表现为:自杀。

在与上面相反的环境下,可能会形成另一种外责型(Extropunitive)的性格。这种性格的人倾向于把责任归咎于外部因素或其他人身上,从而对压力和冲突表示愤慨。最为极端的表现为:杀人。

另一种类型是防御型(Defensive)。这种类型的人认为自己与外界都对冲突有责任,那么为了逃避冲突与压力,他们可能会否认压力的存在、寻找合理的解释,或者指出冲突与压力中的好处。

用两个维度可以清楚的区分上面四种类型。一个维度是一个人倾向于把责任归咎于外部因素的程度;另一个维度是一个人倾向于把责任归咎于自身原因的程度。上面四种类型是人们试图逃避压力与冲突时采取的策略,那么当人们试图应对压力与冲突时,也会有四种不同的类型:

不执型(Impersistive):对应无责型,低外因+低内因。期望时间和环境可以解决问题。通常都很顺从和有耐心。
自执型(Intropersistive):对应内责型,低外因+高内因。会自己采取行动来解决压力与冲突。
他执型(Extropersistive):对应外责型,高外因+低内因。期望他人采取行动或提出解决方案。
联合型(Interpersistive):对应防御型,高外因+高内因。会期望与他人一起来解决问题。

了解这些应对压力于冲突的类型会帮助我们站在对方立场上理解沟通对像的反应,从而更有效的进行沟通。虽然这些心理学上的知识不见得是leadership或coaching的必修课,但它的确会对有所帮助。我之所以写了以上这些内容和例子是受到了前一段时间一个关于leadership的课程的启发。很多具体的内容来源于一篇讲Role Stress的文章。

想想真是神奇啊,我们后天的性格在母亲拿起奶瓶的那一刻就开始形成了。





2009年1月15日星期四

周星驰电影道具中的“高内聚,低耦合”




“高内聚、低耦合”







这是软件系统设计的追求。在其它系统中这种关系也存在。周星驰的电影中经常会用到很多道具,其中很多可以做为系统设计的例子来帮助我们理解这名话。


“高内聚”是指模块内部紧凑一致并且专注。一个内聚性差的例子是周星驰电影《国产007》中达文西发明的"集合十种杀人武器于一身的超级武器霸王"。因为十种“武器”之间并没啥联系,而我想用其中的手电筒的话还要带上另外九样东西。内聚性好的例子当然要数周星驰电影《食神》里的折凳了。折登专注于两件事:坐、打。折凳内部由两部分构成:凳腿和凳面。用来坐时凳腿用来支撑,凳面用来托屁股;用来打人时凳腿用来握住,凳面用于击打。折凳这个模块精巧的内部设计一点浪费都没有,即不需要其它模块支持也不需要复杂操作,“不愧是七种武器之首”。




“低耦合”是指模块间相对低的依赖关系统。还是先举个失败的例子,《大话西游》中的“月光宝盒”。月光宝盒与与月光之间的耦合关系统使得它只能在有月光的晚上还要念动口诀才能使用。电影当中常常可以利用这种耦合性来产生一些戏剧效果,例如在晚上打开月光宝盒大叫“般若波罗蜜”,但是偏偏没有月光,无法实现时空穿越。而不能使用的月光宝盒正像唐僧说的那样是“就算不砸到小朋友,砸到花花草草也不好”的“垃圾”(“悟空,你又乱扔垃圾”)。出色的低耦合系统要数《大内密探零零发》中的“武装攻击直升机了”。它由手动的螺旋桨和含在嘴里的火炮系统构成,相互之间没有什么依赖关系,即可以联合起来升空打击敌人,又可以折分开来单独使用。

“月光宝盒”是紫霞仙子研发的么?她可真不容易,因为想测试月光宝盒不单要天时地利人和,而且涉及到时间穿梭。想TA就更难了。

模块内部的高内聚会带来模块之间的低耦合,反之亦然。

有时低耦合是以牺牲性能为代价的。比方说Windows中的COM组件,用起来灵活但速度很慢。再比如脚本语言对系统依赖性很小,但没有对系统依赖性大的编译语言执行快。为了得到更好的可扩展性和可维护性,系统设计者往往乐于牺牲一部分性能来换取较低的耦合性。但事实也并非总是如此,很多开源软件和同等规模的商业软件比起来性能没什么差别,但开源软件往往有更低的耦合性(见我之前写的Conway's law)。这说明很多时候其实系统架构设计是有改善的空间的。
内聚与耦合其实在我们的生活中是无所不在的。一方面,我们生活在一个高度模块化的世界中,电视、音响、汽车,各司其职。人们需要最大程度的忽略模块的细节(使其高内聚,普通人没必要知道电视机的构造),同时也期望各模块之间可以灵活搭配减少不必要的依赖性(使其低耦合,电视坏了可以听音响,就算家里没电了汽车还是可以开)。另一方面,我们也生活在一个高度耦合的世界,这往往来源于现代社会对于性能的不断追求。几十年前,会开车的人几乎都会点修车技术。现在可不行了,汽车内部零件复杂的耦合性让我们望而却步,没办法,性能的代价。






2009年1月14日星期三

Conway's Law













"...organizations which design systems ... are constrained to produce designs which are copies of the communication structures of these organizations."





"设计系统的组织不可避免的要产生与其组织的沟通结构一样的设计。"





                                   ---Conway's law


例如由4个团队来开发一个编译器,那么最终的产品很有可能包含4个编译过程。
Conway's Law告诉我们的应该不是一个宿命,而是一个我们要努力摆脱的东西。不是说既然系统结构的宿命是抄袭组织的结构,研发机构就应该乖乖的按component teams的方式来组织。Feature Team的目的就是让组员以end-to-end customer feature为目标,横跨系统架构而工作,充分利用团队的协作与沟通。这使得component架构成为开发的工具而不是宿命,更不是目标。

然而最近读到了一篇文章为Conway's Law提供了大量事实的证据,似乎与上面的观点相冲突:


http://www.hbs.edu/research/pdf/08-039.pdf

该论文以软件行业为例子,研究开发团队组织与沟通结构对软件产品结构的影响。它从软件模块化与耦合性的角度在开源软件和商业软件之间进行比较。结果发现:

  • 开源软件被划分成很多小的模块,模块之间的依赖性很小。
  • 商业软件却往往有很大的模块,在其内部各单元间依赖性很强,或者模块间的依赖性很强。
产生这种差别的原因可能是:



  1. 设计的自然演化:
    • 商业软件往往是在同一家公司,同一地点,由专职的团队开发的。通常问题可以通过面对面交流来解决。因为容易获得模块内部信息,可以在架构内进一步压榨系统的能力。这些都潜移默化地增加了系统的耦合性。尽管这不是管理者主观的选择,其结果往往是软件架构变得耦合性越来越高。而在开源软件的开发中则刚好相反。大型的开源软件分布在世界各地开发,开发人员可能从未谋面。这使得开源软件越来越模块化。
  2. 架构设计者的主观选择:
    • 商业化软件要面临竞争,在给定时间内实现最大的功能。这种情况下模块化的好处可能不会被看重。而模块化对开源软件却至关重要,没有模块化,开源软件的贡献者几乎不可能理解设计并做出自己的贡献,也没办法在不影响其它功能的前提下开发新的feature或改正旧的错误。所以开源软件需要模块化来吸引开发人员和协调他们工作。











他们的研究对软件合作与外包提供了一些理论上的支持。这可能与我看到的现实情况不符,很多企业因为外包了部分工作而搞得焦头烂额。我猜其原因可能与发包企业的期望有关。发包企业理所当然的期望更好的在短期内应对竞争,而要做到这一点,不单要有开发人员间有效的沟通,也可以从尽量发挥系统模块间的耦合获得大量的即得利益。这些都是外包所不支持的,尤其是off-shore形式的外包。当然,发包企业也关心系统的可维护性和可扩展性。但眼前的竞争看上去事关企业存亡,这使得企业难以发现长远的好处。







更好的模块化并不见得一定以其它性能为代价,而往往是架构设计仍有改进的空间。

那么是不是没有流畅的沟通会更有利于产生模块化、低耦合、易于维护和扩展的系统呢?文章并没有讨论这个问题。但我想,当我们有更好的沟通条件和组织方式时,我们其实多了更多的选择。通过洽当的软件工程手段和管理手段应该能够灵活的吸取在开源软件中积极的经验吧。正如我前面说到的,系统架构应该是工具,而不是宿命。除非真的象Mel Conway在68年时说的那样,这种映射关系对于organization来讲是“constrained”的,或者我在另外一个来源里看到的是“inevitable”的。

我希望那个是玩笑。


记录一下接下来打算写写的几个话题吧:
  • 软件系统设计中的耦合性
  • 系统耦合性与可测性的关系
  • 解耦合的一些手段












2009年1月10日星期六

Big Daddy






Last autumn, Bas Vodde and James Grenning were visiting Hangzhou. Bas Vodde is a friend and ex-colleague of mine. James is an Agile coach. He's one of the principal authors of the Manifesto for Agile Software Development. They came to coach TDD for our company.
We went to a Korean restaurant after work one day and I drove the car. When the CD in my car played:
"M-I-C-K-E-Y..."
Bas said, "I don't know that you like Disney music."
"You know", I said, " I'm a dad now." (My baby was about 1 month old at the moment.) "I have to learn these children songs. You know, in case one day I have to teach her."
And Bas asked, "Bah! But isn't it more fun to learn together when she grows up?"
It seemed that I had never thought about that as an option, although what Bas said was simply true. I wanted to learn those songs before my daughter even knows anything, so that she will think that her almighty big daddy knows everything. I'm not sure if it's my own problem, or every daddy is just trying to do the same thing:P Showing off shouldn't be more important than enjoying the fun with my baby. These coaches, they always know how to pinpoint the subtle problems and challenge you directly.
I answered Bas, "You are damn right!" And then I switched the CD.
"Oh, your baby likes Pink Floyd.", said James Grenning.
I said "No, no, that's for myself. Shall I switch back to Disney music?"
"Pink Floyd is fine."


2009年1月8日星期四

面试技巧与被面试技巧

前不久应朋友之邀,为在校既将毕业的学生准备了一些关于面试的材料,放在他们的“英语求职”课程里。现在来总结一下。






面试,其实是一个预测活动。是用语言沟通的形式,对一但双方结成某种关系以后,某一方的行为进行的预测活动。女孩子找男朋友,是为了和对方结成某种关系(情侣、夫妻、一夜情,因人而异),她通过语言上的沟通(聊天、相亲、威逼色诱等)去了解对方从而做出决定,这也是一种面试。因此,我们从女孩子选男朋友中能学到很多招聘和面试的技巧。






女孩子选男朋友,首先看重的是什么?我问过很多人,有人说“要有钱!”,“要长得帅!”,“有男人味!”,“能力强!”(男生开始起哄了)。可是如果冷静的想一想,很多女生会选“动机”。动机最重要,如果我想要的是天长地久,你想要的是ONS,那么也就失去了一切的前提。所以面试中了解动机(motivation)是很重要的。

动机之后是什么,那就很难说了。有的说“能力!”(钱,权,潜力,容貌,健康等),有的说“人品!”(性格,道德,是否浪漫等)。相信不同的女孩子有不同的要求吧。面试中需要去了解的无非也就是这些:Competence,Professionalism,当然,最重要的是要清楚招聘岗位的具体需求。

OK,现在知道了什么是我们要在面试中去了解的,那么如何去了解呢?一个经典的女孩儿面试男孩儿的问题是:“如果我和你妈都掉到河里了,你先救谁?”如果不是开玩笑,那么她为什么要问这样的问题呢?因为她想通过男孩子的答案来判断他将来的行为。目前为止,我所见过的最好的答案是:“我会先救我妈。然后如果你不幸淹死了,我会跳下去和你一起死。”怎么样,听了眼泪哗哗的吧。可是这完全达不到女孩儿预想的作用,最终花言巧语者胜出。我想,这也许就是为什么经常有那么多女孩子被骗的原因吧。没有经验的面试者问的80%都是这类问题。例如:“你为什么离开你上一家公司啊?”我的建议是,完全没必要和他说真话,编得越好听你的机会就越大。“如果你到了一个新团队,你会怎么样和大家合作呢?”如果面试官这么问你,记住:忽悠,接着忽悠。忽悠不是一味的能言善辩,偶尔装出大智若愚的样子效果会更好。如果要找好例子,看看我上面回答女孩儿的那句话,实在不行,找个Obama的演讲看看。

如果这是个美国女孩儿的话,这种忽悠可能不见得有效了。我们可以先看看美国人的信用体系:没人听你忽悠,关键看你的信用纪录,如果你以前纪录好那么我就信任你,如果被我知道你有一次不守信用,那么就只能对不起了。所以美国女孩也许更懂得通过男孩儿的过去去了解他,而不是通过他的将来。当然啦,美国的信用体系也是有问题的,要不然就不会有次级货危机,要不然说不定能说会道的Obama就不会当选了,不过这个和本话题无关。
现在比较流行的一种面试技巧,叫做“基于行为的面试”(Behavior-Based Interview)。这种面试技巧关注的就是被面试者过去做过什么,而不是打算做什么。例如:“请告诉我(们)一次你处理客户恶意投诉的经历。”也许你会说:“不行,这不公平!我没有工作经验!”那么想象一下你的信用卡吧,没有担保,没有信用记录的情况下你想享受VIP待遇是不可能的。所以,你能做的就是多做些实习,积累经验。正如你心仪的女孩儿不喜欢宅男,那么想赢得芳心的话你还是多出去锻炼一下吧。

也许你觉得就算是“基于行为的面试”也是可以忽悠的。我想,可能没那么容易。设想一个咄咄逼人,凡事都要问到点子上的小心眼儿女朋友在拷问一个结结巴巴,眼珠乱转的男朋友的情形吧。也许编故事并不那么简单。“基于行为的面试”会采用一种漏斗式的提问方法。先用一个大的问题把你套进来,然后一点一点收紧问题的范围。当你回答第一个问题时,可能跟本猜不到他为什么会问这个。当第二个问题来了的时候,如果你刚才是编故事的,那么现在可能会后悔刚才不该那么编来的。然而好的面试官和精明的女孩儿一样,他们从一开始就知道他们想考察你的关键点在哪里。想反,拙劣的面试官和笨女人一样,问了问题,也得到了答案,然后才想起来:“我为什么要问这个问题?”

如果你要面试的是一家大公司,尤其是外企,那么很有可能他们主要的面试方式就是这个。除了平时多积累,自己适当的总结前面的经验也是很重要的。针对你对面试岗位要求的理解,把以前自己做过的相关的不错的事例回想一下。也不能全是好的事情,你不是完人,也要有一些并不那么好的。当然,你的目的不是告诉面试官你有多差,这没意义,因此就算是起初不好的事情,也要是那种在你的努力下后来改善了的;就算是后来也没改善,你总要从中学到了什么。总而言之,happy ending。

当然,同找朋友一样,面试也不是完全那么不浪漫的。面试官也会问一些让你发挥的内容,但不会太多,从而了解你另外的一些性格,比方说:self-awareness(自知之明)。有时也会问一问你的业余爱好,不过通常这只是为了调节气氛或者给面试一个轻松的收尾。

最后,附上给学生们的演示稿:http://docs.google.com/Presentation?id=dgntdd4g_28gzd9txfv



2009年1月7日星期三

Cargo Cult与土鳖











有一种文化,英文叫做Cargo Cult。有人把它翻译成“货物崇拜”。



有个电影,叫做《上帝也疯狂》
The Gods Must Be Crazy),相信很多人看过。片中有一群与世隔绝的非洲土人拾到了一个可口可乐玻璃瓶,这个来自对他们来讲一无所知的文明世界的“货物”对他们的生活产生了巨大的影响。他们对这个可乐瓶的崇拜就是所谓的“货物崇拜”。

中文wikipedia中说:


最为知名的货物崇拜,是于第二次世界大战太平洋战争时,美军于太平洋一个小岛建立一临时基地。当时岛上的土著看见美军于「大铁船」(军舰)内出来,皆觉得十分惊讶。此外他们也看到有一些「大铁鸟」(军用飞机)运送穿着美军军服的人,以及很多物资。这些土著看见这种情况均感到很惊讶,并觉得这些「大铁船」及「大铁鸟」十分厉害。加上美军也提供部份给土著。而这些物资对土著来说十分有用,结果令这些土著将美军当作神。 

第二次世界大战完结后,美军离开这个小岛,只留下一些美军军服及一些货物。这些土著便认为这些货物具有神奇力量,又相信「神」(美军)他日会回来,带来更多货物,使他们展开一个幸福新时代。但是美军当然一直也再没有回来,因此这些土著便自己发展出一套敬拜仪式,崇拜美军军服以及货物。该宗教的表现形式是土著会穿着美军军服升起美国国旗,图腾则是木刻的飞机。一般把这种新的宗教被称为「约翰布鲁姆教」。

看来所谓“货物崇拜者”不就是“土鳖”么。如果在YourTube上找一找,还真的能找到不少这种纪录片。
无独有偶,在软件行业中,有一种编程方法叫做“Cargo Cult Programming”,有一种设计方法叫做“Cargo Cult Engineering”,我想翻译成“土鳖编程”和“土鳖软件工程”比较合适。其实在我们博大精深的中华文化中早就对此现象有了精确的概括:“形而上学”。就是照搬死的经验。例如,有人把设计软件比做盖房子:“人家建筑业都那么成熟了,我们可以照搬他们的方法”。其实如果不了解各自的细节,我们没办法发现,在另一个行业中行得通的东西并不见得在这个行业中也行得通。

最后,让我来引用The Mythical Man-Month (The MMM, 人月神话)的作者Fred Brooks的话:
Good judgement is the result of experience ... Experience is the result of bad judgment.




2009年1月6日星期二

The Paradoxical Theory of Change


关于改变的悖论



前不久在上一个管理方面的课。讲课的老师是一个丹麦人,年纪不小了,很久以前是搞心理学的,后来做了很多年的管理咨询。这个培训时间很长,断断续续要历时一年。老师说他的目的之一就是让我们建立在管理方面的自信。


然而我所见到的是他在不断的打击我们。“啊,你们真是报怨方面的专家,你们太擅长报怨了。”他会抓住我们的一个问题不停的打击我们,在我听起来那就是在挖苦人。他也会盯住一个人不停的重复他身上的问题,所有人就在那里听着,“你听起来就像是个4岁的孩子,不管别人说什么,你只管说‘不’......”


有一次我终于受不了了。我就对他说:“你不是说要为我们建立自信么?可是我见到的是你在不断的打击我们的自信!”


然后他就和我们讲了“关于改变的悖论” (The paradoxical theory of change )。







“关于改变的悖论”是格式塔心理疗法中关于改变的理论。



......Gestalt's paradoxical theory of change. The paradox is that the more one attempts to be who one is not, the more one remains the same (Yontef, 2005). Conversely, when people identify with their current experience, the conditions of wholeness and growth support change. Put another way, change comes about as a result of "full acceptance of what is, rather than a striving to be different" (Houston, 2003).





......格式塔的关于改变的悖论。这个悖论就是一个人越是试图成为一个与自己不同的样子,他就越会保持原样。相反,当人们意识到自身的现状,这种完整与成长会帮助他们改变。换句话来讲,改变是“完全承认现状,而不是争取不同”的结果。







所以这个老师所做的就是让我们这些学生们承认我们的现状,只有在这个基础上才有机会改变。回想一下我们面对现状时的态度,基本上就是拒绝接受。因为来参加培训了同事至少也都有几年的管理经验,当老师指出问题时,我们首先想到的就是反驳。这时,人很少有机会能改变。当我们心平气和的接受,并重复说:“是的,我就是一个爱报怨的人,我是这方面的专家。”时,我们接受了现实,并且可以选择改变了。






不愧是搞心理学的,居然有这么整人的理论。

写这篇文章时,顺便看了看格式塔理论和格式塔疗法。格式塔理论是一个心理学理论,太深奥,看不懂。不过格式塔疗法倒是挺有意思的。估计随着国人慢慢的富裕起来,也会像美国人那样关心起自己脆弱的小心灵,心理医生会越来越多吧。以下抄录一些格式塔疗法的基本原则,与改变的悖论并无直接联系,只是看着有趣,呵呵。







【格式塔疗法的九个基本原则】


一、生活在现在



不要老是惦念明天的事,也不要总是懊悔昨天发生的事,而把精神集中在今天要干什么上。因为遗憾、悔恨、内疚和难过并不能改变过去,只会使目前的工作难以进行下去。



二、生活在这里



我们对远方发生的事无能为力,想也没有用;杞人忧天,徒劳无益;惶惶不安,对于事情毫无帮助。记住自己就是生活在此处此地,而不是遥远的其他地方。



三、停止猜想,面向实际



很多心理上的纠纷和障碍,往往是因为自己没有实际根据的“想当然”所造成的。如果你向领导或同事打招呼,他们没反应,你可能怀疑他们对自己有意见。其实,也许他们心事重重、情绪不安,没有留神你罢了。因此,不必毫无意义地胡乱猜想推测。



四、暂停思考,多去感受



很多人整天所想的就是怎样做好工作,怎样考出好成绩,怎样搞好和领导、同事的关系等等,因而往往容易忽视或者没有心思去观赏美景,聆听悦耳的音乐等等。格式塔疗法强调作为思考基础的“感受”,比起思考本身更为重要。没有感受就无从思考,感受可以调整、丰富你的思考。



五、也要接受不愉快的情感



愉快和不愉快是相对的,也是可以相互转化的。因此,人们要有接受不愉快情绪的思想准备。如果一个人成年累月,总是“愉快”、“兴奋”,那反而是不正常现象.



六、不要先判断,先发表参考意见



人们往往容易在别人稍有差错或者失败的时候,就立刻下结论。“格式塔疗法”认为,对他人的态度和处理人际关系的正确做法应该是:先不要判断,先要谈出你是怎样认为的。这样做,就可以防止和避免与他人不必要的摩擦和矛盾冲突,而你自己也可以避免产生无谓的烦恼与苦闷。



七、不要盲目地崇拜偶像和权威



现代社会,有很多变相的权威和偶像,它们会禁锢你的头脑,束缚你的手脚,比如,学历、金钱等等。格式塔疗法对这些一概持否定的态度。我们不要盲目地附和众议,从而丧失独立思考的习性;也不要无原则地屈从他人,从而被剥夺自主行动的能力。



八、我就是我,对自己负责



不要说什么,我若是某某人我就一定会成功。应该从自己的起点做起,充分发挥自己的潜能。不必怨天尤人;要从我做起,从现在做起,竭尽全力地发挥自己的才能,做好我能够做的事情。



人们往往容易逃避责任。比如,考试成绩不好,会把失败原因归罪为自己的家庭环绕不好,学校不好;工作不好,会推诿说领导不力,条件太差等。把自己的过错、失败都推到客观原因上。“格式塔疗法”的一项重要原则,就是要求自己做事自己承担责任。



九、正确地自我估计



把自己摆在准确的位置上。每个人在社会中,都占据着一个特定的位置,所以你就得按照这个特定位置的要求,去履行你的权利和义务,你如果不按照社会一致公认和大家都共同遵守的这个规范去做,那你就会受到社会和他人对你的谴责和反对。








Good Code,15年的转变

如何写Good Code? 15年的转变


前不久刚刚读了一本新书, 《Clean Code》 (Robert C. Martin, Prentice Hall, Aug 2008 ) (我的读书笔记)。里面讲到了什么是good code,什么是bad code。


刚好同事手中有一本翻译的电子书《编程精粹》,打开来一看,英文名也叫做Writing Clean Code。上网核对了一下,其实原书并不叫这个名字,而是Writing Solid Code: Microsoft's Techniques for Developing Bug-Free C Programs (Microsoft Press, May 1993) 可能是Clean Code比Solid Code更时髦吧,翻译的人不知是不是有意的改了原书的名字。







从May 1993到Aug 2008,十五年另三个月,什么东西变了,什么东西还没变?







1993年,面向对象的方法仍在萌芽期,那时还是面向过程语言的天下,尤其是C语言。“高内聚,低耦合 ”的设计理念虽然早已提出,但那时只是系统设计的原则,似乎与代码并没有什么关系。因此,那时的书中更多的是从代码的正确性、可读性、稳定性等方面去描述“good code”。







命名:早期C语言比较流行的匈牙利命名法甚至一直延用到C++中,我不知道现在的VC程序员是不是仍是采用这种方法。可能一方面因为老的编程语言把标识符长度限制得很短,另一方面C不是强类型语言,所以在名字中要用pch之类的前缀来表示这是一个指向字符的指针。现在不同了,标识符长度并不是问题,类型检查也不是问题。现在的命名更注重清楚、明确地表达名字所代表的逻辑含意,可以让读代码的人不依靠注释或或文档就能轻松的了解设计者的意图。Robert C. Martin甚至固执的要求这些名字一定要是可以拼读出声,并且由有含意的单词组成的。







单元测试:1993年的书上强调了单元测试的重要性,但作者也提到:“......因为尽管单元测试也与无错代码的编写有关,但它实际上属于另一个不同的类别,即如何为程序编写测试程序。......”那时的观点是:如果有单元测试的话,一定要在修改程序之前运行单元测试。而在2008年的书上单元测试早已成了编写代码密不可分的一部分。TDD或测试驱动开发慢慢的成为了一种标准。并且,有“控制反转 ”这样的方法帮助写出可测性更强的代码。






Lint:代码静态检查工具。在1993年的书中不断的被强调,而在2008年的书中却很少提及。Lint早成为编译器的一部分。







调试:相信对于代码走读的重要性在这些年里并没有太大的变化,只不过现在有了peer review和pair programming这些新名词而已。不过这些在2008年的这本书中并没有很多提及。不同的是在1993年的书中除了代码走读之外,还花了很大的篇幅来讲解代码的调试和单步跟踪技术。这在2008年书中是不被鼓励的,因为程序会有单元测试来保证,一但有问题会被随时发现并且精确定位,要尽量减少浪费在调试跟踪上的时间。还记得在Python“八荣八耻” 里提到:“......以打印日志为荣 , 以单步跟踪为耻;......以单元测试为荣 , 以人工测试为耻;......”









If:1993年的书上提到“避免无关紧要的if语句”,显然这是为了降低代码的复杂度,然而书中并没有给出切实可行的替代方法。而在2008年的书中提出了很多if甚至switch都可以用类的多态来替代。不禁又让我想起了Python“八荣八耻”:“......以多态应用为荣 , 以分支判断为耻;......”







断言与错误处理:在1993年的书中强调对“无定义”情况的断言及对错误情况的处理。而在2008年的书中却鼓励用“exception”来代替错误处理。这样可以避免影响代码的主逻辑。







函数:1993年的书中要求:“不要编写多种功能集于一身的函数。为了对参数进行更强的确认,要编写功能单一的函数”以及“要避免布尔参数”等。这与2008年的书中的观点是一致的。不同的是,除了“一个函数只做一件事”之外,作者还要求我们在一个函数中“只对一个层次进行抽像”等等。








易读性:1993年的书中要求“为一般水平的程序员编写代码”,对于这一点以及其它易读性相关的要求在2008年的书中只有更多,更高。

重复:在1993年的书中并没有提及“重复”在代码中的危害性。而在2008年的这本书中,DRY(不要重复)这个极限编程的原则不断的被强调。甚至声称它是一切代码问题的根源。







整理代码:1993年的书中提到“除非关系产品的成败,否则不要整理代码”。这条观点在2008年的书中要一分为二的来看了。基本上两者的观点是冲突的,因为有了完整的单元测试作保证,加上相关的技术,使得对代码的不断重构成为可能,但这并不是一本关于重构的书。另一方面,2008年的书中提到的“开关原则”讲到:“软件实体(类、模块、函数等)应当对于扩展开放,对于修改关闭”相信是系统设计的原则,与前者的提法无关。不得不提一下2008年书中提及的童子军格言:“离开时,让露营地比我们来之前更干净。”






过份设计:在1993年的书中就已经提到了过份设计的危害,但只是在代码层面上。它提到:“不要实现没有战略意义的特征”,“不设自由特征”,“不允许没有必要的灵活性”。受到lean principle的影响,在2008年的书中有更具体的描述,并提出要避免BDUF(Big Design UpFront)。这不单单只是一个编码原则,而是一个系统设计原则了。






试一试:在1993年的书中要求“在找到正确的解法之前,不要一味地“试”,要花时间寻求正确的解”,有时间多去读读文档。而这个与2008年书中的观点大有不同。2008年的书中强调建立系统边界,并且编写测试代码去测试这些边界外的行为。






数据抽象:在1993年的书中提醒我们要密切关观数据流,因为它是程序的命脉。而在面向对象的2008年的书中更多的是讲数据抽象。并且强调迪米特法则






系统设计前面提到了,1993年的书只是在代码级别讨论。而面向对象和面向方面(Aspect-Oriented Programming )的方法给2008年这本书提供了更多可以为good code做的事情。书中更强调降低模块(如类)间的耦合性,提高内聚性。并且书中专门写到了一些系统设计的“关注点分离”原则。它提到:“软件系统与物理系统不同。如果我们可以恰当的分离关注点,软件系统的架构可以增量成长。”





1993年的Writing Solid Code2008年的Clean Code
侧重点:代码的细节好的设计
 
命名匈牙利命名法描述性,可以读出声
静态检查一定要用没强调
调试强调调试与跟踪的技术避免调试代码
IF/ELSE避免无关紧要的if语句使用多态
错误处理要面面俱到使用exception
函数不要写多种功能于一身的函数只做一件事,只在一个层次进行抽象
易读性为一般水平的程序员写代码小的就是美的,用代码代替注释
重复(copy-paste)没提及是一切代码问题的根源
单元测试一定要写TDD、控制反转
整理代码除非关系产品的成败,否则不要整理代码“离开时,让露营地比我们来之前更干净。”
过份设计不要没有意义的特性TDD
数据抽象数据流迪米特法则
系统设计不是重点关注点分离


OO使得代码设计与系统设计更好的结合,所以2008年的书中在新的good code的定义中加入了更多对系统架构的要求。随着Aspect Oriented Programming以及系统TDD的出现,我们可以看到代码设计的将会越来越靠近实际的业务逻辑,远离琐碎的底层细节。