函数写得长,加班加到爽
第四章讲"函数",这章简直是为我这种"函数越写越长星人"量身定做的。看完才发现,自己以前写的不是函数,是"代码垃圾堆"。
函数的第一铁律:短小!再短小!
作者说"函数的第一规则是要短小,第二条规则是还要更短小"。看到这句话时,我默默打开自己上周写的一个800行长函数,羞愧地低下了头。
书中说函数应该短到"每个代码块都只有一行",最好是函数调用。想想也是,你看这段代码:
java
// 反例
if (isTestPage) {
// 10行设置代码
// 20行处理逻辑
// 5行清理工作
}
// 正例
if (isTestPage) {
includeSetupAndTeardownPages();
}
后者一眼就能看出在干嘛,前者得逐行扒逻辑。函数长了,逻辑就藏起来了,debug时跟找针一样难。
函数只能做一件事,多一件都不行
作者说"函数应该做一件事,做好这件事,只做这件事"。但怎么判断是不是"一件事"呢?
一个简单的方法:看能不能再拆出一个函数,而且这个函数不只在重复解释原函数的实现。比如一个calculateOrder
函数,如果里面既有计算价格,又有生成订单号,那肯定超标了。
想起之前写的submitForm
函数,又验证表单、又调接口、又更新UI,改一个小逻辑就得通读全函数。现在才懂:函数做的事越多,被修改的理由就越多,出bug的概率就越大。
函数的参数,越少越好
看到"最理想的参数数量是零"时,我惊了。但仔细想想,确实如此:
- 无参数:
getUserInfo()
一看就懂 - 单参数:
getUserById(id)
也还行 - 双参数:
calculateDistance(x1, y1, x2, y2)
开始混乱了 - 多参数:
createOrder(user, goods, address, coupon, payType)
堪称灾难
书中说"三元函数要特殊理由才能用",太对了。遇到多参数,不如封装成对象,比如createOrder(OrderParam param)
,既清晰又灵活。
最坑的是布尔参数,比如render(true)
,鬼知道这个true是"是否显示头部"还是"是否加载数据"?不如拆成renderWithHeader()
和renderWithoutHeader()
。
函数的命名,要像起外号一样精准
函数名必须准确描述它做的事。比如calculateTotalPrice()
就比compute()
好,validateUserSession()
就比check()
好。
更妙的是"动词+名词"的组合:saveUser()
、deleteOrder()
、generateToken()
,读起来就像在说中文,根本不用猜。
想起自己以前给函数起名dealWithData()
,现在看来就是摆烂------这函数到底是处理数据格式,还是过滤数据,还是计算数据?命名偷懒的代价,就是每次调用都要翻实现。
错误处理别捣乱,单独放一边
作者说"try/catch块要单独抽成函数",深以为然。比如:
java
// 反例
public void deletePage() {
try {
// 10行删除逻辑
} catch (Exception e) {
// 5行日志处理
}
// 还有20行其他逻辑
}
// 正例
public void deletePage() {
try {
doDeletePage();
} catch (Exception e) {
logDeleteError(e);
}
}
private void doDeletePage() throws Exception {
// 10行删除逻辑
}
private void logDeleteError(Exception e) {
// 5行日志处理
}
后者把"删除"和"处理错误"彻底分开,逻辑一目了然。错误处理是必要的恶,但别让它弄脏主逻辑。
最后动手改了个函数
把之前那个800行长函数拆成了12个小函数,每个函数都不超过20行。改完之后:
- 同事说"终于能看懂了"
- 改bug时不用翻来翻去了
- 新增逻辑直接加新函数就行
突然明白,写短函数看似多花了时间,其实是在给未来的自己省钱。毕竟,调试3行函数的时间,比调试300行函数的时间,差了100倍。
(下一章讲注释,想起自己写的"这里是循环"这种注释,感觉又要被骂了...)