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