如何重构“箭头型”代码

news/2025/4/22 1:13:16

所谓箭头型代码,基本上来说就是下面这个图片所示的情况。

clipboard.png

那么,这样“箭头型”的代码有什么问题呢?看上去也挺好看的,有对称美。但是……

关于箭头型代码的问题有如下几个:

1)我的显示器不够宽,箭头型代码缩进太狠了,需要我来回拉水平滚动条,这让我在读代码的时候,相当的不舒服。

2)除了宽度外还有长度,有的代码的if-else里的if-else里的if-else的代码太多,读到中间你都不知道中间的代码是经过了什么样的层层检查才来到这里的。

总而言之,“箭头型代码”如果嵌套太多,代码太长的话,会相当容易让维护代码的人(包括自己)迷失在代码中,因为看到最内层的代码时,你已经不知道前面的那一层一层的条件判断是什么样的,代码是怎么运行到这里的,所以,箭头型代码是非常难以维护和Debug的。

代码量如果再大一点,嵌套再多一点,你很容易会在条件中迷失掉(下面这个示例只是那个“大箭头”下的一个小箭头)

FOREACH(Ptr<WfExpression>, argument, node->arguments) {

int index = manager->expressionResolvings.Keys().IndexOf(argument.Obj());
if (index != -1) {auto type = manager->expressionResolvings.Values()[index].type;if (! types.Contains(type.Obj())) {types.Add(type.Obj());if (auto group = type->GetTypeDescriptor()->GetMethodGroupByName(L"CastResult", true)) {int count = group->GetMethodCount();for (int i = 0; i < count; i++) { auto method = group->GetMethod(i);if (method->IsStatic()) {if (method->GetParameterCount() == 1 &&method->GetParameter(0)->GetType()->GetTypeDescriptor() == description::GetTypeDescriptor<DescriptableObject>() &&method->GetReturn()->GetTypeDescriptor() != description::GetTypeDescriptor<void>() ) {symbol->typeInfo = CopyTypeInfo(method->GetReturn());break;}}}}}
}

}

上面这段代码,可以把条件反过来写,然后就可以把箭头型的代码解掉了,重构的代码如下所示:
FOREACH(Ptr<WfExpression>, argument, node->arguments) {

int index = manager->expressionResolvings.Keys().IndexOf(argument.Obj());
if (index == -1)  continue;auto type = manager->expressionResolvings.Values()[index].type;
if ( types.Contains(type.Obj()))  continue;types.Add(type.Obj());auto group = type->GetTypeDescriptor()->GetMethodGroupByName(L"CastResult", true);
if  ( ! group ) continue;int count = group->GetMethodCount();
for (int i = 0; i < count; i++) { auto method = group->GetMethod(i);if (! method->IsStatic()) continue;if ( method->GetParameterCount() == 1 &&method->GetParameter(0)->GetType()->GetTypeDescriptor() == description::GetTypeDescriptor<DescriptableObject>() &&method->GetReturn()->GetTypeDescriptor() != description::GetTypeDescriptor<void>() ) {symbol->typeInfo = CopyTypeInfo(method->GetReturn());break;}
}

}

这里的思路其实就是,让出错的代码先返回,前面把所有的错误判断全判断掉,然后就剩下的就是正常的代码了。

对于 if-else 语句来说,一般来说,就是检查两件事:错误 和 状态。

检查错误
对于检查错误来说,使用 Guard Clauses 会是一种标准解,但我们还需要注意下面几件事:

1)当然,出现错误的时候,还会出现需要释放资源的情况。你可以使用 goto fail; 这样的方式,但是最优雅的方式应该是C++面向对象式的 RAII 方式。

2)以错误码返回是一种比较简单的方式,这种方式有很一些问题,比如,如果错误码太多,判断出错的代码会非常复杂,另外,正常的代码和错误的代码会混在一起,影响可读性。所以,在更为高组的语言中,使用 try-catch 异常捕捉的方式,会让代码更为易读一些。

检查状态
对于检查状态来说,实际中一定有更为复杂的情况,比如下面几种情况:

1)像TCP协议中的两端的状态变化。

2)像shell各个命令的命令选项的各种组合。

3)像游戏中的状态变化(一棵非常复杂的状态树)。

4)像语法分析那样的状态变化。

对于这些复杂的状态变化,其本上来说,你需要先定义一个状态机,或是一个子状态的组合状态的查询表,或是一个状态查询分析树。

写代码时,代码的运行中的控制状态或业务状态是会让你的代码流程变得混乱的一个重要原因,重构“箭头型”代码的一个很重要的工作就是重新梳理和描述这些状态的变迁关系。

总结
好了,下面总结一下,把“箭头型”代码重构掉的几个手段如下:

1)使用 Guard Clauses 。 尽可能的让出错的先返回, 这样后面就会得到干净的代码。

2)把条件中的语句块抽取成函数。 有人说:“如果代码不共享,就不要抽取成函数!”,持有这个观点的人太死读书了。函数是代码的封装或是抽象,并不一定用来作代码共享使用,函数用于屏蔽细节,让其它代码耦合于接口而不是细节实现,这会让我们的代码更为简单,简单的东西都能让人易读也易维护,写出让人易读易维护的代码才是重构代码的初衷!

3)对于出错处理,使用try-catch异常处理和RAII机制。返回码的出错处理有很多问题,比如:A) 返回码可以被忽略,B) 出错处理的代码和正常处理的代码混在一起,C) 造成函数接口污染,比如像atoi()这种错误码和返回值共用的糟糕的函数。

4)对于多个状态的判断和组合,如果复杂了,可以使用“组合状态表”,或是状态机加Observer的状态订阅的设计模式。这样的代码即解了耦,也干净简单,同样有很强的扩展性。

5) 重构“箭头型”代码其实是在帮你重新梳理所有的代码和逻辑,这个过程非常值得为之付出。重新整思路去想尽一切办法简化代码的过程本身就可以让人成长。


https://dhexx.cn/news/show-4211846.html

相关文章

[Git] 拉开发分支的代码报错

Git拉开发分支的代码报错&#xff1a; fatal: The remote end hung up unexpectedly fatal: early EOF fatal: index-pack failed 解决办法&#xff1a; git config --global core.compression -1 转载于:https://www.cnblogs.com/MasterMonkInTemple/p/10754596.html

js滚动效果

http://images.yoka.com/pic/div/yokajs/jshelp/run.html

大咖 | 美国工程院院士Glynn:基于数据的决策,仿真与库存管理(附PPT下载)...

今天我主要是基于相关的研究问题&#xff0c;给大家分享一个观点——基于数据的决策。为解决各类问题&#xff0c;给大家展示一个由机器学习、人工智能和其他工具共同搭建的平台。 我们讨论利用仿真来进行决策&#xff0c;尤其是在库存管理的背景下。机器学习和人工智能正对世界…

3、MongoDB基本管理命令

目录(?)[] MongoDB是一个NoSQL数据库系统&#xff1a;一个数据库可以包含多个集合&#xff08;Collection&#xff09;&#xff0c;每个集合对应于关系数据库中的表&#xff1b;而每个集合中可以存储一组由列标识的记录&#xff0c;列是可以自由定义的&#xff0c;非常灵活&am…

第一次结对作业代码复审

一、代码功能及地址 功能&#xff1a;足球比赛两队控球时间比例显示牌 地址&#xff1a;https://github.com/1297340141/LED-/blob/master/1.c 二、复审结果 功能模块名称 足球比赛两队控球时间比例显示牌审查人 刘家兴 审查日期 219.04.23代码名称 1.c 代码作者 张纯鹤 文…

python的冒泡法和二分法的总结

一&#xff1a;二分法 首先介绍二分法 二分法查找&#xff0c;每次能够排除掉一半的数据&#xff0c;查找的效率非常高&#xff0c;但是局限性比较大&#xff0c;必须是有序的序列才可以使用二分法查找 要求&#xff1a;查找的序列必须是有序序列 ----------------------------…

两级关键词,复选框级联选择。借助hiddenField

1. 级联复选框 关键词分两级&#xff0c;子级选中时父级自动选中&#xff1b;父级取消选中时子级自动取消选中&#xff1b; 由于checkboxlist的SelectedIndexChanged事件无法确定当前改变选择的复选框。因此采用HiddenField辅助完成。将改变选择之前选中的复选框value值以逗…

JConsole观察分析Java程序

一、JConsole是什么 从Java 5开始 引入了 JConsole。JConsole 是一个内置 Java 性能分析器&#xff0c;可以从命令行或在 GUI shell 中运行。您可以轻松地使用 JConsole&#xff08;或者&#xff0c;它更高端的 “近亲” VisualVM &#xff09;来监控 Java 应用程序性能和跟踪 …

php 设计模式(转)

PhpDesignPatterns 【PHP 中的设计模式】 一、 Introduction【介绍】 设计模式&#xff1a;提供了一种广泛的可重用的方式来解决我们日常编程中常常遇见的问题。设计模式并不一定就是一个类库或者第三方框架&#xff0c;它们更多的表现为一种思想并且广泛地应用在系统中。它们也…

JMeter-Java压力测试工具-02

这节介绍几个Listener下面的组件 Aggregate Report-汇总报告 从左到右依次&#xff1a;具有相同标签的样本数、一组结果的平均时间、一组结果的中间时间&#xff08;50%的样本不超过这个时间&#xff09;、90&#xff05;的样品不超过这个时间、95&#xff05;的样品不超过这个…