函数的意义

问题背景:

A:这个函数的圈复杂度降不下去了,只能这样了
B:现在的圈复杂度是多少?
A:17
B:这么高,怎么会降不了?
A:要拆函数也可以拆,只是不想拆出那么多没意义的函数。

问题描述:

怎样的函数才算是有意义的函数?
比如这样的函数有没有意义?
(1)只被调用一次的函数
(2)只有一句实现内容的函数
(3)函数很多的参数
比如一个函数,有很多局部变量,如果要拆成多个函数,那么每个函数都要传递这些局部变量,会造成函数的参数很多。

在直接回答这个问题之前,先分析一下把一个大函数拆分多个小函数的优点与缺点。

拆分函数的优点:

(1)抽象级上的封装,一个函数里的所有语句都是同一个抽象级的
(2)相关概念的封装,一个函数里的所有语句都是为了做同一件事,达到同一个目的
(3)不相关概念的区分,不同的事分别放在不同的函数里,改名相互干扰
(4)让代码更易阅读
总结:读得方便

拆分函数的缺点:

(1)加一个函数麻烦,又只被调用一次,写得不值
(2)写得时候需要跳跃思考
第一遍写代码时,常常是想到哪就写到哪,不管是业务逻辑还是算法细节,都是按照执行顺序一步一步地往下写
但拆分函数时,不是按照执行顺序去思考代码的,而是按照抽象级去思考代码的。
(3)函数调用开销
这通常是最次要的原因,几乎可以忽略
(4)代码相关耦合性太高,难以拆分
就像《问题描述》中的(3)
(5)拆分函数又要考虑起名字的问题
总结:写得不方便

回到前面的问题:

一个函数到底有没有意义,说到底就是写得方便与读得方便的权衡问题。
怎样权衡呢,这里以数组与链表的区别为例。
数组和链表都是线性结构的数据组织方式,究竟哪种方式更好呢?
应该说各有各的优缺点。数组方式相对链表来说读效率高O(1),写效率低O(n)。链表方式相对数组方式来说,读效率低O(n/2),写效率高O(n/2)。
在实际应用中,以读为主的实现通常用数组,以写为组的实现通过用链表。
在拆分函数这个问题上,我们要考虑我们在开发过程中,是以写为主?还是以读为主?
Uncle Bob刚好在这方便做过一些统计,他发现,即使是在开发过程中,大部分时间也是在读代码的。
那么更何况后期维护,即使是修改代码,也是要以读懂代码为基础的。
很显然,我们的开发是以读为主的,因此,便于阅读的代码就是有意义的代码。适当地牺牲“写的方便性”来实现“读的方便性”是有必要的。

就事论事:

(1)只被调用一次的函数是否有意义?
答:如果它有助于阅读,就是有意义的,比如init();
(2)只有一句实现内容的函数是否有意义?
答:如果它有助于阅读,就是有意义的,比如将多个判断条件合并 (3)有很参数的函数是否有意义?
答:如果“读得方便”的代价是“写得很不方便”,这并不意味着它不应该被拆分。而是说明这个函数本身设计不合理。它不只要被拆分,更要被重构。

怎样让函数变成有意义:

(1)在第一次写代码时,常常是跟着灵感走,所以会写得杂乱无章,如果用TDD会好一点
(2)即使在写的时候没有TDD,最终也是要把UT补全的。
(3)有了UT,就可以放心大胆地重构,这是一个很大的话题

哪些代码可以拆分?

(1)函数缩进达到3级。(假设一开始是1级)
(2)函数长度超过12行
(3)几乎相似的代码出现了3遍
(4)判断条件3个以上
(5)函数参数3个以上
(6)不得不使用注释
(7)代码逻辑明显分成几块