需要重构箭头反模式的想法

发布于 2024-07-06 19:14:21 字数 802 浏览 8 评论 0原文

我继承了一个怪物。

它伪装成 .NET 1.1 应用程序处理符合医疗保健索赔支付 (ANSI 835) 标准的文本文件,但它是一个怪物。 正在处理的信息涉及医疗保健索赔、EOB 和报销。 这些文件由在前几个位置具有标识符的记录和根据该类型记录的规范格式化的数据字段组成。 一些记录 ID 是控制段 ID,它们分隔与特定类型事务相关的记录组。

为了处理文件,我的小怪物读取第一条记录,确定即将发生的事务类型,然后根据当前正在处理的事务类型开始处理其他记录。 为此,它使用嵌套的 if。 由于记录类型有多种,因此需要做出许多决定。 每个决策都涉及一些处理以及需要根据先前决策做出的 2-3 个其他决策。 这意味着嵌套的 if 有很多嵌套。 这就是我的问题所在。

这个嵌套 if 有 715 行长。 恩,那就对了。 七百零五行。 我不是代码分析专家,所以我下载了几个免费软件分析工具,并得出了 49 的 McCabe 圈复杂度评级。他们告诉我这是一个相当高的数字。 亚特兰大地区的花粉计数很高,100 是高的标准,新闻上说“今天的花粉计数是 1,523”。 这是我有幸看到的箭头反模式的最好例子之一。 最高时,压痕深度为 15 个标签。

我的问题是,您建议采用什么方法来重构或重组这样的事情?

我花了一些时间寻找想法,但没有任何东西给我一个良好的立足点。 例如,用保护条件代替级别是一种方法。 我只有其中之一。 一窝已下,还有十四窝。

也许有一种设计模式可能会有所帮助。 指挥链会是解决这个问题的一种方法吗? 请记住,它必须保留在 .NET 1.1 中。

感谢您的任何和所有想法。

I have inherited a monster.

It is masquerading as a .NET 1.1 application processes text files that conform to Healthcare Claim Payment (ANSI 835) standards, but it's a monster. The information being processed relates to healthcare claims, EOBs, and reimbursements. These files consist of records that have an identifier in the first few positions and data fields formatted according to the specs for that type of record. Some record ids are Control Segment ids, which delimit groups of records relating to a particular type of transaction.

To process a file, my little monster reads the first record, determines the kind of transaction that is about to take place, then begins to process other records based on what kind of transaction it is currently processing. To do this, it uses a nested if. Since there are a number of record types, there are a number decisions that need to be made. Each decision involves some processing and 2-3 other decisions that need to be made based on previous decisions. That means the nested if has a lot of nests. That's where my problem lies.

This one nested if is 715 lines long. Yes, that's right. Seven-Hundred-And-Fif-Teen Lines. I'm no code analysis expert, so I downloaded a couple of freeware analysis tools and came up with a McCabe Cyclomatic Complexity rating of 49. They tell me that's a pretty high number. High as in pollen count in the Atlanta area where 100 is the standard for high and the news says "Today's pollen count is 1,523". This is one of the finest examples of the Arrow Anti-Pattern I have ever been priveleged to see. At its highest, the indentation goes 15 tabs deep.

My question is, what methods would you suggest to refactor or restructure such a thing?

I have spent some time searching for ideas, but nothing has given me a good foothold. For example, substituting a guard condition for a level is one method. I have only one of those. One nest down, fourteen to go.

Perhaps there is a design pattern that could be helpful. Would Chain of Command be a way to approach this? Keep in mind that it must stay in .NET 1.1.

Thanks for any and all ideas.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

第几種人 2024-07-13 19:14:21

本周我刚刚在工作中遇到了一些遗留代码,这些代码与您所描述的类似(尽管没有那么可怕)。

没有一件事能让你摆脱困境。 状态机可能是您的代码采用的最终形式,但事实并非如此。 > 将帮助您到达那里,您也不应该在解决已有的混乱之前决定这样的解决方案。

我要采取的第一步是为现有代码编写测试。 此测试并不是要显示代码是否正确,而是要确保在开始重构时没有破坏某些内容。 获取大量数据进行处理,将其提供给怪物,并获得输出。 这是你的试金石。 如果您可以使用代码覆盖率工具来做到这一点,您将看到您的测试未覆盖哪些内容。 如果可以的话,构建一些也将执行此代码的人工记录,然后重复。 一旦您感觉自己已经完成了该任务的任务,输出数据就会成为您的预期测试结果。

重构不应该改变代码的行为。 请记住这一点。 这就是为什么您拥有已知的输入和已知的输出数据集来验证您不会破坏事物。 这是你的安全网。

现在重构!

我做了一些我发现有用的事情:

反转if语句

我遇到的一个大问题是当我找不到相应的else<时只是阅读代码/code> 语句,我注意到很多块看起来像这样

if (someCondition)
{
  100+ lines of code
  {
    ...
  }
}
else
{
  simple statement here
}

通过反转 if 我可以看到简单的情况,然后转到更复杂的块,知道另一个块已经做了什么。 虽然变化不大,但帮助我理解了。

提取方法

我经常使用这个方法。拿一些复杂的多行块,摸索它并将它推到一边在它自己的方法中。 这使我能够更轻松地查看哪里存在代码重复。

现在,希望您没有破坏您的代码(测试仍然通过,对吗?),并且您拥有更具可读性和更好理解的过程代码。 看看已经进步了! 但是您之前编写的测试还不够好......它只告诉您您复制了原始代码的功能(错误和所有),而这只是您覆盖的行,因为我确信您会发现你无法弄清楚如何击中或根本无法击中的代码块(我在工作中见过这两种情况)。

现在,所有大牌模式发挥作用的重大变化是当您开始考虑如何以适当的 OO 方式重构它时。 给这只猫剥皮的方法不止一种,而且会涉及多种模式。 由于不知道您正在解析的这些文件的格式的详细信息,我只能提出一些有用的建议,这些建议可能是也可能不是最好的解决方案。

重构模式是一本很棒的书,可以帮助解释对这些方面有帮助的模式情况。

你想吃掉一头大象,除了一次咬一口之外没有其他办法。 祝你好运。

I just had some legacy code at work this week that was similar (although not as dire) as what you are describing.

There is no one thing that will get you out of this. The state machine might be the final form your code takes, but thats not going to help you get there, nor should you decide on such a solution before untangling the mess you already have.

First step I would take is to write a test for the existing code. This test isn't to show that the code is correct but to make sure you have not broken something when you start refactoring. Get a big wad of data to process, feed it to the monster, and get the output. That's your litmus test. if you can do this with a code coverage tool you will see what you test does not cover. If you can, construct some artificial records that will also exercise this code, and repeat. Once you feel you have done what you can with this task, the output data becomes your expected result for your test.

Refactoring should not change the behavior of the code. Remember that. This is why you have known input and known output data sets to validate you are not going to break things. This is your safety net.

Now Refactor!

A couple things I did that i found useful:

Invert if statements

A huge problem I had was just reading the code when I couldn't find the corresponding else statement, I noticed that a lot of the blocks looked like this

if (someCondition)
{
  100+ lines of code
  {
    ...
  }
}
else
{
  simple statement here
}

By inverting the if I could see the simple case and then move onto the more complex block knowing what the other one already did. not a huge change, but helped me in understanding.

Extract Method

I used this a lot.Take some complex multi line block, grok it and shove it aside in it's own method. this allowed me to more easily see where there was code duplication.

Now, hopefully, you haven't broken your code (test still passes right?), and you have more readable and better understood procedural code. Look it's already improved! But that test you wrote earlier isn't really good enough... it only tells you that you a duplicating the functionality (bugs and all) of the original code, and thats only the line you had coverage on as I'm sure you would find blocks of code that you can't figure out how to hit or just cannot ever hit (I've seen both in my work).

Now the big changes where all the big name patterns come into play is when you start looking at how you can refactor this in a proper OO fashion. There is more than one way to skin this cat, and it will involve multiple patterns. Not knowing details about the format of these files you're parsing I can only toss around some helpful suggestions that may or may not be the best solutions.

Refactoring to Patterns is a great book to assist in explainging patterns that are helpful in these situations.

You're trying to eat an elephant, and there's no other way to do it but one bite at a time. Good luck.

乖不如嘢 2024-07-13 19:14:21

状态机似乎是合乎逻辑的起点,如果你可以摆动它,就使用 WF (听起来你不能)。

您仍然可以在没有 WF 的情况下实现一个,只需您自己完成即可。 然而,从一开始就将其视为状态机可能会为您提供更好的实现,然后创建一个检查每个操作的内部状态的程序怪物。

绘制出您的状态以及导致转变的原因。 处理记录的实际代码应该被分解出来,并在状态执行时调用(如果特定状态需要它)。

因此 State1 的执行调用您的“读取记录”,然后根据该记录转换到另一个状态。

下一个状态可能会读取多个记录并调用记录处理指令,然后转换回State1。

A state machine seems like the logical place to start, and using WF if you can swing it (sounds like you can't).

You can still implement one without WF, you just have to do it yourself. However, thinking of it like a state machine from the start will probably give you a better implementation then creating a procedural monster that checks internal state on every action.

Diagram out your states, what causes a transition. The actual code to process a record should be factored out, and called when the state executes (if that particular state requires it).

So State1's execute calls your "read a record", then based on that record transitions to another state.

The next state may read multiple records and call record processing instructions, then transition back to State1.

国产ˉ祖宗 2024-07-13 19:14:21

在这些情况下我做的一件事是使用“组合方法”模式。 请参阅 Jeremy Miller 的博客文章 关于这个主题。 基本思想是使用 IDE 中的重构工具来提取有意义的小方法。 完成此操作后,您也许能够进一步重构并提取有意义的类。

One thing I do in these cases is to use the 'Composed Method' pattern. See Jeremy Miller's Blog Post on this subject. The basic idea is to use the refactoring tools in your IDE to extract small meaningful methods. Once you've done that, you may be able to further refactor and extract meaningful classes.

倾城泪 2024-07-13 19:14:21

我会从不受限制地使用提取方法开始。 如果您当前的 Visual Studio IDE 中没有它,您可以获取第 3 方插件,或者在较新的 VS 中加载您的项目。 (它会尝试升级您的项目,但您会小心地忽略这些更改,而不是将它们签入。)

您说您的代码缩进了 15 级。 从大约 1/2 处开始,采用提取方法。 如果你能想出一个好名字,就使用它,但如果你不能,无论如何提取。 再次劈成两半。 你不会在这里追求理想的结构; 你试图将代码分解成适合你大脑的片段。 我的脑子不是很大,所以我会不断地打破和打破。 折断,直到不再痛为止。

在进行过程中,寻找任何看起来与其他方法不同的新的长方法; 将这些内容纳入新班级。 现在只需使用一个只有一种方法的简单类。 哎呀,使该方法静态就可以了。 不是因为你认为它们是好课程,而是因为你非常渴望有一些组织。

经常检查,这样您就可以检查您的工作,稍后了解历史记录,准备好做一些“实际工作”而无需合并,并为您的队友省去硬合并的麻烦。

最终,您需要返回并确保方法名称正确,您创建的方法集有意义,清理新类等。

如果您有高度可靠的提取方法工具,您可以获得没有良好的自动化测试。 (例如,我在这方面信任 VS。)否则,请确保您没有破坏东西,否则您最终会比开始时更糟糕:程序根本无法运行。

结对伙伴在这里会很有帮助。

I would start with uninhibited use of Extract Method. If you don't have it in your current Visual Studio IDE, you can either get a 3rd-party addin, or load your project in a newer VS. (It'll try to upgrade your project, but you will carefully ignore those changes instead of checking them in.)

You said that you have code indented 15 levels. Start about 1/2-way out, and Extract Method. If you can come up with a good name, use it, but if you can't, extract anyway. Split in half again. You're not going for the ideal structure here; you're trying to break the code in to pieces that will fit in your brain. My brain is not very big, so I'd keep breaking & breaking until it doesn't hurt any more.

As you go, look for any new long methods that seem to be different than the rest; make these in to new classes. Just use a simple class that has only one method for now. Heck, making the method static is fine. Not because you think they're good classes, but because you are so desperate for some organization.

Check in often as you go, so you can checkpoint your work, understand the history later, be ready to do some "real work" without needing to merge, and save your teammates the hassle of hard merging.

Eventually you'll need to go back and make sure the method names are good, that the set of methods you've created make sense, clean up the new classes, etc.

If you have a highly reliable Extract Method tool, you can get away without good automated tests. (I'd trust VS in this, for example.) Otherwise, make sure you're not breaking things, or you'll end up worse than you started: with a program that doesn't work at all.

A pairing partner would be helpful here.

旧人哭 2024-07-13 19:14:21

从描述来看,状态机可能是处理它的最佳方法。 使用枚举变量来存储当前状态,并将处理实现为记录上的循环,并使用 switch 或 if 语句来根据当前状态和输入数据选择要采取的操作。 如果工作量太大,您还可以使用函数指针根据状态轻松地将工作分派给单独的函数。

Judging by the description, a state machine might be the best way to deal with it. Have an enum variable to store the current state, and implement the processing as a loop over the records, with a switch or if statements to select the action to take based on the current state and the input data. You can also easily dispatch the work to separate functions based on the state using function pointers, too, if it's getting too bulky.

弄潮 2024-07-13 19:14:21

Coding Horror 上有一篇关于它的非常好的博客文章。 我只遇到过这种反模式一次,而且我几乎只是遵循他的步骤。

There was a pretty good blog post about it at Coding Horror. I've only come across this anti-pattern once, and I pretty much just followed his steps.

不再让梦枯萎 2024-07-13 19:14:21

有时我将状态模式与堆栈结合起来。

它适用于层次结构; 父元素知道将什么状态推入堆栈以处理子元素,但子元素不必知道有关其父元素的任何信息。 换句话说,子进程不知道下一个状态是什么,它只是发出信号表示它已“完成”并从堆栈中弹出。 这有助于通过保持单向依赖关系来将状态彼此解耦。

它非常适合使用 SAX 解析器处理 XML(内容处理程序只是在元素输入和退出时推送和弹出状态来更改其行为)。 EDI 也应该适合这种方法。

Sometimes I combine the state pattern with a stack.

It works well for hierarchical structures; a parent element knows what state to push onto the stack to handle a child element, but a child doesn't have to know anything about its parent. In other words, the child doesn't know what the next state is, it simply signals that it is "complete" and gets popped off the stack. This helps to decouple the states from each other by keeping dependencies uni-directional.

It works great for processing XML with a SAX parser (the content handler just pushes and pops states to change its behavior as elements are entered and exited). EDI should lend itself to this approach too.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文