从状态图框架中删除依赖项
我目前正在进行的项目有很多问题。该项目已有 10 多年的历史,它基于 90 年代非常流行的商业 C++ 框架之一。问题出在状态图上。该框架提供了相当通用的状态模式实现。每个状态都是一个单独的类,具有进入时的操作、状态中的操作等。有一个开关可以根据接收到的事件设置当前状态。
魔鬼隐藏在细节中。那个工程是巨大的。大概2000 KLOC吧。状态图肯定太多了(我见过使用状态图实现的“for”循环)。更重要的是......框架允许将状态图嵌入到另一个状态图中,因此有许多状态图具有七个甚至更多级别的嵌套。因为状态图在不同的线程中运行,并且可以在状态图之间发送事件,所以我们有很多同步问题(以及接口中的大混乱)。
我必须承认这个问题的规模是巨大的,我不知道如何处理它。我的第一个想法是从状态图中删除尽可能多的代码并将其放入单独的类中。然后从状态图中委托这些类来完成工作。但结果我们将拥有许多独立的函数,这些函数在逻辑上没有任何特定的功能,并且状态图架构的任何更改也需要更改该类和函数。
所以我寻求帮助: 你知道有什么书籍/文章/魔法文物可以帮助我解决这个问题吗?我希望至少从状态图中分离出尽可能多的代码,而不引入任何隐藏的依赖关系,并保持分离的代码可维护、可测试和可重用。
如果您对如何处理此问题有任何建议,请告诉我。
I've got lots of problems with project i am currently working on. The project is more than 10 years old and it was based on one of those commercial C++ frameworks which were very populary in the 90's. The problem is with statecharts. The framework provides quite common implementation of state pattern. Each state is a separate class, with action on entry, action in state etc. There is a switch which sets current state according to received events.
Devil is hidden in details. That project is enormous. It's something about 2000 KLOC. There is definitely too much statecharts (i've seen "for" loops implemented using statecharts). What's more ... framework allows to embed statechart in another statechart so there are many statecherts with seven or even more levels of nesting. Because statecharts run in different threads, and it's possible to send events between statecharts we have lots of synchronization problems (and big mess in interfaces).
I must admit that scale of this problem is overwhelming and I don't know how to touch it. My first idea was to remove as much code as I can from statecharts and put it into separate classes. Then delegate these classes from statechart to do a job. But in result we will have many separate functions, which logically don't have any specific functionality and any change in statechart architecture will need also a change of that classes and functions.
So I asking for help:
Do you know any books/articles/magic artefacts which can help me to fix this ? I would like to at least separate as much code as I can from statechart without introducing any hidden dependencies and keep separated code maintainable, testable and reusable.
If you have any suggestion how to handle this, please let me know.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
状态图模式旨在专门用于删除 switch 语句,因此这听起来像是一种可怕的滥用。此外,状态应该只在异步事件发生时改变。如果您正在处理一个事件并通过多个状态(或 for 循环等)进行更改,那么这也是对该模式的可怕滥用。
我将从这两点开始,因为它们只需修复它们即可解决大部分并发问题。您需要确定的是:
像这样:
它应该无需任何开关即可工作。但请注意,即使对于某些不能跨异步事件工作的对象也可能出现这种情况。这也表明您可以在哪里使用继承来获得相同的效果。如果您有:
通常,正确的 OOP 方法是创建两个实际类型 Type1、Type2,两者均派生自抽象基 TypeBase,并使用虚拟方法 doSomething() 执行您所需的操作。这很有用的原因是因为它意味着您可以“关闭”处理(按照开放/关闭原则的含义),并且仍然可以通过根据需要添加新的派生类型来扩展功能(使其对扩展保持开放)。这可以疯狂地避免错误,因为它可以让开发人员摆脱那些可能变得非常丑陋和复杂的 switch 语句,而是将每个单独的行为封装在单独的类中。
4 - 现在解决您的线程问题。识别多个线程使用的所有对象。列一个清单。现在,这些是如何使用的?其中一些总是一起使用吗?开始分组。这里的目标是找到最适合这些对象的封装级别,将对象分成控制其自身同步的单独类,找出对象的实际“事务”的原子级别,并使类的方法暴露那些有意义的事务,在幕后用适当的互斥体、条件变量等包裹起来。
您可能会说“这听起来像是很多工作!为什么要做所有这些而不是自己写一遍呢?”好问题! :) 原因实际上很简单:如果您打算自己完成这一切,那么这些都是您应该执行的步骤。您应该识别您的状态、动态多态性,并掌握多线程事务。但是,如果您从现有代码开始,您还会拥有所有那些从未记录下来的不言而喻的业务规则,并且可能会导致各种意外的错误。您不必将所有内容都提交 - 如果您怀疑这是一个错误,请与过去使用该系统的人员(如果有的话)、质量检查人员或任何可能识别错误的人员讨论逻辑,并看看它是否真的存在应该结转。但无论哪种方式,您都需要实际评估错误是什么,否则您可能不会编写实际需要编码的内容。
最后,这是一个手动过程,是软件工程的一部分。有些 CASE 工具可以帮助绘制状态图,甚至将它们发布为代码,有些重构工具(如许多 IDE 中的工具)可以帮助在函数和类之间移动代码,还有类似的工具可以帮助识别线程需求。然而,这些东西不应该被单个项目所接受。它们需要在你的整个职业生涯中学习,在多年的工作中不断地学习和更深入地学习它们,因为它们是成为软件工程师的一部分。他们不为你做这件事。您仍然需要知道原因和方法,它们只是帮助您更有效地完成任务。
The statechart pattern is intended to be used specifically to remove switch statements, so this sounds like a horrid abuse. Additionally, states should only change on asynchronous events. If you are processing an event and you change through multiple states (or for loop, etc.), then this is also a horrid abuse of the pattern.
I would start from these two points, as they will solve much of your concurrency issues just fixing them up. What you need to determine is:
Something like:
and it should work without any switch. But notice, this may be the case even for some of those objects that don't work across asynchronous events. This is also an indication of where you may just use inheritance to get the same effect. If you have:
usually the correct OOP method to do is to create two actual types Type1, Type2, both derived from an abstract base TypeBase, with a virtual method doSomething() that does what you need. The reason this is useful is because it means you can "close" the handling (in the meaning of the Open/Closed Principle), and still extend the functionality by adding new derived types as needed (leaving it open to extension). This saves bugs like crazy because it gets developers hands out of those switch statements, which can get quite ugly and convoluted, instead encapsulating each separate behavior in separate classes.
4 - Now look to fix up your thread issues. Identify all objects used from multiple threads. Make a list. Now, how are these used? Are some of them always used together? Start making groups. The goal here is to find the level of encapsulation that best works for these objects, separate the objects into individual classes that control their own synchronisation, figure out the atomic level of actual "transactions" for the objects, and make methods of the classes that expose those meaningful transactions, wrapped behind the scenes with the appropriate mutexes, condition variables, etc.
You might be saying "that sounds like a lot of work! Why do all that instead of just writing it all over myself?" Good question! :) The reason is actually straightforward: if you are going to do it all by yourself, those are the steps you should be doing anyway. You should be identifying your states, your dynamic polymorphism, and getting a handle on the multithreaded transactions. But, if you start with the existing code, you also have all of those unspoken business rules that were never documented and may cause all sorts of unexpected bugs down the line. You don't have to bring everything over - if you suspect it's a bug, discuss the logic with the people who have worked with the system in the past (if available), QA, or whoever might identify bugs, and see if it really should be carried over. But you need to actually evaluate what the bugs are either way, or you may not code something that actually needed coding.
In the end, this is a manual process that is a part of software engineering. There are CASE tools that can help draw up the state diagrams and even publish them to code, there are refactoring tools, like those found in many IDEs, that can help move code between functions and classes, and similar tools which can help identify threading needs. However, those things shouldn't be picked up for a single project. They need to be learned throughout your career, picking them up and learning them more deeply over years of work, as they are a part of being a software engineer. They don't do it for you. You still need to know the whys and hows, and they just help get it done more efficiently.
状态图(包括嵌套状态图)是指定、理解甚至模拟/验证复杂控制流的强大方法。但为了获得好处,您需要在合适的工具中使用状态图模型(我以前使用过 Statemate,不确定它是否仍然可用),再加上从图表到代码的可靠映射(Statemate 用于生成代码) ) - 那么你就可以忘记状态管理代码(大部分)!在你的情况下,如果你没有模型,我会尝试从代码中反转模型 - 正如 Ira 所说,原始开发人员很有可能拥有某种形式的模型,并且你可能会发现代码做了很多当模型出现时就有意义了。如果成功,您将拥有一个非常好的代码规范/模型,这应该使将来的代码编辑变得更加容易(即使您不想自动生成代码,并手动维护代码/模型映射(但是你需要小心谨慎!!))
Statecharts (including nested Statecharts) are a powerful way to specify, understand and even simulate/validate complex control flow. But to gain the benefit, you need the statechart model in a suitable tool (I used Statemate way back in the day, not sure if it's still available), plus a reliable mapping from the chart to the code (Statemate used to generate the code) - then you can forget about the state management code (mostly)! In your situation, if you don't have the model, I would try to reverse one from the code - as Ira says, chances are high that the original developers had a model in some form, and you may find the code making a lot of sense as the model emerges. If this works out, you will have a really good spec/model of the code which should make future code edits much easier (even if you don't want to go to automatic code generation, and maintain the code/model mapping manually (but you'll need to be meticulous!!))
在我看来,如果它像你所看到的那样严重损坏,那么你最好的选择是(咕噜!)可能从头开始。有任何文档吗?您可以根据文档开始构建一些更理智的软件吗?
如果完全重写不是一个选项(而且在我的经验中从来没有),我会尝试以下一些操作:
我自己没有读过它们,但我听说过有关这些书的好消息,其中可能有一些您可以使用的建议:
祝你好运! :-)
Sounds to me like your best bet is (gulp!) likely to start from scratch if it's as horrifically broken as you make out. Is there any documentation? Could you begin to build some saner software based on the docs?
If a complete re-write isn't an option (and they never are in my experience) I'd try some of the following:
I've not read them myself, but I've heard good things about these books which may have some advice you can use:
Good luck! :-)