今年春节时,我写了一篇《TDD并不是看上去的那么美》,在这篇文章中我列举了一些关于使用TDD的一些难点和对TDD的质疑,后来出现了一些争论(可参见那篇文章的评论),以及Todd同学的《TDD到底美不美》,还有infoQ中文上的那个几乎没有营养离线讨论。今天,有网友给我推来一个英文版infoQ的视频——“Coplien and Martin Debate TDD, CDD and Professionalism”,这是2008年2月18日的视频,视频的主角两个人争论TDD好还是不好,一个是敏捷社区的教主级的人物——Robert Martin(大家称之为“Bob大叔”),另一个是C++,OO,多范式编程的大师Jim Coplien(大家都叫他Cope)。这两个人对TDD的见解有分歧。Coplien的很多观点和我之前的不谋而合,而他自己称他是坚决强烈地站在TDD的对立面上。下面是Jim的原话:
I have adopted a very strong position against what particularly the XP community is calling test driven development.
InfoQ的视频很多时候相当的不给力,就像有前列腺的患者撒尿一样,半天都挤不出一滴。不过,好在那里有这两个人对话的摘录。在这里,我给大家摘要一下:
——————————————————正文分割线————————————————————
Coplien首先让Uncle Bob定义了一下TDD,Uncle Bob说明了他的三个法则:(敏捷的同学一定不陌生)
一个测试驱动的程序员,其不会在写出一个测试失败的Unit Test前,去写一句可用在生产线上的代码。(没有测试之前不要写任何功能代码)
在编写用于生产线上代码之前,不写过多的测试失败的Unit Test。(只编写刚好能体现一个失败情况的测试代码)
在现有代码通过Unit Test前,不写更多的用于生产线上的代码。(只编写恰好能通过测试的功能代码)
Coplien说他有意见的不是这三个法则,而是因为这个三个法则是孤立说出来的。Coplien说他和一些咨询师或是Scrum Master参与过很多的项目,他们发现这些项目都有两个问题:
他们使用TDD的时候,软件没有一个架构或是framework。当然,Kent Beck说——TDD可以驱使你去做架构。但是,TDD和Unit Test 是一回事吗?Unit Test是一个伟大的事,尤其是当你去写API和类库的时候。今天XP所说的TDD和UT很不一样。如果你使用TDD来驱动你的软件系统架构,那么,基本上来说,三个迭代以后,你开发的软件就会crash掉,而且无法再往前开发。 因为什么?因为连软件团队自己都受不了这三个迭代出来的架构,而且你还会发现,你根本没去去重构。
第二个问题是,TDD这种方法破坏了GUI(图形界面),就算是Kent也说:“你永远不可以在一个漂亮的界面后面隐藏一个糟糕的架构”,Coplien强烈地相信软件的架构是通过界面来发出其光芒。他觉得如果没有一个好的软件架构,这个会影响用户的操作。
Coplien接着说,如果我们使用Uncle Bob的三条法则,我们也许没有什么问题,但Coplien想告诉大家另一个非常重要的事,那就是软件架构。并说:“我根本不接受TDD是软件专业化实践的论点”。
Bob大叔说,让我们回到99年,那时的敏捷社区觉得软件架构是无关的,不需要软件架构,只需要做一堆tests,做一堆stories,以及足够快的迭代,这样就可以让那些代码魔幻式地拼装起来,这就是horse shit。对于大多数的敏捷拥护者来说,这的确是愚蠢的。今天你再和Knet说这个事,他也会说那不过是一种说法。
Coplien回应到,实际上,Knet在解释XP的时候,在他的书131页的位置说过,“是的,你得做些前期的架构,但也别把自己搞乱了”。
Bob大叔把话题转回来,继续聊关于架构方面的事,他说软件的架构很重要,他也写很一些关于架构的书,他说他也是一个架构方面的怪才,但是他认为架构自己并不会形成软件的所有的外表。他觉得好的软件架构和设计能力应该出现在若干次迭代之后。他觉得你在架构软件的时候,你会创造一些东西,也会破坏一些东西,并且会在几次迭代中做一些试验性的工作,来尝试一下不同的架构。在2到3次迭代以后,你可以知道那一种架构是对的,这样,你可以在后面的迭代中进行调整 。因此,他认为架构是需要进化和发展的,而不会因为被可执行的代码所形成,也不会因为你所写的测试而形成。
Coplien赞同架构进化的观点,而且他相信软件的架构的演变和进化不是因为你写的代码,也不是因为Use Case,也不是告诉你你的软件需求的范围和其中的关系,但是如果你做的方法是以增量式的,以用户驱动式的,而你却在和用户沟通时没有一些前期的业务知识,那么这一定是相当有风险的,并且你一定会把事搞砸的。
Coplien接着说,他在Knet早期提到TDD的时候和Knet时,提到YAGNI(陈皓注:You Aren’t Gonna Need It,XP的一个法则,也就是只做最简单的事)时,Kent说到:“让我们来做一个银行帐户,一个储蓄帐户”,储蓄帐户其实就是对余额进行一些加加减减的事,就像一个计算器一样。Copilen继续解释到,但是如果你要做一个真正的银行系统,你的软件架构根本不可能从一个储蓄帐户的对象(计算器)重构出来。因为储蓄帐户根本就不是一个对象,其是一个流程,后面有一个数据库的查帐索引事务,还有存款保证多和利息,还有一些转帐功能。就算是这样,这也只是用户的功能,你还需要支持税务人员和精算会计师等这些人,这会让银行系统成为一个错综复杂的软件架构,这绝对不是你可以用迭代干出来的事。当然,Bob大叔是可以的,因为他有40年的银行系统的经验。但是Bob大叔你的这40年可真不敏捷啊。
Coplien接着说, 因为Bob大叔可以在软件前期做很多很重要的决定,这让得后面的事变得相对比较简单。Coplien根本不相信只要你把代码往那一放,在上面披上一层皮,再设置好一些角色,设置好接口,在文档里写上整个业务结构,而你只有在有人花钱的时候你才会在其中填充进真正的代码,反之就违反了你的YAGNI原则。所以,你只是在你需要的时候做你要做的事,但你却还是要提前得到你的软件架构,否则你一定会把你自己逼进死角的。
Bob大叔辩解到,我说的可能和你说的这个有点不同。我们应该不会像你所说的往接口中写一些抽象成员函数,而是创建一些有抽象接口的对象。当然,我不会把一下子为这个对象装载上一堆方法。那些是我需要使用测试驱动或是需求驱动来做的事,我还会随时随地在看是否哪里软件架构可以让我拆分接口。
Coplien说,问题 是你得知道你要干什么?他说他非常同意Knet的书”XP Explained”里说的——“你不能去猜”,然后他举了一个例子,一个他曾经在一个电信项目中重新架构软件的例子,这是一个长途交换机的项目,项目组特别喜欢用面向对象,有一个人需要去做一个“Recovery Object”(应该是系统恢复对象),Coplien说这是很扯的一件事,因为系统恢复根本就不是一个对象,因为他对业务不熟,所以想这么做。而当你在细节上分析的时候,你会发现这根本就不是一个有成员方法的对象。我个人认为,Coplien想用这个例子来说Bob大叔的先定义对象的抽象接口并不是一个好的需求分析的方法。Coplien还说,这个事情今天被资本化成了SOA,真是在玩火啊。
Bob大叔说,这个他很同意。你的确需要知道这个对象的意义是什么。而且他和Coplien都同意应该根据可运行的代码来决定未来,而不是基于投机心理搞一个巨大无比的架构。
此时,Bob大叔把话题又带回原地,他问Coplien:“你需要多少的时间才能写出可运行的代码?是不是一个系统需要写200万行代码才能算?”,Coplien说,在他的经历中,200万行代码算是小项目了,他的项目都是几亿行代码的。而在让代码可以跑起来,他至少需要让所有的对象都联系起来。
Bob追问到,“那么你是怎么测试这些对象的连接性的?”,Coplien说,我当然要测试,我会测试系统启动和停止,看看有没有内存问题,半小时就好了。Bob大叔似乎找到了突破点,于是说到:“Excellent!那么我们间的分歧是什么呢?也许你只是不同意TDD的概念和其专业化,当然,这是另外一个话题了”。
然后,Coplien说了一段我非常非常认同的话——“我看到很多人正在做正确的事,来避免我们之前讨论的那些问题,当然那不是TDD的扩展,而是Dan North所说的BDD。可见,软件开发中很多人在开发软件中都是在用正确的很好的方法,而我对此有意见的是,有人把这个事说成TDD,然后人们就去买相关的书来了解TDD,并且看到“architecture only comes from tests”,我在过去6个月中听到过4次这样的说法,这就像你所说的,完全就是horse shit。而关于你所说的专业化的事,如果你没有见过一个专业化你怎么知道?”。(不是吗?大多数人都知道怎么开发软件,而不是TDD才是专业化的软件开发。)
然后,Bob想多谈谈专业化的事,Bob说,在今天,一个不负责任的程序会提交一段他没有跑过单元测试的代码,所以,要确定你没有把一条没有测试过的代码提交到代码库里的最佳做法就是TDD。
Coplien完全不同意这个说法。他觉得底层的东西是更重要的。他用了一个示例来攻击Bob大叔的这个观点,他先是说代码走查和结对编程都有好的有价值的地方,当然和这个话题不相关。然后他又说了Unit Test,想想我们的单元测试,可能我们的测试案例并不可能测试我们程序中参数的各种状态,这些状态有可能只是半打,有可能是一百个,有可能是2的32次方个,所以,我们可以命中一些状态,也会没有测试到一些状态,我们的测试真的只是试验性的,所以,如果你在测试中发现bug,你真的很幸运。
随后,Coplien推崇了一个叫“Design By Contract” – 契约式设计的方法(我在软件设计中那些方法中提到过,),这个方法认为软件有前验条件,后验条件,还有不变的。这个方法是Eiffel项目使用的一个方法,使用这个方法你可以静态的去做一些检查,相当于你做了一个基础架构来干这些事。Coplien相信这个方法有TDD所有的优点——我需要努力思考我的代码,我需要思考软件的外部接口,而且,Coplien发现这么做会比做测试更有效。这会让你对那些参数的范围考虑地更为宽广,而不是只在测试案例写几个随机分散的值来测试。
今天,Bertrand Meyer(Eiffel语言的创造者,他也不赞同TDD)把这个方法推进了一步,叫CDD - Contract Driven Development,这个是一种关注于对象间关系,其在程序运行前提条件和运行后的后验条中达成一种契约,可以通过对契约条件的动态或静态的检查,来对程序的功能进行验证。这样可以让你更有效地测试程序。这种方法需要对业务的重点部位非常好的了解。这是TDD很难做到的(这就是我在《TDD并不是看上去的那么美》一文中说的TDD的测试范围是个很大的问题)。
Bob大叔似乎在努力回忆CDD和Eiffel,然后他说,TDD不就是干这个的吗?TDD就是把契约变成单元测试,不但测试输入,也测试返回值,这不就是先验条件和后验条件,而且他说,Unit Test和代码结合得更紧,而契约没有和代码结合得紧密,这是他觉得很不舒服的地方。
Coplien说Bob大叔创建了不应该创建的二元论。他说代码在哪里,UT就跟到哪里,代码有多臃肿,UT就有多臃肿,而UT也是代码,也会有BUG,所以,其实这真是事半功倍。还有一个最有名的示例是ADA编译器,其使用了TDD,反而增加了代码中的BUG,因为你的代码多,测试就多,代码就更多,整个代码就太过臃肿。如果你测试中使用了断言,这意味着你就耦合上了代码,你的测试案例和你的代码耦合地越多,你的代码就越难维护。这就是我在《TDD并不是看上去的那么美》一文中说的TDD的代码臃肿和维护问题)
Bob大叔为Coplien对代码臃肿的说法感到惊讶。Coplien说,这就是他的经历,他看到的。Bob大叔承认有很多混乱的测试和混乱的代码,他觉得像XUnit这样的工具被滥用了。Coplien打断道,这不是要和你争论的,我争论的是这就是我看到大家在实践的东西。
Bob大叔反回到,你有没有看到CDD也被滥用的情况?Coplien说,他只觉得目前,软件业对CDD用的还不够。
最后,时间不够了,Bob大叔问了一个不相干的问题,他说,我们这里有BDD,CDD, TDD, 关于DD,他不知道谁是最先第一个使用带DD这个词的,他说他好像记得一个RDD - Responsibility Driven Development。
Coplien对这个问题可能很无语,他只能说——“DD,这是Unix的一个命令嘛,Disk Dump,但这可能算。谢谢你Bob,很高兴又一次见到你 ”
——————————————————正文分割线————————————————————
看完后,我的感觉如下:
这是2008年就在讨论的事,而在2011年我发布了《TDD并不是看上去的那么美》后中国这边才开始讨论。(InfoQ和 Thoughtworks怎么不去找Coplien?)
英语很重要,不懂英语,只看国内的东西,你就容易被洗脑,你就需要更多的时间和精力去思考那些早被人思考过的问题。
开发和测试,都是需要充分地了解业务,充分的思考,充分权衡后才能做得好的事。并不是你用了哪个方法后就专业了,就NB了。
相当BS——上不谈业务,下不谈技术,只谈方法论的人和公司,这是绝对的扭曲。
我在我的微博上说过这样一段话,我想在这里把我的这个观点阐述地更完整一些。
@左耳朵耗子:聪明的程序员使用50%-70%的时间用来思考,尝试和权衡各种设计和实现,而用30% – 50%的时间是在忙碌着编码,调试和测试。聪明的老板也会让团队这样做。而傻逼的老板,苦逼的程序员会拿出来100%-150%的时间来忙着赶进度,返工,重构,fix 大量的bug… 所以, 越差的团队一般会越忙,而且还忙不完。
在现在这个浮躁的时期,再加上敏捷咨询师们念的歪经,他们让人感觉上就像是软件产品是可以在很短的时间内高质量的完成的,这令那些管理者们很兴奋,就像巴甫洛夫的条件反射实验中的狗看到了肉就会流口水那样兴奋。他们使用TDD,快速迭代,不断重构,持续集成直至持续部署的方法在进行软件开发。
软件开发真是这样的吗?难道不需要花时间去思考吗?对此,有些观点在Todd的《“品质在于构建过程”吗?》以及《Bob大叔和Jim Coplien对TDD的论战》中谈到过了。我只想想表达下面的观点:
软件的精髓在于设计,设计是一件很费大脑的事件。对于软件来说,设计没有完美的,它总是一件需要取舍需要权衡的事,比如:时间换空间,空间换时间,TCP或UDP,同步还是异步,数据冗余还不冗余等等。那怕是一个小小的observers模式是pull方式还是push方式 都需要仔细讨论。这些的东西需要时间和做前期尝试。
TDD、快速原型和迭代可能会对软件和团队产生负面影响。在一开始,你需要花很大的精力来让你的软件从无到有(做过软件的人都知道,从零开始写代码是很痛苦的事),但是因为你没有想好,先做再说,所以,后期你会面临更多的质量问题而让你需要花更多的时间精力。当然,那些咨询师会让你用持续集成和持续部署这样的方法。但我想告诉你,这并不解决你软件设计的缺陷。举个例子——TDD、迭代、原型只关注功能性需求,其不会关注非功能性需求,比如性能问题,高可用性问题,系统维护问题(模块的耦合问题),等等。而这些问题往往都可以让你的软件设计重新来过。
重构是恶梦,重构应该越少越好。当你维护一个复杂的系统时你会知道重构是一件多么恐怖的事情(参看《重构代码的7个阶段》)。如果一开始没有想好,你要面临的不单单是re-design, re-architect,还要面对时间和人力成本的增加,最难的是你还要面对的是团队士气因为不断的rework而逐渐低落并产生厌倦和懈怠情绪。
所以,如果你能有多一些时间去和客户讨论一下需求和未来可能的变化,去调查一下实现的技术难点和细节,去和其他有经验的人讨论并推敲一下架构和设计,去思考设计上的缺陷,那么,你的coding会变得非常地直,直到你一眼就看到尽头,你的测试案例也会写得非常地好,你会几乎不需要重构,于是,你会在未来少写很多代码,从而你的软件开发会越来越轻松,直到技术开始换代。
我现在在做的项目,花了几乎4个月的时间来做设计,在这个过程中,我们反复思考、讨论和权衡若干种实现方法,并尽可能地穷举所有的场景和细节以及未来可能的变化(那怕是那些简单的模块),有个模块被重写了至少三次,每次都是写到一半就被推翻重写,我们整个团队不断地在和其它团队讨论,并在对系统不断地认识中对系统进行简化和优化,并力求达到完美。现在看来,没有贸然使用Scrum是明智的。
这就好像我们修路造桥一样,我们需要花大量的时间勘测地形地质,分析数据,思考可能出现的各种问题(各种自然灾害),评估不同的设计方案,而不是先尽快建好再说。
所以,多一些时间,不是让你多做几次迭代,多完成几个模块,而是可以让你少写一些代码,更快的交付一个更好的产品。
我相信你会有很多疑问,下面是我觉得你可能会有下面的一些观点,让我一条一条来回复:
首当其冲的一定会是项目的deadline,或是那种你没有活语权的项目。比如做那种“甲乙方合同式的项目”,我把这种项目统一认为是“外包项目”,在这种项目性质下,你很难有话语权。对此,我觉得,1)作为乙方的你还是应该和甲方在项目计划上争取一下,晓之以情,动之以理。2)如果不行,只能在时间、需求范围和质量上做一个权衡。另外,在这种情况下你要找一个方法,把你的压力和痛苦分担给用户和领导。(找到这个方法的前提需要你找到用户和领导他们害怕什么,嘿嘿)
过度设计和纸上谈兵。有人说会不会设计太多,造成过度设计,或是在设计上花太多的时间。这有可能。我上一家公司的一个项目团队就花了1年多的时间来不停不停的开会和做设计,结果release的时候还有1000多个bug。这个问题的原因是,这个团队的设计是在纸上谈兵,开会是开神仙会,讨论的设计都是浮云。所以,设计并不是讨论和思考,还需要去尝试,我认为当你的设计完成的时候,你的骨干核心代码都基本完成了。
我的团队成员水平太差,不会思考。首先,先恭喜你找到一堆码农,当然,这不怪你,这是中国教育和大环境的问题,让人不会思考。对于这样的情况,我有两个建议,1)量力而行,使多大的碗就吃多少饭。2)鼓励思考,那怕那些想法很不靠谱,因为如果不开始,那么将永远不会思考。
必需使用快速迭代。很多公司都在强行上敏捷,他们希望产品越快release越好,而没有充分的时间思考和讨论。对于这种项目,我的建议是,1)找有丰富经验的人来做。2)迭代过程中力求架构和程序逻辑的简单,简单,再简单,力求代码间的高内聚,低耦合。不然,重构的时候你就好玩了。
创业团队必需要快。做得快就是做得好吗?很多时候,不是谁快谁就能笑到最后的。这样的例子太多了。第一个做出来的人并不一定就会占领市场,其很有可能会成为先驱。
有钱的公司才会让团队用更多的时间去思考。错了,你们没有见过有钱的公司,有钱的公司可以招一堆干不成活的人,可以把事搞乱了再新来过,甚至可以把做失败的项目换个名字再重新立项。这些真正的有钱的公司只求快,只求人多,不怕做错决定。像我们这些没钱的人,干什么事都是小心翼翼地,生怕做错决定。
原文来源:http://coolshell.cn/articles/5686.html
http://coolshell.cn/articles/4891.html
I have adopted a very strong position against what particularly the XP community is calling test driven development.
InfoQ的视频很多时候相当的不给力,就像有前列腺的患者撒尿一样,半天都挤不出一滴。不过,好在那里有这两个人对话的摘录。在这里,我给大家摘要一下:
——————————————————正文分割线————————————————————
Coplien首先让Uncle Bob定义了一下TDD,Uncle Bob说明了他的三个法则:(敏捷的同学一定不陌生)
一个测试驱动的程序员,其不会在写出一个测试失败的Unit Test前,去写一句可用在生产线上的代码。(没有测试之前不要写任何功能代码)
在编写用于生产线上代码之前,不写过多的测试失败的Unit Test。(只编写刚好能体现一个失败情况的测试代码)
在现有代码通过Unit Test前,不写更多的用于生产线上的代码。(只编写恰好能通过测试的功能代码)
Coplien说他有意见的不是这三个法则,而是因为这个三个法则是孤立说出来的。Coplien说他和一些咨询师或是Scrum Master参与过很多的项目,他们发现这些项目都有两个问题:
他们使用TDD的时候,软件没有一个架构或是framework。当然,Kent Beck说——TDD可以驱使你去做架构。但是,TDD和Unit Test 是一回事吗?Unit Test是一个伟大的事,尤其是当你去写API和类库的时候。今天XP所说的TDD和UT很不一样。如果你使用TDD来驱动你的软件系统架构,那么,基本上来说,三个迭代以后,你开发的软件就会crash掉,而且无法再往前开发。 因为什么?因为连软件团队自己都受不了这三个迭代出来的架构,而且你还会发现,你根本没去去重构。
第二个问题是,TDD这种方法破坏了GUI(图形界面),就算是Kent也说:“你永远不可以在一个漂亮的界面后面隐藏一个糟糕的架构”,Coplien强烈地相信软件的架构是通过界面来发出其光芒。他觉得如果没有一个好的软件架构,这个会影响用户的操作。
Coplien接着说,如果我们使用Uncle Bob的三条法则,我们也许没有什么问题,但Coplien想告诉大家另一个非常重要的事,那就是软件架构。并说:“我根本不接受TDD是软件专业化实践的论点”。
Bob大叔说,让我们回到99年,那时的敏捷社区觉得软件架构是无关的,不需要软件架构,只需要做一堆tests,做一堆stories,以及足够快的迭代,这样就可以让那些代码魔幻式地拼装起来,这就是horse shit。对于大多数的敏捷拥护者来说,这的确是愚蠢的。今天你再和Knet说这个事,他也会说那不过是一种说法。
Coplien回应到,实际上,Knet在解释XP的时候,在他的书131页的位置说过,“是的,你得做些前期的架构,但也别把自己搞乱了”。
Bob大叔把话题转回来,继续聊关于架构方面的事,他说软件的架构很重要,他也写很一些关于架构的书,他说他也是一个架构方面的怪才,但是他认为架构自己并不会形成软件的所有的外表。他觉得好的软件架构和设计能力应该出现在若干次迭代之后。他觉得你在架构软件的时候,你会创造一些东西,也会破坏一些东西,并且会在几次迭代中做一些试验性的工作,来尝试一下不同的架构。在2到3次迭代以后,你可以知道那一种架构是对的,这样,你可以在后面的迭代中进行调整 。因此,他认为架构是需要进化和发展的,而不会因为被可执行的代码所形成,也不会因为你所写的测试而形成。
Coplien赞同架构进化的观点,而且他相信软件的架构的演变和进化不是因为你写的代码,也不是因为Use Case,也不是告诉你你的软件需求的范围和其中的关系,但是如果你做的方法是以增量式的,以用户驱动式的,而你却在和用户沟通时没有一些前期的业务知识,那么这一定是相当有风险的,并且你一定会把事搞砸的。
Coplien接着说,他在Knet早期提到TDD的时候和Knet时,提到YAGNI(陈皓注:You Aren’t Gonna Need It,XP的一个法则,也就是只做最简单的事)时,Kent说到:“让我们来做一个银行帐户,一个储蓄帐户”,储蓄帐户其实就是对余额进行一些加加减减的事,就像一个计算器一样。Copilen继续解释到,但是如果你要做一个真正的银行系统,你的软件架构根本不可能从一个储蓄帐户的对象(计算器)重构出来。因为储蓄帐户根本就不是一个对象,其是一个流程,后面有一个数据库的查帐索引事务,还有存款保证多和利息,还有一些转帐功能。就算是这样,这也只是用户的功能,你还需要支持税务人员和精算会计师等这些人,这会让银行系统成为一个错综复杂的软件架构,这绝对不是你可以用迭代干出来的事。当然,Bob大叔是可以的,因为他有40年的银行系统的经验。但是Bob大叔你的这40年可真不敏捷啊。
Coplien接着说, 因为Bob大叔可以在软件前期做很多很重要的决定,这让得后面的事变得相对比较简单。Coplien根本不相信只要你把代码往那一放,在上面披上一层皮,再设置好一些角色,设置好接口,在文档里写上整个业务结构,而你只有在有人花钱的时候你才会在其中填充进真正的代码,反之就违反了你的YAGNI原则。所以,你只是在你需要的时候做你要做的事,但你却还是要提前得到你的软件架构,否则你一定会把你自己逼进死角的。
Bob大叔辩解到,我说的可能和你说的这个有点不同。我们应该不会像你所说的往接口中写一些抽象成员函数,而是创建一些有抽象接口的对象。当然,我不会把一下子为这个对象装载上一堆方法。那些是我需要使用测试驱动或是需求驱动来做的事,我还会随时随地在看是否哪里软件架构可以让我拆分接口。
Coplien说,问题 是你得知道你要干什么?他说他非常同意Knet的书”XP Explained”里说的——“你不能去猜”,然后他举了一个例子,一个他曾经在一个电信项目中重新架构软件的例子,这是一个长途交换机的项目,项目组特别喜欢用面向对象,有一个人需要去做一个“Recovery Object”(应该是系统恢复对象),Coplien说这是很扯的一件事,因为系统恢复根本就不是一个对象,因为他对业务不熟,所以想这么做。而当你在细节上分析的时候,你会发现这根本就不是一个有成员方法的对象。我个人认为,Coplien想用这个例子来说Bob大叔的先定义对象的抽象接口并不是一个好的需求分析的方法。Coplien还说,这个事情今天被资本化成了SOA,真是在玩火啊。
Bob大叔说,这个他很同意。你的确需要知道这个对象的意义是什么。而且他和Coplien都同意应该根据可运行的代码来决定未来,而不是基于投机心理搞一个巨大无比的架构。
此时,Bob大叔把话题又带回原地,他问Coplien:“你需要多少的时间才能写出可运行的代码?是不是一个系统需要写200万行代码才能算?”,Coplien说,在他的经历中,200万行代码算是小项目了,他的项目都是几亿行代码的。而在让代码可以跑起来,他至少需要让所有的对象都联系起来。
Bob追问到,“那么你是怎么测试这些对象的连接性的?”,Coplien说,我当然要测试,我会测试系统启动和停止,看看有没有内存问题,半小时就好了。Bob大叔似乎找到了突破点,于是说到:“Excellent!那么我们间的分歧是什么呢?也许你只是不同意TDD的概念和其专业化,当然,这是另外一个话题了”。
然后,Coplien说了一段我非常非常认同的话——“我看到很多人正在做正确的事,来避免我们之前讨论的那些问题,当然那不是TDD的扩展,而是Dan North所说的BDD。可见,软件开发中很多人在开发软件中都是在用正确的很好的方法,而我对此有意见的是,有人把这个事说成TDD,然后人们就去买相关的书来了解TDD,并且看到“architecture only comes from tests”,我在过去6个月中听到过4次这样的说法,这就像你所说的,完全就是horse shit。而关于你所说的专业化的事,如果你没有见过一个专业化你怎么知道?”。(不是吗?大多数人都知道怎么开发软件,而不是TDD才是专业化的软件开发。)
然后,Bob想多谈谈专业化的事,Bob说,在今天,一个不负责任的程序会提交一段他没有跑过单元测试的代码,所以,要确定你没有把一条没有测试过的代码提交到代码库里的最佳做法就是TDD。
Coplien完全不同意这个说法。他觉得底层的东西是更重要的。他用了一个示例来攻击Bob大叔的这个观点,他先是说代码走查和结对编程都有好的有价值的地方,当然和这个话题不相关。然后他又说了Unit Test,想想我们的单元测试,可能我们的测试案例并不可能测试我们程序中参数的各种状态,这些状态有可能只是半打,有可能是一百个,有可能是2的32次方个,所以,我们可以命中一些状态,也会没有测试到一些状态,我们的测试真的只是试验性的,所以,如果你在测试中发现bug,你真的很幸运。
随后,Coplien推崇了一个叫“Design By Contract” – 契约式设计的方法(我在软件设计中那些方法中提到过,),这个方法认为软件有前验条件,后验条件,还有不变的。这个方法是Eiffel项目使用的一个方法,使用这个方法你可以静态的去做一些检查,相当于你做了一个基础架构来干这些事。Coplien相信这个方法有TDD所有的优点——我需要努力思考我的代码,我需要思考软件的外部接口,而且,Coplien发现这么做会比做测试更有效。这会让你对那些参数的范围考虑地更为宽广,而不是只在测试案例写几个随机分散的值来测试。
今天,Bertrand Meyer(Eiffel语言的创造者,他也不赞同TDD)把这个方法推进了一步,叫CDD - Contract Driven Development,这个是一种关注于对象间关系,其在程序运行前提条件和运行后的后验条中达成一种契约,可以通过对契约条件的动态或静态的检查,来对程序的功能进行验证。这样可以让你更有效地测试程序。这种方法需要对业务的重点部位非常好的了解。这是TDD很难做到的(这就是我在《TDD并不是看上去的那么美》一文中说的TDD的测试范围是个很大的问题)。
Bob大叔似乎在努力回忆CDD和Eiffel,然后他说,TDD不就是干这个的吗?TDD就是把契约变成单元测试,不但测试输入,也测试返回值,这不就是先验条件和后验条件,而且他说,Unit Test和代码结合得更紧,而契约没有和代码结合得紧密,这是他觉得很不舒服的地方。
Coplien说Bob大叔创建了不应该创建的二元论。他说代码在哪里,UT就跟到哪里,代码有多臃肿,UT就有多臃肿,而UT也是代码,也会有BUG,所以,其实这真是事半功倍。还有一个最有名的示例是ADA编译器,其使用了TDD,反而增加了代码中的BUG,因为你的代码多,测试就多,代码就更多,整个代码就太过臃肿。如果你测试中使用了断言,这意味着你就耦合上了代码,你的测试案例和你的代码耦合地越多,你的代码就越难维护。这就是我在《TDD并不是看上去的那么美》一文中说的TDD的代码臃肿和维护问题)
Bob大叔为Coplien对代码臃肿的说法感到惊讶。Coplien说,这就是他的经历,他看到的。Bob大叔承认有很多混乱的测试和混乱的代码,他觉得像XUnit这样的工具被滥用了。Coplien打断道,这不是要和你争论的,我争论的是这就是我看到大家在实践的东西。
Bob大叔反回到,你有没有看到CDD也被滥用的情况?Coplien说,他只觉得目前,软件业对CDD用的还不够。
最后,时间不够了,Bob大叔问了一个不相干的问题,他说,我们这里有BDD,CDD, TDD, 关于DD,他不知道谁是最先第一个使用带DD这个词的,他说他好像记得一个RDD - Responsibility Driven Development。
Coplien对这个问题可能很无语,他只能说——“DD,这是Unix的一个命令嘛,Disk Dump,但这可能算。谢谢你Bob,很高兴又一次见到你 ”
——————————————————正文分割线————————————————————
看完后,我的感觉如下:
这是2008年就在讨论的事,而在2011年我发布了《TDD并不是看上去的那么美》后中国这边才开始讨论。(InfoQ和 Thoughtworks怎么不去找Coplien?)
英语很重要,不懂英语,只看国内的东西,你就容易被洗脑,你就需要更多的时间和精力去思考那些早被人思考过的问题。
开发和测试,都是需要充分地了解业务,充分的思考,充分权衡后才能做得好的事。并不是你用了哪个方法后就专业了,就NB了。
相当BS——上不谈业务,下不谈技术,只谈方法论的人和公司,这是绝对的扭曲。
我在我的微博上说过这样一段话,我想在这里把我的这个观点阐述地更完整一些。
@左耳朵耗子:聪明的程序员使用50%-70%的时间用来思考,尝试和权衡各种设计和实现,而用30% – 50%的时间是在忙碌着编码,调试和测试。聪明的老板也会让团队这样做。而傻逼的老板,苦逼的程序员会拿出来100%-150%的时间来忙着赶进度,返工,重构,fix 大量的bug… 所以, 越差的团队一般会越忙,而且还忙不完。
在现在这个浮躁的时期,再加上敏捷咨询师们念的歪经,他们让人感觉上就像是软件产品是可以在很短的时间内高质量的完成的,这令那些管理者们很兴奋,就像巴甫洛夫的条件反射实验中的狗看到了肉就会流口水那样兴奋。他们使用TDD,快速迭代,不断重构,持续集成直至持续部署的方法在进行软件开发。
软件开发真是这样的吗?难道不需要花时间去思考吗?对此,有些观点在Todd的《“品质在于构建过程”吗?》以及《Bob大叔和Jim Coplien对TDD的论战》中谈到过了。我只想想表达下面的观点:
软件的精髓在于设计,设计是一件很费大脑的事件。对于软件来说,设计没有完美的,它总是一件需要取舍需要权衡的事,比如:时间换空间,空间换时间,TCP或UDP,同步还是异步,数据冗余还不冗余等等。那怕是一个小小的observers模式是pull方式还是push方式 都需要仔细讨论。这些的东西需要时间和做前期尝试。
TDD、快速原型和迭代可能会对软件和团队产生负面影响。在一开始,你需要花很大的精力来让你的软件从无到有(做过软件的人都知道,从零开始写代码是很痛苦的事),但是因为你没有想好,先做再说,所以,后期你会面临更多的质量问题而让你需要花更多的时间精力。当然,那些咨询师会让你用持续集成和持续部署这样的方法。但我想告诉你,这并不解决你软件设计的缺陷。举个例子——TDD、迭代、原型只关注功能性需求,其不会关注非功能性需求,比如性能问题,高可用性问题,系统维护问题(模块的耦合问题),等等。而这些问题往往都可以让你的软件设计重新来过。
重构是恶梦,重构应该越少越好。当你维护一个复杂的系统时你会知道重构是一件多么恐怖的事情(参看《重构代码的7个阶段》)。如果一开始没有想好,你要面临的不单单是re-design, re-architect,还要面对时间和人力成本的增加,最难的是你还要面对的是团队士气因为不断的rework而逐渐低落并产生厌倦和懈怠情绪。
所以,如果你能有多一些时间去和客户讨论一下需求和未来可能的变化,去调查一下实现的技术难点和细节,去和其他有经验的人讨论并推敲一下架构和设计,去思考设计上的缺陷,那么,你的coding会变得非常地直,直到你一眼就看到尽头,你的测试案例也会写得非常地好,你会几乎不需要重构,于是,你会在未来少写很多代码,从而你的软件开发会越来越轻松,直到技术开始换代。
我现在在做的项目,花了几乎4个月的时间来做设计,在这个过程中,我们反复思考、讨论和权衡若干种实现方法,并尽可能地穷举所有的场景和细节以及未来可能的变化(那怕是那些简单的模块),有个模块被重写了至少三次,每次都是写到一半就被推翻重写,我们整个团队不断地在和其它团队讨论,并在对系统不断地认识中对系统进行简化和优化,并力求达到完美。现在看来,没有贸然使用Scrum是明智的。
这就好像我们修路造桥一样,我们需要花大量的时间勘测地形地质,分析数据,思考可能出现的各种问题(各种自然灾害),评估不同的设计方案,而不是先尽快建好再说。
所以,多一些时间,不是让你多做几次迭代,多完成几个模块,而是可以让你少写一些代码,更快的交付一个更好的产品。
我相信你会有很多疑问,下面是我觉得你可能会有下面的一些观点,让我一条一条来回复:
首当其冲的一定会是项目的deadline,或是那种你没有活语权的项目。比如做那种“甲乙方合同式的项目”,我把这种项目统一认为是“外包项目”,在这种项目性质下,你很难有话语权。对此,我觉得,1)作为乙方的你还是应该和甲方在项目计划上争取一下,晓之以情,动之以理。2)如果不行,只能在时间、需求范围和质量上做一个权衡。另外,在这种情况下你要找一个方法,把你的压力和痛苦分担给用户和领导。(找到这个方法的前提需要你找到用户和领导他们害怕什么,嘿嘿)
过度设计和纸上谈兵。有人说会不会设计太多,造成过度设计,或是在设计上花太多的时间。这有可能。我上一家公司的一个项目团队就花了1年多的时间来不停不停的开会和做设计,结果release的时候还有1000多个bug。这个问题的原因是,这个团队的设计是在纸上谈兵,开会是开神仙会,讨论的设计都是浮云。所以,设计并不是讨论和思考,还需要去尝试,我认为当你的设计完成的时候,你的骨干核心代码都基本完成了。
我的团队成员水平太差,不会思考。首先,先恭喜你找到一堆码农,当然,这不怪你,这是中国教育和大环境的问题,让人不会思考。对于这样的情况,我有两个建议,1)量力而行,使多大的碗就吃多少饭。2)鼓励思考,那怕那些想法很不靠谱,因为如果不开始,那么将永远不会思考。
必需使用快速迭代。很多公司都在强行上敏捷,他们希望产品越快release越好,而没有充分的时间思考和讨论。对于这种项目,我的建议是,1)找有丰富经验的人来做。2)迭代过程中力求架构和程序逻辑的简单,简单,再简单,力求代码间的高内聚,低耦合。不然,重构的时候你就好玩了。
创业团队必需要快。做得快就是做得好吗?很多时候,不是谁快谁就能笑到最后的。这样的例子太多了。第一个做出来的人并不一定就会占领市场,其很有可能会成为先驱。
有钱的公司才会让团队用更多的时间去思考。错了,你们没有见过有钱的公司,有钱的公司可以招一堆干不成活的人,可以把事搞乱了再新来过,甚至可以把做失败的项目换个名字再重新立项。这些真正的有钱的公司只求快,只求人多,不怕做错决定。像我们这些没钱的人,干什么事都是小心翼翼地,生怕做错决定。
原文来源:http://coolshell.cn/articles/5686.html
http://coolshell.cn/articles/4891.html
作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:https://jackxiang.com/post/4756/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!
评论列表