转载

你写的代码就是你的犯罪证据

最近我工作的主要内容,是在和别人结对编程,以对一个大型的遗留系统项目进行重构。

过程中,我发现一个特别有意思的东西,我重构了很多的 if 语句。从这些 if 语句里,大抵是映射出了业务的变化。于是,我便想写一篇文章来记录一下相关的心得。

你写的 if 就是你的犯罪证据

业务的复杂性,导致了架构的复杂性。在这些代码故事里,发生得最多的地方就是 if 语句。所以,你可以从大部分的 if 语句里,看到一些代码上的坏味道。

业务条件复杂

你先写了一个 if 语句里面只有一个条件,没问题。但是后来的人,又加了一个条件,因为业务上确实需要这么做。于是,后来,又不得加了一个if 语句,导致了这个条件变得更加复杂。

if (isCondition && isNotASwitchCase && .... && ....) {
}

所以,完了,这些代码越来越难以维护。

于是,我们应对于这类条件判断,有两种做法: 提取变量提取方法 。当你的判断条件是一个方法的时候,你可以想象一下它的架构是多么的复杂。

难以阅读的字符串判断

开始的人加了一个简单的条件判断,因为当时真的只有这么一种业务场景。你又不能过度设计,成一个 switch-case。但是,后来又多了好多个场景。

if (aCondition == "A")  {
} else if (bCondition == "B")  {

}

更不要提有人在每个 if 里写一个: if (myString.toUpperCase().equals(myOtherString.toUpperCase()))

针对于有限的 if 语句来说,可以转为 switch case(在 IDEA 里只需要 alt + enter 就可以自动完成)。

随着时间的推移我们的条件越来越复杂,我们的 if 语句会越来越复杂。

多层嵌套 if 语句

随着 if 条件进一步扩大化,我们的条件语句就变成了一个多层嵌套的循环语句。每多一层嵌套代码复杂度就 * 2,它的阅读难度就越来越大。于是乎:

if (condition) {
     if (blabla) {
         ...
     }
}

面对这一类 if 条件语句,我们能所做的就是:

  • 提供方法
  • 反转 if 语句

诸如于:

if (!condition) {return}; // 为了演示方便
if(blabla){...}

又或者是诸如于三元表达式,不过我讨厌难以阅读的三元表达式——但是,只是 true 和 false 的情况下,还是相当不错的。

复杂的 if 块内逻辑

当业务进一步复杂化的时候,我们的 if 条件里就充斥着各种各样的逻辑。

if (conditionA) {
    blablaA();
    blaA(blabla).blabla();
}

我们的 if 方法随之变得越来越长,于是尝试去抽成一个方法。但是,当你又遇到一个新的场景时,你又加了一个 if 语句。后来,又又加了一个 if 语句。你才发现说,『咦,不对,这些 If 语句违反了开闭原则』。

于是,你尝试把代码重构成多态以替换 if 语句。

你开心的话,还可以转为 Factory + Strategy。

你开心的话,你也可以将它转为 HashMap 。

但是,在你写下第一个 if 的时候,你并不知道它会变成什么样的。所以,不要提前去把它转为这么复杂的架构。

上帝 if

如果你的业务场景真的超级复杂,那么你可能会看到一个非常长的 if 代码。它可能有几十个条件,有几百行到几千行的规模。

那么,你可以尝试使用 注册表模式 + 注解,通过反射的方式来重构你的 if 语句。

重构

在你进一步修改代码之前,让我们来又双叕明确一下什么叫重构

重构(Refactoring)就是通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。

换句话来说,重构只是在改善现有的代码,使其更易于阅读,换句话来说就是:Clean Code。而当我们说整洁的代码(Clean Code),说的是 易于理解、修改和测试 的。易于理解和修改意味着:

  • 易于理解整个系统的架构
  • 易于理解整个应用程序的执行流程
  • 易于理解不同对象如何相互协作
  • 易于理解理解每种方法的作用
  • 易于理解每个表达式和变量的目的是什么

而易于理解的前提便是能 让每个团队成员快速理解 。(PS:当然了,若是有些人智商不够或者经验不够,他/她需要去需要去增强这方面的能力)。这便意味着,出于这样的目的,你不能编写过于抽象、简练的逻辑。而你又不能写得过于繁琐,充满大量地无用字符。

若是想使代码易于测试,则要先 使代码可测试 。而在这没有测试之前,我们是难以对代码进行大规模重构。所以,我们就陷入了一个死循环,没有测试,测试不了,没法重构。

WHY

等等,那我们为什么要进行重构呢?为了 ¥¥¥¥¥¥¥$$$$$$$$$ => 快速发布软件。

当软件是一个产品而不是一个项目的时候,我们就需要不断发布新功能,以满足客户的要求。而为了快速发布应用,我们需要让每次的改动最小,测试最少,才能实现快速发布。基于这样一个目标,我们会发现我们的诸多实践都是以此为出发点的。比如说,我们采用插件化、微服务化、组件化的方式,都是为了将软件的改动变小,这样一来,就减少了相应部分的测试工作,从某种意义上来说,就加快了软件发布的流程,从而更好的实现业务价值。因此,我们的第一步就是使 二进制改动最小 。而要做到二进制改动最小,那么我们就要做到 高内聚、低耦合

因此,不论是在编程还是在设计架构的时候,我们都要尽量满足 SOLID 五项原则中的:

  • 单一职责原则:它规定一个类应该只有一个发生变化的原因。
  • 开闭原则: 软件中的对象应该对于扩展是开放的,但是对于修改是封闭的

回到问题上

既然,我们都已经知道了,如何去重构,如何用设计模式来解决问题。那么,我们会让我们的代码变得更好吗?不会,因为在流水线式的生产里,每个人都能找到合理的理由。

我们日常开发的模式是:红-绿-重构。而因为时间的原因,我们少去了重构这一步。

上吊绳驱动开发

在上吊绳(deadline)的驱动下,我写了一这篇文章。尽管预先写好了文章的大纲,但是有很多字是打错的。

而对于真实的业务开发来说,要事先设计好相关功能的架构,意味着你得有充足的时间。这样一来说,你在大的方面上才不会犯错。可是呢,你真的有那么多的时间可以设计吗?你今天加的班,还好吗?

代码所有权

改动了你的代码,我就要负责。所以,我不去修改别人的代码。

惧怕修改

没有测试,难以理解代码背后的业务原因。外加之组织文化,导致的沟通障碍;又或者是大家都很忙,没人愿意解释/回顾一下这一块的代码。

能力不够

对,大部分的问题本质都是人的问题。

因为你只需要按下 IDEA 的快捷键,就能完成上面的大部分重构工作。当然了,需要有技巧的按,而不是像 Monkey 一样弹钢琴。

结论

开心就好。

原文  http://www.phodal.com/blog/code-history-in-crime/
正文到此结束
Loading...