从状态图框架中删除依赖项

时间:2011-09-12 20:30:29

标签: c++ architecture refactoring design-patterns state

我目前正在处理的项目有很多问题。该项目已有10多年的历史,它基于90年代非常流行的商业C ++框架之一。问题在于状态图。该框架提供了相当普遍的状态模式实现。每个州都是一个单独的类,有进入动作,动作状态等。有一个开关根据收到的事件设置当前状态。

魔鬼隐藏在细节中。那个项目是巨大的。这是关于2000 KLOC的事情。肯定有太多的状态图(我已经看到使用状态图实现的“for”循环)。更重要的是......框架允许将状态图嵌入到另一个状态图中,因此有许多状态图具有七个甚至更多级别的嵌套。因为状态图在不同的线程中运行,并且可以在状态图之间发送事件,所以我们会遇到很多同步问题(并且界面上很乱)。

我必须承认,这个问题的规模是巨大的,我不知道如何触摸它。我的第一个想法是从状态图中删除尽可能多的代码并将其放入单独的类中。然后从状态图委派这些类来完成工作。但结果我们将有许多单独的函数,逻辑上没有任何特定的功能,状态图架构的任何更改都需要更改这些类和函数。

所以我请求帮助: 你知道哪些书籍/文章/魔术文物可以帮助我解决这个问题吗?我想至少从状态图中分离尽可能多的代码,而不引入任何隐藏的依赖关系,并保持分离的代码可维护,可测试和可重用。

如果您有任何建议如何处理,请告诉我。

3 个答案:

答案 0 :(得分:1)

状态图模式旨在专门用于删除switch语句,因此这听起来像是一种可怕的滥用行为。此外,状态应仅在异步事件上更改。如果您正在处理一个事件并且您通过多个状态(或循环等)进行更改,那么这也是对该模式的可怕滥用。

我将从这两点开始,因为它们将解决你的大部分并发问题。您需要确定的是:

  1. 您对系统的外部异步事件是什么?这些是唯一应该确定状态转换的东西,而不是事件处理期间发生的事情。事件可能导致0或1个状态转换。获得这些状态转换的列表后,您可以重建系统的实际状态。如果您了解UML状态图,那么这将是在图表程序中绘制一个图的完美时间,不仅仅是为了您自己(尽管它会对您有很大的帮助),而且对于将来必须返回到项目。如你所知,这种情况会发生。
  2. 现在你知道什么是真正的状态,列出代码中不应该是什么状态。这通常表明某些东西可以“功能分解”。而不是每个状态对象,可能所需要的只是一个单独的功能。这将减少很多状态对象的开销,并且应该极大地清理代码。
  3. 现在是时候解决你提到的那些可怕的转换声明了。如果他们真的以国家为基础,你根本不需要一个。相反,您应该能够直接调用状态机。
  4. 类似的东西:

    myStateMachine->myEvent();
    

    它应该没有任何开关工作。但请注意,即使对于某些不能跨异步事件工作的对象,情况也许如此。这也表明您可以使用继承来获得相同的效果。如果你有:

    switch (someTypeIdentifier)
    {
    case type1:
      doSomething();
      break;
    
    case type2:
      doSomethingElse();
      break;
    }
    

    通常正确的OOP方法是创建两个实际类型Type1,Type2,它们都是从抽象基础TypeBase派生而来的,虚拟方法doSomething()可以满足您的需要。这有用的原因是因为它意味着您可以“关闭”处理(在开放/封闭原则的意义上),并且仍然通过根据需要添加新的派生类型来扩展功能(使其对扩展开放)。这可以节省像疯狂一样的错误,因为它可以让开发人员摆脱那些转换语句,这可能会变得非常丑陋和错综复杂,而是将每个单独的行为封装在不同的类中。

    4 - 现在看看修复你的线程问题。识别从多个线程使用的所有对象。做一个列表。现在,这些如何使用?它们中的一些总是一起使用吗?开始制作小组。这里的目标是找到最适合这些对象的封装级别,将对象分成控制自己同步的单个类,找出对象的实际“事务”的原子级别,并创建类的方法暴露那些有意义的事务,用适当的互斥体,条件变量等包装在幕后。

    你可能会说“这听起来像是很多工作!为什么要这样做而不只是把它全部写在自己身上?”好问题! :)原因实际上很简单:如果您要自己完成所有这些,那么您应该采取的步骤。您应该确定您的状态,动态多态性以及处理多线程事务。但是,如果您从现有代码开始,那么您还拥有所有那些从未记录过的未说明的业务规则,并可能导致各种意外错误。您不必将所有内容都包括在内 - 如果您怀疑它是一个错误,请与过去使用该系统的人(如果可用),QA或任何可能识别错误的人讨论逻辑,并查看它是否真的应该结转。但是你需要实际评估错误是什么,或者你可能不编码实际需要编码的东西。

    最后,这是一个手动过程,是软件工程的一部分。有一些CASE工具可以帮助绘制状态图,甚至可以将它们发布到代码中,有许多IDE中的重构工具,可以帮助在函数和类之间移动代码,以及类似的工具,可以帮助识别线程需求。但是,不应该为单个项目选择那些东西。他们需要在整个职业生涯中学习,在多年的工作中更深入地学习,因为他们是软件工程师的一部分。他们不是为你做的。你仍然需要了解他们的原因和方法,他们只是帮助他们更有效地完成工作。

答案 1 :(得分:0)

听起来像我最好的选择(gulp!)可能会从头开始,如果它像你一样可怕地被打破了。有文件吗?你能开始基于文档构建一些更健全的软件吗?

如果完全重写不是一个选项(他们从来没有我的经验)我会尝试以下一些:

  1. 如果您还没有它,请绘制整个系统的架构图。勾勒出所有位假设如何协同工作,这将有助于您将系统分解为可能可管理/可测试的部分。
  2. 您是否有任何要求或测试计划?如果没有,你可以编写一个并开始为已经存在的各种代码/功能块进行单元测试吗?如果你能做到这一点,你可以开始重构事物,而不会破坏目前的工作。
  3. 一旦你把事情搞砸了,就开始将你的单元测试构建到集成测试中,这些测试将更多功能结合在一起。
  4. 我自己没有读过这些书,但我听说过这些书可能有一些你可以使用的建议:

    1. Refactoring: Improving the Design of Existing Code (Object Technology Series)
    2. Working Effectively with Legacy Code (Robert C. Martin)
    3. 祝你好运! : - )

答案 2 :(得分:0)

Statecharts(包括嵌套Statecharts)是指定,理解甚至模拟/验证复杂控制流的强大方法。但是为了获得好处,你需要在一个合适的工具中使用状态图模型(我在当天使用Statemate,不确定它是否仍然可用),以及从图表到代码的可靠映射(Statemate用于生成代码) ) - 那么你可以忘记状态管理代码(大多数)!在你的情况下,如果你没有这个模型,我会尝试从代码中反转一个 - 正如Ira说的那样,原始开发人员有某种形式的模型的可能性很高,你可能会发现代码很多模型出现时的感觉。如果这样做,你将有一个非常好的规范/模型,这将使​​未来的代码编辑更容易(即使你不想去自动代码生成,并手动维护代码/模型映射(但你需要一丝不苟!!))