代码设计的探索
出处:51CMM
作者:河清
计算的世界每时每刻都在变化,步伐看起来是越来越快。程序员必须不断应付新的语言、新的工具和新的系统,它们总有一些与老东西不兼容的新特性。程序越来越大,界面越来越复杂,而任务的时限也越来越短。但是,总有某些东西是不变的,总有一些稳定点,在这种地方从过去中学到的东西和洞察力,对于未来必定能有所帮助。
简单性和清晰性是第一位的、最重要的。因为几乎所有其他东西都只能跟着它们而来。做那种最简单的能解决问题的东西,选择那些应该是足够快的最简单的算法、能够满足需要的最简单的数据结构;用整齐清楚的代码把它们组合起来。除非性能测试的结果说明需要做更多的事,我们绝不要把事情复杂化。接口应该是整齐和简单的,至少是在发现无可辩驳的证据、说明把它弄得复杂一些有极大优越性之前。
普遍性常与简单性同在。它使我们可能一次就完全解决了问题,而不是对各种情况一个个地重复去做。普遍性常常也是达到可移植的正确途径:找一个一般性的,能够在所有系统上工作的解,而不是去扩大不同系统之间的差异。
不断进化。想第一次就构造出一个绝好的程序通常是不可能的。发现正确解决方法的必要洞察力只能来自思考和经验的结合;纯粹的内省不可能造就出好系统,纯粹靠玩命干也不行。由用户得来的反馈在这个地方非常重要。通过循环的方式:原型、试验、用户反馈和进一步精化,常常是最有效的。我们自己构建的程序常常进化的不够;从别人那里买来的大程序变得太快,根本没经过必要的改进。
减少耦合。接口是程序设计战斗中的一个大战场,接口问题出现在许多不同的地方。程序库是最明显的例子,还有不同程序之间的、程序与用户之间的接口问题。对简单性和普遍性的需求在接口设计方面表现得特别强烈。我们应该使接口具有一致性,容易学习和使用,应该一丝不苟地追求这些东西。应该把实现细节都隐蔽在边界后面,这永远是最安全的方法。
符号标记。一直被忽视,实际上它远不仅仅是程序员告诉计算机去做什么时采用的方式。符号标记提供了一种组织框架,在实现应用领域非常广泛的各种工具方面,对指导人们去构造那种写程序的程序方面都很有价值。我们对大型的通用程序设计语言都很习惯了,它们为我们的大量程序设计工作服务。但有时也会发现面前的工作变得非常集中,已经理解得很清楚,写这种程序几乎完全是机械性的。这可能就预示着一个时机,说明应该建立一种能自然地表达有关工作的符号标记和一个实现它的语言。正则表达式是我们最喜爱的例子,实际中存在着无数的这种机会,在那里我们可以为对付特殊工作建立一个小语言。当然,这种语言绝不应该复杂到抵消掉其收益的程度。
作为程序员,我们很容易沉醉于新的工具和技术,但是,从长远的观点看,真正起作用的还是看我们在使用一些基础技术和规则的情况下工作的怎么样。通过应用本文中的某些思想,你将能够发现你的代码更容易用了,你的排错过程也不再那么痛苦了,你对自己的程序设计更加有自信心了。希望你在自己的程序员生涯中获得更多的成果和回报。
代码设计的过程
他(洛克菲勒)用经营管理的方法将打球(高尔夫)的动作分解开;一天他将球打出了160码远,于是它的妻子惊叹:“我应当知道,你做什么都比别人强”
------ 荣 . 切尔诺
我开始这样做的时候,我的代码至少减少了50%的BUG,而开发的效率提高了更多;当我成了项目经理的时候,我毫不迟疑的将它推广到整个项目组,它如此优秀超出了我的预想,以至于我觉得要向你收取费用!
目标的认识
对代码干什么、有哪些限制和约束、编码规范是什么等等,要有清晰、明确的认识,如果文档或者任务安排有不明白的地方要“不耻下问”,当然有时会有争论,但是总比最后痛苦的修改代码强。
算法和技术的准备
好多算法和技术不是那么清楚,没有办法只能学习加研究。往往会有很多的实验性代码。对于这些代码一定不要直接“拷贝”到程序中去。如果你这样做了,80%的情况下,你会后悔。
结构的规划
进入了正式的工作,根据任务的特点画一画流程图、状态图、协作图,考虑一下变量和常量,这些会使你工作的有条不紊,而且代码结构优良。现在我发现UML真是一个好东西。
编写代码
好了,你将建设自己的大厦。当然施工的过程中有一些“魔杖”,我们会在后面描述。
测试
没有经过有效测试的代码只是一堆文字。如果你们公司有单元测试规范,那么按照它进行。在XP方法中要求在编码前就设计测试,我觉得只要进行了有效测试就可以了,至于有效测试是怎样的,我们就不在这里讨论了。
排错
对有些项目,调试可占到整个开发时间的50%。对许多程序员来说,调试是编程最为困难的部分。曾经为了解决的程序中的一个错误睡眠中也念念不忘,接连几天没日没夜地工作——即使所编的程序并不复杂,在这里往往会体验到编程中有某种令人兴奋的东西。
注释的编写
不要忘了这一步。有时为了确认注释是否有效,可以找另外一位程序员看看,这样往往可以发现一些自身的盲点。有效注释不是时间的浪费,但是太多注释和没有注释一样糟糕。
代码的特性
代码的味道是对程序的一种感觉,他们具备一种能力,能看出你的代码的好坏。 如果代码有异味,他们会捂着鼻子走开
2002年6月15日
我们评价一个代码有很多的标准,如果我们进行总结,往往发现他们其实也不是那么的繁多。这里只总结了朴素的四条,通过一个售票员来说明,希望您的代码不会被“解雇”。
业务
“你是干什么的?”“我是卖票的”“那你怎么在扫垃圾”。是的,代码首先应当做它应当承担的责任。无论打扫的如何干净,他都不是一个好的售票员。不要小看这个问题,许多程序员经常自作主张添加一些额外的“精彩”内容,而“本职”的却在那墙角的边上。好的代码做好了它的职责,同样不会去做它不应当做的事情,即便那很精彩。
效率
“干得怎么样?”“一分钟卖一张”“太慢了”,老板的脸色已经有些难看了。我们评价一个代码还要看看它完成任务的效率和开销。不过在当前的硬件技术水平下,我们大多数时候可以把它放在其他“味道”的后面。
结构
“你卖票的过程是怎样的”“这个随便了,先收钱、或者先给票都有”,老板已经愤怒了。代码功能的划分、代码段的大小、代码之间的接口非常重要,有些研究者认为“首先为人编写程序,其次才是计算机”。没有好的代码结构,再多的注释也是惘然。结构不好,注释的结构也不好,甚至无法描述,如果你的程序是一个迷宫,那么迷宫的地图也会让你头昏脑胀。
容错
“如果卖票出了错怎么办?”“没办法只能听天由命”,于是老板决定解雇这个售票员。代码就像一辆汽车,它驾驶员的行为都是不可预测的。在容错性编程中,其中心思想是,即使一个子程序被传入了坏数据,它也不会被伤害,哪怕这个数据是由其它子程序错误而产生的。不管它被输入的是什么,一个好程序的特点是“输入垃圾,什么也不产生”,或“输入垃圾,输出错误信息”,也可以是“不允许垃圾进入”。从现在的观点来看“输入垃圾,输出垃圾”,往往是劣质程序。
当我对代码的味道有了解,我将我碰到的程序员进行了分类,可能不会包括所有,但是应当覆盖了大部分。我遇到过一些让我敬佩的程序员,有时甚至让我自卑,当然是前两种!
强悍型
他们追求代码执行的效率,总是在考虑更高的执行速度、更小的资源消耗、更大的安全性;他们的世界主要就是计算,他们的思维就是0和1的运算,有时反应快的就像闪电。
系统型
他们总是在编码前很好的规划代码的结构,追求代码结构的合理;在他们的手里无论多么复杂的算法就像建房子一样,许多的简单却结合成一个宏伟的大厦。他们的代码就像诗歌,一样让人充满了美感。
卖弄型
他们往往了解一些低层的东西,所以总是使用低层的东西,而不考虑哪些简单的办法;他们最大的骄傲在于大家都不懂的他的算法。
涂画型
我遇到最多的一种类型,她们总是做到哪里改到哪里的;他们常常将试验代码直接拷贝,然后花费大量的时间来修改代码;他们的代码,常常就像一个孩子的涂鸦。
代码编写的规则
如果人能从历史中学习,我们将能学到多少东西啊!而经验的光亮就像船的艉灯,能在我们背后的波涛上留下一点余辉,也可能告诉我们前途的暗礁
2000年6月16日
汇总了一些规则或准则,把它收集到一起,作为探索的结束。这些规则有些属于我个人,大部分来自我的朋友和那些深邃而且简单的资料。这是一些经验的总结,随着时间的推移可能会失去它的意义,但是它们都来源于代码设计的思想,这些思想在我看来是那种永恒的东西。有一天,可能你可以从这些思想出发,重新修正我下面的内容,那么恭喜你,你已经超越了很多人。
风格
总体要求:
最好不要使用省略的表达形式
保持一致性、要准确、要清晰
以缩行形式显示程序结构
给函数和全局数据加注释
不要注释不好的代码,应该重写代码
注释是说明why而不是what的好地方
变量和常量:
全局变量用具有描述意义的名字
局部变量用短名字
把数定义为常量,不要定义为宏
使用字符形式的常量,不要用整数
函数:
函数采用动作性的名字
分解复杂的函数
接口
总体:
在各处都用同样方式做同样的事
规划接口的大小,最好遵循一个统一的规则
一个接口:
隐蔽实现的细节
选择一组正交的基本操作,不要随便增加新的对外接口
每个对外接口都要设计异常处理机制
在低层检查错误,在高层处理
释放资源与分配资源应该在同一层次进行
排错
总体:
现在排除,而不是以后
不要两次犯同样错误
寻找熟悉的模式
分而治之
单个排错:
把你的代码解释给别人
显示输出,使搜索局部化
使用工具
画一个图
研究错误的计数特性
写自检测代码
测试
测试路线:
首先测试最简单的部分
以递增方式做测试
度量测试的覆盖率
自动回归测试
测试一个模块:
弄清所期望的输出
测试代码的边界情况
测试前条件和后条件
检查错误的返回值
检验那些应当保持的特征
性能
性能分析:
画一个图
使用轮廓程序
自动计时测量
集中注意瓶颈
不要优化无关紧要的东西
收集公共表达式
特殊情况特殊处理
不存储经常重算的东西
提高性能:
用低代价操作代替高代价操作
写专用的存储分配程序
考虑中断的优先级
调整进程的并行关系
使用某个低级语言里重写代码
使用尽可能小的数据类型以节约存储
调整代码,让编译程序做优化
对输入输出做缓冲
可移植性
策略:
遵循通用标准
使用主流技术
使用标准库
只使用到处都能用的特征
编码:
警惕语言的麻烦特性
避免条件编译
系统设计:
用正文做数据交换
数据交换时用固定的字节序
把系统依赖性局限到独立文件里
不要假定是A S C I I或者是英语
用多个编译系统试验
好程序员经常思考的问题
你的老板并不能强迫你成为一个好的程序员,甚至过了很长一段时间你的老板也无法判断你是否是一个称职的程序员。如果你想成为一个高手,你得全靠你自己下功夫。它和你个人性格有关
本来计划在上面的文字完成,这个文章就结束了,但是我想起一个我常常考虑的问题,那些好的程序员都在想些什么。我想这个对我的程序生涯会有很大的帮助,于是我经常去和一些好的程序员讨论他们的想法,也就有了这些补充内容。
锤子只能定钉子
不要僵化、绝对的使用最新的技术,它不过是一件在某些场合下有用,而在某些场合下又无用的技术。如果你拥有的唯一工具就是一把锤子,不要把整个世界都当作一个钉子。所以很多的好程序员还在使用一些不那么“先进”的技术,因为这些技术最好解决这个问题。
纪律和创造性
当建造与地王大厦“同等”规模的软件时,需要同等严密的计划和规则。我发现两种态度,一种认为规则和纪律是夏天的衣服,虽然很热,但是我们不能在很多人面前脱下它;另一种认为规则和纪律是工地的安全头盔,会束缚个人自由但是会保障项目的安全。无论是那种态度,都不认为组织的纪律和规则会压制他们的创造性,只是有时会有些不习惯。许多有很高创造力的人都能很好地遵守纪律,他们在规则和纪律的限定范围内进行优美的工作。
不要匆忙编码
一些好的程序员抵制不了立刻开始进行编码工作的渴望。另一些好的程序员在不重视准备工作的管理环境中,那里的管理人员往往不理解那些在创建准备工作上花费时间的程序员。所以他们经常要克制自己,以及与别人沟通。他们常用来说服领导的故事是:如果在开始的时候做出的是辆马车,你尽可以用你所喜欢的各种手段修正它,但是无论你怎样修正,它也决不会变成一辆汽车。或许你所得到的是一辆最好的马车,但如果你想要的是汽车,你就不得不从开始的时候进行诸如问题定义、规定解决办法等等工作时,我所进行的就是这样的工作。
应对变更
变动产生的原因可能是由于反复无常的数据结构,也可能是由于文件格式和系统功能改变,新的性能等而引起的。这些变动有时是为了增加新的能力以便强化功能,也有时是版本增加而引起的。好的程序员总是动态的看待代码,他们往往通过良好的结构设计增强系统的灵活性,以便容纳这类变动。
勇于放弃
知道在什么时候放弃是困难的,但是这是必须面对的一个问题。当觉得自己屡屡受挫的时候,可能意味着是对自己的行动设置规范的时候了:“如果我不能用这种方法在3天内解决问题,我将用十分钟时间考虑谁可能帮助我,并在下一个十分钟内请教他。很多好的程序员都非常谦虚,而且乐于向别人请教。