资讯专栏INFORMATION COLUMN

如何重构“箭头型”代码

cloud / 1222人阅读

摘要:写代码时,代码的运行中的控制状态或业务状态是会让你的代码流程变得混乱的一个重要原因,重构箭头型代码的一个很重要的工作就是重新梳理和描述这些状态的变迁关系。重构箭头型代码其实是在帮你重新梳理所有的代码和逻辑,这个过程非常值得为之付出。

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

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

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

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

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

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

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

FOREACH(Ptr, 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() &&
                        method->GetReturn()->GetTypeDescriptor() != description::GetTypeDescriptor() ) {
                        symbol->typeInfo = CopyTypeInfo(method->GetReturn());
                        break;
                    }
                }
            }
        }
    }
}

}

上面这段代码,可以把条件反过来写,然后就可以把箭头型的代码解掉了,重构的代码如下所示:
FOREACH(Ptr, 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() &&
           method->GetReturn()->GetTypeDescriptor() != description::GetTypeDescriptor() ) {
        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://www.ucloud.cn/yun/76719.html

相关文章

  • 重构:一项常常被忽略的基本功

    摘要:无论如何,单元测试一直是一中非常重要却常常被忽视的技能。在实践中,重构的要求是很高的它需要有足够详尽的单元测试,需要有持续集成的环境,需要随时随地在小步伐地永远让代码处于可工作状态下去进行改善。 showImg(https://segmentfault.com/img/bVbttWF?w=1000&h=528); 五月初的时候朋友和我说《重构》出第 2 版了,我才兴冲冲地下单,花了一个...

    idealcn 评论0 收藏0
  • 如何用微服务重构应用程序

    摘要:以下两个要点将会对任何微服务重构策略产生重大影响。批量替换通过批发更换,您可以一次性重构整个应用程序,直接从单体式转移到一组微服务器。如果您通过使用破解您的微服务器,那么每个域将围绕一个用例,或者更常见的,一组相互关联的用例。 在决定使用微服务之后,为了将微服务付诸实践,也许你已经开始重构你的应用程序或把重构工作列入了待办事项清单。 无论是哪种情况,如果这是你第一次重构应用程序,那么您...

    KevinYan 评论0 收藏0
  • 如何重构

    摘要:重构定义重构是对软件内部结构的调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。重构节奏小步前进,频繁测试。 1.重构定义: 重构是对软件内部结构的调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。 2.重构节奏: 小步前进,频繁测试。 3.重构意义: 1.改进软件设计 2.使软件更容易被理解 3.帮助找到bug 4.提高编程速度 恶...

    iamyoung001 评论0 收藏0
  • 如何编写高质量代码

    摘要:如果你不能以高标准来要求自己,即使你看再多的如何写出高质量代码,懂再多的代码规范,也是没有用,最终还是会写出低质量代码。建议先从代码规范开始,熟悉代码规范,遵循规范写代码,直到成为习惯,然后再学习其它方法,最终写出高质量代码。 更多文章 什么是高质量代码? 高质量代码具有以下几个特点: 可读性高 结构清晰 可扩展(方便维护) 代码风格统一 低复杂性 简练 编写高质量代码主要遵循以下...

    only_do 评论0 收藏0
  • 重构-改善既有代码的设计(二) --重构原则

    摘要:改进代码设计的一个重要原则就是消除重复代码使软件更容易被理解优秀的代码能够让接收你代码的付出更少的学习成本。重构更容易找到重构能加深对代码的理解。可以重构的情况添加功能时可以重构。说明你没有发现代码的错误。需要重构复审代码时可以重构。 为何重构 重构不是银弹,但是帮助你达到以下几个目的 改进软件设计 不良的程序需要更多的代码。而代码越多,正确的修改就越困难。改进代码设计的一个重要原则就...

    myshell 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<