课程大纲
编程价值观
要编写出高质量的代码,就必须树立正确的编程价值观,从态度、习惯与技能三个方面端正对高质量代码的认识。
什么是高质量代码
高质量代码体现了软件系统的内部质量,包括清晰、正确、可用、健壮、灵活和可维护这几个特征。
• 清晰:意味着代码结构是清晰分明的,代码是可读而优雅的,能够很好地表达开发者的意图。
• 正确:则意味着编写的功能满足客户需求,且尽可能避免出现缺陷。
• 可用:意味着软件系统是正常可以运行的,不会因为用户的错误使用或者其他安全功能导致系统出现问题。
• 健壮:则要求系统能够建立良好的容错功能,不会因为某个功能的错误而导致整个系统崩溃。
• 灵活:则意味着程序设计的可扩展性好,可以很好地应对需求的变化。
• 可维护:则意味着代码易于维护,易于扩展和修改,能够降低维护成本。
本部分内容将从正反两方面给出代码示例,通过对比的方式展现高质量代码的特征。
简单设计
Kent Beck提出来的简单设计原则,可以指导我们编写出高质量代码。该原则具有实证主义的特征,强调根据具体场景选择最简单的方案满足客户需求,同时针对可重用性与良好的可读性,通过程序的清晰简单来保证代码的高质量。
简单设计原则是遵循极简主义的方式给出软件开发的方法指导,虽然只有短短的五句描述,但其中的内涵却值得我们认真思考。
整洁代码
编程哲学
如何写出高质量代码?这需要我们在提高编码技能的同时,以严谨的态度对待编程工作,反复推敲和修改代码。这部分内容主要讨论:
• 编写模块化的代码
• 编写可读的代码
• 编写简单的代码
• 编写直观的代码
• 编写健壮的代码
可读性
命名
可读性的一个关键是命名,要求对变量、函数、类以及模块等的命名能够很好地传达设计者意图,让代码尽可能地清晰易懂。命名时,应充分考虑对各种词性的运用,避免过长的名称,避免因为命名不当导致程序出现歧义。
表达式
不要将表达式的细节暴露在外,这会认为制造阅读代码的障碍,针对长表达式,更需要对表达式进行合理封装。有时候,我们在使用条件判断时,可能用错了地方,没有将真正存在差异的地方分辨出来。我们需要让判断条件做真正的选择。要注意处理布尔型标志参数。
合理的分段
在编写代码时,通过对代码进行合理的分段,可以使得代码结构更加清晰,可读性更高。分段时,也是对代码的理解,尤其是促进对职责的分辨。此时,可以考虑标注合适的注释,使得代码更容易懂。同时,这种分段与注释,其实也是对代码进行改造的基础。
DSL
DSL(Domain Specific Language),即领域特定语言。它通过运用一些模式与方法,使得代码更好地体现领域知识,展现出让领域专家也能读懂的良好可读性。
在编写代码时,我们最常使用的一个公共 DSL 模式,称之为“连贯接口(Fluence Interface)”,他将其定义为能够为一系列方法调用中转或维护指令上下文的行为。
案例分析:若干代码片段,多数来自于真实项目的丑陋代码,演示如何提高这些代码的可读性,使得代码结构更加清晰。
高质量函数
整洁的函数
函数的第一规则是要短小。第二条规则是还要更短小。它应该遵循单一职责原则。保证函数只做一件事,就可以有效地保证写出短小的函数,写出可读性高的函数;同时也有利于函数的重用。前面的案例正是因为违背了这一原则,导致函数变长,且不易理解。
阅读函数时,要注意识别和提取无关的子问题域,辨别函数的最高目标,从而改进函数。应保证每个函数一个抽象层级。
案例分析:Fitness代码分析
异常处理
使用异常替代返回错误码。
采用错误码,就不可避免需要使用分支,从而可能导致更深层次的嵌套结构。其次,若能使用异常来处理错误分支,则可以使得错误处理代码能够从主路径代码中分离出来。
案例分析:版本升级管理系统的异常处理
高质量设计
高内聚松耦合
内聚性即软件单位内部的关联紧密程度,这意味着在软件设计中,我们需要合理分辨对象的职责,并让对象尽可能满足单一职责原则。而所谓“职责”,就是变化原因,因而保持内聚性,就是要充分地识别变化。耦合性指两个或多个软件单位之间的关联紧密程度。如果软件单位之间存在耦合,就说明它会因为变化而产生影响,属于需要封装变化,保证API的抽象。
因而,高内聚松耦合原则推导出一个重要概念,即“变化”。而要有效地应对变化,对内则需要高内聚,对外则需要保证松耦合。
对象的合理封装
对于对象的封装而言,我认为有两个步骤,可以帮助获得合理的封装。
首先是分辨职责,即弄清楚职责应该分配给什么对象。这个识别职责的过程同时也是寻找对象的过程。在这个步骤中,可以尝试用一句话来描述职责,只要你描述清楚了,则它应该依附的对象就应该自然而然显现。第二步则是判别哪些是实现细节,那些是可以公开的接口,以保证对细节的合理隐藏。暴露太多的细节可能会产生不合理的依赖,而从职责的角度来讲,这些细节并不是调用者所关心的内容。
对象的封装应该遵循信息专家模式与迪米特法则,又或者从代码层面,识别出的坏味道为Feature Envy。
案例分析:报表系统之参数处理
继承与委派的区别
继承是一种扩展机制,一种快速实现的重用手法。它在面向对象设计领域中的地位举足轻重,却又经常被滥用。委派虽然失去了继承在重用方面的简单性和在多态方面的优势,但却比继承更加地灵活,耦合度较低。一个良好的设计原则是优先使用委派(即组合)而非继承。
案例分析:两种分离方案的对比
多态与抽象
抽象是保证可扩展设计的要素,同时,它也是多态的基础。多态是指一个对象在不同时间表现为不同实例类型的能力。利用这种运行时可替换的特点,就可以根据不同的条件,替换为不同的对象,实现功能的可替换,以满足功能的变化。多态保证了程序的灵活性,因为它将对象形态的决定权交给了调用者。同时,它还保证了程序的稳定性。由于抽象抹去了具体实现细节的差异,使得调用者不因实现细节的变化而改变原来定义的抽象类型,从而达到了隔离变化的目的。
对于抽象而言,常常会与封装结合起来,分离变与不变,并对可能发生变化的部分进行抽象。
代码质量改进
重构
代码不及时重构,就会让技术债越欠越多,最后导致整个系统的代码质量积重难返,修改的成本越高,就越没有人愿意去改进代码,这就是所谓的“破窗理论”。重构就是要偿还技术债,保证在不修改原有功能的情况下改善代码。要进行正确的重构,首先需要识别代码的坏味道,包括:重复的代码、过长的方法、过大的类、发散式变化、霰弹式修改等。然后,则针对这些坏味道选择正确的重构手法。
案例实践:
• 影片租赁系统
• 分布式系统消息处理的测试
• JBehave测试用例
解依赖技术
依赖是引起代码产生混乱增加代码复杂度的根源。如果没有正确地识别职责,就可能导致不必要的依赖;如果没有提前识别变化点,就可能带来修改上的麻烦。为了更好地解除依赖,需要在事先设计与事后重构两个方面针对性地进行,包括的要素为:
• 共性与可变性分析
• 接缝技术
• 具体的解依赖技术
代码质量体系
Java编程规范阐释
以业界主流的Java编程规范作为主要蓝本,内容覆盖Java开发的诸多编程实践。包括:
• 命名规范:常量、变量、方法、类、接口、枚举、包的命名规约,测试代码的命名规范
• 格式规范:对类、方法体大小的规范,代码块排版格式的规范,对注释的要求
• OOP规范:静态方法与实例方法的定义,抽象类与接口,判断相等方法的约束,基本类型与自定义类型,类成员的访问控制
• 集合规范:集合类型的选择,集合类型的转换,集合元素的操作,包括遍历、添加和移除
• 并发规范:编写线程安全的类,高并发访问的同步要求,volatile与synchronize,使用JDK并发库
• 异常规范:异常的处理方式,受控异常与非受控异常的选择,错误日志
代码静态检查
内容包括:
• 在IDE中使用CheckStyle
• 在Maven中使用CheckStyle
• 使用Sonar对代码做静态检查
代码评审
内容包括:
• 代码评审的内容和范围
• 代码评审的原则与实践
• 常用的Java代码评审工具