代码整洁之道
软件质量不但依赖于架构及项目管理,还与代码质量紧密相关。
代码质量与整洁度成正比,干净的代码即在质量上较为可靠,也为后期维护、升级奠定良好基础。
另一种概念叫做极限编程
原则
- SOLID
- DRY
- KISS
童子军军规
让营地比你来时更干净
有意义的命名
- 名副其实,表达真实意义
- 不误导
- 做有意义的区分,避免 a、b、c
- 可读、可搜索
函数
- 单一职责
- 行数 20-100
函数参数
- 0参数最佳,3个参数已经勉为其难
- 标识参数丑陋不堪,向参数传入布尔值骇人听闻
- 如果函数需要三个以上的参数,说明这些参数应该封装为类了
Circle makeCircle(double x,double y,double radius);
Circle makeCircle(Point center,double radius);
class Foo {
public function bar($flag = true) {
}
无副作用
函数承诺只做一件事,实际上还做了其他的。
public class UserValidator {
private Cryptographer cryptographer;
public boolean checkPassword(String userName, String password){
User user = UserGateway.findByName(userName) ;
if (user != User.NULL) {
String codedPhrase = user.getPhraseEncodedByPassword();
String phrase = cryptographer.decrypt (codedPhrase,password );
if("Valid Password".equals(phrase)){
Session.initialize(); //实际上还做了session 初始化的操作
return true;
}
return false;
}
要么抽离Session.initialize()
,要么重命名为checkPasswordAndInitializeSession
不要给人误导。
使用异常代替错误码
使用错误码就要定义错误码枚举,枚举类被大量导入调用。一旦增加或修改错误枚举,就要对所有引入的文件进行编译。
if (deletePage(page)==E_OK){
if (registry.deleteReference(page.name)==E_OK){
if (configKeys.deleteKey(page.name.makeKey())==E_OK){
logger.log("page deleted");
} else {
logger.log("configKey not deleted");
} else {
logger. log("deleteReference from registry failed");
} else {
logger.log("delete failed");
}
return E_ERROR;
}
try {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
catch (Exception e) {
logger.log(e.getMessage());
}
注释
注释并不像辛德勒的名单。它们并不“纯然地好”。实际上,注释最多也就是一种必须的恶。若编程语言足够有表达力,或者我们长于用这些语言来表达意图,就不那么需要注释——也许根本不需要。
注释掉的代码
20世纪60年代,曾经有那么一段时间,注释掉的代码可能有用。但我们已经拥有优良的源代码控制系统如此之久,这些系统可以为我们记住不要的代码。我们无需再用注释来标记,删掉即可,它们丢不了。我担保。
格式
文件长度
200-500
行字符数
上限120
对象和数据结构
德莫特定律
著名的得墨式耳律(The Law of Demeter)认为,模块不应了解它所操作对象的内部形。如上节所见,对象隐藏数据,曝露操作。这意味着对象不应通过存取器曝露其内部结构
因为这样更像是曝露而非隐藏其内部结构。
更准确地说,得墨式耳律认为,类C的方法f只应该调用以下对象的方法:
- C
- 由f创建的对象;
- 作为参数传递给f的对象;
- 由C的实体变量持有的对象。
null
- 别传null值
- 别返回null值
系统
“复杂要人命。它消磨开发者的生命,让产品难以规划、构建和测试。”
——Ray Ozzie,微软公司首席技术官
依赖注入
在依赖管理情景中,对象不应负责实体化对自身的依赖。反之,它应当将这份权责移交给其他“有权力”的机制,从而实现控制的反转。因为初始设置是一种全局问题,这种授权机制通常要么是main例程,要么是有特定目的的容器。
扩容
“一开始就做对系统”纯属神话。
代理
AOP有时会与实现它的技术相混淆,例如方法拦截和通过代理做的“封包”。AOP系统的真正价值在于用简洁和模块化的方式指定系统行为。
并发编程
并发编程很难,非常难。如果你不那么细
心,就会搞出不堪入目的东西来。看看以下常见的迷思和误解:
-
并发总能改进性能
并发有时能改进性能,但只在多个线程或处理器之间能分享大量等待时间的时候管用。
事情没那么简单。 -
编写并发程序无需修改设计
事实上,并发算法的设计有可能与单线程系统的设计极不相同。目的与时机的解藕往往对系统结构产生巨大影响。 -
在采用Web或EJB容器的时候,理解并发问题并不重要
实际上,你最好了解容器在做什么,了解如何对付本章后文将提到的并发更新、死锁等问题。
下面是一些有关编写并发软件的中肯说法:
- 并发会在性能和编写额外代码上增加一些开销;
- 正确的并发是复杂的,即便对于简单的问题也是如此;
并发缺陷并非总能重现,所以常被看做偶发事件而忽略,未被当做真的缺陷看待; - 并发常常需要对设计策略的根本性修改。
味道建议
命名常量代替魔术数
准确
- 用浮点数表示货币几近于犯罪。
- 因为你不想做并发更新就避免使用锁和/或事务管理往好处说也是一种懒惰行为。在代码中做决定时,确认自己足够准确。
- 明确自己为何要这么做,如果遇到异常情况如何处理。
- 别懒得理会决定的准确性。如果你打算调用可能返回null的函数,确认自己检查了null值。
- 如果查询你认为是数据库中唯一的记录,确保代码检查不存在其他记录。
- 如果要处理货币数据,使用整数!并恰当地处理四舍五入。
- 如果可能有并发更新,确认你实现了某种锁定机制。
- 代码中的含糊和不准确要么是意见不同的结果,要么源于懒惰。无论原因是什么,都要消除。
返回异常
/**
*
* @param array $awbnos
* @throws Exception|ConnectionTimeOutException
* @return array Description
*/
public function fetchTrace(array $awbnos):array
{
}
php 利用 phpdoc 和 php7 特性能支持让调用者注意异常和返回值的正确处理。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
}
java 函数定义语法天生支持。
避免过多嵌套
基本法
- 单一职责,贯彻落实
- 短、少,类1000行,方法120行,参数3个,严格要求。
- 消灭mess,有1个就会有无数个
- Later equals never 稍后等于永不,别等现在就去做
- 打磨,分解函数、修改名称、消除重复。缩短和重新安置方法。有拆散类。同时保持测试通过。
保持重构、热爱重构、注意单元测试回归测试
推荐阅读
- 《Clean Code》
- http://kaelzhang81.github.io/2020/04/10/译-设计高质量软件/