2

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

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

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

所以我寻求帮助:你知道任何可以帮助我解决这个问题的书籍/文章/魔法文物吗?我想至少在不引入任何隐藏依赖项的情况下将尽可能多的代码从状态图中分离出来,并保持分离的代码可维护、可测试和可重用。

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

4

3 回答 3

1

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

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

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

就像是:

myStateMachine->myEvent();

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

switch (someTypeIdentifier)
{
case type1:
  doSomething();
  break;

case type2:
  doSomethingElse();
  break;
}

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

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

你可能会说“这听起来像是很多工作!为什么要做所有这些而不是自己写呢?” 好问题!:) 原因实际上很简单:如果您要自己完成所有操作,那么无论如何您都应该执行这些步骤。您应该识别您的状态、您的动态多态性并处理多线程事务。但是,如果您从现有代码开始,您还会拥有所有那些从未记录在案的未说出口的业务规则,并且可能会导致各种意外错误。你不必把所有东西都带过来——如果你怀疑这是一个错误,请与过去使用过系统的人(如果有的话)、QA 或任何可能发现错误的人讨论逻辑,看看它是否真的应该结转。

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

于 2011-09-12T22:11:10.793 回答
0

状态图(包括嵌套状态图)是指定、理解甚至模拟/验证复杂控制流的强大方法。但是要获得好处,您需要在合适的工具中使用状态图模型(我以前使用过 Statemate,不确定它是否仍然可用),以及从图表到代码的可靠映射(Statemate 用于生成代码) - 那么你可以忘记状态管理代码(大部分)!在您的情况下,如果您没有模型,我会尝试从代码中反转一个 - 正如 Ira 所说,原始开发人员很有可能拥有某种形式的模型,您可能会发现代码制作很多模型出现时的意义。如果这行得通,您将拥有一个非常好的代码规范/模型,这将使​​未来的代码编辑更加容易(即使您不这样做)

于 2011-09-14T10:04:59.633 回答
0

在我看来,你最好的选择是(大口大口地!)如果它像你想象的那样糟糕透顶,可能会从头开始。有文件吗?你能开始基于文档构建一些更健全的软件吗?

如果无法选择完全重写(而且在我的经验中从来没有),我会尝试以下一些方法:

  1. 如果您还没有它,请绘制整个系统的架构图。勾勒出所有部分应该如何协同工作,这将帮助您将系统分解为潜在的可管理/可测试的部分。
  2. 您是否有任何类型的要求或测试计划?如果没有,您是否可以编写一个并开始为已经存在的各种代码/功能块进行单元测试?如果你能做到这一点,你就可以开始重构事物,而不会破坏当前工作的大部分内容
  3. 一旦你把事情分解了一点,开始将你的单元测试构建成集成测试,它将更多的功能组合在一起。

我自己没有读过它们,但我听说过关于这些书的好消息,可能会有一些您可以使用的建议:

  1. 重构:改进现有代码的设计(对象技术系列)
  2. 有效地使用遗留代码 (Robert C. Martin)

祝你好运!:-)

于 2011-09-12T21:33:51.397 回答