读《代码不朽:编写可维护软件的10大要则》

代码不朽:编写可维护软件的10大要则(Java版)
因为本科并非软工和计算机专业,所以对软件工程中的一些概念理解欠缺。我的编程实践大部分来自于图像处理算法程序的编写,在编程过程中对代码规范,软件设计方面考虑较少。如果要成为一个专业的程序员,就需要学习已形成工业化的软件构建方式。
#可维护性解释

这本书解释了可维护软件中的“维护”的意思:可维护性是软件质量的一个标准,代表一个系统可被修改的难易程度。所以它是面向程序员的,假设两个软件完成相同的功能,但一个软件的源码,让其他人或者一段时间之后的自己,很难理解,更不用提修改了,就说明这个软件的可维护性比另一个差。维护的工作包括发现并纠正bug(纠正性维护);适应操作系统或运行环境的改变(适应性维护);根据需求增加新的功能(完善性维护);改进代码质量预防bug产生(预防性维护)。
#对10大要则的理解
按照从小到大,从细微到宏观的层次,这本书提取了编写可维护软件中10大编程原则,小到程序开发者应当时刻注意的代码规范,大到系统架构师应该考虑的系统重构、组件和及接口的设计准则。
##编写短小的代码单元
代码单元即面向对象编程里的方法或函数。这个原则要求每个函数的长度不应超过15行。
###动机
小的函数的好处?作者提出,小的函数容易重用,因为一个巨型的方法会包含很多细节,导致很难有一模一样的场景使用这个方法。作者提出,小的方法更易理解和进行单元测试。若超过15行,则意味着方法可以被拆分了。
###如何使用本原则
拆分重构的方式有提取方法将方法替换为方法对象
提取方法很容易理解,即从一个函数中提取一段代码,写成一个新的方法。但如果提取方法时发现,这个方法访问了很多局部变量,如果都作为新方法的参数的话,势必会导致参数列表过长。还有返回值的问题,如果这个方法会产生不止一个结果变量,那么这个方法就无法构建,因为java中一个方法只能有一个返回值。一个重构技巧是将这个方法替换成一个方法对象,将不同的局部变量和结果变量作为类的成员,然后调用类方法。
##编写简单的代码单元
这里的“简单”体现代码单元的分支点,所以这个原则可量化为:限制每个代码单元分支点的数量不超过4个。Java中常见的分支点代码就是if和switch语句。
###动机
让代码单元保持简单基于两个原因,一是简单的代码更容易修改,二是简单的代码更容易测试,分支点过多,意味着要有更多的测试用例。
###如何使用本原则
复杂的代码单元可能是因为其中包含很多互不相关的代码块,这种情况可以采用“提取方法”
若是其它复杂的情况,比如碰到链式的条件语句,如下判断国旗的语句:

...
List<Color> result;
switch(nationality) {
  case CHINA:
    result=Arrays.asList(Color.RED,Color.YELLOW);
    break;
  case FRENCH:
    result=Arrays.asList(Color.BLUE,Color.WHITE,Color.RED);
    break;
...
}

第一种方法是引入Map数据结构,将国家映射到指定的FLAG对象上;
第二种方法是使用“使用多态来代替条件判断”,实现同一个接口,代表广泛的国旗类型,然后为每个国家的国旗实现一个类。
再比如碰到嵌套的条件语句,为了使代码简单,可以使用“使用卫语句来代替嵌套的条件语句”的重构技巧,即标识出各种独立的情况,并插入return语句来代替嵌套式的条件语句。
例如

if(...) {
  if(...) {
    //TODO CASE1
  } else {
    //TODO CASE2
  }
} else {
  //TODO CASE3
}

可以改写成

if(...) {
  //TODO CASE1
  return;
}
if(...) {
  //TODO CASE2
  return;
}
if(...) {
  //TODO CASE3
  return;
}

可以看到分支点并未减少,然后可以再用“提取方法”减少复杂度。
##不写重复代码
对重复代码的定义是,一段至少6行都相同的代码。
###动机
如果复制代码,相同的代码出现在不同的地方,不利于源码的定位;如果需要修改的地方正是重复的代码,意味着要做很多重复性的工作,而且容易出错。
###如何使用本原则
首先想到的是提取方法;但若是一个方法是另一个类的私有方法怎么办?这时应当将提取的方法放到一个工具类中。
如果重复代码(6行以上完全相同)已不存在,但代码相似,具有相同的逻辑,这时应该考虑提取父类。
##保持代码单元的接口简单
限制每个代码单元的参数不能超过4个。
###动机
较少的接口参数能够保持简单的上下文,易于重用、理解和修改。
###如何使用本原则
将多个参数包装成对象,比如输入坐标参数, x x x y y y,可以包装成一个点对象。
使用“使用方法对象替换方法”的重构技巧,此处和前面有重合。
##分离模块之间的关注点
模块对应类的概念。
实际上就是要求类要保持小的体积,不要过大过复杂。
###动机
小的体积的类带来了类之间的松耦合,松耦合意味着类能更灵活的适应将来的变化。如果一个类做了很多事情,其耦合度会越来越紧,积攒大量代码,导致代码很难阅读和修改。
###如何使用本原则
第一种方法:根据功能将大类拆分为很小的类。一个类一开始可能很小,只是实现单一功能,但都不可避免负责越来越多的职责,当意识到这个类承担了不止一个职责时,就应该将这个类进行拆分。
第二种方法:提取一个接口,实现松耦合。比如一开始为一台相机设计了简单的相机类,只具备拍照,闪光灯打开和关闭3个方法。后来这个类的使用扩展到新的移动设备上,增加了定时功能。这时类变大,而且只有一个类,还需要检查旧设备上的代码有没有受影响。为了降低耦合度,可以使用一个接口,它只定义所有相机都需要实现的功能。
第三种方法:使用第三方库和框架来替代自定义的实现。
##架构组件松耦合
组件是比模块(类)更高一层的单元,设计到系统的架构。此原则要求尽可能减少当前模块暴露给(例如,被调用)其它组件中模块的相关代码。
###动机
独立的组件可以单独进行维护,方便划分职责,让测试变得容易。
###如何使用本原则
使用抽象工厂设计模式,简单的讲就是类的实例不能直接被创建(new一个),而是通过工厂类的方法返回。这种通用的工厂接口背后,隐藏了具体产品的创建过程。在这个环境下,产品通常都不止有一种类型。如果要使用其中的逻辑,需要通过创建通用的工厂对象调用类方法成员。
注:抽象工厂不同于工厂模式,简单理解就是抽象工厂的类型不止一个,所以产品至少有两个。参见抽象工厂模式和工厂模式的区别?-caoglish的回答-知乎

##保持架构组件之间的平衡
保持源代码中的组件数量接近于9。
###动机
好的组件平衡让查找和分析代码更容易,提供清晰的功能边界,分离维护职责。
###如何使用本原则
软件系统的开发有两种组织模式:
**基于功能领域划分的系统:**好处是可以从高层功能的角度来分析代码,坏处是技术人员需要了解多个技术栈
**基于技术划分的系统:**根据技术专长来划分,可能会有前端,后端,接口、日志等组件。
软件架构师需要选择如何组合功能的合适原则。明确系统的领域并坚持下去。
##保持小规模代码库
###动机
大型系统更加难以维护,易出现更密集的缺陷,以大型代码库为目标的项目更容易失败。
###如何使用本原则
**功能层面:**控制需求蔓延,功能标准化
**技术层面:**不要复制黏贴代码,重构代码,使用第三方库和框架(这同样是前面提到的准则)
##自动化开发部署和测试
测试包含单元测试、集成测试、端对端测试、回归测试、验收测试。不同类型的测试需要不同的自动化框架。
###动机
自动化测试可重复,有效率;自动化测试里的断言(assert)可以充当注释;通过编写测试可以反过来推促编写可测试的代码,提高代码质量。
###如何使用本原则
使编写单元测试成为每个开发人员的职责,比如使用Java中的单元测试框架jUnit。
使用像stubbing或者mocking这样的技术。stub即测试桩。需要测试桩是因为有些影响测试结果的测试条件是易变、无法统一的。比如拍照,两次拍摄的环境不可能完全相同,结果无法验证,所以需要一个假对象,即测试桩。mocking(模拟)是因为测试中某些函数是沉默的,不包含任何结果,可以在函数中添加计数来验证函数执行过。mock技术有自动化的框架。
建议生产代码和测试代码一比一,提高覆盖率。
##编写简洁的代码
给程序开发人员总结了7条“童子军军规”:
1、编写单元级别的良好代码
2、不要编写不好的注释
3、不要注释代码
4、不要保留废弃代码
注:包括3,同时还有其它的形式,比如不可能执行到的代码、无用的私有方法、注释中的代码
5、不要使用过长的标识符名称
6、不要使用魔术常量
注:指表达式中突兀出现的数字,应该先定义。
7、不要使用未正确处理的异常
注:包括以下情况,捕获异常却不处理(catch为空),直接捕获通用异常(比如RuntimeException异常,这些异常不会提供触发失败的状态或事件信息,所以没意义),将异常信息展示给终端用户(避免用户困惑或暴露信息,应该先转换为通用信息)

你可能感兴趣的:(又在写BUG呢)