今天的主题会参考这本非常有名的书 Clean Code。
写程序到最後,除了最基本的,商业功能要能正常运作以外,其实大部分的时候都是在追求,如何让 code 好读、好维护、好重构,而这也是我这个系列文的核心目标。
而这本书也是朝向这个目标,是需要对自己有要求,期待朝向「更好」的 developer 迈进。因此,今天来好好讨论,有哪些小细节可以优化。
名字就是认识这个变数最快的途径,只要名字取得好,光是把所有变数看过一次,就可以大致了解这段程序的目的。
Naming 一直都不是一件容易的事情,因为英文真的不好,有时候要表达一个抽象的概念时,也不容易用英文去形容,而且命名即便取不好,程序仍然可以正常执行,所以就更不容易好好看待这件事。
命名的最高指导原则:好猜
没错就是这麽简单两个字!我都做不到QQ
无论是变数命名或函式命名,都要遵循这个原则,更精确来说,是要让人「容易理解意图」,那要怎麽衡量有没有符合这个原则呢?
基本上在 Day 7 的时候讨论过一次了,这边简单复习一下:
很多时候程序写得正火热(?),逻辑已经想到第 30 步了,但连第 1 个变数都还没想好名字,很容易就随便给了个「好像有意义但其实没意义」的命名,大致分成下列几类:
data
、element
、value
、temp
、item
:完全不知道是什麽num
、arr
、list
、func
:只看得出变数类型i
、index
:十分泛滥是不是很常见?
没错我在铁人赛赶稿的时候,就是这个死样XD:
const arr = [
{ id: 'item1', name: 'TV', price: 13500 },
{ id: 'item2', name: 'washing machine', price: 8200 },
{ id: 'item3', name: 'laptop', price: 25000 }
];
const displayArr = arr.map(item => item.name).join('、');
但如果是在那种意图单纯、用一次就结束的 scope 内,还算是比较可以接受(好啦是我自己可以接受啦QQ),比如上例第 6 行的 item
:
const displayArr = arr.map(item => item.name).join('、');
item
只有在 map
function 内的 scope 使用,所以只要使用一次的情况下,其实本身就比较容易理解,很多时候我甚至会直接用物件解构赋值,省掉一次命名:
const displayArr = arr.map(({ name }) => name).join('、');
要改进的话,array 的变数可以在後面加个 s
或 list
复数型来表示,这样如果跑 map
,里面的 iterator 就可以变成单数型:
const products = [
{ id: 'item1', name: 'TV', price: 13500 },
{ id: 'item2', name: 'washing machine', price: 8200 },
{ id: 'item3', name: 'laptop', price: 25000 }
];
const displayArr = products.map(product => product.name).join('、');
但规则尽量是统一的,不要偶尔
s
偶尔list
注解也是写在程序码里面的一部分,只是不会被执行,那注解跟一般的程序语法比起来,差别、用意究竟是什麽?
程序语法是写给电脑看的,而注解是写给人看的。
当这个区分出来之後,我们的问题就更清晰了一点:
如果注解是写给人看的,那人们期待在注解上面看到什麽?
毕竟语法是写给电脑看的,很多时候在不同的情境下,我们其实想表达不同的意思,但语法就是那几个 if
、for
、switch
在排列组合,有点像是只用 500 个英文单字过生活一样(?)
因此,很自然就会衍生出,使用注解来解释这段 code 的意图,这个用意。
这一点虽然很直觉,但别忘记,变数名称也是可以自由命名的,所以其实不是只有 500 个单字能用,你仍然可以用变数名称来解释变数的意图,用函式名称来解释整段 code 的意图。
正是基於这一点,网路上看到有些 developer 对於「把注解拿来解释程序意图」这件事,是抱有稍微贬意的,因为他们认为「就是因为你变数命名没做好,才会需要仰赖注解」。
关於这种批评我是觉得大可不必,原因如下:
不可否认大部分的时候,JavaScript 的新手的确是因为对於命名不够熟悉,或者单纯就是函式拆得不够细微,再加上可能词汇量有限(比如只会用 if
/else
不会用 switch
),所以呈现出来的程序也比较冗杂与单一。
const color = 'red';
let colorCode;
// 根据 color 对应的颜色转成色码
if (color === 'blue') {
colorCode = '#0000FF';
} else if (color === 'green') {
colorCode = '#00FF00';
} else if (color === 'red') {
colorCode = '#FF0000';
}
console.log(colorCode); // '#FF0000'
比较适合改成:
const color = 'red';
let colorCode;
switch (color) {
case 'blue':
colorCode = '#0000FF';
break;
case 'green':
colorCode = '#00FF00';
break;
case 'red':
colorCode = '#FF0000';
break;
}
console.log(colorCode); // '#FF0000'
但基本上肯学习的新手都会自己找学习资源,如果这时候在网路上又看到有人说:「啊写注解就是因为菜」,有些人会不小心得到一个结论:即便不是很擅长命名,也不要去写注解。
当然我没有要一竿子打翻一船人,也是会有人因此而认真去练习命名,减少注解的数量。
但我个人比较倾向从鼓励、优化的角度来看这件事,毕竟程序语言也是语言的一种,没有人刚开始学语言就很溜的。
因为变数命名都是用英文,所以外国人当然会觉得不用写注解啊((误
其实最主要还是因为,英文跟中文有一些概念没有对应的词汇,所以要对应就没那麽方便。
再加上,台湾人天生看中文就比较快啊,一些比较难懂的概念,使用熟悉的中文来写注解,会比用不熟的英文来帮变数命名,还容易理解。
如果是站在学习的角度,的确可以自己好好练习在没有注解的情况下,用命名来解释 code;但公司不是请人来学习的,真的用英文表达不清的情况下,请直接写中文注解,下一个接手的人会感谢你的!
通常命名取得好,大多也是在解释「这个变数」或者「这个函式」所做的事情,很像是在帮每一片拼图命名,但很多片拼图拼出来的一个局部,整体在做什麽,就很难用命名来解决。
比如下面这段程序码:
const studentList = [
{ name: 'Joey', grade: 25 },
{ name: 'Susan', grade: 49 },
{ name: 'Allen', grade: 64 },
{ name: 'Alice', grade: 100 }
];
const passList = [];
studentList.forEach(student => {
const adjustedScore = Math.sqrt(student.grade) * 10;
const isHigherThan60 = adjustedScore > 60;
if (isHigherThan60) {
passList.push(student);
}
});
const displayPassList = passList.map(student => student.name).join('、');
console.log(`恭喜以下同学:${displayPassList}`);
执行结果
恭喜以下同学:Susan、Allen、Alice
如果多了注解来说明就会清楚得多:
const studentList = [
{ name: 'Joey', grade: 25 },
{ name: 'Susan', grade: 49 },
{ name: 'Allen', grade: 64 },
{ name: 'Alice', grade: 100 }
];
const passList = [];
// 计算每个学生调分後是否及格,并列出及格名单
studentList.forEach(student => {
const adjustedScore = Math.sqrt(student.grade) * 10;
const isHigherThan60 = adjustedScore > 60;
if (isHigherThan60) {
passList.push(student);
}
});
const displayPassList = passList.map(student => student.name).join('、');
console.log(`恭喜以下同学:${displayPassList}`);
今天讲的都是偏向变数命名、注解写法,这系列的讨论其实都是很小细节的东西,但如果真的有做好,看程序码也能够让人感到如沐春风!
期许每天都在每个细节上更进步一点,每天多懂一点,才赶得上忘记的速度((误
名字的背後
藏着浅显的暗示
与深层的灵魂
<<: [Day31] 再访碰撞侦测与解析(三) - 解析很复杂!
Redis 资料型态GEO 储存地理空间资讯. 可用指令 GEOADD GEOPOS GEODIST...
上一次说到了getRange 与 getDataRange 但是这只是「取得资料的位置与范围」 如果...
有一些模型像是逻辑回归和决策树,背後运作的原因相当简单明了,容易解释模型是如何得出其输出的。但随着更...
Google Drive(云端硬碟) Service 使用到的 API 程序介绍 今日要点: 》D...
//jQuery 使用Id指定 $('#my-div') //jQuery 使用Class指定 $(...