网易首页 > 网易号 > 正文 申请入驻

【蓝因子教育】如何优化【if else】的面条代码

0
分享至

相信不少同学在维护老项目时,都遇到过在深深的 if else 之间纠缠的业务逻辑。面对这样的一团乱麻,简单粗暴地继续增量修改常常只会让复杂度越来越高,可读性越来越差,有没有固定的套路来梳理它呢?这里分享三种简单通用的重构方式。

什么是面条代码

所谓的【面条代码】,常见于对复杂业务流程的处理中。它一般会满足这么几个特点:

✿ 内容长

✿ 结构乱

✿ 嵌套深

我们知道,主流的编程语言均有函数或方法来组织代码。对于面条代码,不妨认为它就是满足这几个特征的函数吧。根据语言语义的区别,可以将它区分为两种基本类型:

if...if 型

这种类型的代码结构形如:

function demo (a, b, c) { if (f(a, b, c)) { if (g(a, b, c)) { // ... } // ... if (h(a, b, c)) { // ... } } if (j(a, b, c)) { // ... } if (k(a, b, c)) { // ... }}

流程图形如:



它通过从上到下嵌套的 if,让单个函数内的控制流不停增长。不要以为控制流增长时,复杂度只会线性增加

我们知道函数处理的是数据,而每个 if 内一般都会有对数据的处理逻辑。

那么,即便在不存在嵌套的情形下,如果有 3 段这样的 if,那么根据每个 if 是否执行,数据状态就有 2 ^ 3 = 8 种。

如果有 6 段,那么状态就有 2 ^ 6 = 64 种。从而在项目规模扩大时,函数的调试难度会指数级上升!这在数量级上,与《人月神话》的经验一致。

else if...else if 型

这个类型的代码控制流,同样是非常常见的。形如:

function demo (a, b, c) { if (f(a, b, c)) { if (g(a, b, c)) { // ... } // ... else if (h(a, b, c)) { // ... } // ... } else if (j(a, b, c)) { // ... } else if (k(a, b, c)) { // ... }}

流程图形如:



else if最终只会走入其中的某一个分支,因此并不会出现上面组合爆炸的情形。但是,在深度嵌套时,复杂度同样不低。

假设嵌套 3 层,每层存在 3 个else if,那么这时就会出现 3 ^ 3 = 27 个出口。

如果每种出口对应一种处理数据的方式,那么一个函数内封装这么多逻辑,也显然是违背单一职责原则的。并且,上述两种类型可以无缝组合,进一步增加复杂度,降低可读性。

但为什么在这个有了各种先进的框架和类库的时代,还是经常会出现这样的代码呢?

个人的观点是,复用的模块确实能够让我们少写【模板代码】,但业务本身无论再怎么封装,也是需要开发者去编写逻辑的。而即便是简单的 if else,也能让控制流的复杂度指数级上升。

从这个角度上说,如果没有基本的编程素养,不论速成掌握再优秀的框架与类库,同样会把项目写得一团糟

重构策略

上文中,我们已经讨论了面条代码的两种类型,并量化地论证了它们是如何让控制流复杂度指数级激增的。然而,在现代的编程语言中,这种复杂度其实是完全可控的。下面分几种情形,列出改善面条代码的编程技巧。

基本情形

对看起来复杂度增长最快的 if...if 型面条代码,通过基本的函数即可将其拆分。下图中每个绿框代表拆分出的一个新函数:



由于现代编程语言摒弃了goto,因此不论控制流再复杂,函数体内代码的执行顺序也都是从上而下的。

因此,我们完全有能力在不改变控制流逻辑的前提下,将一个单体的大函数,自上而下拆逐步分为多个小函数,而后逐个调用之。这是有经验的同学经常使用的技巧,具体代码实现在此不做赘述了。

需要注意的是,这种做法中所谓的不改变控制流逻辑,意味着改动并不需要更改业务逻辑的执行方式,只是简单地【把代码移出去,然后用函数包一层】而已。有些同学可能会认为这种方式治标不治本,不过是把一大段面条切成了几小段,并没有本质的区别。

然而真的是这样吗?通过这种方式,我们能够把一个有 64 种状态的大函数,拆分为 6 个只返回 2 种不同状态的小函数,以及一个逐个调用它们的 main 函数。这样一来,每个函数复杂度的增长速度,就从指数级降低到了线性级

这样一来,我们就解决了if...if类型面条代码了,那么对于else if...else if类型的呢?

查找表

对于else if...else if类型的面条代码,一种最简单的重构策略是使用所谓的查找表。它通过键值对的形式来封装每个 else if 中的逻辑:

const rules = { x: function (a, b, c) { /* ... */ }, y: function (a, b, c) { /* ... */ }, z: function (a, b, c) { /* ... */ }}function demo (a, b, c) { const action = determineAction(a, b, c) return rules[action](a, b, c)}

每个 else if 中的逻辑都被改写为一个独立的函数,这时我们就能够将流程按照如下所示的方式拆分了:



对于先天支持反射的脚本语言来说,这也算是较为trivial的技巧了。但对于更复杂的else if条件,这种方式会重新把控制流的复杂度集中到处理【该走哪个分支】问题的determineAction中。有没有更好的处理方式呢?

职责链模式

在上文中,查找表是用键值对实现的,对于每个分支都是 else if (x === 'foo') 这样简单判断的情形时,'foo' 就可以作为重构后集合的键了。

但如果每个 else if 分支都包含了复杂的条件判断,且其对执行的先后顺序有所要求,那么我们可以用职责链模式来更好地重构这样的逻辑。

对 else if 而言,注意到每个分支其实是从上到下依次判断,最后仅走入其中一个的。

这就意味着,我们可以通过存储【判定规则】的数组,来实现这种行为。如果规则匹配,那么就执行这条规则对应的分支。我们把这样的数组称为【职责链】,这种模式下的执行流程如下图:



在代码实现上,我们可以通过一个职责链数组来定义与 else if 完全等效的规则:

const rules = [ { match: function (a, b, c) { /* ... */ }, action: function (a, b, c) { /* ... */ } }, { match: function (a, b, c) { /* ... */ }, action: function (a, b, c) { /* ... */ } }, { match: function (a, b, c) { /* ... */ }, action: function (a, b, c) { /* ... */ } } // ...]

rules中的每一项都具有matchaction属性。这时我们可以将原有函数的else if改写对职责链数组的遍历:

function demo (a, b, c) { for (let i = 0; i < rules.length; i++) { if (rules[i].match(a, b, c)) { return rules[i].action(a, b, c) } }}

这时每个职责一旦匹配,原函数就会直接返回,这也完全符合else if的语义。通过这种方式,我们就实现了对单体复杂else if逻辑的拆分了。

总结

面条代码其实容易出现在不加思考的【糙快猛】式开发中。很多简单粗暴地【在这里加个 if,在那里多个 return】的 bug 修复方式,再加上注释的匮乏,很容易让代码可读性越来越差,复杂度越来越高。

在实现常见业务功能时,掌握好编程语言,梳理好需求,用最简单的代码将其实现,就已经是最优解了。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
中国终于出手,12月1日起执行,外媒:你断我芯片,我卡你脖子

中国终于出手,12月1日起执行,外媒:你断我芯片,我卡你脖子

看世界的人
2024-11-19 17:31:35
A股:信号很强烈!都做好准备,明天很有可能继续迎来全线大涨。

A股:信号很强烈!都做好准备,明天很有可能继续迎来全线大涨。

飞花文史
2024-11-19 15:17:53
1992年,陈百强紧急抢救时,留下的镜头,全身插满管子,次年离世

1992年,陈百强紧急抢救时,留下的镜头,全身插满管子,次年离世

李律讲法
2024-11-17 22:35:02
江苏官方:全省共有野猪1.1万头,部分地区呈上升趋势

江苏官方:全省共有野猪1.1万头,部分地区呈上升趋势

澎湃新闻
2024-11-19 16:10:27
3-1!沙特无解,创21年纪录,吊打印尼,取胜=助攻国足3分,出线

3-1!沙特无解,创21年纪录,吊打印尼,取胜=助攻国足3分,出线

体育达人榜
2024-11-19 10:11:14
取款需公安局同意!工厂老板取款100万发工资,被银行当场拒绝

取款需公安局同意!工厂老板取款100万发工资,被银行当场拒绝

数字财经智库
2024-11-18 20:38:07
欧洲不要中国要!中俄能源管道已完工,4亿人将用上俄罗斯天然气

欧洲不要中国要!中俄能源管道已完工,4亿人将用上俄罗斯天然气

现代春秋
2024-11-18 21:47:15
看完特朗普的一日三餐,再看看普京的,网友:没有对比就没有伤害

看完特朗普的一日三餐,再看看普京的,网友:没有对比就没有伤害

猫小狸同学
2024-11-13 15:20:03
青岛28岁女教师被奸杀,惨叫声持续3小时,禽兽真的存在

青岛28岁女教师被奸杀,惨叫声持续3小时,禽兽真的存在

晓艾故事汇
2024-11-14 21:01:42
他已任福建省发改委党组副书记

他已任福建省发改委党组副书记

鲁中晨报
2024-11-19 22:16:03
金鸡奖这一夜,不尊重刘德华礼节的陈凯歌,不再被网友宽容了

金鸡奖这一夜,不尊重刘德华礼节的陈凯歌,不再被网友宽容了

喵喵娱乐团
2024-11-18 17:07:15
鉴宝|累坏牛的那片田,是筱田悠的田!

鉴宝|累坏牛的那片田,是筱田悠的田!

贵圈真乱
2024-11-07 21:12:51
配比亚迪“刀片电池”,续航720km,仅售14.98万起,可惜无人识货

配比亚迪“刀片电池”,续航720km,仅售14.98万起,可惜无人识货

隔壁说车老王
2024-11-19 16:15:22
ESPN:曼城已准备瓜帅离开应急计划,并希望瓜帅1月前做出决定

ESPN:曼城已准备瓜帅离开应急计划,并希望瓜帅1月前做出决定

直播吧
2024-11-20 00:11:41
冯小刚宣布与妻子离婚,财产平分因爱上他人,网友热议不断

冯小刚宣布与妻子离婚,财产平分因爱上他人,网友热议不断

小啾咪侃侃史
2024-11-01 07:45:13
新型卖淫方式正在悄悄蔓延!让人预想不到,但却真实存在

新型卖淫方式正在悄悄蔓延!让人预想不到,但却真实存在

南城无双
2024-11-13 12:37:08
看了范丞丞,再看王安宇,真是没对比就没伤害 ,正午“口碑”保住

看了范丞丞,再看王安宇,真是没对比就没伤害 ,正午“口碑”保住

圈里的甜橙子
2024-11-19 18:20:13
死守库里还能够完成一些得分!后场铁闸是快船今夏的最大收获?

死守库里还能够完成一些得分!后场铁闸是快船今夏的最大收获?

稻谷与小麦
2024-11-19 22:58:31
网友们曝出了关于职校的更多内幕

网友们曝出了关于职校的更多内幕

清晖有墨
2024-11-18 19:06:33
马斯克与女议员开会照火了!一个转身一个趴桌上,热聊画面引热议

马斯克与女议员开会照火了!一个转身一个趴桌上,热聊画面引热议

有趣的火烈鸟
2024-11-18 21:30:19
2024-11-20 00:28:49
原画在线课堂vv
原画在线课堂vv
一家有温度的原画教育机构
1123文章数 1关注度
往期回顾 全部

科技要闻

小鹏Q3营收破百亿,交付4.7万辆,亏18.1亿

头条要闻

国足1比3不敌日本 征战18强赛的最大顽疾被对手再利用

头条要闻

国足1比3不敌日本 征战18强赛的最大顽疾被对手再利用

体育要闻

15胜0负的骑士,真的在模仿勇士

娱乐要闻

金鸡奖颁奖,流量至上,都在座位表上体现了

财经要闻

专家称我国还缺1亿套保障房

汽车要闻

后排够大 智能化提升 全新探岳L是你的菜么?

态度原创

家居
时尚
本地
数码
手机

家居要闻

纯白极简 现代简约

50岁贝嫂:生孩子不会变老,穷和不自律才会

本地新闻

重庆记忆|山城特色“过山车”上天入地穿花海

数码要闻

微软发布 Windows 365 Link Cloud PC 迷你主机,349 美元

手机要闻

OPPO新机潮在路上:超大杯、小屏、S版本、一加小屏均被确认了!

无障碍浏览 进入关怀版