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的出现,我们可以看到代码设计的将会越来越靠近实际的业务逻辑,远离琐碎的底层细节。