说实话,程序员这行干久了,脾气真的会变差。不是因为需求变来变去,而是因为维护别人的代码(或者自己三个月前写的代码)真的很痛苦。
昨晚我在改一个老项目的 Bug,有个函数足足写了 800 行。为了搞懂它到底在干嘛,我盯着屏幕看了俩小时,最后不仅没改好,血压还上来了。
痛定思痛,我结合经典的《代码整洁之道》和这些年踩过的坑,想跟兄弟们聊聊关于写函数的几句真心话。这几条不是什么金科玉律,而是我的“保命指南”。
1. 别骗我:函数名和干的事儿得对得上
我最怕的一种函数叫“挂羊头卖狗肉”。 名字叫 getUserInfo(),结果点进去一看,好家伙,它不仅查了库,还顺手更新了用户最后登录时间,甚至发了一条推送消息。
兄弟,如果你要发消息,请你叫 getUserAndNotify()。起名哪怕长一点,也别撒谎。 这种“副作用”是 Bug 之源,因为调用者根本不知道这一行代码下去会引发什么连锁反应。
2. 小,必须得小
以前我觉得写出几百行的逻辑很牛逼,显得逻辑复杂。现在我看到超过 20 行的函数就头大。
一个函数应该小到什么程度? 小到你不需要拉滚动条就能看完。 那如果逻辑真的多怎么办? 拆啊!把大象装冰箱还得三步呢。如果一个函数能被拆成三个有意义的小函数,那就果断拆。越小的代码块,越不容易藏污纳垢。
3. 一个函数只干一件事
这点和上一条是连着的。如果你的函数里出现了 if (type == A) { ... } else { ... } 这种大段的分支,或者代码分了好几块逻辑(先校验、再计算、再入库),那它肯定不止干了一件事。
专注一点。验证就是验证,保存就是保存。一旦一个函数想拯救世界,它通常会毁灭世界。
4. 参数别搞那么多,看着眼晕
每次看到这种方法我就想死: public void createOrder(String id, String name, int age, String address, boolean isVip, String date, ...)
这谁记得住第 5 个参数是地址还是日期? IDE 提示都救不了我。 原则上,参数超过 3 个我就开始慌了。 如果真的需要传这么多参数,大概率说明这些参数是有关联的,不如封装成一个对象(比如 OrderContext)传进去。
5. 别再用 true/false 当参数了,求你了
render(true) 这种代码,除了写的人,没人知道 true 代表什么。是“强制渲染”?还是“渲染头部”?
这就叫 Flag 参数。当你传入一个布尔值,就等于公然宣称:“看,我这个函数内部有一套分裂的逻辑,true 走左边,false 走右边。”
既然都分裂了,为什么不直接写两个函数 renderHeader() 和 renderBody() 呢? 别为了省那几行代码去折磨阅读者的脑细胞。
6. 参数名得说是人话
别用 d 代表天数,别用 e 代表异常(虽然 catch 里经常这么干),别用 flag 代表标志位。 代码是写给人看的。多打几个字母累不死人,写成 daysOverdue,哪怕是刚毕业的实习生也能一眼看懂。
7. 别传 Null,也别返 Null
NullPointerException 绝对是 Java 程序员的噩梦之首。 为了防这个空指针,我们不得不在函数开头写一大堆 if (arg == null) 的防御代码,丑陋至极。
如果可以,约定好不要传 null。 同样,函数返回值也别给 null。如果查不到数据,返回一个空列表 Collections.emptyList() 或者抛个异常,都比返回 null 强。返回 null 就是在给调用你代码的人挖坑,他要是忘了判空,生产环境就炸了。
8. 真正的代码,是改出来的
我也不是神,我不可能一上来就写出上面说的那种完美代码。 写代码和写文章一样,第一版通常都是像屎一样的草稿。
先把功能跑通(Make it work),这时候函数可能很长、参数很乱。没关系,但这只是第一步。 关键在于第二步:重构(Make it right)。 回头看一眼,把那坨 800 行的代码拆了,把命名改了。很多人只做了第一步就提交代码了,这就是屎山的由来。
9. 代码永远整洁不完
代码整洁这事儿,永远没有“完成”的一天。需求在变,原本干净的代码加了几个补丁后又会变脏。 这就像打扫房间,不是说今天大扫除一次以后就不用管了,而是得随时随地看到垃圾就捡一下。
写在最后
其实说千道万,核心思想就一条:把看代码的人当人。
那个“看代码的人”,很可能是三个月后的你自己。为了让未来的自己少加点班,现在的自己多花点心思在函数设计上吧。共勉。