15种编写自我文档化的JavaScript方式

在代码里面找到一个彻底没有地方或没有用的注释是否是颇有趣?node

这是一个很容易犯的错误:你改变了一些代码,但忘记删除或更新注释。坏的注释不会破坏你的代码,但你能够想象一下调试时会发生什么。你读了注释,但代码却在作另外一件事,也许最终你浪费了一些时间来弄懂它,甚至最坏的状况是,它误导了你。程序员

但没有编写任何注释的代码不是一个选择。在我超过15年的编程经验里,我历来没有见过一个代码库,其中的评论是彻底没必要要的。算法

注释不只有助于使咱们的代码更容易理解,也能够帮助咱们改进整个程序的设计。express

这种类型的编码叫作自我文档化,如今让我来告诉你如何采用这种方式编程。虽然在这里个人例子使用的是JavaScript,但你能够应用到期其余的语言和技术中去。编程

技术概述

一些程序员把注释做为代码自我文档化的一部分,在本文中,咱们只关注代码,注释当然很重要,但它是一个须要单独讨论的大话题。数组

咱们能够将代码自我文档化的技术分为3大类:安全

  • structural(结构):其中代码或目录的结构用于阐明目的async

  • naming related(命名相关):例如函数或变量的命名函数

  • syntax related(语法相关):咱们利用(或者避免使用)语言的特征来是代码清晰ui

其中不少是看起来很简单,挑战来自于你要知道何时用什么技术。我会告诉你一些实际例子,咱们将会处理的每个例子。

结构

首先,咱们来看一下结构类别。结构变化时为了加强代码的清晰度而移动代码。

将代码移动到函数中

这与提取代码重构相同——意味着咱们采用现有代码并将其移动到一个新的函数中:咱们将代码提取到一个新函数中。

例如,猜测一下下面的代码是作什么的:

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

那么,让咱们来谈谈如何使用合理的命名来使咱们的代码自我文档化。

重命名函数

函数的命名通常不太难,这里有一些简单的规则,你能够遵循:

  • 避免使用handlemanage这样的模糊词:handleLinks(), manageObjects(),这些都作了什么?

  • 使用主动性动词:cutGrass(), sendFile()函数积极地执行了某事

  • 代表返回值:getMagicBullet(), readFile(),这不是你老是能够作到的,但赋予它意义是有帮助的

  • 强类型的语言可使用类型命名来帮助代表返回值

重命名变量

对于变量,这里有两个好的经验法则:

  • 代表单位:若是有数字参数,能够包含参数的预期单位。例如,widthPX而不是width代表值得单位是像素而不是其余单位

  • 不要使用快捷方式:ab不是可接受的变量名称, 除了在循环计算器中

遵循既定的命名约定

尝试在代码中遵循相同的命名约定。例如,若是你有一个特定类型的对象,调用它相同的名称:

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文档是很重要的,所以单纯靠阅读代码是不可取的,除非你的库很是小。

相关文章
相关标签/搜索