如何从有限状态机故障中恢复?


14

我的问题似乎很科学,但我认为这是一个普遍的问题,经验丰富的开发人员和程序员希望能提供一些建议来避免我在标题中提到的问题。顺便说一句,我描述的波纹管是我要在我的iOS项目中主动解决的一个实际问题,我想不惜一切代价避免它。

通过有限状态机,我的意思是:>我有一个带有几个按钮的UI,与该UI相关的几个会话状态以及该UI表示的内容,我有一些数据,这些数据的值部分显示在UI中,我接收并处理了一些外部触发器(由传感器的回调表示)。我制作了状态图,以更好地映射在该UI和应用程序中理想和可实现的相关方案。随着我慢慢实现代码,应用程序开始表现得越来越像它应该的样子。但是,我不是很自信。我的怀疑来自观察自己的想法和实施过程。我确信我已经涵盖了所有内容,但是足以在UI中进行一些粗暴的测试,并且我很快意识到在行为方面仍然存在差距。然而,由于每个组件都依赖于其他组件的输入并根据其他组件的行为运行,因此来自用户或某些外部源的某些输入会触发一系列事件,状态更改等。我有几个组件,每个组件的行为都像这样:输入时收到触发器->触发器,并分析其发送者->基于分析输出一些信息(消息,状态更改)

问题是,这不是完全独立的,并且我的组件(数据库项,会话状态,某些按钮的状态)...可以在事件链范围之外进行更改,影响,删除或以其他方式修改。理想的情况。(电话崩溃,电池突然没电了,这会导致系统无效),这可能会导致系统无法恢复。我在苹果商店上的许多竞争对手应用程序中都看到了这种情况(人们没有意识到这是问题所在),客户编写了这样的内容:“我添加了三个文档,到那儿走了之后,我无法打开它们,即使看到他们。” 或“我每天都录制视频,但是录制了太多日志的视频之后,我无法在其上打开字幕..,并且用于字幕的按钮没有

这些只是简短的示例,客户通常会对其进行更详细的描述。.根据它们中描述的描述和行为,我认为特定应用具有FSM故障。

因此,最终的问题是如何避免这种情况,以及如何保护系统避免自身阻塞?

编辑>我是在电话中一个viewcontroller视图的上下文中谈论,我的意思是应用程序的一部分。我了解MVC模式,我具有用于不同功能的单独模块。.我描述的所有内容都与UI上的一个画布相关。


2
听起来像是单元测试的案例!
Michael K

Answers:


7

我相信您已经知道这一点,但以防万一:

  1. 确保状态图中的每个节点都有一个用于每种合法输入的输出弧(或将输入划分为类,每个输入类别都有一个输出弧)。

    我所看到的每个状态机示例都仅将一个出站弧用于任何错误输入。

    如果没有确定每次输入将执行的操作的确切答案,则可能是错误情况,或者是缺少另一个输入(这将始终导致输入到新节点的弧线)。

    如果节点对于一种类型的输入不具有弧,则这是假设该输入在现实生活中将永远不会发生(这是状态机无法处理的潜在错误条件)。

  2. 确保状态机响应接收到的输入仅能走或跟随一个弧(不超过一个弧)。

    如果存在不同类型的错误情况,或在状态机设计时无法识别的输入种类,则错误情况和未知输入应转换为具有与“正常电弧”完全不同的路径的状态。

    IE,如果在任何“已知”状态下接收到错误或未知信息,则由于错误处理/未知输入而导致的电弧不应回到机器仅处于已知状态时所处的任何状态。

  3. 一旦到达终端(结束)状态,您就不能仅返回到一个开始(初始)状态而返回非终端。

  4. 对于一个状态机,不应有一个以上的起始或初始状态(基于我所看到的示例)。

  5. 根据我所看到的,一个状态机只能代表一个问题或场景的状态。
    在一个状态图中,一次不应存在多个可能的状态。
    如果看到多个并发状态的可能性,这告诉我需要将状态图拆分为2个或更多个独立的状态机,每个状态机都有可能独立修改每个状态。


10

有限状态机的要点是,对于状态中可能发生的所有事情,它都有明确的规则。这就是为什么它是有限的

例如:

if a:
  print a
elif b:
  print b

不是有限的,因为我们可以得到输入c。这个:

if a:
  print a
elif b:
  print b
else:
  print error

是有限的,因为考虑了所有可能的输入。这考虑到状态的可能输入,该状态可以与错误检查分开。想象一下具有以下状态的状态机:

No money state. 
Not enough money state.
Pick soda state.

在定义的状态内,将对所有可能的输入进行处理,以投入资金和选择苏打水。电源故障在状态机外部,并且“不存在”。状态机只能处理其所具有状态的输入,因此您有两种选择。

  1. 确保所有动作都是原子的。机器可能有总功率损耗,但仍然使一切保持稳定,正确的状态。
  2. 扩展您的状态以包括未知问题,并让错误将您踢到处理问题的状态。

作为参考,有关状态机Wiki文章非常详尽。对于构建稳定,可靠的软件的章节,我也建议使用Code Complete


“我们可以获取输入c”-这就是为什么类型安全语言如此重要的原因。如果您的输入类型是布尔值,则可以获取truefalse,但没有别的。即使如此,它了解你的类型是很重要的-例如可空类型,浮点NaN,等等
MSalters

5

正则表达式实现为有限状态机。库代码生成的转换表将内置一个故障状态,以处理输入与模式不匹配时发生的情况。从几乎所有其他状态到故障状态至少有一个隐式过渡。

编程语言语法不是FSM,但是解析器生成器(例如Yacc或bison)通常具有一种或多种错误状态的方式,因此,意外输入可能导致生成的代码最终处于错误状态。

听起来您的FSM需要错误状态或故障状态或道德上的对等,以及显式(对于您预期的情况)和隐式(对于您不预期的情况)到故障或错误状态之一的转换。


如果我的问题听起来很傻,请原谅我,因为我没有接受过CS的正规教育,而我只是学习编程几个月。这是否意味着,当我说一个处理程序方法时,对于按钮的按下事件,在该方法中,我有一个相当复杂的if-else-switch-condition结构(20-30行代码),我应该始终明确地处理不想要的输入吗?还是在“全球”层面上讲它?我是否应该有一个单独的班级观看此FSM,并且当问题发生时,它将重置值和状态?
Earl Gray

解释Yacc或Bison生成的解析器超出了我的范围,但是通常,您要处理已知的情况,然后有一个小的代码块来表示“其他所有东西都进入错误或失败状态”。错误/失败状态的代码将全部重置。您可能必须具有一个额外的值,该值说明了为什么进入故障状态。
Bruce Ediger'4

您的FSM应该至少具有一个错误状态,或者针对不同类型的错误具有多个错误状态。
whatsisname 2012年

3

避免这种情况的最佳方法是自动测试

对您的代码基于某些输入所做的事情有真正信心的唯一方法是测试它们。您可以在应用程序中单击并尝试做错事情,但这不能很好地扩展以确保您没有任何回归。相反,您可以创建一个测试,将错误的输入传递到代码的组件中,并确保以合理的方式处理它。

这将无法证明状态机永远不会中断,但是它将告诉您许多常见情况已得到正确处理,并且不会中断其他事情。


2

您正在寻找的是异常处理。避免停留在不一致状态的设计理念记录为the way of the samurai:胜利返回或不返回。换句话说:组件应检查其所有输入,并确保它能够正常处理它们。并非如此,它应该引发一个包含有用信息的异常。

一旦引发异常,它就会冒出堆栈。您应该定义一个错误处理层,该层将知道该怎么做。如果用户文件已损坏,请向客户端说明数据已丢失,然后重新创建干净的空文件。

这里的重要部分是回到工作状态,并避免错误传播。完成此操作后,您可以处理单个组件以使其更坚固。

我不是Objective-C专家,但是此页应该是一个很好的起点:

http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocExceptionHandling.html


1

忘记您的有限状态机。您在这里遇到的是严重的多线程情况。任何时候都可以按下任何按钮,并且外部触发器可以随时关闭。这些按钮可能全都在一个线程上,但是触发器可能在与按钮之一或一个,多个或所有其他触发器相同的瞬间乱扔掉。

您必须做的是在决定采取行动时就确定自己的状态。获取所有按钮和触发状态。将它们保存在局部变量中。每次查看原始值时,它们可能都会更改。然后根据实际情况采取行动。它是系统如何看待某一点的快照。一毫秒后,它看起来可能会非常不同,但是使用多线程时,没有任何实际的“现在”可以挂起,只有保存在局部变量中的图片。

然后,您必须响应保存的历史状态。一切都是固定的,您应该针对所有可能的状态采取措施。它不会考虑在拍摄快照到显示结果之间所做的更改,但这就是多线程世界中的生活。而且,您可能需要使用同步来防止快照过于模糊。(您不可能是最新的,但是您可以接近从某个特定时刻获取整个状态。)

阅读多线程。你有很多东西要学。而且由于这些触发器,我认为您不能使用通常提供的许多技巧来简化并行处理(“ Worker Threads”之类)。您没有在进行“并行处理”;您没有尝试使用8个内核中的75%。您使用的是整个CPU的1%,但是您拥有高度独立,高度交互的线程,因此需要进行大量同步才能使它们同步并防止同步锁定系统。

在单核和多核机器上进行测试;我发现它们在多线程中的行为截然不同。单核计算机出现的多线程错误更少,但是这些错误却更加奇怪。(尽管多核计算机会让您不知所措,直到您习惯了它们。)

最后一个不愉快的想法:这不是容易测试的东西。您将需要生成随机触发器和按钮,并让系统运行一段时间以查看结果。多线程代码不是确定性的。十亿分之一的运行可能会失败,仅因为计时时间已过了十亿分之一秒。放入调试语句(使用谨慎的if语句避免999,999,999不必要的消息),您需要进行十亿次运行才能获得一条有用的消息。幸运的是,如今这些机器的速度非常快。

抱歉,在您职业生涯的初期就把这一切都丢给了您。希望有人能针对所有这些问题提出另一个答案(我认为那里有可能驯服触发器的东西,但您仍然遇到触发器/按钮冲突)。如果是这样,此答案将至少让您知道所缺少的内容。祝好运。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.