一、那天,我打开了同事的PR
事情发生在上周四。
同事离职了,他的代码交接给我。领导说:“他的模块你熟悉一下,下周可能要加个需求。”
我打开他的PR(Pull Request),看着看着,开始怀疑人生:
const handleData = (a, b, c) => {
const d = a?.b?.c ?? b ?? {};
const e = d.map(x => x?.value ?? 0).reduce((p, n) => p + n, 0);
const f = Object.keys(c).filter(k => c[k]?.flag).map(k => ({ id: k, ...c[k] }));
return { e, f };
};
10行代码,用了6个问号。
我问自己三个问题:
- 这函数是干嘛的?❌
- a、b、c是什么类型?❌
- e和f分别代表什么?❌
那一刻,我体会到了什么叫“每个问号都是我对人生的迷茫”。
二、为什么我们看不懂同事的代码?
先别急着骂同事。看不懂代码,通常有三个原因:
原因1:命名像“密码”
// 这是真实存在的代码
const data = getData();
const data2 = formatData(data);
const d = data2.filter(x => x.type === 3);
data、data2、d、x、3——每一个都像摩斯密码。
问题在哪:命名只描述了“是什么类型”(data),没描述“是什么业务”(userList、formattedOrders)。
原因2:逻辑像“方便面”
// 20行代码做了5件事
useEffect(() => {
if (user) {
fetchOrders(user.id).then(setOrders);
}
setLoading(false);
if (orders.length > 0) {
const total = orders.reduce((p, n) => p + n.amount, 0);
setTotal(total);
}
checkPermission(user?.role) && setCanEdit(true);
}, [user]);
这个useEffect干了什么?
- 获取订单 ✅
- 设置loading ❌(不管有没有数据都设置false?)
- 计算总额 ✅
- 检查权限 ✅
- 依赖数组只有
[user],但用了orders?
问题在哪:多个逻辑混在一起,像方便面一样纠缠不清。
原因3:代码像“压缩包”
// 高手写的“优雅”代码
const result = data
.filter(x => x.type === 'order')
.flatMap(x => x.items)
.map(x => ({ ...x, price: x.price * 1.2 }))
.sort((a, b) => b.price - a.price)
.slice(0, 10)
.reduce((acc, cur) => ({ ...acc, [cur.id]: cur }), {});
每一行都懂,合起来看不懂。代码被压缩得太紧,解压需要时间。
三、看懂同事代码的4个技巧
技巧1:先当“考古学家”,别当“侦探”
看不懂代码时,别急着逐行分析。先问三个问题:
1. 这段代码在哪个页面?
打开页面,看看这个功能长什么样。
2. 它在处理什么数据?
console.log大法:
// 不知道a、b、c是什么?打出来看看
console.log('a是什么神仙:', a);
console.log('b是干什么的:', b);
console.log('c又是谁:', c);
const result = handleData(a, b, c);
console.log('最终返回了什么:', result);
3. 谁调用了它?
往上追溯,看看调用者传了什么。
我的习惯:接到看不懂的代码,先打开页面点一点,看控制台输出,把代码跑起来再分析。
技巧2:把“压缩包”解压
看到那种链式调用的“压缩包”代码,我的做法是:手动拆开。
// 压缩包版
const result = data.filter(x => x.type === 'order').flatMap(x => x.items).map(x => ({ ...x, price: x.price * 1.2 }));
// 解压后
const orders = data.filter(x => x.type === 'order'); // 只保留订单
const allItems = orders.flatMap(x => x.items); // 提取所有商品
const itemsWithTax = allItems.map(x => ({ // 计算含税价
...x,
price: x.price * 1.2
}));
解压后,每一步都清晰了。理解之后,可以和同事商量:能不能保持这样?毕竟代码是写给人看的,顺便给机器执行。
技巧3:给“摩斯密码”加注释
看到那种data、d、x、3的代码,我的做法是:边读边加注释。
// 原代码
const data = getData();
const data2 = formatData(data);
const d = data2.filter(x => x.type === 3);
// 加注释后
const userList = getData(); // 获取用户列表
const formattedUsers = formatData(userList); // 格式化手机号
const activeUsers = formattedUsers.filter(user => user.status === 3); // 3=激活状态
注意:这些注释是给自己看的,理解完可以删掉。但如果发现这些注释很有价值,可以找同事(或上级)商量:要不要重构一下命名?
技巧4:画“地图”不画“流水账”
遇到复杂的逻辑,别在脑子里记,画出来。
工具:Xmind、ProcessOn,甚至纸笔
画什么:
- 输入 → 处理1 → 中间数据 → 处理2 → 输出
- 条件分支:什么情况走A,什么情况走B
- 依赖关系:这段代码依赖哪些外部数据
一张图胜过100行注释。
四、如果代码真的写得很烂,怎么办?
看懂之后,你可能会发现:不是我的问题,是代码真的烂。
这时候有几种选择:
选项1:小范围优化(推荐)
看懂一部分,就优化一部分:
// 原代码(看不懂,但跑得通)
const d = a?.b?.c ?? b ?? {};
// 优化后(加了类型说明和默认值)
const defaultConfig = { pageSize: 10 };
const userConfig = a?.b?.c ?? b;
const finalConfig = userConfig ?? defaultConfig; // 优先使用用户配置,没有就用默认
原则:不改功能,只改可读性。改完跑一遍测试,确保没坏。
选项2:写“翻译文档”
如果暂时不能改代码(怕影响稳定性),就写文档:
# XXX模块解读
## 核心函数:handleData(a, b, c)
- a: 用户配置对象,从API获取
- b: 默认配置对象,从config文件导入
- c: 权限对象,用于筛选可用功能
- 返回值:{ e: 总数, f: 可用功能列表 }
## 调用链路
UserPage.jsx → useUserData.js → handleData.js
这份文档,既帮了自己,也帮了下一个接手的人。
选项3:发起“可读性重构”
如果代码烂到影响开发效率,可以发起重构:
话术:
这个模块我们现在加个需求要3天,因为代码很难看懂。能不能花1天重构一下可读性,以后加需求就只要1天了?
用数据说话,而不是用情绪说话。
五、给“写代码的人”的建议
作为代码的“作者”,怎么让别人看得懂你的代码?
1. 命名要说人话
// 不推荐
const data = getData();
// 推荐
const userList = getActiveUsers();
2. 一个函数只做一件事
// 不推荐:一个函数干了3件事
function handleUser(user) {
// 验证手机号
// 计算积分
// 发送欢迎短信
}
// 推荐:拆成3个函数
function validatePhone(phone) { ... }
function calculatePoints(user) { ... }
function sendWelcomeMsg(user) { ... }
3. 复杂逻辑要加注释
// 这个正则匹配的是:手机号可能有空格、短横线、国家码
// 先把所有非数字去掉,然后处理86开头的情况
const cleanPhone = phone.replace(/[^\d]/g, '');
const finalPhone = cleanPhone.startsWith('86') ? '+' + cleanPhone : cleanPhone;
注释写“为什么” ,不要写“是什么”。
六、最后说几句
回到开头的问题:同事写的代码看不懂,怎么办?
我的答案是:
- 别急,先跑起来,打出来,画出来
- 别骂,可能他有他的考虑,只是没表达清楚
- 别忍,看懂之后,能优化就优化,能文档就文档
代码是写给人看的,顺便给机器执行。
如果机器能看懂但人看不懂,那这台机器迟早会被人砸了。