Mi pregunta puede parecer muy científica, pero creo que es un problema común y los desarrolladores y programadores experimentados con suerte 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 iOS, quiero evitarlo a toda costa.
Por máquina de estados finitos quiero decir esto> Tengo una interfaz de usuario con algunos 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 desencadenantes externos (representado por devoluciones de llamada de sensores). Hice diagramas de estado para mapear mejor los escenarios relevantes que son deseables y permitibles en esa interfaz de usuario y aplicación. A medida que implemento lentamente el código, la aplicación comienza a comportarse más y 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. Estaba seguro de 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 vacíos en el comportamiento ... Los parcheé. Sin embargo, como cada componente depende y se comporta en base a 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 de ellos se comporta como este Trigger recibido en la entrada -> disparador y su remitente analizado -> genera algo (un mensaje, un cambio de estado) basado en el análisis
El problema es que esto no está completamente autocontenido, y mis componentes (un elemento de la base de datos, un estado de sesión, el estado de algún botón) ... PODRÍAN cambiarse, influenciarse, eliminarse o modificarse de otro modo, fuera del alcance de la cadena de eventos o Escenario deseable. (el teléfono se bloquea, la batería está vacía y el teléfono se apaga repentinamente) Esto introducirá una situación no válida en el sistema, de la cual el sistema NO PODRÍA PODER recuperarse. Veo esto (aunque las personas no se dan cuenta de que este es el problema) en muchas de las aplicaciones de mis competidores que están en la tienda de Apple, los clientes escriben cosas como esta> "Agregué tres documentos, y después de ir de un lado a otro, 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 desactivar los subtítulos en ellos ..., y el botón para subtítulos no
Estos son solo ejemplos abreviados, los clientes a menudo lo describen con más detalle ... a partir de las descripciones y el comportamiento descritos en ellos, supongo que la aplicación en particular tiene un desglose de 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 una funcionalidad distinta ... todo lo que describo es relevante para un lienzo en la interfaz de usuario.
Respuestas:
Estoy seguro de que ya lo sabes, pero por las dudas:
Asegúrese de que cada nodo en el diagrama de estado tenga un arco saliente para CADA tipo de entrada legal (o divida las entradas en clases, con un arco saliente para cada clase de entrada).
Cada ejemplo que he visto de una máquina de estado usa solo un arco saliente para CUALQUIER entrada errónea.
Si no hay una respuesta definitiva sobre lo que hará una entrada cada vez, es una condición de error o falta otra entrada (que debería dar como resultado un arco para las entradas que van a un nuevo nodo).
Si un nodo no tiene un arco para un tipo de entrada, entonces es una suposición de que la entrada nunca ocurrirá en la vida real (esta es una condición de error potencial que no sería manejada por la máquina de estado).
Asegúrese de que la máquina de estado solo pueda tomar o seguir UN solo arco en respuesta a la entrada recibida (no más de un arco).
Si hay diferentes tipos de escenarios de error, o tipos de entradas que no se pueden identificar en el tiempo de diseño de la máquina de estado, los escenarios de error y las entradas desconocidas deben arquearse a un estado con una ruta completamente separada, separada de los "arcos normales".
IE si se recibe un error o desconocido en cualquier estado "conocido", entonces los arcos seguidos como resultado de la entrada de manejo de errores / desconocido no deberían volver a ningún estado en el que estaría la máquina si solo recibiera entradas conocidas.
Una vez que alcanza un estado terminal (final), no debería poder regresar a un estado no terminal solo al estado inicial (inicial).
Para una máquina de estado no debe haber más de un estado inicial o inicial (según los ejemplos que he visto).
Según lo que he visto, una máquina de estado solo puede representar el estado de un problema o escenario.
Nunca debe haber múltiples estados posibles al mismo tiempo en un diagrama de estado.
Si veo el potencial de múltiples estados concurrentes, 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 que cada estado sea modificado independientemente.
fuente
El objetivo 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:
No es finito, porque podríamos obtener información
c
. Esta:es finito, porque todas las entradas posibles se contabilizan. Esto explica las posibles entradas a un estado , que pueden estar separadas de la comprobación de errores. Imagine una máquina de estados con los siguientes estados:
Dentro de los estados definidos, todas las entradas posibles se manejan por dinero insertado y se seleccionan refrescos. La falla de energía está fuera de la máquina de estado y "no existe". Una máquina de estados solo puede manejar entradas para estados que tiene, por lo que tiene dos opciones.
Como referencia, el artículo wiki sobre máquinas de estado es completo. También sugiero Code Complete , para los capítulos sobre la construcción de software estable y confiable.
fuente
true
yfalse
, pero nada más. Incluso entonces es importante para entender sus tipos - por ejemplo, los tipos anulables, punto flotanteNaN
, etcLas 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 de analizadores (como Yacc o bison) generalmente tienen una forma de poner en 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.
Parece que su FSM necesita un estado de error o un estado de falla o el equivalente moral, junto con transiciones explícitas (para los casos que anticipa) e implícitas (para los casos que no anticipa) a uno de los estados de falla o error.
fuente
La mejor manera de evitar esto es la prueba automatizada .
La única forma de tener una confianza real sobre lo que hace su código en función de ciertas entradas es probarlas. Puede hacer clic en su aplicación e intentar hacer las cosas incorrectamente, pero eso no se escala bien para garantizar que no tenga regresiones. En cambio, puede crear una prueba que pase una entrada incorrecta a un componente de su código y se asegure de que lo maneja de una manera sensata.
Esto no podrá probar que la máquina de estado nunca se puede romper, pero le dirá que muchos de los casos comunes se manejan correctamente y no rompen otras cosas.
fuente
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
: regresar victorioso o no regresar. En otras palabras: un componente debe verificar todas sus entradas y asegurarse de que podrá procesarlas normalmente. Si no es el caso, debe generar una excepción que contenga información útil.Una vez que se genera una excepción, burbujea la pila. Debe 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 pierden y vuelva a crear un archivo vacío y limpio.
La parte importante aquí es volver a un estado de funcionamiento 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 Objective-C, pero esta página debería ser un buen punto de partida:
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocExceptionHandling.html
fuente
Olvida tu máquina de estado finito. Lo que tienes aquí es una grave situación de subprocesos múltiples . Se puede presionar cualquier botón en cualquier momento y sus disparadores externos pueden activarse en cualquier momento. Los botones probablemente están todos en un hilo, pero un disparador podría activarse literalmente en el mismo instante que uno de los botones o uno, muchos o todos los otros disparadores.
Lo que debe hacer es determinar su estado en el momento en que decide actuar. Obtenga todos los botones y estados de activación. Guárdelos en variables locales . Los valores originales pueden cambiar cada vez que los mire. Luego, actúe según la situación como la tiene. Es una instantánea de cómo se veía 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.
Entonces tienes que responder a tu estado histórico guardado. Todo está arreglado y debe 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 sus resultados, pero así es la vida en el mundo de subprocesos múltiples. Y es posible que deba usar la sincronización para evitar que su instantánea sea demasiado borrosa. (No puede ser de hasta al día, pero se puede llegar cerca de conseguir todo el estado de un instante determinado en el tiempo.)
Lea sobre subprocesos múltiples. Tienes mucho que aprender. Y debido a esos desencadenantes, no creo que pueda usar muchos de los trucos que a menudo se proporcionan para facilitar el procesamiento en paralelo ("Subprocesos de trabajo" y demás). No estás haciendo "procesamiento paralelo"; No está intentando utilizar el 75% de 8 núcleos. Está utilizando el 1% de toda la CPU, pero tiene hilos altamente independientes e interactivos, y necesitará mucho pensamiento para sincronizarlos y evitar que la sincronización bloquee el sistema.
Prueba en máquinas de un solo núcleo y de múltiples núcleos; He descubierto que se comportan de manera bastante diferente con subprocesos múltiples. Las máquinas de un solo núcleo producen menos errores de subprocesos múltiples, pero esos errores son mucho más extraños. (Aunque las máquinas multinúcleo te aturdirán hasta que te acostumbres a ellas).
Un último pensamiento desagradable: esto no es algo fácil de probar. Tendrá que generar disparadores aleatorios y presionar botones y dejar que el sistema se ejecute por un tiempo para ver qué sucede. El código multiproceso no es determinista. Algo puede fallar una vez cada mil millones de corridas, solo porque el tiempo no estuvo disponible por un nanosegundo. Agregue declaraciones de depuración (con declaraciones if cuidadosas 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 decirte todo esto tan temprano en tu carrera. Esperemos que alguien encuentre otra respuesta con una forma de evitar todo esto (creo que hay cosas por ahí que podrían domar los disparadores, pero aún tienes el conflicto de disparador / botón). Si es así, esta respuesta al menos te hará saber lo que te estás perdiendo. Buena suerte.
fuente