这篇算是 一堂如何提高代码质量的培训课 的读后感。作者网名fangang. 这系列文章涵盖很多内容,包括设计模式,DDD,代码审核等等。这些话题大于“代码质量”。更像是“提高软件质量的培训课”。培训课本身就是高质量的,赞!网上有不少转载,比如http://www.tech-q.cn/thread-3831-1-1.html。赞同作者的很多观点,也收益不少。总结一下:
[fangang]我们评价高质量代码有三要素:可读性、可维护性、可变更性。
[vcycyv]同意这三个方面。我总这样想,软件的核心是可维护性。可维护性主要就体现在可读性和扩展性。
[fangang](可读性)不要编写大段的代码
[vcycyv]Martin Fowler管"从大段代码抽取独立的方法"叫做"extract method",这是常见而重要的重构手段。在他的refactor书里从技术层面做了详细的阐述。分解的过程中,比较容易形成技术障碍的local variable. 引一段书里的话,专门讨论这个话题的:
• Copy the extracted code from the source method into the new target method.
• Scan the extracted code for references to any variables that are local in scope to the
source method. These are local variables and parameters to the method.
• See whether any temporary variables are used only within this extracted code. If so,
declare them in the target method as temporary variables.
• Look to see whether any of these local-scope variables are modified by the extracted
code. If one variable is modified, see whether you can treat the extracted code as a query
and assign the result to the variable concerned. If this is awkward, or if there is more than
one such variable, you can't extract the method as it stands. You may need to use Split
Temporary Variable and try again. You can eliminate temporary variables with
Replace Temp with Query (see the discussion in the examples).
• Pass into the target method as parameters local-scope variables that are read from the
extracted code.
• Compile when you have dealt with all the locally-scoped variables.
• Replace the extracted code in the source method with a call to the target method.
• Compile and test.
前两年看到一个讨论的很火的话题,说任意复杂的产品,都能break into一系列的小方法,每个方法行数不超过5行。当然没必要这么狠,但如果你真这么狠,我倒觉得没啥明显的坏处。
"而一些更有经验的程序员会采用另外一种从上往下的编写方式。当他们在编写程序的时候,每个被分出去的程序,可以暂时只写一个空程序而不去具体实现功能。当主程序完成以后,再一个个实现它的所有子程序。采用这样的编写方式,可以使复杂程序有更好的规划,避免只见树木不见森林的弊病。"这也是一个好的实践。
提到这种自上而下的编程方法,顺便推荐 《斯坦福大学开放课程:编程方法》。这是一系列视频教程。google一下有很多地方可以下载或在线观看,比如优酷http://v.youku.com/v_show/id_XMjE4NzQ0NDI0.html 在视频教程里教授也演示了自上而下的编程方法。 这是入门级的教程,本来想通过它练练听力,后来发现这课程根本就是喜剧片,看的过程一直狂笑,教授特逗。
跟可读性相关的一个话题就是注释。近年来一个广泛被接受的做法是,尽量通过 避免大段代码 以及良好的变量命名达到可读性。注释在这个过程中被淡化了。我基本同意这种做法,程序结构清晰,自解释的变量名都是重要的实践。变量名尽量不要写缩写,如果写缩写的话,一定要有缩写字典有据可查。看spring的代码,可以发现很多类名或方法名在30个字符以上。大段注释往往暗示程序结构的不合理。不鼓励写过多注释的结果就是,剩下的注释都是重要不可或缺的,注释是程序的一部分,需要和程序保持一致,也需要被维护。另一个基本而重要的原则是,要把public package protected private方法按照顺序写。更重要的、可访问性更高的方法放前面,尽管eclipse里可以方便地在视图过滤非public方法。
[fangang](可维护性)但是,事实却不一样,对机关级次计算的代码遍布整个项目,甚至有些还写入到了那些复杂的SQL语句中。
[vcycyv] Domain Driven Disign强调,domain的逻辑要圈在一定边界中。往上不能爬到service, 甚至UI层。往下不能渗透到repository以下,比如sql语句里。 Domain Driven Design的书在这里下载。更多电子书可以看之前写的博客 分享十二本经典电子书
[fangang]将专用代码提升为通用代码
[vcycyv] 这个过程大家都会遇到。什么代码算通用代码并不容易界定。过多的通用代码淹没了真正有通用意义的方法,不足的通用代码容易造成代码重复。这个过程有这么几个方面需要注意。
一,避免重复造轮子。 要对JDK常用的API以及经典第三方的软件包有一定了解。举两个例子:古典的singleton在JDK1.5之后可以用单一元素的enum表示,而且不用考虑serializable的readresolve问题; apache commons里的io, lang等包提供了很多常用的API。我在我们的产品里,用到io的FileNameUtils和ExceptionUtils的一些方法。包括取文件后缀名,获取exception的root cause以及excepiton stack的字符串。 要尽量重用可靠的第三方代码, 原因是你自己写的代码多半不如Sun和apache的人好。更重要的是,你自己写的每一行code都要自己维护。代码行数本身就是代码维护量的一个因素。最近一两年疯狂地喜欢删除代码,估计再发展下去我就要被删除了。
二, 还是domain为先。当你犹豫一段比较“通用”的代码应该放在module里还是拿出来的时候,尽量还是保留在module里吧。因为如果你犹豫来,犹豫去之后,决定它是global的通用代码,下一个人非常可能在module里找不到,就认为没有这种code,自己又写一遍。太激进的写一堆通用代码,一旦组织不好,就没人爱去从通用代码里寻宝了。关于这一点,martin大叔有近似的阐述, 在Patterns of Enterprise Application Architecture里
A common concern with domain logic is bloated domain objects. As you build a screen to manipulate orders you'll notice that some of the order behavior is only needed only for it. If you put these responsibilities on the order, the risk is that the Order class will become too big because it's full of responsibilities that are only used in a single use case. This concern leads people to consider whether some responsibility is general, in which case it should sit in the order class, or specific, in which case it should sit in some usage-specific class, which might be a Transaction Script (110) or perhaps the presentation itself.
The problem with separating usage-specific behavior is that it can lead to duplication. Behavior that's separated from the order is harder to find, so people tend to not see it and duplicate it instead. Duplication can quickly lead to more complexity and inconsistency, but I've found that bloating occurs much less frequently than predicted. If it does occur, it's relatively easy to see and not difficult to fix. My advice is not to separate usage-specific behavior. Put it all in the object that's the natural fit. Fix the bloating when, and if, it becomes a problem.
三、通用代码一定要有整体设计。如果调用通用代码的那些invoker不完全在你控制的范围,比如你写的程序有其他项目组在调用,那么你修改API的签名一定会被骂死的。所以通用代码要思前想后design清楚了才release.结构清楚的通用代码也比较方便以后查找。好的程序总是分层的,通用代码要尽量往底层放。
[fangang]一个快速提高软件质量的捷径就是利用设计模式。
[vcycyv]设计模式除了是一种工具以外,学习、思考设计模式能扩展coding的思维方式。自己也总结了一篇博客 串讲23种设计模式 。
[fangang]拿一个员工工资系统来说吧。当人力资源在发放一个月工资的时候,以及离职的员工肯定不能再发放工资了。在系统设计的期初,开发人员商量好,在员工信息中设定一个“离职标志”字段。编写工资发放的开发人员通过查询,将“离职标志”为false的员工查询出来,并为他们计算和发放工资。但是,随着这个系统的不断使用,编写员工管理的开发人员发现,“离职标志”字段已经不能满足客户的需求,因而将“离职标志”字段废弃,并增加了一个“离职时间”字段来管理离职的员工。然而,编写工资发放的开发人员并不知道这样的变更,依然使用着“离职标志”字段。显然,这样的结果就是,软件系统开始对离职员工发放工资了。仔细分析这个问题的原因,我们不难发现,确认员工是否离职,并不是“发放工资”软件类应当完成的工作,而应当是“员工管理”软件类应当完成的。如果将“获取非离职员工”的任务交给“员工管理”软件类,而“发放工资”软件类仅仅只是去调用,那么离职功能由“离职标志”字段改为了“离职时间”字段,其实就与“发放工资”软件类毫无关系。而作为“员工管理”的开发人员,一旦发生这样的变更,他当然知道去修改自己相应的“获取非离职员工”函数,这样就不会发生以上问题。
[vcycyv]哈哈,我喜欢这个例子。DDD的核心思想就是这个。当然DDD的理论还有一些具体的实践。Eric Evan在他的DDD书里讲了一个cargo的例子,当时我还找了一下代码,没找到,从fangang的博客评论里居然发现哪里能找到代码了,http://dddsample.sourceforge.net/, 还没来得及具体看。以后有机会深入了解一下。
[fangang](代码审查) 被审查后的代码如果还出现缺陷,审查者应当负有责任
[vcycyv] 这个系列的最后一篇文章主题是代码审查。作者所说的最佳实践很有道理,但不是在任何环境下可操作性都强的。我不知道最佳实践是什么,但我觉得有一些问题是值得注意的。
一、代码审查的范围要小,最好是one-one的,跟绩效没有关系,范围小可以保证审查不会太伤“面子”,否则会影响团结氛围。
二、审查者在指出错误的同时,一定要说如何修正问题。我非常非常反感那种善于用大道理指出别人错误,自己却把错误犯得更夸张的那种人。如果你自己没有更好的解决方案,就别说那是问题。
三、代码审查制度的推行以良好的团队氛围为前提。大家都是为了产品着想,这样代码审查才不至于扯皮。
四、关于审查者的责任。我同意fangang说的"被审查后的代码如果还出现缺陷,审查者应当负有责任", 但是谁来追究责任,如何界定责任,怎么追究责任呢?呵呵,这个恐怕不好操作。代码审查显然不能杜绝defect, 但是明显的设计,编码错误是应该看到的,但怎么又算"明显"呢。软件本身不像数学那样严格,容易界定问题。纪律在软件行业作用有限,团队氛围比纪律重要。但怎么营造好的团队氛围又是一个太宽泛的问题。