代码重复
如果同一段代码反复出现,就表现某种想法未在代码中得到良好的体现。那么需要我们尽力去找出那到底是什么,然后再尽力将其更清晰的表达出来。
使用可搜索的名称
对于单字母名称和数字常量,有一个问题,就是很难在一大篇文字中找出来。
假如一个订单有5中状态,分别是待付款,已通过,已发货,已收货,已完结。
我们可以用waitPayState,passState,sendState,receiveState,finishState,而不是用12345。直接用12345,我们一般称之为魔鬼数字。
这样有2个好处
- 假如我们后期在已通过状态后面加一个申请退款状态,那么我们可以搜索代码中哪些地方引用了passState,而不是说全局去搜索2。
- 在项目多人开发的情况下,另外一个同事看见代码的状态是12345,根本搞不清楚什么意思,也会增加沟通成本,如果表的字段没有加好注释的话,可能过了一段时间,原先的开发工程师都搞不清楚12345分别代表什么意思了。
这种情况最好搞一个枚举类,下面我们以PHP为例子
类名和方法名
类名和对象名应该是名词或名词短语。例如,Customer,WikiPage,Account,避免使用Manager,Processor,Info这样的类名。类名不应该是动词。
方法名应当是动词或动词短语,如postPayment,deletePage或Save。属性访问器(acccessor),修改器(mutator)和断言(perdicate)应该根据其值命名,并依Javabean标准加上前缀get,set和is。
每个概念对应一个词
给每个抽象概念选一个词,并且一以贯之,例如,使用fetch,retrieve和get来给在多个类中的方法命名。你怎么记得住是哪个类中的哪个方法呢?很悲哀,你总是要记住编写库或类的公司,机构或个人,才能想得起来用的是哪个术语,否则,就得耗费大把时间浏览各个文件头及前面的代码
别用双关语
避免将同一单词用于不同目的。同一术语用于不同概念,基本上就是双关语了。
短小
函数的第一条规则是要短小,第二条规则是还要更短小。
这个规则需要大家各自把握,没有说一定要限制到多少行。但一个函数里面几百行,上千行,但大概率是有问题的,里面很多逻辑是可以提取出来为一个个小函数的。
只做一件事
函数应该做一件事,做好这件事,只做这一件事情。
要判断函数是否不止做了一件事,就是看它是否能再拆出一个函数。该函数不仅只是单纯地重新诠释其实现。
给函数取个好名字
函数越短小,功能越集中,就越便于起个好名字。
别害怕长名称,长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数起个能说明其功用的名称。
别害怕花时间起名字,你当尝试不同的名称,实测其阅读效果。
命名方式要保持一致,使用与模块名一脉相承的短语,名词和动词给函数命名。
函数参数
最理想的参数数量是0,其次是1,再次是2,应尽量避免3。有足够特殊的理由才能用3个以上参数。
从测试的角度看,参数甚至更叫人为难。想想看,要编写能确保参数的各种组合运行正常的测试用例,是多么困难的事情。如果没有参数,就是小菜一碟。如果只有一个参数,也不太困难,有两个参数,问题就麻烦多了。如果参数多于两个,测试覆盖所有可能值的组合简直令人生畏。
如果函数看起来需要2个,3个或者3个以上参数,就说明其中一些参数应该封装为类了。
例如下面两个声明的差别:
Circle makeCircle(double x,double y,double radius);
Circle makeCircle(Point center,double radius);
从参数创建对象,从而减少参数数量,看起来就像是在作弊,但实则并非如此,当一组参数被共同传递,就像上例中的x和y一样,往往就是该有自己名称的某个概念的一部分。
分隔指令和询问
函数要么做什么事,要么回答什么事,但二者不可兼得。函数应该修改某对象的状态,或是返回该对象的有关信息,如果两样都干,就会导致混乱。
注释
什么也比不上放置良好的注释来得有用,什么也比不上乱七八糟的注释更有本事搞乱一个模块。什么也不会比陈旧,提供错误信息的注释更有破坏性。
注意:注释总是一种失败,我们总无法找到不用注释就能表达自我的方法,所以总要有注释,这并不值得庆贺。
如果你发现自己需要写注释,就再想想看能否有办法翻盘,用代码来表达。
真实只在一处地方有:代码。只有代码能忠实地告诉你它做的事,那是唯一真正准确的信息来源,所以,尽管有时也需要注释,但是我们也该多花信息尽量减少注释量。
格式
你应该保持良好的代码格式,你应该选用一套管理代码格式的简单规则,然后贯彻这些规则。如果你在团队中工作,则团队应该一致同意采用一套简单的格式规则,所有成员都要遵守这套规则。
格式的目的
几乎所有的代码都是从上往下读,从左往右读。每行展现一个表达式或一个字句。每组代码行展示一条完成的思路。这些思路用空白行隔开来。
若某个函数调用了另外一个,就应该把它们放到一起,而且调用者应该尽可能放在被调用者上面。这样,程序就会有自然的顺序。若坚定地遵循这条约定,读者将能够确信函数声明总会在其调用后很快出现。
错误处理
错误处理很重要,但如果它搞乱了代码逻辑,就是错误的做法。
先写try-catch-finally语句
异常的妙处之一是,它们在程序中定义了范围。执行try-catch-finally语句中try部分的代码时,你是在表明可随时取消执行,并在catch语句中接续。
在某种意义上,try代码块就像是事务。catch代码块将程序维持在一种持续状态,无法try代码中发生了什么都是如此。所以,在编写可能抛出异常的代码时,最好先写出try-catch-finally语句,这能帮你定义该代码的用户应该期待什么,无论try代码块中执行的代码出什么错都一样。
给出异常发生的环境说明
你抛出的每个异常,都应当提供足够的环境说明,以便判断错误的来源和位置。
应创建信息充分的错误消息,并和异常一起传递出去,在消息中,应包括失败的操作和失败类型。如果你的应用程序有日志系统,可以传递足够的信息给catch块,并记录下来。
null值
1:不返回null值
2:别传递null值
TDD三定律
- 第一定律 在编写不能通过的单元测试前,不可编写生产代码
- 第二定律 只可编写刚好无法通过的单元测试,不能编译也算不通过。
- 第三定律 只可编写刚好足以通过当天失效测试的生产代码。
类应该短小
关于类的第一条规则是类应该短小,第二条规则是还要更短小。
对于函数,我们通过计算代码行数衡量其大小,对于类,我们采用不同的衡量方法,即计算其权责。
单一权责原则
单一权责原则(SRP)认为,类或模块应该有且只有一条加以修改的理由。该原则既给出了权责的定义,又是关于类的长度的指导方针。类只有一个权责—–只有一个修改的理由。
系统应该由许多短小的类而不是少量巨大的类组成,每个小类封装一个权责,只有一个修改的理由,并与少数其他类一起协同达到期望的系统行为。
注释掉的代码
看到注释掉的代码,就删除它。别担心,源代码控制系统还会记得它。如果有人真的需要,可以签出交旧的版本。
依赖注入
有一种强大的机制可以实现分离构造与使用,那就是依赖注入(Dependency Injection,DI)。控制反转(Inversion of Control IoC)在依赖管理中的一种应用手段。控制反转将第二权责从对象中拿出来,转移到另一个专注于此的对象中,从而遵循了单一权责原则。
在依赖管理情景中,对象不应负责实体化对自身的依赖,反之,它应当将这份权责移交给其他“有权利”的机制,从而实现控制的反转。因此初始设置是一种全局问题,所以通常这种授权机制要么是main例程,要么是有特定目的的容器。
简单设计的4条规则
只要遵循以下规则,设计就能变得“简单”
- 运行所有测试
- 不可重复
- 表达了程序员的意图
- 尽可能减少类和方法的数量
渐进
破坏程序的最好方法之一就是以改进之名大动其结构。有些程序永远不能从这种所谓”改进“中恢复过来。问题在于,很难让程序以“改进”之前的方式工作。
参考资料
《代码整洁之道》