写出高质量代码,提高复杂业务代码的设计和开发能力
数据结构和算法是教你如何写出高效代码,那设计模式讲的是如何写出可扩展、可读、可维护的高质量代码。
设计模式能更直接地提高开发能力。把写高质量代码培养成一种开发习惯,形成一种代码质量意识,把设计原则和设计模式在项目中锻炼运用,写出好用的代码。
需要用到设计模式的一般是复杂的业务需求;
优秀的开源项目、框架、中间件,代码量、类的个数都会比较多,类结构、类之间的关系极其复杂,为了保证代码的扩展性、灵活性、可维护性等,代码中会使用到很多设计模式、设计原则或者设计思想。
考察候选人的设计能力、代码能力,那设计模式相关的问题便是一个很好的考察点:
- 指导培养初级员工、新人,以及 code review 的工作;
- 技术 leader,负责一个项目整体的开发工作,你就需要为开发进度、开发效率和项目质量负责。杜绝堆砌垃圾代码,让整个项目无法维护;代码质量低还会导致线上 bug 频发,排查困难。
从以下几个问题思考: 为什么要有这种设计原则、思想或者模式?它能解决什么编程问题?有哪些应用场景?又该如何权衡、恰当地在项目中应用?
- 总结本质的定义,解决了什么问题?
- 在具体的需求一步步演进过程中,每次演进分析代码缺陷在哪里,如何通过设计原则、思想、模式来优化?(如何进行需求分析、如何落地编码的具体方法论)
评价代码质量的高低的标准:
最常用的评价标准有哪几个?
评判代码质量最重要的3个标准: 可维护性、可读性、可扩展性 ; 最常用到几个评判代码质量的标准是:可维护性、可读性、可扩展性、灵活性、简洁性、可复用性、可测试性。
不同的评价维度也并不是完全独立的,有些是具有包含关系、重叠关系或者可以互相影响的。比如,代码的可读性好、可扩展性好,就意味着代码的可维护性好。代码质量高低是一个综合各种因素得到的结论。
1. 可维护性(maintainability)
落实到编码开发,所谓“代码易维护”就是指,在不破坏原有代码设计、不引入新的 bug 的情况下,能够快速地修改或者添加代码。 “代码不易维护”就是指,修改或者添加代码需要冒着极大的引入新 bug 的风险,并且需要花费很长的时间才能完成。
对于一个项目来说,维护代码的时间远远大于编写代码的时间。工程师大部分的时间可能都是花在修修 bug、改改老的功能逻辑、添加一些新的功能逻辑之类的工作上。
可维护性也是一个很难量化、偏向对代码整体的评价标准。
代码的可读性好、简洁、可扩展性好,就会使得代码易维护;相反,就会使得代码不易维护。
更细化地讲,如果代码分层清晰、模块化好、高内聚低耦合、遵从基于接口而非实现编程的设计原则等等,那就可能意味着代码易维护。除此之外,代码的易维护性还跟项目代码量的多少、业务的复杂程度、利用到的技术的复杂程度、文档是否全面、团队成员的开发水平等诸多因素有关。
2. 可读性(readability)
好的程序员能够编写人能够理解的代码。 代码被阅读的次数远远超过被编写和执行的次数。
我们在编写代码的时候,时刻要考虑到代码是否易读、易理解,在修改bug和添加新功能首要的事情就是要读懂代码。 除此之外,代码的可读性在非常大程度上会影响代码的可维护性。毕竟,不管是修改 bug,还是修改添加功能代码,我们首先要做的事情就是读懂代码。代码读不大懂,就很有可能因为考虑不周全,而引入新的 bug。
如何评价一段代码的可读性呢?
我们需要看代码是否符合编码规范、命名是否达意、注释是否详尽、函数是否长短合适、模块划分是否清晰、是否符合高内聚低耦合等等。你应该也能感觉到,从正面上,我们很难给出一个覆盖所有评价指标的列表。这也是我们无法量化可读性的原因。
实际上,code review 是一个很好的测验代码可读性的手段。如果你的同事可以轻松地读懂你写的代码,那说明你的代码可读性很好;如果同事在读你的代码时,有很多疑问,那就说明你的代码可读性有待提高了。
3. 可扩展性(extensibility)
可扩展性表示 我们的代码应对未来需求变化的能力。 跟可读性一样,代码是否易扩展也很大程度上决定代码是否易维护。
代码的可扩展性表示,我们 在不修改或少量修改原有代码的情况下,通过扩展的方式添加新的功能代码。
说直白点就是,代码预留了一些功能扩展点,你可以把新功能代码,直接插到扩展点上,而不需要因为要添加一个功能改动大量的原始代码。
4. 简洁性(simplicity)
尽量保持代码简单。代码简单、逻辑清晰,也就意味着易读、易维护。 我们在编写代码的时候,往往也会把简单、清晰放到首位。
思从深而行从简,真正的高手能云淡风轻地用最简单的方法解决最复杂的问题。
简洁的代码背后往往是业务理解深度的代码逻辑体现。
5. 可复用性(reusability)
尽量减少重复代码的编写,复用已有的代码。
比如,当讲到面向对象特性的时候,我们会讲到
继承、多态存在的目的之一,就是为了提高代码的可复用性;
当讲到设计原则的时候,我们会讲到单一职责原则也跟代码的可复用性相关;
当讲到重构技巧的时候,我们会讲到解耦、高内聚、模块化等都能提高代码的可复用性。
可见,可复用性也是一个非常重要的代码评价标准,是很多设计原则、思想、模式等所要达到的最终效果。
6. 可测试性(testability)
代码的可测试性是一个相对较少被提及,但又非常重要的代码质量评价标准。代码可测试性的好坏,能从侧面上非常准确地反应代码质量的好坏。
代码的可测试性差,比较难写单元测试,那基本上就能说明代码设计得有问题。
面向对象、设计原则、设计模式、编程规范、重构
要写出满足这些评价标准的高质量代码,我们需要掌握一些更加细化、更加能落地的编程方法论,包括面向对象设计思想、设计原则、设计模式、编码规范、重构技巧等。
- 面向对象中的继承、多态能让我们写出可复用的代码;
- 编码规范能让我们写出可读性好的代码;
- 设计原则中的单一职责、DRY、基于接口而非实现、里式替换原则等,可以让我们写出可复用、灵活、可读性好、易扩展、易维护的代码;
- 设计模式可以让我们写出易扩展的代码;
- 持续重构可以时刻保持代码的可维护性等等。
面向对象
主流的编程范式或者是编程风格有三种,它们分别是面向过程、面向对象和函数式编程。面向对象这种编程风格又是这其中最主流的。大部分项目也都是基于面向对象编程风格开发的。
面向对象编程因为其具有丰富的特性(封装、抽象、继承、多态),可以实现很多复杂的设计思路,是很多设计原则、设计模式编码实现的基础。
需要掌握下面这 7 个大的知识点:
- 面向对象的四大特性:封装、抽象、继承、多态
- 面向对象编程与面向过程编程的区别和联系
- 面向对象分析、面向对象设计、面向对象编程
- 接口和抽象类的区别以及各自的应用场景
- 基于接口而非实现编程的设计思想
- 多用组合少用继承的设计思想
- 面向过程的贫血模型和面向对象的充血模型
设计原则
设计原则是指导我们代码设计的一些经验总结。
设计原则这块儿的知识有一个非常大的特点,那就是这些原则听起来都比较抽象,定义描述都比较模糊,不同的人会有不同的解读。所以,如果单纯地去记忆定义,对于编程、设计能力的提高,意义并不大。
对于每一种设计原则,我们需要掌握它的设计初衷,能解决哪些编程问题,有哪些应用场景。只有这样,我们才能在项目中灵活恰当地应用这些原则。
对于这一部分内容,你需要透彻理解并且掌握,如何应用下面这样几个常用的设计原则。
- SOLID 原则 -SRP 单一职责原则
- SOLID 原则 -OCP 开闭原则
- SOLID 原则 -LSP 里式替换原则
- SOLID 原则 -ISP 接口隔离原则
- SOLID 原则 -DIP 依赖倒置原则
- DRY 原则、KISS 原则、YAGNI 原则、LOD 法则
设计模式
设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。
大部分设计模式要解决的都是代码的可扩展性问题。 设计模式相对于设计原则来说,没有那么抽象,而且大部分都不难理解,代码实现也并不复杂。这一块的学习难点是了解它们都能解决哪些问题,掌握典型的应用场景,并且懂得不过度应用。 设计模式分为三大类:创建型、结构型、行为型。对于常用的设计模式,我们要花多点时间理解掌握。对于不常用的设计模式,我们只需要稍微了解即可。
1. 创建型
常用的有:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式。
不常用的有:原型模式。
2. 结构型
常用的有:代理模式、桥接模式、装饰者模式、适配器模式。
不常用的有:门面模式、组合模式、享元模式。
3. 行为型
常用的有:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式。
不常用的有:访问者模式、备忘录模式、命令模式、解释器模式、中介模式。
编程规范
编码规范解决的主要是可读性问题,设计模式主要解决的是可扩展性问题。 也就是注意到了这些,代码的可读性和可扩展性将大大提高。
编程规范主要解决的是代码的可读性问题。 编码规范相对于设计原则、设计模式,更加具体、更加偏重代码细节。比如,如何给变量、类、函数命名,如何写代码注释,函数不宜过长、参数不能过多等等。
对于编码规范,考虑到很多书籍已经讲得很好了(比如《重构》《代码大全》《代码整洁之道》等)。而且,每条编码规范都非常简单、非常明确,比较偏向于记忆,你只要照着来做可以。它不像设计原则,需要融入很多个人的理解和思考。
除了编码规范,我们还会介绍一些代码的坏味道,让你知道什么样的代码是不符合规范的,应该如何优化。参照编码规范,你可以写出可读性好的代码;参照代码的坏味道,你可以找出代码存在的可读性问题。
代码重构(持续重构意识)
在软件开发中,只要软件在不停地迭代,就没有一劳永逸的设计。
随着需求的变化,代码的不停堆砌,原有的设计必定会存在这样那样的问题。针对这些问题,我们就需要进行代码重构。重构是软件开发中非常重要的一个环节。持续重构是保持代码质量不下降的有效手段。
而重构的工具就是我们前面罗列的那些面向对象设计思想、设计原则、设计模式、编码规范。
实际上,设计思想、设计原则、设计模式一个最重要的应用场景就是在重构的时候。
使用设计模式可以提高代码的可扩展性,但过度不恰当地使用,也会增加代码的复杂度,影响代码的可读性。
在开发初期,除非特别必须,我们一定不要过度设计,应用复杂的设计模式。而是当代码出现问题的时候,我们再针对问题,应用原则和模式进行重构。这样就能有效避免前期的过度设计。
对于重构这部分内容,你需要掌握以下几个知识点:
- 重构的目的(why)、对象(what)、时机(when)、方法(how);
- 保证重构不出错的技术手段:单元测试和代码的可测试性;
- 两种不同规模的重构:大重构(大规模高层次)和小重构(小规模低层次)。
不仅仅是掌握一些重构技巧、套路,更重要的是建立持续重构意识,把重构当作开发的一部分,融入到日常的开发中。
五者之间的联系
实际上,面向对象、设计原则、设计模式、编程规范、代码重构,这五者都是保持或者提高代码质量的方法论,本质上都是服务于编写高质量代码这一件事的。
当我们追本逐源,看清这个本质之后,很多事情怎么做就清楚了,很多选择怎么选也清楚了。比如,在某个场景下,该不该用这个设计模式,那就看能不能提高代码的可扩展性;要不要重构,那就看重代码是否存在可读、可维护问题等。
- 面向对象编程因为其具有丰富的特性(封装、抽象、继承、多态),可以实现很多复杂的设计思路,是很多设计原则、设计模式等编码实现的基础。
- 设计原则是指导我们代码设计的一些经验总结,对于某些场景下,是否应该应用某种设计模式,具有指导意义。比如,“开闭原则”是很多设计模式(策略、模板等)的指导原则。
- 设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。应用设计模式的主要目的是提高代码的可扩展性。从抽象程度上来讲,设计原则比设计模式更抽象。设计模式更加具体、更加可执行。
- 编程规范主要解决的是代码的可读性问题。编码规范相对于设计原则、设计模式,更加具体、更加偏重代码细节、更加能落地。持续的小重构依赖的理论基础主要就是编程规范。
- 重构作为保持代码质量不下降的有效手段,利用的就是面向对象、设计原则、设计模式、编码规范这些理论。