最近维护公司的老旧项目,面对未加设计且迭代好几年的项目,有点头疼,一怒之下,买了这本《重构-改善既有代码的设计》,希望能找到治理代码的方法。

我对好代码的理解

有一句话叫做愚蠢不可怕,可怕的是自己愚蠢,却不知道自己愚蠢,还觉得自己特聪明。写代码也是一样。最可怕的不是代码很烂,而是自己明明写的很烂,却浑然不知,觉得自己很牛。

所以首先要认识自己的代码是烂的还是好的, 这一点很重要,那么烂的代码是怎么样的呢?其实我觉得烂的代码应该各式各样的,为什么这样说呢?代码写的烂,肯定是不遵循编程的准则,范式,没有束缚,随意发挥导致的,最经典的烂代码,就是一大堆代码堆到一起,没有注释,没有章法,俗称“屎山”。

那么什么是好的代码呢?我觉得,好的代码应该是一个样。因为好的代码,遵循编程的准则,约束,范式,使用前人经过验证切实可行的套路来组织代码,而且同行业人都遵守这些套路,当你看别人写的代码时,你很容易就能够理解他人这么写的意图,也很轻易的按照原来的思路写下去。

好的代码应该具有很好的组织结构,明确的抽象层次,准确的变量命名,良好的类型推导,最大限度地支持开闭原则。

代码有两类读者:

一类是电脑,只要能跑起来的代码,电脑都能接受。

另一类读者是程序员,这类读者对代码的要求就很高,所以,至少要保证别的程序员能读懂我们的代码。

我觉得代码,应该就像一本书一样;

高级别的函数调用的低级别函数,就像书的目录一样,目录能清晰的展示这本书的结构,以及各个部分分别写的是什么内容,而被调用的函数清单,需要能够清晰得表明第一步干什么,第二步干什么,第三步干什么,而且这些步骤的抽象等级应该是一样的,不能将抽象的函数调用和一些实现细节混合在一起,就像不能在书的目录上写详细的内容一样,因为目录和内容不是一个抽象等级;

函数的命名需要准确,必须高度概括出他实现的功能;否则在函数调用的时候我们就不知道他具体干了什么,需要弄懂他干了什么,我们还需要看函数内部的实现逻辑。如果你在阅读代码的时候,频繁地在函数的调用和函数的定义定义之间来回切换,是很容易打断你阅读代码地思路,增加阅读代码地难度。就好比一本书,如果对每个章节的题目命名很随意,命名和内容不符,那么我们想通过目录来了解这本书的内容根本不切实际,如果想了解这本书的内容,就必须通读内容,然后自己概括。但是有时候,我们即使通读了内容,还不一定准确把握作者的中心思想,作者想表达什么,只有他自己最清楚,而我们的代码,也只有我们最清楚。

如果无法准确命名,那很可能是这个函数实现了太多的功能,无法使用一两个单词来概括,违反了单一职责原则,所以这个时候要做的,就是将函数拆分为更细的函数,确保一个函数只实现一个功能;

在使用想Javascript这种弱类型语言编程的时候,默认情况下,是可以随时随意给对象新增,删除属性和方法的,这就产生了对象类型的不确定性。

我们都知道,在vue2的options api中,需要把created, mounted, data,methods,computed,watch等声明在一个组件文件中,因为options api是声明式编程,针对上述提到的vue特性,开发者在编码阶段做的工作是把各部分的功能以一个描述对象罗列出来,然后vue在编译阶段根据这个描述对象构造一个真实的vue组件对象,也就说在编码阶段看到的对象和在运行阶段的对象结构是不一样的,我们之所以能够在methods,computed等内部通过this访问data,props等其他部分的属性,是因为编译的时候,编译器将描述对象各个部分的内容复制到vue对象上。因为开发者在开发阶段接触的描述对象,并不是真实的vue对象,因此描述对象内部的this也就不会指向指向真实的vue对象,所以编码阶段的类型推导就会失败,或者说不准确。面对大型系统,不能进行准确的类型推导,应该是很致命的。

在vue2中,因为需要将所有特性声明在一个描述对象内,如果组件功能比较复杂,那么这个描述对象就会变得很大,因此vue提供了mixins,和provide/inject机制来做抽象和分离的工作,可以使用mixins将逻辑抽离成单独文件,让不同组件引入从而具有该功能,使用provide/inject机制,让父组件给子组件注入属性或者功能。 但是这两种机制同样是无法进行类型推导的,并且如果存在一个组件有多个mixins或者多个祖先组件存在provide的时候,这时候人工推导就更加复杂。vue3.0面世后,尤雨溪就明确表示,不建议使用mixins和provide这种模式。

因为vue2的逻辑拆解和引入无法进行类型推导,所以vue2就不能像组织一本书一样去组织代码。因为不能进行类型推导,就好比一本书的目录,只有章节标题,没有章节对应的页码。所以大项目往往使用react,这是其中一个原因。

到了vue3,新的composition api能够很好将逻辑拆解和组合,因为composition api的面向的就是原生的javascript函数编程,组合的时候,显式地导入函数并使用,这样使得代码具备了可推导性,在大型项目面前更加从容了。

最后,好的代码应该是遵循开闭原则的-对修改关闭,对扩展开放。开发的时候,不仅要实现当下的功能,还要考虑将来可能新增的功能,要做到将来新增功能的时候,不需要修改现有代码,只需要增加对应功能的代码即可。这就要求对当前代码有一定的设计,才能无破坏地对接将来的代码。就好比一本书,在写第一版地时候,必须有严谨地逻辑,在以后需要新增内容的时候,不会与已有内容产生冲突,这样新增内容的时候,就不需要修改原有内容了。当然也不要过度设计,如果现在就为几乎不可能新增的功能提供支持,也是很愚蠢的事情。

什么时候重构

按照作者马丁 福勒的话说,就是在不改变外部可观察行为的前提下,去修改软件内部的结构。
马丁在书中多次明示和暗示,重构不是大刀阔斧的去修改软件,而是碎步前进的持续修改。大刀阔斧的修改,那就“结构调整”;重构和“结构调整”的区别在于:

  1. 重构是持续的小修改,“结构调整”大到阔斧的修改;
  2. 重构的小修改让程序处于不可用的状态时间很短,“结构调整”则会很长;
  3. 重构不需要单独规划时间来进行,而是融入到开发周期中去,“结构调整”需要单独规划时间;