阅读 117

[译]编写自带文档的(self-documenting)代码的一些技巧

原文地址: Tips for writing self-documenting code

所谓自带文档的代码其实指的就是“可读代码”(可读性很高的代码)。虽然可读代码不可以替代真正的文档和合理的代码注释,可是编写更高质量的代码终究不会有什么坏处,至少在团队合作中,你的代码不会让你的队友找不着北。现在让我们看一下都有哪些编码习惯可以让你的代码变得更加“可读”。

不要使用魔术数字

先看一个例子来理解一下什么是魔术数字:

if (students.length > 23) {
复制代码

上面的代码可读性就很差,因为你不知道23这个数字是什么意思。其实这个23就是我们所说的魔术数字,魔术数字这个名字可能听起来很炫酷有趣,其实这在程序设计里面是一个贬义词,代表不好的编码习惯。所谓的魔术数字就是那些看起来很重要却完全没有提供上下文的数字。在我们的编码中要避免使用魔术数字,对于有重要意义的数字要用一个有含义的变量去表示:

const maxClassSize = 23;
if (students.length > maxClassSize) {
复制代码

你看,现在的代码瞬间变得可读了:如果学生的数量超过一个班最大允许的学生数量,就做某件事。那么如何给一个变量起名字呢?这就是接下来我要说的:

使用明确的变量名

不知道为什么我一直都很自觉地写那些超长的变量名。举个例子,我觉得rStuNmsfStuNms这两个简短变量名字和rawStudentNamesfilteredStudents这两个长变量名比起来就很糟糕。虽然后面两个变量名很长,不过我可以告诉你,如果你采用第一种方式命名变量,两周后当你再次看你的代码,你可能记不起你写的这些缩略词是什么意思了。变量名其实是一个非常重要的东西,因为你可以通过变量名去告诉你的读者你的代码在做什么事情:

const fStuNms = stus.map(s => s.n)
// vs
const filteredStudentNames = students.map(student => student.name)
复制代码

上面这个例子可能没什么实际含义,不过你应该能明白我想表达的意思。对于变量命名,还有一个技巧就是要使用一些命名约定。如果你的变量代表的是一个布尔值,它的名字就要以is或者has开头,例如isEnrolled: true。如果你的变量是用来存储一个数组的,你的变量名字就应该是一个复数,例如students。代表数字的变量名在有可能的情况下应该用min或者max作为前缀。对于函数名称,应该以动词作为开头,例如createSchedule或者updateNickName

使用精简的命名函数来重构

明确的变量名不是唯一一个可以用来解释你的代码行为的办法,其实函数名称也是一个很好的选择!我之前一直认为函数只能用来复用代码逻辑,可是最近我发现函数也可以用来提高代码可读性。花几秒钟看下以下代码然后告诉我它做了什么:

const handleSubmit = (event) => {
    event.preventDefault()
    NoteAdapter.update(currentNote)
        .then(() => {
            setCurrentAlert('Saved!')
            setIsAlertVisible(true)
            setTimeout(() => setIsAlertVisible(false), 2000)
        })
        .then(() => {
            if (hasTitleChanged) {
                context.setRefreshTitles(true)
                setHasTitleChanged(false)
            }
        })
}
复制代码

你可能得花点精力才能理解以上的代码,我们可以通过提炼函数的方法来重构一下:

const showSaveAlertFor = (milliseconds) => () => {
    setCurrentAlert('Saved')
    setIsAlertVisible(true)
    setTimeout(() => setIsAlertVisible(false), milliseconds)
}
const updateTitleIfNew = () => {
    if (hasTitleChanged) {
        context.setRefreshTitles(true)
        setHasTitleChanged(false)
    }
}

const handleSubmit = (event) => {
    event.preventDefault()
    NoteAdapter.update(currentNote)
        .then(showSaveAlertFor(2000))
        .then(updateTitleIfNew)
}
复制代码

看,整个代码的逻辑和思路是不是清晰了很多?我们做的只是将几行代码封装到不同的函数里面,就这么简单就可以大大提高代码的可读性。showSaveAlertFor这个函数如果我们想还可以在其他地方使用。我们将关联的代码封装在函数定义里面,这些函数定义其实就足够告诉你这些代码的作用,所以一般情况下你不用直接阅读里面具体的代码除非它们真的出现了什么问题。在以上重构完的代码例子中,handleSubmit这个方法只是维护了一个高层次的事件链,指明了某些事件发生后应该执行哪个方法,而不是指明具体的操作,具体的操作在各个方法里面被执行。

添加有用的测试描述

应该很少人知道文档其实是可以写进测试里面的。假定我们有以下方法:

const getDailySchedule = (student, dayOfWeek) => {
复制代码

假使getDailySchedule是一个会调用很多方法的一个驱动函数,它会根据不同的情况去做不同的事情:

  • 如果当天是周末,返回空数组
  • 正常返回学生的全天任务
  • 学生没有注册,打印一个Gif
  • ...

你当然可以把以上的所有情况写在这个函数的注释里面,不过这样会使你的代码看起来很奇怪。最好的放置这些情况文案的地方其实在测试里面:

describe('getDailySchedule tests', () => {
    it("retrieves the student's full schedule", () => {})
    it('returns an empty array if given a weekend day', () => {})
    it('prints a gif link if student not enrolled yet', () => {})
    ...
})
复制代码

这是把“注释”放到代码里面而又不添加注释的最简单直接的方法。

底线:可读比聪明更重要

衡量一个好的开发的标准不是他可以写出最聪明的代码,而是他是不是一个好的队友。高质量的软件很少是被一个人单独开发出来的,最后都会有第二个人去阅读你写的代码。即使你真的只有自己一个人编码,今天的你和两三周后的你其实已经是两个不同的人了,你不一定记得住你两三周前写代码时的思路。提高代码可读性的办法有很多种,例如可以添加好的注释,可是最重要的事情是你得开始往这方面考虑了。

关注下面的标签,发现更多相似文章
评论