在代码内里找到一个完全没有处所或没有效的解释是否是很风趣?
这是一个很轻易犯的毛病:你转变了一些代码,但遗忘删除或更新解释。坏的解释不会损坏你的代码,但你能够设想一下调试时会发作什么。你读了解释,但代码却在做另一件事,也许终究你浪费了一些时刻来弄懂它,以至最坏的状况是,它误导了你。
但没有编写任何解释的代码不是一个挑选。在我凌驾15年的编程履历里,我从来没有见过一个代码库,个中的批评是完全没必要要的。
解释不仅有助于使我们的代码更轻易邃晓,也能够协助我们革新悉数顺序的设想。
这类范例的编码叫做自我文档化,如今让我来通知你怎样采纳这类体式格局编程。虽然在这里我的例子运用的是JavaScript,但你能够运用到期其他的言语和手艺中去。
手艺概述
一些顺序员把解释作为代码自我文档化的一部份,在本文中,我们只关注代码,解释当然很主要,但它是一个须要零丁议论的大话题。
我们能够将代码自我文档化的手艺分为3大类:
structural(构造):个中代码或目次的构造用于申明目标
naming related(定名相干):比方函数或变量的定名
syntax related(语法相干):我们应用(也许防止运用)言语的特性来是代码清晰
个中许多是看起来很简朴,应战来自于你要晓得什么时刻用什么手艺。我会通知你一些现实例子,我们将会处置惩罚的每一个例子。
构造
起首,我们来看一下构造种别。构造变化时为了加强代码的清晰度而挪动代码。
将代码挪动到函数中
这与提庖代码
重构雷同——意味着我们采纳现有代码并将其挪动到一个新的函数中:我们将代码提取
到一个新函数中。
比方,猜测一下下面的代码是做什么的:
var width = (value - 0.5) * 16;
上述代码不是很清晰,此时解释多是异常有效的,但也许我们能够提取一个函数,使其自我文档化:
var width = emToPixels(value);
function emToPixels(ems) {
return (ems - 0.5) * 16;
}
唯一的变化时我把盘算挪动到一个函数里,函数的称号形貌了它的作用,所以代码不再须要解释。作为一个分外的优点,我们如今有了一个有效的函数,我们能够在其他处所运用这个函数,这类要领有助于削减代码反复冗余。
用函数替代前提表达式
许多时刻带有多个操作数的代码,假如没有解释是很难邃晓的。我们能够运用相似上述的要领来使代码清晰:
if(!el.offsetWidth || !el.offsetHeight) {
}
上诉前提的目标是什么?
function isVisible(el) {
return el.offsetWidth && el.offsetHeight;
}
if(!isVisible(el)) {
}
再一次,我们把代码挪动到一个函数内,代码马上更轻易邃晓。
用变量替代表达式
用变量替代某个东西相似于将代码挪动到一个函数中,而不是一个函数,此时我们只须要一个变量。
让我们再看一下if前提语句的例子:
if(!el.offsetWidth || !el.offsetHeight) {
}
我们还能够经由过程引入一个变量,而不是提取一个函数,来使我们的代码自我文档化:
var isVisible = el.offsetWidth && el.offsetHeight;
if(!isVisible) {
}
这多是比提取函数更好的挑选,比方,当你当你想要申明的逻辑关于仅在一个处所运用的某个算法异常特定时。
这类要领最罕见的是用于数字表达式:
return a * b + (c / d);
我们能够经由过程支解盘算来使上述代码更清晰:
var multiplier = a * b;
var divisor = c / d;
return multiplier + divisor;
由于我畏惧数学,设想上述的例子照样有一些算法的。在任何状况下,代码自我文档化的症结是你能够将庞杂的表达式挪动到变量中,并增添意义,不然你的代码是难以的邃晓的。
类和模块接口
类和模块的接口——即大众要领和属性,能够作为其运用的文档。
让我们来看一下这个例子:
class Box {
setState(state) {
this.state = state;
}
getState() {
return this.state;
}
}
这个类能够包含一些其他的代码。我有意坚持示例简朴,以申明大众接口是怎样自我文档化的。
你能通知我应当怎样运用这个类吗?也许有一点点作用,但它不明显。
这两个函数都有合理的名字:它们要做的是申明本身的名字。然则尽管如此,它不是很清晰你应当怎样运用它们,极能够你须要浏览更多的代码或类的文档来弄清晰。
假如我们把它改成如许:
class Box {
open() {
this.state = 'open';
}
close() {
this.state = 'closed';
}
isOpen() {
return this.state === 'open';
}
}
这是更轻易邃晓的用法,你不以为吗?注重我们只是转变了大众接口,内部示意依然与this.satte
属性雷同。
如今你能够一眼就看出Box类是怎样运用的了。这表明这表明纵然第一个版本的函数具有优越的称号,但完全的包依然是杂沓的,怎样经由过程如许简朴的变化,你能够有一个异常大的影响。许多时刻你须要想一想大局。
代码分组
代码分组的差别部份也能够作为一种文档情势。
比方,你应当将变量声明尽量地接近它们被运用的位置,并尝试将变量运用组合在一起。
这能够用于指导代码差别部份之间的关联,以便未来变动它的任何人都能够更轻易地找到他们须要查阅的部份。
思索以下的例子:
var foo = 1;
blah()
xyz();
bar(foo);
baz(1337);
quux(foo);
你能一眼看出foo
被挪用了多少次吗?对照下面的例子:
var foo = 1;
bar(foo);
quux(foo);
blah()
xyz();
baz(1337);
经由过程把foo
的所用用处分组在一起,我们很轻易能够看出代码的哪些部份取决于它。
运用纯函数
纯函数比依靠性强的函数更轻易邃晓。
什么是纯函数?当挪用一个具有雷同参数的函数时,假如它老是发生雷同的输出,它很有多是一个“纯”函数。这意味着纯函数不该当有任何副作用或依靠状况,如时刻、对象属性、Ajax等。
这类范例的函数更轻易邃晓,由于影响其输出的任何值都邃晓通报,你没必要弄清晰个中的某个值是什么、来自那边,或什么要素会影响效果,由于它是一览无余的。
这类范例的函数发生更多的自我文档化代码的另一个原因是你能够信托他们的输出。不论什么时刻,函数老是输出基于你通报给它的参数的值,它也不会影响任何的外部代码,所以你能够置信它不会致使意想不到的副作用。
一个很好的例子是,毛病地运用document.write()
,有履历的JS开发者晓得不该当运用它,然则许多初学者都被它绊倒。有时刻它事变的很好,但在其他时刻,在某些状况下,它能够把悉数页面擦清洁。谈一个副作用的痛!
为了更好地阐释纯函数是什么,能够检察Functional Programming: Pure Functions。
目次和文件构造
当定名文件或目次时,遵照项目中用到的定名商定。假如项目中没有邃晓的定名商定,请遵照您挑选的言语定名规范。
比方,你要增加有关UI的新的代码,请找到项目中安排相似功用的位置,假如UI相干的代码放在src/ui
中,那你应当安排在这里。
基于你已晓得项目中的其他代码段,目次和文件构造清晰使得你更轻易找到代码, 并邃晓其目标。一切的UI代码都放在同一个处所,所以它必需是和UI相干的代码。
定名
这里有一个盛行的摘引关于盘算机科学的两个困难的方面:
There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton
那末,让我们来谈谈怎样运用合理的定名来使我们的代码自我文档化。
重定名函数
函数的定名平常不太难,这里有一些简朴的划定规矩,你能够遵照:
防止运用
handle
或manage
如许的隐约词:handleLinks()
,manageObjects()
,这些都做了什么?运用主动性动词:
cutGrass()
,sendFile()
函数积极地执行了某事表明返回值:
getMagicBullet()
,readFile()
,这不是你老是能够做到的,但给予它意义是有协助的强范例的言语能够运用范例定名来协助表明返回值
重定名变量
关于变量,这里有两个好的履历轨则:
表明单元:假如有数字参数,能够包含参数的预期单元。比方,
widthPX
而不是width
表明值得单元是像素而不是其他单元不要运用快捷体式格局:
a
或b
不是可接受的变量称号, 除了在轮回盘算器中
遵照既定的定名商定
尝试在代码中遵照雷同的定名商定。比方,假如你有一个特定范例的对象,挪用它雷同的称号:
var element = getElement();
不必倏忽以为称之为node:
var node = getElement();
假如你遵照与代码库中其他处所雷同的定名商定,浏览代码的任何人都能够基于此变量在别的处所的定名寄义安全地假定它在此处的寄义。
运用有意义的毛病
未定义不是一个对象!
每一个人的最爱。让我们抛开JavaScript的例子,让我们确保代码抛出的任何毛病都是有意义的音讯。
什么能够使毛病音讯有意义?
它应当形貌毛病是什么
假如能够,它应当包含任何致使毛病地变量值或其他数据
症结点:抛出的毛病应当协助我们找出那边出错了——因而毛病音讯应当像函数那样通知我们应当怎么做
语法
自我文档化代码的语法相干要领能够有一些言语特点。比方,Ruby和Perl许可你写一些新鲜的语法技能,平常来说,应当防止。
让我们来看几个在JavaScript中碰到的题目:
不要运用语法技能
不要运用语法技能。这很轻易让人迷惑:
imTricky && doMagic();
上面的这行代码相当于以下更健全的代码:
if(imTricky) {
doMagic();
}
习气运用后一种写法,语法技能并不讨任何人的喜好。
运用常量定名,防止运用magic值
假如你的代码中有特别值——比方数字或字符串值,请斟酌运用常量定名。纵然如今看起来很清晰,但在一个月也许两个月后,没人会晓得为何这么一个特定的号码放在那边,意义是什么。
const MEANING_OF_LIFE = 42;
(假如你不运用ES6,你能够用var
,是一样的。)
防止运用布尔值
布尔值会让人难以邃晓代码,斟酌这个:
myThing.setData({ x: 1 }, true);
此处true
的作用是什么呢?除非找到setDate()
要领并浏览它。
相反你能够增加另一个函数,或重定名现有的函数:
myThing.mergeData({ x: 1 });
如今,你马上就能够晓得这行代码发作了什么。
运用言语上风
我们以至能够运用我们编写的言语的一些特性来更好地表述代码背地的意义。
JavaScript中一个很好的例子是数组的迭代:
var ids = [];
for(var i = 0; i < things.length; i++) {
ids.push(things[i].id);
}
上面的代码将一个ID列表收集到一个新的数组中,然则为了邃晓这块代码是做什么的,我们须要浏览悉数轮回的悉数。下面我们运用map()
来举行比较:
var ids = things.map(function(thing) {
return thing.id;
});
在这类状况下,我们马上晓得这会发生一系列的新东西由于这是map()
的目标。假如你有更庞杂的轮回逻辑,这是很有益的写法。list of other iteration functions on MDN
JavaScript的另一个好例子是const
症结字。
一般,你声明的变量值应当永久不会转变,一个罕见的例子是运用CommonJS加载模块时:
var async = require('async');
你能够用以下写法做出不糊转变企图的语句:
const async = require('async');
作为一个分外的优点,假如有人不小心试图转变这一点,我们将会获得一个毛病。
反形式
经由过程一切这些要领,你能够做许多事变,然则,有些事变你应当注重。
Extracting for the sake of having short functions
有些人主意运用简短的小函数,假如你把一切东西都提取出来,那就是你能获得的。然则,这能够不利于代码的邃晓水平。
比方,假定你正在调试一些代码。你想检察a()
函数,然后你会发明b()
函数,接着你会发明运用到c()
函数,等等。
虽然简短的功用能够很好而且易于邃晓,但假如你只在一个处所运用该功用,那末请斟酌运用replace expression with variable
要领。
别强迫
像平常那样,没有相对正确处所法来使代码自我文档化。因而,假如某些东西似乎是一个好主意,但不能强迫运用。
总结
使你的代码自我文档化能够大大提高代码的可保护性,每一个解释都是须要分外保护的,所以在有能够删除解释的状况下,编写自我文档化的代码是一个好挑选。
然则自我文档化的代码并不能庖代文档也许解释,比方,代码本身在表达企图的时刻收到限定时,你照样须要有很好的解释的。在一些库中,API文档是很主要的,因而纯真靠浏览代码是不可取的,除非你的库异常小。