需要箭头反模式的重构思路
我继承了一个怪物。
当.NET 1.1应用程序处理符合医疗保健索赔付款(ANSI 835)标准的文本文件时,它是一种伪装,但这真是个庞然大物。正在处理的信息与医疗保健索赔,EOB和报销有关。这些文件由在前几个位置具有标识符的记录以及根据该记录类型的规范格式化的数据字段组成。一些记录ID是控制段ID,它分隔与特定交易类型相关的记录组。
为了处理文件,我的小怪物读取了第一条记录,确定了将要进行的交易的类型,然后根据当前正在处理的交易类型开始处理其他记录。为此,它使用嵌套的if。由于存在多种记录类型,因此需要做出许多决定。每个决策涉及一些处理以及需要根据以前的决策制定的2-3个其他决策。这意味着嵌套的if有很多嵌套。那就是我的问题所在。
如果嵌套此行,则为715行长。恩,那就对了。七百零十五条线。我不是代码分析专家,所以我下载了一些免费软件分析工具,并得出了McCabe Cyclomatic Complexity评分为49. 他们告诉我,这个数字很高。亚特兰大地区的花粉数量居高不下,那里的花粉数量最高为100,新闻称"今天的花粉数量为1,523"。这是我有幸看到的"反箭"最好的例子之一。凹痕最高处有15个标签。
我的问题是,我们建议使用哪种方法来重构或者重组这种东西?
我花了一些时间来寻找想法,但是没有什么能给我立足之地。例如,用保护条件代替等级是一种方法。我只有其中之一。一窝下来,十四个要走。
也许有一种设计模式可能会有所帮助。指挥链会是解决这一问题的一种方法吗?请记住,它必须保留在.NET 1.1中。
感谢我们提出的所有想法。
解决方案
状态机似乎是启动逻辑的地方,如果可以摆动,请使用WF(听起来像是不行)。
我们仍然可以在没有WF的情况下实现一个,而我们只需要自己做即可。但是,从一开始就将其视为状态机可能会为我们提供更好的实现,然后创建一个过程怪兽来检查每个动作的内部状态。
弄清楚你的状态,是什么导致转变。应该分解出处理记录的实际代码,并在状态执行时调用(如果该特定状态需要它)。
因此State1的execute调用"读取记录",然后根据该记录转换到另一个状态。
下一个状态可以读取多个记录并调用记录处理指令,然后转换回State1.
从描述来看,状态机可能是处理它的最佳方法。有一个enum变量来存储当前状态,并通过开关或者if语句实现对记录的循环处理,以根据当前状态和输入数据选择要执行的操作。如果工作量太大,我们也可以使用功能指针根据状态轻松地将工作分派到单独的功能。
在Coding Horror上有一篇很好的博客文章。我只遇到过一次这种反模式,而我几乎只是遵循了他的步骤。
在这些情况下,我要做的一件事是使用"组合方法"模式。有关此主题,请参见Jeremy Miller的博客文章。基本思想是使用IDE中的重构工具提取有意义的小方法。完成此操作后,我们便可以进一步重构并提取有意义的类。
有时我将状态模式与堆栈结合在一起。
它适用于层次结构;父元素知道将什么状态压入堆栈以处理子元素,但是子元素不必了解其父元素。换句话说,孩子不知道下一个状态是什么,它只是表示它已"完成"并从堆栈中弹出。通过使依赖关系保持单向,这有助于使状态彼此分离。
它非常适用于使用SAX解析器处理XML(内容处理程序只是在元素进入和退出时,通过推送和弹出状态来更改其行为)。 EDI也应采用这种方法。
我本周刚刚工作了一些遗留代码,它们与我们所描述的相似(尽管不那么可怕)。
没有一件事会让我们摆脱困境。状态机可能是代码采用的最终形式,但这并不能到达那里,也不要在弄乱已有的混乱之前决定采用这种解决方案。
我要采取的第一步是为现有代码编写测试。该测试不是为了表明代码正确无误,而是要确保在开始重构时没有破坏某些内容。获取一大堆数据进行处理,将其提供给怪物,然后获得输出。那是石蕊测试。如果我们可以使用代码覆盖率工具来执行此操作,则将看到我们测试的内容未覆盖。如果可以的话,构造一些也会执行此代码的人工记录,然后重复。一旦感觉完成了此任务可以做的一切,输出数据便成为测试的预期结果。
重构不应更改代码的行为。记住这一点。这就是为什么我们拥有已知的输入和已知的输出数据集以验证我们不会破坏事情的原因。这是安全网。
现在重构!
我做了几件对我有用的事情:
反转if
语句
我遇到的一个大问题是,当我找不到对应的else
语句时,只是阅读代码,我注意到很多块看起来像这样
if (someCondition) { 100+ lines of code { ... } } else { simple statement here }
通过反转" if",我可以看到简单的情况,然后移到更复杂的块,知道另一个已经完成了什么。变化不大,但有助于我理解。
提取方法
我经常使用这个功能,拿一些复杂的多行代码块,用它自己的方法把它塞进去并推到一边。这使我可以更轻松地查看代码重复的位置。
现在,希望我们没有破坏代码(测试是否还可以通过?),并且我们具有更具可读性和更好理解的过程代码。看起来已经改善了!但是我们之前编写的测试还不够好……它只是告诉我们复制了原始代码的功能(错误和所有功能),仅此而已,因为我敢肯定,会找到我们无法弄清楚怎么打或者根本打不出来的代码块(我在工作中已经看到过)。
现在,所有大牌模式都会发挥作用的重大变化是当我们开始研究如何以适当的OO方式重构它时。剥皮这只猫的方法不只一种,而且会涉及多种模式。不知道有关我们正在解析的这些文件的格式的详细信息,我只能提出一些有用的建议,这些建议可能是最佳解决方案,也可能不是最佳解决方案。
重构模式是一本很棒的书,可以解释在这些情况下有用的模式。
我们正在尝试吃一头大象,没有其他方法可以做,只能一次咬一口。祝你好运。
我将从禁止使用提取方法开始。如果当前的Visual Studio IDE中没有它,则可以获取第三方插件,也可以在较新的VS中加载项目。 (它将尝试升级项目,但是我们将仔细忽略这些更改,而不是将其检入。)
我们说代码缩进了15个级别。开始约1/2次,然后提取方法。如果我们能拿出一个好名字,请使用它,但如果不能,请提取。再分成两半。我们不打算在这里使用理想的结构;我们正在尝试将代码分解为适合我们大脑的部分。我的大脑不是很大,所以我会不断挣扎,直到不再受伤为止。
进行时,请寻找似乎与其他方法不同的任何新的长方法。使它们进入新的班级。只需使用一个目前只有一个方法的简单类。哎呀,使方法静态化是可以的。不是因为我们认为他们是好班,而是因为我们对某些组织如此渴望。
经常检查,以便我们可以检查工作,稍后了解历史记录,准备进行一些"实际工作"而无需合并,并节省了队友进行合并的麻烦。
最终,我们需要返回并确保方法名称正确,所创建的方法集有意义,清理新类等。
如果我们拥有高度可靠的"提取方法"工具,则无需进行良好的自动化测试就可以摆脱困境。 (例如,我会信任VS。)否则,请确保我们没有破坏任何事情,否则最终结果会比开始时还糟:程序根本不起作用。
配对伙伴在这里会很有帮助。