[JavaScript]重构:代码的”坏味道”清单

“重构的意义在于:你永远不必说对不起——只要把出问题的地方修补好就行了”

上文提到,日常开发中有太多槽点,而这些槽点具体是什么,这篇就挑选一些主要的帮助大家梳理。

神秘命名

命名在很长一段时间都是一件被低估的“小”事,但又是所有编码规范中必然提及的。

代码是写给人看的,只是顺便交给机器去运行。

大到项目名称,小到变量名称,起个好名字,好处非常多,糟糕的名字无异于制造bug。

什么样的名字是好的?—— 能清晰地表明自己的功能和用法

如果第一次名字没有起好,重构的时候改名就是性价比最高的事。

如果想不出好名字,只能说明那段代码的意义没理清,或者本身逻辑的设计不够好,需要做调整。

重复代码

很多人看到一段现有的代码,自己也要用,下意识不是封装调用,而是复制一份,等到另一人也要用,看到已经有两份了,他再复制一份,如此重复,我们就会看到同一种用途的代码被分散在各处。

一旦有重复代码存在,阅读它们就必须加倍仔细,留意其间细微的差异,如果要修改,要找出所有的副本来修改,这无疑让人头疼。

漏改代码是造成bug最常见的因素之一。

怎么办,当然是第二次用到的时候就把它提炼出来,作为公共代码,这样修改的时候只要改一处,而且另一个人看到有封装好的代码,也会去引用它,而不是复制它。

过长的函数

函数越长,就越难理解

对于变量,最重要的是体现“是什么”,而对于函数,最重要的是体现“做什么”,如果一个函数过长,就算它起了一个很好的名字,你也不敢断定它到底做了什么,是否“越界”,当然,还会存在很多“副作用”。

对于设计糟糕的代码来说,你甚至分不清谁跟谁是“一伙”的。

怎么办,一个简单的标准,每当感觉需要以注释来向别人解释做什么的时候,就说明那段代码适合写进一个独立函数中,并以其用途命名。有时甚至可以是一行代码。

除此之外,条件表达式和循环也是很好的信号。

过长的参数

初学编程的时候,一段段代码重复写很麻烦,于是遇到了函数,但如果一个函数每次调用结果都一样,它的威力就大打折扣,于是又遇到了参数。

参数使函数的威力无限放大。

但是,如果说“过长的函数”是第一宗罪,“过长的参数”就是第二宗罪。

比如下面这段代码:

csharp

getStudentInfo(1,"a","common",null,true)

函数名很明确,获取学生信息,可是这一堆参数代表什么?共有几种可能的值?都不知道,导致使用和修改的成本都大大增加。

怎么办,至少两种策略:一、将它们封装成为一个对象,每个属性标明含义;二、其中某个参数是否能由其他参数计算得来,如果可以,就去掉。

全局数据

全局数据的问题在于,从代码的任何一个角落都可以修改它,而且没有任何机制可以探测出到底哪段代码做出了修改。一次又一次,全局数据造成了那些诡异的bug,而问题的根源却在遥远的别处,想要找到出错的代码难如登天。

首要的防御手段是封装变量,用什么封装?函数,这样做至少你能看见修改它的地方,并可以控制对它的访问,对,关键词就是“可控”。

可变数据

大多数编程语言都允许修改变量的值,当然这也是合理的,不然它就不叫变量,叫常量。

ES6之前的JavaScript是没有“常量”的,ES6引入了常量(const),允许修改代表什么,就是你不知道谁改了它,也不知道为什么改,这是一个定时炸弹。何解?

一个办法是,总是使用常量来定义变量,除非它到了需要修改的时候。

第二个,还是封装变量,一个数据如果可变,就让它在有限的几个封装内可变,这样能够知晓它的变化逻辑,以及跟其他代码之间的关系。

外部交互

在推崇模块化开发的今天,难免涉及不少父子组件及跨组件交互,一些框架的内部机制为之提供了便利的操作方法,但也正是基于这种便利,会让一些人在代码设计环节偷懒,没有想好组件的颗粒度和职责到底怎样划分,分了之后又发现,好多东西都要传值或者抛出方法才能搞定,这样反而成为理解和维护中的负担。

避免出现这种情况,就要减少代码间的依赖,职责清晰且封闭,尽量将处理逻辑都包含在内部。

数据集合

一般程序员的本能,是需要什么数据,就定义什么数据,使用什么数据。

比如:学生姓名(studentName)。后来做着又发现需要用到年龄,又定义一个学生年龄(studentAge),后来又一个学生性别(studentSex)…

随着开发的深入,变量越来越多,而且这些变量不止一个地方用,很多地方都会用,它们就像小虫子一样,往程序的各个角落里钻。

如果换成这样:

csharp

const student = {
 name:'',
 age:0,
 sex:''
}

就让这些原本分散的变量有了归属,不论是在哪里使用和修改,你都知道在干什么,程序需要这种确定性。

switch 和 if

当我们面对一个变量不同值的情况,本能反应,用if进行条件判断,如果不喜欢用if,会换成switch。

这两种方式都能用,也常用,但也存在一个共同的问题,就是但凡需要的地方,都要不断重复。

但如果将重复的条件判断改成一个类的多个扩展类,或者一组映射关系,情况就会不同,你不必将条件分支到处写了,只要将值传入,交给它们找到自己的值/方法即可。

临时字段

临时字段的含义是,在主流程、主框架的设计之初,没有想到会需要,但在某个处理逻辑中突然需要的。

临时字段难以避免,它可能会对代码的组织和表达有帮助,需要注意的是它的适用范围,计算方式及命名即可,否则,你会搞不清它为何而设计,又跟哪些代码有关系,别人能不能用?或者不用了能不能删?均不确定,就成了“坏味道”。

注释

我们常说,代码要写注释,但不是所有地方都要写,或者注释怎样写都“好”。

什么是“好”,必要的,合理的。

如果你看到一段代码有着长长的注释,第一反应可能不是注释写得好,而是,代码得有多难懂才需要这么多注释?

好的代码需要能够“自解释”,干了什么让人一目了然。

如果的确有特殊情况,或者说为了主流程更清晰,为了说明背景,加上简明扼要的注释即可,否则它将成为负担。

小结

已经聊了不少,但也只是一部分,而且大部分朋友应该跟他们很熟悉,我们每天都在遭遇这些问题,终归还是不能逃避,要解决,解决问题的能力,本身就是优秀程序员应该具备的。

标签

发表评论