高质量代码究竟依赖设计还是重构而来?

高质量代码究竟依赖设计还是重构而来?

导读

一个有所追求的程序员必定都期望自己能够写出高质量的代码,但高质量代码从何而来呢?有人以为是规划出来的,就像一栋稳固的大厦,假如没有前期优秀的规划那么必定难逃豆腐渣工程的命运;也有人以为是重构出来的,软件的一个基本特性便是易变,跟着时刻的推移软件会不断堕落,因而需求不断重构来保持代码的高质量。哪种说法更有道理?今天就跟咱们聊一聊重构、规划与高质量代码的联系。欢迎阅览。

目录

1 从一个事例说起

1.1 紊乱

1.2 次序

2 代码质量

2.1 编写易于了解(可读)的代码

2.2 防止意外

2.3 编码难以被误用的代码

2.4 完结代码模块化

2.5 编写可重用可推行的代码

2.6 编写可测验的代码并恰当测验

2.7 小结

3 编程范式、规划准则和规划方式

3.1 编程范式

3.2 规划准则

3.3 规划方式

4 技能债、代码坏滋味和重构

4.1 技能债

4.2 代码坏滋味

4.3 重构

5 总结

01、从一个事例说起

故事还得从一个功能优化的经历说起,信息流事务中常常需求展示图片,假如图片的体积过大则会导致功能问题,体积过小用户看得模糊体验又不好,因而需求一个适中的图片体积。得益于腾讯强壮的基建,事务方不需求自己对图片进行处理,而只需求修正图片链接的参数即可调整图片的裁剪和紧缩尺寸。

高质量代码究竟依赖设计还是重构而来?

这儿需求知道的是,我写了一个图片优化东西用来将代表原图的图片链接修正为合适的图片链接。举个比方,下图中上面的链接是原图链接,所谓原图便是用户上传的没有经过处理的图片。原图经过处理后就得到下面新的图片链接,新链接与原图链接的差异便是加了两个参数,别离代表裁剪的份额和紧缩的尺寸。

高质量代码究竟依赖设计还是重构而来?

接下来的问题便是,这个“图片优化东西”的代码怎样去写。

1.1 紊乱

高质量代码究竟依赖设计还是重构而来?

梳理一下事务逻辑,完结原图链接到最佳链接的进程能够划分为 3 个:

判别原图链接是否契合裁剪规矩,假如契合就核算出最优裁剪份额。不能随便给个qq.com就直接去裁剪了,要对链接进行校验; 判别原图链接是否契合紧缩规矩,假如契合就核算出最优紧缩规矩。道理与上面相似,有的图片不支撑紧缩,因而也需求判别; 拼接新图链接。

既然逻辑已然明晰,代码也不难写。目光所及的伪代码现已充斥了 if else 的条件分支,看似简略的一段代码实则弯弯绕绕。

if (必要参数是否填写) {
if (指定域名 && 契合裁剪规矩) { // 核算最优裁剪份额 }
if (指定域名 && 契合紧缩规矩) { // 核算最优紧缩规矩 }
if (指定域名) { // 核算新图链接 }
}

这还没完,忽然产品司理说:“再支撑两个新域名”。由于一些前史原因,司内存在不少相似的服务,但才干都迥然不同,都是经过改动链接中的参数来修正图片的裁剪和紧缩尺寸。比方下图所示,有的域名一同支撑裁剪和紧缩,但需求直接修正途径;有的域名不支撑裁剪,参数也是经过 query 方式传入。因而咱们需求针对不同域名进行不同判别。

高质量代码究竟依赖设计还是重构而来?

兵来将挡水来土掩,来个新需求就改代码嘛,这好像现已成了惯例。在这个事务场景里,便是用 if else 来判别一下不同域名,然后再各自处理。

if (A 域名) {
// 核算 A 域名最佳链接
} else if (B 域名) {
// 核算 B 域名最佳链接
} else if (...) {
  ...
}

这儿不得不提一个与软件质量息息相关的方针:圈杂乱度。依据维基百科的界说,“圈杂乱度是一种软件度量,用来表明程序的杂乱度,循环杂乱度由程序的源代码中量测线性独立途径的个数”。处理单个域名的逻辑现已超过了 10 个圈杂乱度,每加一个新域名,圈杂乱度就会线性增加 10,那可想而知,再增加几个新域名,这段逻辑的杂乱程度可就高到难以了解了。因而,对这段代码重构火烧眉毛。

1.2 次序

程序的大部分威力来历于条件逻辑,可是十分不幸的是,程序的杂乱度也往往来历于条件逻辑。从刚刚的比方中能够看出,假如不加以操控,咱们的代码里很快就充斥了 if else,因而咱们需求简化条件逻辑。《重构》这本书中供给了 6 种主张。

分化条件表达式 合并条件表达式 以卫句子替代嵌套条件表达式 以多态替代条件表达式 引进特例 引进断语

咱们看看哪些主张能够用得上。

1.2.1 重构办法 1:分化条件表达式

所谓分化条件表达式便是将一个巨大的逻辑代码块分化为一个个独立的函数,每个函数的命名能够表达其意图,就像比方中这么一改造,代码就能够愈加杰出条件逻辑,每个分支的作用也愈加明晰。

高质量代码究竟依赖设计还是重构而来?

1.2.2 重构办法 2:以多态替代条件表达式

只是做了分化条件表达式就够了吗,必定不够。由于这样的代码在结构上还不够明晰,实践上咱们能够将不同的条件逻辑分拆到不同的场景中去,利用多态将逻辑拆分得愈加明晰。

高质量代码究竟依赖设计还是重构而来?

高质量代码究竟依赖设计还是重构而来?

首要声明一个 Strategy 基类,在结构函数中传入必要的参数,供给一个 getter 函数,用于核算最佳图片链接。每当有新的域名需求处理,就声明一个类承继 Strategy 基类。就这样,利用了多态的代码在逻辑上显得更为明晰。

1.2.3 重构办法 3:战略方式

《规划方式》这本书提到:“组合优于承继”。咱们再细细剖析上面这段包含多态的代码,就会发现 AdomainStrategy 与 Stragtegy 之间并不天然存在承继联系,而且承继会增加体系耦合,而组合能够更好地完结运行时的灵活性。那咱们能够用战略方式进一步重构这段代码。

所谓“战略方式”,便是“界说一系列的算法,把它们一个个封装起来,而且使它们能够彼此替换”。

高质量代码究竟依赖设计还是重构而来?

高质量代码究竟依赖设计还是重构而来?

每个详细的战略类经过结构函数传入所需的参数,供给一个名为 getImgUrl 的公共办法。而在战略上下文类中,设置详细的战略以及供给获取最优图片地址的办法。

1.2.4 重构办法 4:模版办法方式

进一步剖析,发现无论是 A 域名仍是 B 域名,或许是其他任何域名,处理逻辑具有相同部分,即“核算裁剪份额” -> “核算紧缩规格” -> “拼接新的链接” -> “返回新的链接”。

高质量代码究竟依赖设计还是重构而来?

写高质量代码时应该要警惕重复代码,这儿也不破例,咱们能够运用“模版办法方式”来进一步重构,所谓“模板办法方式”,便是“在模板办法方式中,子类完结中的相同部分被上移到父类中,而将不同的部分留待子类来完结”。落实到代码中便是,声明一个笼统类,将公共的逻辑都抽到该笼统类中,然后在子类中再完结详细的事务逻辑。

高质量代码究竟依赖设计还是重构而来?

1.2.5 小结

这是一段不算杂乱的逻辑,但怎样放任不管,代码则会越来越杂乱,直至无法保护。因而,咱们对这段代码进行了多轮重构,从一开端分化条件表达式,到选用多态使得代码在结构上更为明晰,进一步地选用了战略方式和模板办法方式,不断提升了代码质量。

那现在假如再遇到产品司理说再支撑两个新域名,那就十分简略了。只需求承继 ImageStrategy,然后顺次完结事务相关的 3 个办法就能够了。

高质量代码究竟依赖设计还是重构而来?

02、代码质量

前面咱们花了很大的篇幅介绍了图片优化东西是怎样一步步重构的,你或许对高质量代码有了一些感觉,但什么是代码质量,当咱们在谈论代码质量时到底在谈论什么呢?下面咱们就将这一概念变得愈加明晰。

《好代码,坏代码》这本书提出,编写的代码应该满意 4 个高层方针:

代码应该正常作业:这不必多说,假如写出来的代码都不能正常作业,那就更提不上高质量代码了。但需求提一下的是,这儿说的“正常作业”的前提是咱们应该要正确了解需求,假如需求对功能或许安全性要求很高,那么这些要求就应该被归入到“正常作业”的范畴; 代码应该持续正常作业:代码刚上线时或许还能正常作业,但你是不是常常遇到过一觉醒来代码忽然停止了正常作业?这是由于咱们的代码并不是与世隔绝的,而是与周围有各种依靠、交互,比方咱们的代码或许会依靠于其他代码,那些代码或许会呈现异常; 代码应该习惯不断改变的需求:软件之所以被称为软件,正是由于其简略改动的特性。面临未来或许会有也或许没有的改动,咱们能够有两种办法来应对,一种试图预测一切或许更改的地方,然后留下许多拓宽点;一种是完全无视未来的改变。很明显这是两个极点,咱们应该在两者中寻求平衡; 代码不该该重复别人做过的作业:这个方针能够从两个方向上来了解,假如其他工程师有现成的处理计划,咱们应该复用;而假如咱们自己编写了个处理计划,应该以更好的方式进行结构,以便其他工程师能够轻松的复用。

尽管咱们知道了高质量代码的方针,但只是知道方针也没有办法下手,咱们还需求愈加详细的战略。为什么许多人厌烦鸡汤,便是由于那些人只给了鸡汤而不给勺子,咱们这儿既给方针也给详细的战略。这便是代码质量的六大支柱。接下来我会结合前面所讲的比方别离介绍这六大代码质量的支柱。需求留意的是,每个支柱代表了一种准则,而完结这个准则能够有许多详细的办法。

高质量代码究竟依赖设计还是重构而来?

2.1 编写易于了解(可读)的代码

编写易于了解的代码是代码质量六大支柱的第一个支柱,咱们举 3 个不同的比方来看看怎样完结该支柱的思想。

2.1.1 运用描绘性称号

前面战略上下文类中,“获取最佳图片地址”的办法称号是 getOptimalImgUrl,从这个称号中能够一眼看出其含义。但假如改成 getU 或许 getUrl 或许 calcU,信任你必定得愣几秒乃至半天都或许猜不出什么含义,那这样的代码明显不易于了解。

2.1.2 恰当运用注释

注释往往能够协助咱们更好地了解代码,可是不是一切注释都是好的呢?咱们能够看下面这段代码的注释,里边包含两段注释,一个是描绘办法的用途,另一个是描绘详细的完结。第二个注释便是个糟糕的注释,由于工程师在代码保护时不得不保护这个注释,如若更新了完结办法,则需求记得更改注释。一同,假如 100 行代码中有 50 行像这样的无效注释,那么反而增加阅览代码的难度。

高质量代码究竟依赖设计还是重构而来?

那么该怎样正确地运用注释呢?能够用注释说明以下内容:

解说代码完结的是什么; 解说代码为什么完结这些作业; 供给其他信息,如运用指南。

2.1.3 坚持共同的编程风格

高质量代码究竟依赖设计还是重构而来?

依照咱们的习惯,或许会以为 ImageStrategyContext 是类,getOptimalImgUrl 是其静态办法。呈现这样的误解的原因便是写错了变量的命名风格。

2.2 防止意外

代码往往以多层次的方式进行构建,高层代码依靠于底层代码,咱们所编写的代码往往只是大规划体系中的一小部分,咱们依靠于其别人的代码,别人也或许依靠于咱们的代码,咱们协作的办法便是代码契约,输入是什么输出又是什么,但这种契约许多时分并不是稳固的,常常会呈现意外的状况。而咱们需求做的便是尽或许地防止意外。

2.2.1 防止编写误导性的函数

“防止编写误导性的函数”是一种防止意外的办法。比方下图中的代码,运用 kdImageStrategy 的代码契约便是传入的参数有必要要是方针,而且方针中要包含 imgUrl 和 imgWidth 这两个参数。但遇到不契合要求的参数状况,应该主动抛出过错。

高质量代码究竟依赖设计还是重构而来?

2.3 编写难以被误用的代码

前面提到,一个体系往往是许多人协作而成的成果,假如一段代码很简略误用,依据墨菲规律,那么它早晚要回被误用然后导致软件无法正常运行。因而,咱们应该编写难以被误用的代码。

这段是前面比方中的战略上下文中的代码,这段代码实践上是不契合“编写难以被误用的代码”这条支柱的,由于它对谁调用 setImageStrategy 没有约束,然后使得 ImageStrategyContext 的实例会被误用。

高质量代码究竟依赖设计还是重构而来?

而假如把代码改成只能在构建时设值,那么就能够使得类编程不可变类,大大降低了代码被误用的或许性。

高质量代码究竟依赖设计还是重构而来?

2.4 完结代码模块化

模块化的主旨之一是咱们应创立能够轻松调整和重新配置的代码,而无须精确地知道怎样调整或重新配置这些代码。要想完结这一方针的要害点在于,不同的功能应该能够精确的映射到代码库的不同部分。

2.4.1 考虑运用依靠注入

代码在处理高层次问题时常常需求依靠于低层次,可是子问题并不总是只要一个处理计划,因而在结构代码时答应子问题处理计划重新配置是很有协助的,而依靠注入能够协助咱们完结这个方针。

高质量代码究竟依赖设计还是重构而来?

下面的代码中,ImageStrategyContext 对它的子处理计划是硬编码依靠的,直接在内部声明了两个战略的实例。

高质量代码究竟依赖设计还是重构而来?

咱们能够选用依靠注入的办法,经过结构函数传递将战略实例进来,这样能够更好地完结代码模块化。

高质量代码究竟依赖设计还是重构而来?

2.5 编写可重用可推行的代码

在编写代码时常常会需求引证别人的代码,假如什么都需求自己编写,那么很难完结大规划协作。而这儿的要害便是编写可重用可推行的代码。

2.5.1 保持函数参数的集中度

在下面这段代码的核算最佳紧缩份额办法 setOptimalCompressionRatio 中,传入了 compressionRatio 和 cropRatio 两个参数,但实践上只需求传入 compressionRatio 即可。这种函数参数超出需求的状况,或许会使得代码难以重用。咱们能够删除 cropRatio 这个参数,让函数只取得其所必要的参数。

高质量代码究竟依赖设计还是重构而来?

2.6 编写可测验的代码并恰当测验

当咱们在修正代码时很有或许会无意间引进 bug,因而咱们需求一种手法来确保代码能够持续正常作业,测验便是供给这种确保的首要手法。

2.6.1 一次测验一个行为

咱们应该确保一个用例仅测验一个行为,假如掺杂着许多行为,很有或许会导致较低的测验质量。

高质量代码究竟依赖设计还是重构而来?

2.7 小结

这一末节首要经过前面所讲的图片优化东西的示例来别离介绍代码质量的六大支柱,每个支柱中都有许多详细的完结办法,由于时刻的联系,在第一个支柱中举了 3 个比方来说明“编写易于了解(可读)的代码”,而在剩下的支柱中,别离仅举了 1 个比方。

03、编程范式、规划准则和规划方式

刚刚介绍了高质量代码的四大方针以及六大支柱,你或许会发现代码都选用了面向方针的风格,那是不是就只要面向方针的代码才干写出高质量的代码?下面就跟咱们聊一聊编程范式以及更详细的规划准则、规划方式。

3.1 编程范式

编程范式指的是编程的风格,与详细的编程语言联系不大,比方 JavaScript 便是个多范式语言,即便像 Java 这样一向被咱们所熟知的面向方针语言也加了不少函数式编程的元素。

依照《架构整齐之道》的划分,干流的编程范式首要包括 3 种,结构化编程、面向方针编程和函数式编程。

3.1.1 结构化编程

不知道你是否想过这样一个问题?面向方针编程之所以叫面向方针,是由于其中首要的规划元素是方针,而函数式编程首要的规划元素是函数。那结构化编程呢,难道它的首要规划元素是结构?这好像也不太对。其实,所谓结构化,是相对于非结构化编程而言的。

想要了解结构化编程,必定得回到那个陈旧的时代,看看非结构化编程是怎样样的。下图中左面是一段用 Java 写的代码,这是一段包了面向方针的结构化代码,屏幕的右边是对左面代码反编译后的字节码,代码从第 0 行开端履行,但履行到第 6 行时,会进行判别,假如成果是真就跳到第 14 行,11 行也是相似。咱们能够看到非结构化编程的首要特点之一便是经过 Goto 进行履行逻辑的跳转。

高质量代码究竟依赖设计还是重构而来?

Dijkstra 写了一篇短小精悍的论文:《Goto 是有害的》,他在这篇论文里边指出 Goto 句子会导致程序难以了解和保护,取而代之的是应该用结构化编程供给更明晰的操控流。

在此之前,有人现已证明了能够用次序结构、分支结构、循环结构这三种结构结构出任何程序,就这样结构化编程就诞生了。结构化编程对程序履行的直接操控权进行了约束,不再答应让 Goto 随便跳转。

结构化编程应该是咱们最为了解的编程范式,但咱们在写高质量代码时很少提到结构化编程,那是由于结构化编程不能有效地隔离改变,需求与其他编程范式配合运用。

3.1.2 面向方针编程

面向方针编程或许大多数程序员都听说过,当咱们代码规划逐步膨胀,结构化编程中各模块的依靠联系很强,导致无法有效地隔离,而面向方针能够供给更好地组织代码。

提到面向方针,你或许会想到面向方针的三个特点:封装、承继和多态。封装是面向方针的根基,单元模块封装得好,就会愈加稳定,然后才干构建更大的模块。承继一般能够从两个视点进行看待:假如站在子类的视点往上看,更多考虑的是代码复用,这叫“完结承继”;另一种视点,假如站在父类视点往下看,更多考虑的是多态,这叫“接口承继”。但真正让面向方针发挥才干的是它的第三个特点:多态。咱们能够引证罗伯特马丁在他的《架构整齐之道》里边的一段话:“面向方针编程便是以多态为手法来对源代码中的依靠联系进行操控的才干,这种才干让软件架构师能够构建出某种插件式架构,让高层战略性组件与底层完结性组件相别离,底层组件能够被编译成插件,完结独立于高层组件的开发和部署”。

3.1.3 函数式编程

函数式编程是一种编程范式,它供给给咱们的编程元素便是函数。只不过,这个函数跟高中数学的函数 f(x) 实践上是相同的,回想一下高中的函数 f(x),相同的输入必定会给出相同的输出,在函数式编程中也相同,咱们要尽或许规避状况和副作用。咱们平常所听说过的函数是一等公民、柯里化、管道这些都是函数式编程中所用到的技能。

函数式编程的强壮之处在于不可变性,由于“一切的竞赛问题、死锁问题、并发更新问题都是由可变变量导致的。假如变量永久不会被更改,那就不或许发生竞赛或许并发更新问题。假如锁状况是不可变的,那就永久不会发生死锁问题”。

3.1.4 编程范式的实质

前面介绍了三种干流的编程范式。但咱们有想过为什么要创造出这么些个范式,if else 一把梭不行吗?到底是 KPI 的唆使,仍是职业的内卷?

咱们先看一本经典的书:《算法+数据结构=程序》,这本书中将要点重视在别离算法和数据结构,算法是处理问题的办法和进程,而数据结构是组织和存储数据的办法。

但咱们或许不了解的是就在这本书宣布3年后,又有一篇论文发布,姓名就叫:《算法 = 逻辑 + 操控》。这篇论文以为,“任何算法都会有两个部分, 一个是 Logic 部分,这是用来处理实践问题的。另一个是 Control 部分,这是用来决议用什么战略来处理问题……假如将 Logic 和 Control 部分有效地分开,那么代码就会变得更简略改善和保护”。

拿一段核算阶乘的代码来详细解说一下。所谓阶乘,便是小于等于某个整数的一切正整数的乘积,咱们能够用递归也能够用循环来完结,无论是哪种算法都能够得到相同的成果。因而能够说,递归或许循环是 Control,而阶乘的核算规矩是 Logic。

高质量代码究竟依赖设计还是重构而来?

经过前面两个表达式,咱们很简略得出,程序 = 逻辑 + 操控 + 数据结构,讲了那么多编程范式,其实都是围绕这三件事。因而咱们能够发现,编程范式的实质便是:

操控是能够标准化的; 操控需求处理数据,所以想标准化操控,需求标准化数据结构; 操控还要处理用户的事务逻辑,即逻辑。

想要写出高质量代码,有效地别离逻辑、操控和数据结构便是要害所在。

最终再回到咱们图片优化东西的比方,无论是选用结构化的办法仍是最终选用的面向方针办法,筛选出最合适的图片链接的事务逻辑是相同的,只是操控部分不同,随之而对应的,便是数据结构也需求标准化。

高质量代码究竟依赖设计还是重构而来?

3.2 规划准则

经过编程范式,咱们知道了方针、函数这些规划元素,以及编程的实质便是将逻辑、操控和数据进行别离,那详细该怎样做呢?规划准则给了咱们一些更详细的准则,然后协助咱们更好地达成规划的方针。

SOLID 准则是由罗伯特马丁提出的,在他的《灵敏软件开发:准则、实践与方式》以及《架构整齐之道》中都进行了详细的阐述。SOLID 准则不只是在面向方针范畴有指导含义,更是在软件规划范畴给了咱们规划应该遵循的准则,一同也给了咱们一把尺子,用来衡量规划的有效性。

3.2.1 单一责任准则

单一责任准则看起来是这 5 个准则中最简略的一个,但一同也是最简略被咱们误解的一个,许多人误以为单一责任便是只做一件事。罗伯特马丁在他相隔 20年的两本书中对单一责任别离做了界说。

在《灵敏软件开发》中,罗伯特马丁指出单一责任是指“就一个类而言,应该仅有一个引起它改变的原因”,这个与只做一件事的界说两者最大的差异是将改变归入了考量。

20 年后,罗伯特马丁又在《架构整齐之道》里边临单一责任做了界说,“任何一个软件模块都应该只对某一类行为者负责”。这一次,他不仅把改变归入了考量,也把改变的来历归入了考量。

高质量代码究竟依赖设计还是重构而来?

但一个域名一条核算规矩时,咱们很简略就能够区分两者之间存在差异,因而需求拆分出两个类。但实践事务是杂乱多变的,在 B 域名中又区分了不同事务,每个事务又存在差异,那么 B 域名下的 X 事务和 Y 事务是否还需求分拆?假如对单一责任了解不深,很有或许觉得不需求分拆,但咱们现在知道单一责任需求考虑改变以及改变的来历,那么很自然地就能够知道需求进行分拆。

3.2.2 敞开关闭准则

回想一下咱们的日常作业,一般都是接一个需求就改一次代码,尽管一次两次无伤大雅,但这种对代码持续性修正的行为往往会对代码造成巨大伤害,导致保护本钱越来越高。既然修正行为会给代码引起许多问题,那能不能不修正呢?你或许以为我在说痴话,但敞开关闭准则就给咱们供给了这样一个方向。

跟以往咱们经过修正代码来完结新需求比较,敞开关闭准则主张咱们经过拓宽来完结新需求。假如在重构之前的代码中增加新域名,则需求修正本来的代码,但在重构后契合开闭准则的代码中,则只需求增加一个新的类。

高质量代码究竟依赖设计还是重构而来?

其实开闭准则离咱们很近,每天都陪伴在咱们身边,咱们所用到的 Chrome 插件、VSCode 插件等插件体系,都体现了开闭准则。

3.2.3 里氏替换准则

里氏替换准则的界说有些拗口:“假如对于每个类型是 S 的方针 o1 都存在一个类型为 T 的方针 o2,能使操作 T 类型的程序 P 在用 o2 替换 o1 时行为保持不变,咱们就能够将 S 称为 T 的子类型”。假如直白一些讲,便是“子类型有必要能够替换其父类型”。

长方形/正方形问题是个臭名远扬的违背里氏替换准则的规划事例。在中学课本上,老师会告知咱们正方形是个特别的长方形。因而在软件规划中,咱们会理所当然地将正方形承继于长方形。但问题来了,设置长方形需求设置长和宽两个特点,但正方形只需求设置一个。依照里氏替换准则正方形类应该能够替换长方形类,那咱们应该能够对正方形设置长和宽,但实践上是不行的,咱们不能够一同设置正方形的长和宽,所以这个事例不契合里氏替换准则。

在图片优化东西的事例中,每个详细的战略都是能够自由替换其父类 Strategy 的,因而契合里氏替换准则。

高质量代码究竟依赖设计还是重构而来?

3.2.4 接口隔离准则

接口隔离准则指的是“不该强迫运用者依靠于它们不必的办法”。这个表述看上去很简略了解,尤其是站在运用者的视点,我不需求的接口当然不需求依靠。但作为模块的规划者,都会有将接口规划得过胖的激动。在下图中,OPS 类供给了 op1、op2、op3 办法别离供 User1、User2、User3 运用,在 OPS 中对 op1 做的任何修正或许都会影响到 User2 或 User3。

高质量代码究竟依赖设计还是重构而来?

3.2.5 依靠回转准则

依靠回转准则指的是“高层模块不该依靠于底层模块,二者应该依靠于笼统”。换句话说,便是“笼统不该依靠于细节,细节应依靠于笼统”。详细的比方在前面解说依靠注入时也介绍过了,这儿就不赘述了。

可是要提一句,这儿说引荐选用依靠回转准则,并不是说要把这个准则作为金科玉律,由于实践开发中会不可防止地依靠一些详细的完结。

高质量代码究竟依赖设计还是重构而来?

3.3 规划方式

SOLID 给了咱们 5 个在规划时需求遵循的准则,咱们还需求详细的办法落地,那便是规划方式。

所谓的方式便是针对一些普遍存在的问题给出的处理计划,方式这个概念最早是在建筑职业提出来的,但的确墙内开花墙外香,方式在软件范畴大放异彩。软件范畴的规划方式是指:“规划方式是对被用来在特定场景下处理一般规划问题的类和彼此通信的方针的描绘。”

《规划方式》中供给了 23 种规划方式,其实便是 SOLID 准则在 23 种场景下的应用。

记得之前在学校学习规划方式时老师跟咱们讲过,许多人或许在运用规划方式但不自知,这是有或许的,前提条件便是要对 SOLID 准则比较了解,但假如说既不了解 SOLID 准则,又没有特别学过规划方式,却说自己或许在运用规划方式大概率便是吹嘘了。

这儿我不想过多地介绍规划方式,由于它们实质上都是 SOLID 准则在不同场景下的应用。

04、技能债、代码坏滋味和重构

讲了这么多规划,那是不是就意味着好代码就只是规划出来的呢?

从实践状况来看,尽管咱们在写代码初期进行了不少规划,留下了不少拓宽点,但仍然会发现代码在逐步腐烂。这种的状况是怎样发生的,又该怎样处理呢?

4.1 技能债

所谓技能债,便是“指开发人员为了加快软件开发,在应该选用最佳计划时进行了退让,改用了短期内能加快软件开发的计划,然后在未来给自己带来的额定开发担负”。

这种技能上的选择,就像一笔债款相同,尽管眼前看起来能够得到优点,但有必要在未来归还。咱们知道,要是咱们平常一向借债日子,前期或许还能拆东墙补西墙得过且过,但越来越到后期,跟着债款滚雪球般的增加,个人的破产也随之而来,日子便将难以为继。日子的道理在技能项目中相同适用,假如欠下的技能债越来越多,最终项目也将失利。

《重构》的作者马丁福勒依照有意无意和草率稳重两个维度将技能债分为了四个象限,简称“技能债象限(Technical Debt Quadrant)”。稳重的债款都是经过深思熟虑的,他们知道自己欠下了债款,但在项目早日上线和还清债款之间做了评价后,发现前者的收益远远大于后者的本钱,因而选择先上线,这种技能债便是稳重有意的。草率的债款或许不是无意的,也有或许有意为之,项目团队知道杰出的规划也有才干进行实践,但却以为无法承当得起清偿技能债所需的时刻本钱。但其实对于大多数没有学习过软件工程或许进行过优秀实践的开发人员来说,大多欠下的是草率无意的技能债。最终是稳重无意的,咱们在进行项目开发时,常常一边开发一边学习,你或许需求花一年乃至更长时刻才干理解这个体系的最佳实践。

《人月神话》作者 Fred Brooks 主张或许能够花一年时刻规划一个专门用来学习的体系,然后废弃而且重建,这样才干规划出一个最佳实践的体系。这主张对于绝大多数的人来说是无法承受的,因而咱们应该认识到,在当时咱们进行咱们所以为的最佳实践时,实践上现已在欠下了技能债,这便是稳重无意的技能债。

高质量代码究竟依赖设计还是重构而来?

4.2 代码坏滋味

那该怎样辨认技能债呢,咱们能够经过代码坏滋味来辨认。

灵敏宣言发起者之一 Kent Beck 提出了 Code Smell 代码异味,“在程序开发范畴,代码中的任何或许导致深层次问题的症状都能够叫做代码异味”。

Kent 的奶奶也说过一句饱含哲理的话,“假如尿布臭了,就换掉它”。相同的道理,假如咱们发现代码中存在坏滋味,那咱们换掉它。

对此,马丁福勒在《重构》一书中枚举了 24 种代码异味,这样咱们辨认代码异味便有了抓手,也就能够辨认技能债。

高质量代码究竟依赖设计还是重构而来?

4.3 重构

认识了代码坏滋味,辨认出了技能债,那下面就该还债了,这儿还债的办法便是重构。

《重构》这本书中对重构的界说是:“所谓重构(refactoring)是这样一个进程:在不改动代码外在行为的前提下,对代码做出修正,以改善程序的内部结构。重构是一种经饱经沧桑构成的有条有理的程序收拾办法,能够最大限度地减小收拾进程中引进过错的概率。实质上说,重构便是在代码写好之后改善它的规划。”

举一个坏滋味之一“奥秘命名”的比方。

下图中左面是充溢坏滋味的代码,当你阅览这段代码时,首要是 getThem,你必定很困惑,them 是什么?持续阅览,参数 list 又是什么?再看函数体,list1 是什么?item[0] 是什么?4 又是什么?瞧瞧,一段数行的代码,现已给咱们带来了那么多的困惑,这代码还怎样读。

假如只是把命名改一下,getThem 换成 getFlaggedCells,获取符号的单元格,list 改成 gameBoard,哦本来是个棋盘呀,下面就不必读了,你已然清楚这段代码是用来读取棋盘中现已被符号的棋盘格了。

经过这个简略的小比方,信任你了解了辨认坏滋味到重构的进程。

高质量代码究竟依赖设计还是重构而来?

05、总结

回到本次共享一开端的问题:“好代码是规划仍是演化而来的?”我信任咱们应该都有了答案。

首要咱们介绍了图片优化东西的比方,从一开端最一般的流水账方式的毫无规划的代码,再到运用了一些重构办法后相对简练的代码,最终到应用了战略方式和模板办法方式后的代码。经过这个比方带咱们大略认识了怎样经过规划和重构写出高质量代码,但这还不是结束,而是持续将这个比方贯穿本文的一直。

接着解说了什么是好代码,当咱们在谈论代码质量时又在谈论什么。借用《好代码,坏代码》的话,好代码应该能够完结 4 个高层方针,与之对应的是 6 项准则,咱们不断回过头用咱们一开端的比方来说明它是怎样契合这 6 项准则的。

咱们在比方中要点选用了面向方针范式,无论是面向方针仍是函数式,它们都是编程范式,也便是不同的代码风格,实质都是要将逻辑、操控和数据进行有效地别离。知道了编程的实质,咱们需求一些更详细的准则来指导咱们的行为,因而紧接着我介绍了 SOLID 规划准则,许多人把 SOLID 只是作为面向方针中的规划准则,这很片面,实践上能够作为通用的规划准则。在规划准则的基础上,咱们还介绍了规划方式实践上便是 23 种应用了规划准则的详细事例。

好代码离不开规划,假如完全不懂得规划,好代码将会无从谈起。但跟着项目的推进,无论是开发者有意仍是无意的,稳重仍是草率的,都会逐步积累技能债。跟人欠债过多会破产相同,技能债达到必定程度就会导致项目无法持续进行下去,因而咱们要时不时地归还债款。归还债款的办法便是要辨认坏滋味,然后有针对性地进行重构。

本文内容集合了前人在软件工程范畴总结的切实有效经历。我将这些了解又生疏的概念经过一个比方串在了一同,以期让各位对这些概念有宏观认识。假如觉得文章对你有协助,欢迎你转发共享。

-End-

原创作者|billpchen

技能责编|williamzheng

高质量代码究竟依赖设计还是重构而来?

你都保护过哪些令人抓狂的代码?你是怎样评价代码的质量的,防止写出烂代码你有什么样的技巧呢?欢迎在腾讯云开发者大众号评论区共享,咱们将选取1则最有含义的评论,送出腾讯云开发者-毛毯1个(见下图)。8月1日正午12点开奖。

高质量代码究竟依赖设计还是重构而来?