¿Cómo recuperarse de una falla de máquina en estado finito?

13

Mi pregunta puede parecer muy científica, pero creo que es un problema común y los desarrolladores y programadores experimentados tendrán algunos consejos para evitar el problema que menciono en el título. Por cierto, lo que describo a continuación es un problema real que estoy tratando de resolver de manera proactiva en mi proyecto de iOS, quiero evitarlo a toda costa.

Por máquina de estados finitos quiero decir esto > Tengo una interfaz de usuario con unos pocos botones, varios estados de sesión relevantes para esa interfaz de usuario y lo que representa esta interfaz de usuario, tengo algunos datos cuyos valores se muestran parcialmente en la interfaz de usuario, recibo y manejo algunos disparadores externos (representados por devoluciones de llamada de sensores). Hice diagramas de estado para mapear mejor los escenarios relevantes que son deseables y permitidos en esa interfaz de usuario y aplicación. A medida que implemento el código lentamente, la aplicación comienza a comportarse cada vez más como debería. Sin embargo, no estoy muy seguro de que sea lo suficientemente robusto. Mis dudas provienen de ver mi propio proceso de pensamiento e implementación a medida que avanza. Confiaba en que tenía todo cubierto, pero fue suficiente para hacer algunas pruebas brutas en la interfaz de usuario y rápidamente me di cuenta de que todavía hay lagunas en el comportamiento ... Los parché. Sin embargo, como cada componente depende y se comporta en función de la entrada de algún otro componente, una determinada entrada del usuario o alguna fuente externa desencadena una cadena de eventos, cambios de estado, etc. Tengo varios componentes y cada uno se comporta así. Activación recibida en la entrada - > disparador y su remitente analizado - > mostrar algo (un mensaje, un cambio de estado) basado en el análisis

El problema es que esto no es completamente autocontenido y mis componentes (un elemento de la base de datos, un estado de sesión, el estado de algunos botones) ... PODRÍAN ser modificados, influenciados, eliminados o modificados, fuera del alcance del evento -Cadena o escenario deseable. (El teléfono se estrella, la batería está vacía, de repente el teléfono se vuelve) Esto introducirá una situación no válida en el sistema, desde la cual el sistema NO PODRÍA PODER recuperarse. Veo esto (aunque la gente no se da cuenta de que este es el problema) en muchas de las aplicaciones de mis competidores que se encuentran en Apple Store, los clientes escriben cosas como esta > "Agregué tres documentos, y después de ir allí y allá, no puedo abrirlos, incluso si los veo". o "Grabé videos todos los días, pero después de grabar un video de registro demasiado, no puedo activar los subtítulos en ellos ... y el botón para subtítulos no funciona" ..

Estos son solo ejemplos abreviados, los clientes a menudo lo describen con más detalle ... de las descripciones y el comportamiento que se describen en ellos, asumo que la aplicación en particular tiene un desglose FSM.

Entonces, la pregunta final es ¿cómo puedo evitar esto y cómo proteger el sistema para que no se bloquee?

EDITAR > Estoy hablando en el contexto de la vista de un controlador de vista en el teléfono, me refiero a una parte de la aplicación. Entiendo el patrón MVC, tengo módulos separados para distintas funcionalidades ... todo lo que describo es relevante para un lienzo en la interfaz de usuario.

    
pregunta Earl Grey 05.04.2012 - 13:53

6 respuestas

7

Estoy seguro de que ya lo sabes, pero por si acaso:

  1. Asegúrese de que cada nodo en el diagrama de estado tiene un arco de salida para CADA tipo de entrada legal (o dividir las entradas en clases, con un arco de salida para cada clase de entrada).

    Todos los ejemplos que he visto de una máquina de estado utiliza sólo una salida arco para CUALQUIER entrada errónea.

    Si no hay una respuesta definitiva en cuanto a qué entrada hará cada vez, es o una condición de error, o hay otra entrada que falta (que debería Consecuentemente resulta un arco para las entradas que van a un nuevo nodo).

    Si un nodo no tiene un arco para uno tipo de entrada, entonces eso es una suposición la entrada nunca ocurrirá en la vida real (Esta es una condición de error potencial eso no sería manejado por la máquina de estado).

  2. Asegúrese de que la máquina de estado solo pueda tomar o siga solo UN arco en respuesta a la entrada recibido (no más de un arco).

    Si hay diferentes tipos de escenarios de error, o tipos de entradas que no se pueden identificar en tiempo de diseño de la máquina de estado, el error Escenarios y entradas desconocidas deben ser de arco a un estado con un completo ruta separada separada de los "arcos normales".

    I.E. Si se recibe un error o desconocido en cualquier estado "conocido", entonces los arcos siguieron como resultado del manejo de errores / entrada desconocida No debe volver a entrar en ningún estado. La máquina estaría en si solo recibiera. entradas conocidas.

  3. Una vez que llegue a un terminal (final), establezca No debería ser capaz de volver a un no-terminal solo al estado inicial (inicial).

  4. Para una máquina de estado no debería haber más de una partida o estado inicial (basado en los ejemplos que he visto).

  5. Según lo que he visto, una máquina de estados puede solo representan el estado de un problema o escenario.
    Nunca debe haber múltiples estados posibles en una vez en un diagrama de estado.
    Si veo el potencial para múltiples concurrentes Indica que esto me dice que necesito dividir el diagrama de estado en 2 o más máquinas de estado separadas que tienen el potencial de cada estado para ser modificado independientemente.

respondido por el A B 05.04.2012 - 20:27
9

El punto de una máquina de estados finitos es que tiene reglas explícitas para todo lo que puede suceder en un estado. Por eso es finito .

Por ejemplo:

if a:
  print a
elif b:
  print b

Es no finito, porque podríamos obtener la entrada c . Esto:

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

es finito, porque se tienen en cuenta todas las entradas posibles. Esto explica las posibles entradas a un estado , que pueden estar separadas de la verificación de errores. Imagina una máquina de estados con los siguientes estados:

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

Dentro de los estados definidos, todas las entradas posibles se manejan para el dinero insertado y la soda seleccionada. El fallo de alimentación está fuera de la máquina de estado y "no existe". Una máquina de estado solo puede manejar entradas para los estados que tiene, por lo que tiene dos opciones.

  1. Garantiza que toda acción es atómica. La máquina puede tener una pérdida total de energía y aún así dejar todo en un estado estable y correcto.
  2. Extienda sus estados para incluir un problema desconocido y haga que los errores lo lleven a este estado, donde se manejan los problemas.

Para referencia, el artículo de wiki sobre máquinas de estado es exhaustivo. También sugiero Code Complete , para los capítulos sobre la creación de software estable y confiable.

    
respondido por el Spencer Rathbun 05.04.2012 - 16:34
5

Las expresiones regulares se implementan como máquinas de estados finitos. La tabla de transición generada por el código de la biblioteca tendrá un estado de falla incorporado, para manejar lo que sucede si la entrada no coincide con el patrón. Hay al menos una transición implícita al estado de falla desde casi cualquier otro estado.

Las gramáticas del lenguaje de programación no son FSM, pero los generadores del analizador (como Yacc o bison) generalmente tienen una forma de poner uno o más estados de error, de modo que las entradas inesperadas pueden hacer que el código generado termine en un estado de error.

Suena como que tu FSM necesita un estado de error o un estado de falla o el equivalente moral, junto con las transiciones explícitas (para los casos que anticipas) y las implícitas (para los casos en las que no anticipas) hacia uno de los estados de falla o error .

    
respondido por el Bruce Ediger 05.04.2012 - 14:42
3

La mejor manera de evitar esto es Pruebas automatizadas .

La única forma de tener confianza real sobre lo que hace su código en base a ciertas entradas es probándolas. Puede hacer clic en la aplicación e intentar hacer las cosas incorrectamente, pero eso no se ajusta bien para asegurarse de que no tenga regresiones. En su lugar, puede crear una prueba que pase la entrada incorrecta a un componente de su código y se asegure de que se maneje de forma sana.

Esto no podrá probar que la máquina de estado nunca puede romperse, pero le dirá que muchos de los casos comunes se manejan correctamente y no rompen otras cosas.

    
respondido por el unholysampler 05.04.2012 - 14:06
2

lo que está buscando es el manejo de excepciones. Una filosofía de diseño para evitar permanecer en un estado inconsistente se documenta como the way of the samurai : devuelva victorioso o no regrese. En otras palabras: un componente debe verificar todas sus entradas y asegurarse de que podrá procesarlas normalmente. Si no es el caso, debería generar una excepción que contenga información útil.

Una vez que se levanta una excepción, se acumula la pila. Debes definir una capa de manejo de errores que sepa qué hacer. Si un archivo de usuario está dañado, explique a su cliente que los datos se han perdido y vuelva a crear un archivo vacío y limpio.

La parte importante aquí es volver al estado de trabajo y evitar la propagación de errores. Cuando haya terminado con esto, puede trabajar en componentes individuales para hacerlos más robustos.

No soy un experto en objetivos c, pero esta página debería ser un buen punto de partida:

enlace

    
respondido por el Simon 05.04.2012 - 17:05
1

Olvida tu máquina de estados finitos. Lo que tienes aquí es una grave situación multihilo . Se puede presionar cualquier botón en cualquier momento y sus disparadores externos pueden dispararse en cualquier momento. Es probable que los botones estén todos en un hilo, pero un disparador puede dispararse literalmente en el mismo instante que uno de los botones o uno, muchos, o todos los otros disparadores.

Lo que debes hacer es determinar tu estado en el momento en que decides actuar. Consigue todos los botones y estados de disparo. Guárdelas en variables locales . Los valores originales pueden cambiar cada vez que los mires. Entonces actúa sobre la situación como la tienes. Es una instantánea de cómo se ve el sistema en un punto. Un milisegundo después podría verse muy diferente, pero con el subprocesamiento múltiple no hay un "ahora" actual real al que pueda aferrarse, solo una imagen que haya guardado en variables locales.

Luego tienes que responder a tu estado histórico guardado. Todo está arreglado y deberías tener una acción para todos los estados posibles. No tendrá en cuenta los cambios realizados entre el momento en que tomó la instantánea y el momento en que muestra los resultados, pero esa es la vida en el mundo de subprocesos múltiples. Y es posible que deba usar la sincronización para evitar que la instantánea se vea borrosa. (No puede estar al día, pero puede cerrar obtener su estado completo de un instante particular en el tiempo).

Leer sobre subprocesos múltiples. Usted tiene mucho que aprender. Y debido a esos factores desencadenantes, no creo que pueda usar muchos de los trucos que se proporcionan a menudo para facilitar el procesamiento en paralelo ("Trabajadores de hilos" y demás). No estás haciendo "procesamiento paralelo"; No estás intentando usar el 75% de los 8 núcleos. Está utilizando el 1% de la CPU completa, pero tiene subprocesos altamente interactivos y altamente independientes, y se necesitará mucha reflexión para sincronizarlos y evitar que la sincronización bloquee el sistema.

Prueba en máquinas de un solo núcleo y de varios núcleos; Descubrí que se comportan de manera muy diferente con los subprocesos múltiples. Las máquinas de un solo núcleo muestran menos errores de subprocesamiento múltiple, pero esos errores son mucho más extraños. (Aunque las máquinas de varios núcleos dejarán de pensar hasta que te acostumbres a ellas).

Un último pensamiento desagradable: esto no es algo fácil de probar. Tendrá que generar disparadores aleatorios y pulsaciones de botones y dejar que el sistema se ejecute completamente durante un tiempo para ver qué sucede. El código de subprocesos múltiples es no determinista. Algo puede fallar una vez en mil millones de carreras, solo porque el tiempo estuvo apagado por un nanosegundo. Ponga en las declaraciones de depuración (con cuidadas instrucciones-en-si para evitar 999.999.999 mensajes innecesarios) y necesita hacer mil millones de ejecuciones solo para obtener un mensaje útil. Afortunadamente, las máquinas son muy rápidas en estos días.

Lamento dejar todo esto sobre usted tan pronto en su carrera. Esperemos que alguien encuentre otra respuesta con una forma de evitar todo esto (creo que hay cosas que podrían dominar los factores desencadenantes, pero todavía tienes el conflicto de activación / botón). Si es así, esta respuesta al menos te permitirá saber lo que te estás perdiendo. Buena suerte.

    
respondido por el RalphChapin 05.04.2012 - 16:01

Lea otras preguntas en las etiquetas