程序员必知的代码整洁之道:别让烂代码拖垮你的项目
写代码这件事,入门不难,难的是把代码写明白。刚入行的时候,大家关注的是”能不能跑通”;干了两三年之后,就开始琢磨”别人能不能看懂”。这道坎跨过去,才算真的长大了。
我见过太多项目,功能上没什么毛病,但是维护起来要人命——改一个字段要翻十几个文件,加一个判断被迫复制粘贴一大段,review 的时候同事看了半天回一句”这句是干嘛的”。这些都不算 bug,却真实地拖慢着整个团队的节奏。
今天不聊玄乎的概念,就把”整洁代码”里最实用、最好落地的几条拎出来讲讲。
命名是性价比最高的重构
代码里出现最多的东西就是名字:变量名、函数名、类名、参数名。名字起不好,剩下的一切都是白搭。有个说法我很认同:阅读代码的大部分时间是在理解命名,而不是理解逻辑。
看下面两段,功能完全一样:
# 糟糕的写法
def f(d):
t = 0
for x in d:
if x[1] == 1:
t += x[0]
return t
# 清晰的写法
def calc_active_user_total(users):
total = 0
for user in users:
if user.status == 1:
total += user.amount
return total
第二段一眼就知道在算活跃用户的总金额。代价仅仅是名字长了一点,收益却是别人不用再去看数据长什么样。
几个起名的经验:
- 用能读出来的名字,别用
usrCnt、lstIdx这种缩写拼装,写全userCount、lastIndex。 - 布尔值用问句:
isValid、hasPermission、isActive,读完自然知道结果是真还是假。 - 函数名用动词开头:
fetchUser、saveOrder、deleteCache,一眼能看出它干了什么。 - 别加没用的后缀:
UserInfo、UserData、UserObject里的 Info/Data/Object 基本都是噪音,直接User就好。
函数应该只做一件事
判断一个函数是否过长,有个很朴素的信号:你在描述它的功能时,用到了”和”字。比如”这个函数负责校验参数和保存订单”,那它就该拆成两个函数。
单职责不是洁癖,是有实际回报的:
// 拆之前:一段 80 行的函数,混着校验、组装、落库、发消息
async function placeOrder(input) {
// ... 一堆校验
// ... 组装订单对象
// ... 写入数据库
// ... 发通知
}
// 拆之后:每个函数都能单独理解和测试
async function placeOrder(input) {
const valid = validateOrderInput(input);
if (!valid.ok) return { error: valid.reason };
const order = buildOrder(input);
const saved = await saveOrder(order);
await notifyUser(saved);
return saved;
}
拆分之后带来三个实打实的好处:
- 逻辑变薄,读代码的人很快能建立全局视图。
- 每个小函数可以独立写测试,出问题也好定位。
- 复用变容易——
validateOrderInput换个场景还能接着用。
我留一条判断标准给自己:如果一个函数需要加注释来解释它做了什么,先想想是不是名字和拆分出了问题,而不是急着补注释。
注释是谎言的高发区
很多人以为注释越多越负责,其实恰恰相反。注释最大的风险是它会过时——代码改了,注释没人记得改,慢慢就变成谎言。很多人宁愿相信注释,结果被注释带到沟里。
我的原则是:
- 好的命名胜过注释。代码自己能说清楚的事,别用注释再重复一遍。
- 注释用来解释”为什么”,而不是”是什么”。”是什么”交给代码本身,”为什么”才值得写下来。
# 坏注释:重复代码
# 计算总价
total = price * quantity
# 好注释:解释一个不明显的决策
# 运费在满 99 时免单,这里用 100 是因为闭区间判断,
# 恰好 99.5 的订单也要免掉
if amount >= 100:
shipping = 0
还有一种典型的坏味道:被注释掉的代码。很多人不敢删,觉得”以后可能用得上”。实际上 Git 已经帮你存了历史,删掉它们,代码才会清爽。留着死代码,只会让后来的人困惑”这段怎么不用了,该不该用”。
别让条件判断越来越深
嵌套是整洁代码的头号杀手。一个小改动加一个 if,时间一长就变成”阶梯地狱”:
def process(user):
if user is not None:
if user.is_active:
if user.balance > 0:
if not user.is_banned:
# 真正的业务逻辑
charge(user)
这种写法读起来脑子里要维持好几个条件栈。换一种方式来写,立刻清爽:
def process(user):
if user is None:
return
if not user.is_active:
return
if user.balance <= 0:
return
if user.is_banned:
return
# 真正的业务逻辑
charge(user)
这个技巧叫卫语句(Guard Clause):把不符合条件的情况在最前面”拦”出去,让主逻辑留在最后,处于最浅的层级。读起来顺,改起来也安全。
重复是一切维护成本的根源
重复代码(DRY 原则的反面)是最容易被忽视,又最伤人的问题。今天复制了一段,明天要改逻辑,你就得记得”这段我在另一个文件里也抄过一份”。忘了改,就埋下了 bug。
发现重复,抽个函数或者模块出来,这是最划算的投资:
// 重复出现两次的格式化逻辑
function formatMoney(amount) {
return amount.toLocaleString('zh-CN', {
style: 'currency',
currency: 'CNY',
});
}
// 订单和账单两处直接复用
const orderLine = formatMoney(order.amount);
const billLine = formatMoney(bill.amount);
当然,也别矫枉过正。如果你为了”消除重复”抽出来的函数,最终比原来的两段还难读,那说明它们只是长得像,本质并不是一回事,这时候保留原样反而更好。DRY 的核心是消除真正的重复,不是消灭一切相似。
小结
整洁代码不是某本书里的教条,而是一套能实实在在降低维护成本的习惯。落到日常,就这几条:
- 把命名当回事,它是代码里最频繁的沟通。
- 一个函数只做一件事,拆到能一句话说清为止。
- 注释解释”为什么”,代码说清”是什么”。
- 用卫语句把深层嵌套打散。
- 及时消除真正的重复。
这些事都不难,难的是一直坚持。我的体会是,与其追求一次大重构,不如每次改代码时顺手把眼前这段弄干净一点。积累半年,整个项目的气质会完全不一样。
代码最终是写给人看的,只是顺便让机器去执行。想明白这一点,很多纠结自然就解开了。