Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
在代码里面找到一个完全没有地方或没有用的注释是不是很有趣?
这是一个很容易犯的错误:你改变了一些代码,但忘记删除或更新注释。坏的注释不会破坏你的代码,但你可以想象一下调试时会发生什么。你读了注释,但代码却在做另一件事,也许最终你浪费了一些时间来弄懂它,甚至最坏的情况是,它误导了你。
但没有编写任何注释的代码不是一个选择。在我超过 15 年的编程经验里,我从来没有见过一个代码库,其中的评论是完全不必要的。
注释不仅有助于使我们的代码更容易理解,也可以帮助我们改进整个程序的设计。
这种类型的编码叫做自我文档化,现在让我来告诉你如何采用这种方式编程。虽然在这里我的例子使用的是 JavaScript,但你可以应用到期其他的语言和技术中去。
一些程序员把注释作为代码自我文档化的一部分,在本文中,我们只关注代码,注释固然很重要,但它是一个需要单独讨论的大话题。
我们可以将代码自我文档化的技术分为 3 大类:
其中很多是看起来很简单,挑战来自于你要知道什么时候用什么技术。我会告诉你一些实际例子,我们将会处理的每一个例子。
首先,我们来看一下结构类别。结构变化时为了增强代码的清晰度而移动代码。
这与
重构相同——意味着我们采用现有代码并将其移动到一个新的函数中:我们将代码
- 提取代码
到一个新函数中。
- 提取
例如,猜想一下下面的代码是做什么的:
- 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 等。
这种类型的函数更容易理解,因为影响其输出的任何值都明确传递,你不必弄清楚其中的某个值是什么、来自哪里,或什么因素会影响结果,因为它是一目了然的。
这种类型的函数产生更多的自我文档化代码的另一个原因是你可以信任他们的输出。不管什么时候,函数总是输出基于你传递给它的参数的值,它也不会影响任何的外部代码,所以你可以相信它不会导致意想不到的副作用。
一个很好的例子是,错误地使用
,有经验的 JS 开发者知道不应该使用它,但是很多初学者都被它绊倒。有时候它工作的很好,但在其他时候,在某些情况下,它可以把整个页面擦干净。谈一个副作用的痛!
- document.write()
为了更好地阐释纯函数是什么,可以查看 {aa1aa}。
当命名文件或目录时,遵循项目中用到的命名约定。如果项目中没有明确的命名约定,请遵循您选择的语言命名标准。
例如,你要添加有关 UI 的新的代码,请找到项目中放置类似功能的位置,如果 UI 相关的代码放在
中,那你应该放置在这里。
- src/ui
基于你已经知道项目中的其他代码段,目录和文件结构清晰使得你更容易找到代码, 并明白其目的。所有的 UI 代码都放在同一个地方,所以它必须是和 UI 相关的代码。
这里有一个流行的摘引关于计算机科学的两个艰难的方面:
那么,让我们来谈谈如何使用合理的命名来使我们的代码自我文档化。
函数的命名一般不太难,这里有一些简单的规则,你可以遵循:
或
- 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();
- }
习惯使用后一种写法,语法技巧并不讨任何人的喜欢。
如果你的代码中有特殊值——例如数字或字符串值,请考虑使用常量命名。即使现在看起来很清楚,但在一个月或者两个月后,没人会知道为什么这么一个特定的号码放在那里,意义是什么。
- 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;
- });
在这种情况下,我们立即知道这会产生一系列的新东西因为这是
的目的。如果你有更复杂的循环逻辑,这是很有益的写法。{aa0aa}
- map()
JavaScript 的另一个好例子是
关键字。
- const
通常,你声明的变量值应该永远不会改变,一个常见的例子是使用 CommonJS 加载模块时:
- var async = require('async');
你可以用如下写法做出不糊改变意图的语句:
- const async = require('async');
作为一个额外的好处,如果有人不小心试图改变这一点,我们将会得到一个错误。
通过所有这些方法,你可以做很多事情,但是,有些事情你应该注意。
有些人主张使用简短的小函数,如果你把所有东西都提取出来,那就是你能得到的。但是,这可能不利于代码的理解程度。
例如,假设你正在调试一些代码。你想查看
函数,然后你会发现
- a()
函数,接着你会发现使用到
- b()
函数,等等。
- c()
虽然简短的功能可以很好而且易于理解,但如果你只在一个地方使用该功能,那么请考虑使用
方法。
- replace expression with variable
像往常那样,没有绝对正确地方法来使代码自我文档化。因此,如果某些东西似乎是一个好主意,但不能强制使用。
使你的代码自我文档化可以大大提高代码的可维护性,每个注释都是需要额外维护的,所以在有可能删除注释的情况下,编写自我文档化的代码是一个好选择。
但是自我文档化的代码并不能取代文档或者注释,例如,代码本身在表达意图的时候收到限制时,你还是需要有很好的注释的。在一些库中,API 文档是很重要的,因此单纯靠阅读代码是不可取的,除非你的库非常小。
来源: http://www.phperz.com/article/17/0324/326643.html