为什么我们使用JavaScript、Dart和Python等语言,而不是古老的汇编语言?
这是因为它们与自然语言更接近。
或者说,它们有可能更接近自然语言。
因为有时我们编写代码只是为了让它能工作,而不关心向其他人展示我们在做什么。
而这种做法往往会在日后造成痛苦的反噬。特别是当其中一个"其他人"是未来的自己时。
1. 使用词性命名
当你的代码尽可能地像英语时,你就知道它是自然的。就像一个有趣、描述性的故事。
这意味着你已经智能地创造了故事中的实体和动作,以强有力地表达从开始到完成的代码流程。
名词
我们在谈论哪些实体?
- 变量
- 属性(getter和setter)
- 类和对象
- 模块
每个角色都有一个名字,所以我们用表达力强的名词和名词短语来描述它们。
不要这样:
// ❌ do-examples.ts
// ❌ 难以理解
const f = 'Coding';
const l = 'Beauty';
// ❌ Verb
// ❌ 动词
const makeFullName = `${f} ${l}`;
class Book {
// ❌ Adjectival phrase
// ❌ 形容词短语
createdAt: Date;
}
而要这样:
// ✅ examples.ts
// ✅ 可读性高
const firstName = 'Coding';
const lastName = 'Beauty';
// ✅ Noun
// ✅ 名词
const fullName = `${firstName} ${lastName}`;
class Book {
// ✅ Noun phrase
// ✅ 名词短语
dateCreated: Date;
}
动词
你的代码库中有哪些动作?
- 函数
- 对象方法
动作意味着实体在做某事;命名它们的自然方式是使用描述性的动词和动词短语。
不要这样:
class Product {
constructor(name, price, quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
// ❌ Noun
// ❌ 名词
total() {
return this.price * this.quantity;
}
}
const product = new Product('Pineapple🍍', 5, 8);
console.log(product.total()); // 40
而要这样:
class Product {
constructor(name, price, quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
// ✅ Verb
// ✅ 动词
getTotal() {
return this.price * this.quantity;
}
}
const product = new Product('Banana🍌', 7, 7);
console.log(product.getTotal()); // 49
方法是用来做某事的。属性是用来拥有某物的。
所以更好的做法是:
class Product {
constructor(name, price, quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
get total() {
return this.price * this.quantity;
}
}
const product = new Product('Orange🍊', 7, 10);
console.log(product.total); // 70
副词
记住,副词用来告诉你更多关于名词、动词或另一个副词的信息。
在JavaScript中,这是指任何接受函数并返回另一个函数的函数:一个高阶函数。它们升级了函数。
所以与其这样:
// ❌ 动词
function silence(fn) {
try {
return fn();
} catch (error) {
return null;
}
}
const getFileContents = (filePath) =>
silence(() => fs.readFileSync(filePath, 'utf8'));
不如这样更自然:
// ✅ 副词
function silently({ fn }) { // or "withSilence"
try {
return fn();
} catch (error) {
return null;
}
}
const getFileContents = (filePath) =>
silently({ fn: () => fs.readFileSync(filePath, 'utf8') });
这就像在说"悄悄地获取文件内容"。
2. 编写自然的输入
编码和计算都是关于处理某些输入以产生输出。
在自然代码中,处理是动作,而输入+输出是实体。
假设我们有一个计算矩形面积并将其乘以某个数量的函数。
你能看出这里的问题吗?
const calculateArea = (length, width, height) => length * width * height;
const area = calculateArea(2, 5, 10); // 100
哪个参数是宽度和高度?哪个是乘数?
这段代码读起来不自然;在英语中,我们总是指定动作的对象。
如何修复这个问题?命名参数:
// 输入是实体 - 名词 ✅
const area = calculateArea({ multiplier: 2, height: 5, width: 10 });
function calculateArea({ multiplier, height, width }) {
return multiplier * height * width;
}
这样更容易阅读;我们立即就能理解我们在处理什么输入。
即使只有1个参数,我也建议使用这种方式。
3. 编写自然的输出
我们在输出上也可以同样明确:
const { area } = calculateArea({
multiplier: 2,
height: 5,
width: 10,
});
function calculateArea({ multiplier, height, width }) {
return { area: multiplier * height * width };
}
这也允许我们之后轻松升级函数:
const { area, perimeter } = calculateArea({
multiplier: 2,
height: 5,
width: 10,
});
console.log(area, perimeter); // 100 60
function calculateArea({ multiplier, height, width }) {
return {
area: multiplier * height * width,
perimeter: (height + width) * 2 * multiplier,
};
}
4. 避免魔法数字
编码不是悬疑惊悚片!它是一篇描述性文章;尽可能地描述清楚。
而不是这种令人费解的混乱:
function c(a) {
return a / 13490190;
}
const b = c(40075);
console.log(b); // 0.002970677210624906
在整个代码库的大背景下,所有这些数字和变量意味着什么?它们告诉我们——人类——什么?
什么也没告诉我们。实体和动作的名称要么不存在,要么质量很差。
这就像告诉你的朋友:
是的,我去了这个地方,然后做了这件事,之后我做了某事去了另一个地方,并做了某事,120!
这毫无意义。
自然代码描述一切。为实体使用优雅的名词,为动作使用出色的动词。
const speedstersSpeedKmPerHr = 13490190;
const earthCircumferenceKm = 40075;
function calculateSpeedstersTime(distance) {
return distance / speedstersSpeedKmPerHr;
}
const time = calculateSpeedstersTime(earthCircumferenceKm);
console.log(time); // 0.002970677210624906 ~ 11s
现在你说了些有意义的话。
是的,我去了餐厅,吃了一个鸡肉三明治,然后我开车去了健身房,做了二头肌卷举,120磅!
5. 创建"无用"变量
在自然代码中,变量不再仅仅用于在这里那里存储值。
它们也是解释你在做什么的工具:
这就是为什么我们不这样做:
if (
!user.isBanned &&
user.pricing === 'premium' &&
user.isSubscribedTo(channel)
) {
console.log('Playing video...');
}
而是这样做:
const canUserWatchVideo =
!user.isBanned &&
user.pricing === 'premium' &&
user.isSubscribedTo(channel);
if (canUserWatchVideo) {
console.log('Playing video...');
}
我们只会使用这个变量一次,但这并不重要。它不是一个功能性变量,而是一个装饰性变量;一个自然变量。
最后的思考
代码是为你的同伴人类编写的,而不仅仅是为编译器。
一个不懂编程的人能理解你的代码中发生了什么吗?
毫无疑问,这是一个强有力的指导性问题,可以让你的代码尽可能地易读和自然。