Как оправиться от поломки конечного автомата?


13

Мой вопрос может показаться очень научным, но я думаю, что это общая проблема, и опытные разработчики и программисты, надеюсь, получат несколько советов, чтобы избежать проблемы, о которой я упоминаю в заголовке. Кстати, то, что я описываю ниже, является реальной проблемой, которую я стараюсь активно решать в своем проекте iOS, я хочу избежать этого любой ценой.

Под конечным автоматом я имею в виду это> У меня есть пользовательский интерфейс с несколькими кнопками, несколько состояний сеанса, относящихся к этому пользовательскому интерфейсу, и то, что этот пользовательский интерфейс представляет, у меня есть некоторые данные, значения которых частично отображаются в пользовательском интерфейсе, я получаю и обрабатываю некоторые внешние триггеры (представлен обратными вызовами от датчиков). Я сделал диаграммы состояний, чтобы лучше отобразить соответствующие сценарии, которые желательны и допустимы в этом пользовательском интерфейсе и приложении. По мере того, как я медленно реализую код, приложение начинает вести себя все больше и больше, как и должно. Однако я не очень уверен, что это достаточно надежно. Мои сомнения возникают из-за того, что я наблюдаю за ходом своего мышления и процесса реализации. Я был уверен, что у меня есть все, но этого было достаточно, чтобы сделать несколько грубых тестов в пользовательском интерфейсе, и я быстро понял, что в поведении все еще есть пробелы. Я исправил их. Тем не мение, поскольку каждый компонент зависит и ведет себя на основе входных данных от какого-либо другого компонента, определенного входного сигнала от пользователя или некоторых внешних источников, запускает цепочку событий, изменений состояния ... и т. д. У меня есть несколько компонентов, и каждый из них ведет себя так, как будто Триггер получен на входе -> триггер и его отправитель проанализирован -> вывести что-то (сообщение, изменение состояния) на основе анализа

Проблема в том, что это не является полностью самодостаточным, и мои компоненты (элемент базы данных, состояние сеанса, состояние некоторой кнопки) ... МОГУТ быть изменены, изменены, удалены или иным образом изменены вне области цепочки событий или желаемый сценарий. (телефон выходит из строя, батарея разряжена, телефон внезапно выключается). Это приведет к возникновению недопустимой ситуации в системе, из которой система потенциально НЕ МОЖЕТ быть в состоянии восстановиться. Я вижу это (хотя люди и не понимают, что это проблема) во многих приложениях моих конкурентов, которые есть в магазине Apple, клиенты пишут такие вещи> "Я добавил три документа, и, пройдя туда-сюда, я не могу их открыть, даже если их увидеть. " или "Я записывал видео каждый день, но после записи слишком логичного видео я не могу включить подписи к ним ..., а кнопка для подписей не отображается"

Это только сокращенные примеры, клиенты часто описывают это более подробно. Из описанных в них описаний и поведения я предполагаю, что конкретное приложение имеет пробой FSM.

Итак, главный вопрос: как я могу избежать этого и как защитить систему от самой блокировки?

РЕДАКТИРОВАТЬ> Я говорю в контексте одного представления viewcontroller по телефону, я имею в виду одну часть приложения. Я понимаю шаблон MVC, у меня есть отдельные модули для различной функциональности. Все, что я описываю, относится к одному холсту в пользовательском интерфейсе.


2
Звучит как случай для юнит-тестов!
Майкл К

Ответы:


7

Я уверен, что вы уже знаете это, но на всякий случай:

  1. Убедитесь, что каждый узел на диаграмме состояний имеет исходящую дугу для КАЖДОГО допустимого вида ввода (или разделите входы на классы, с одной выходной дугой для каждого класса ввода).

    Каждый пример конечного автомата, который я видел, использует только одну исходящую дугу для ЛЮБОГО ошибочного ввода.

    Если нет определенного ответа относительно того, что будет делать вход каждый раз, это либо условие ошибки, либо отсутствует другой вход (который должен постоянно приводить к дуге для входов, идущих в новый узел).

    Если у узла нет дуги для одного типа ввода, то это предположение, что ввод никогда не произойдет в реальной жизни (это условие потенциальной ошибки, которое не будет обработано конечным автоматом).

  2. Убедитесь, что конечный автомат может принимать или следовать только ОДНОЙ дуге в ответ на полученный вход (не более одной дуги).

    Если существуют различные виды сценариев ошибок или виды входных данных, которые не могут быть идентифицированы во время разработки конечного автомата, сценарии ошибок и неизвестные входные данные должны иметь состояние с совершенно отдельным путем, отличным от «обычных дуг».

    То есть, если в каком-либо «известном» состоянии получена ошибка или неизвестно, то дуги, следовавшие в результате ввода ошибок / неизвестного ввода, не должны возвращаться ни в какие состояния, в которых машина находилась бы, если бы только принимала известные входные данные.

  3. Как только вы достигнете терминального (конечного) состояния, вы не сможете вернуться к нетерминалу только в одно начальное (начальное) состояние.

  4. Для одного конечного автомата не должно быть более одного начального или начального состояния (на основе примеров, которые я видел).

  5. Исходя из того, что я видел, один конечный автомат может представлять только состояние одной проблемы или сценария.
    Никогда не должно быть нескольких возможных состояний одновременно на одной диаграмме состояний.
    Если я вижу потенциал для нескольких одновременных состояний, это говорит мне, что мне нужно разделить диаграмму состояний на 2 или более отдельных конечных автомата, которые имеют потенциал для каждого состояния, которое можно независимо изменять.


9

Суть конечного автомата в том, что у него есть явные правила для всего, что может происходить в состоянии. Вот почему это конечно .

Например:

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. Расширьте свои состояния, включив в них неизвестную проблему, и ошибки приведут вас в это состояние, где проблемы решаются.

Для справки, статья в вики о конечных автоматах является полной. Я также предлагаю Code Complete для глав по созданию стабильного, надежного программного обеспечения.


«Мы могли бы получить ввод c» - вот почему безопасные языки так важны. Если ваш тип ввода - bool, вы можете получить trueи false, но больше ничего. Даже тогда важно понимать ваши типы - например, обнуляемые типы, числа с плавающей запятой NaNи т. Д.
MSalters

5

Регулярные выражения реализованы в виде конечных автоматов. Таблица переходов, сгенерированная библиотечным кодом, будет иметь встроенное состояние ошибки, чтобы обрабатывать то, что происходит, если ввод не соответствует шаблону. По крайней мере, существует неявный переход в состояние сбоя практически из любого другого состояния.

Грамматики языка программирования не являются FSM, но генераторы синтаксического анализатора (такие как Yacc или bison) обычно имеют способ поместить одно или несколько состояний ошибки, так что неожиданные входные данные могут привести к тому, что сгенерированный код окажется в состоянии ошибки.

Похоже, что ваш FSM нуждается в состоянии ошибки или в состоянии сбоя или в моральном эквиваленте, а также в явном (для случаев, которые вы ожидаете) и неявном (для случаев, которые вы не ожидаете) переходах в одно из состояний сбоя или ошибки.


Простите, если мой вопрос звучит глупо, так как у меня нет формального образования в области CS, и я только несколько месяцев изучаю программирование. Означает ли это, что, когда я позволил, скажем, методу-обработчику для события push для кнопки, и в этом методе у меня есть довольно сложная структура условного перехода if-else-switch (20-30 строк кода), что Я должен всегда обрабатывать нежелательные входы явно? ИЛИ ты имеешь в виду это на "глобальном" уровне? Должен ли я иметь отдельный класс, наблюдающий за этим FSM, и когда возникнет проблема, он сбросит значения и состояния?
Эрл Грей,

Объяснение парсеров, сгенерированных Yacc или Bison, мне не подходит, но обычно вы имеете дело с известными случаями, а затем получаете небольшой блок кода для «все остальное переходит в состояние ошибки или сбоя». Код для состояния ошибки / сбоя будет выполнять весь сброс. Возможно, вам придется иметь дополнительное значение, которое объясняет, почему вы попали в состояние сбоя.
Брюс Эдигер

Ваш FSM должен иметь как минимум одно состояние для ошибок или несколько состояний ошибок для разных типов ошибок.
whatsisname

3

Лучший способ избежать этого - автоматическое тестирование .

Единственный способ получить реальную уверенность в том, что ваш код делает на основе определенных входных данных, - это проверить их. Вы можете щелкнуть по своему приложению и попытаться сделать что-то неправильно, но это плохо масштабируется, чтобы гарантировать, что у вас нет никаких регрессий. Вместо этого вы можете создать тест, который передает неверный ввод в компонент вашего кода и гарантирует, что он будет обрабатывать его разумным способом.

Это не сможет доказать, что конечный автомат никогда не может быть сломан, но это скажет вам, что многие из общих случаев обрабатываются правильно и не ломают другие вещи.


2

то, что вы ищете, это обработка исключений. Философия дизайна, позволяющая избежать пребывания в несовместимом состоянии, задокументирована как the way of the samurai: вернись победителем или не вернись. Другими словами: компонент должен проверить все свои входные данные и убедиться, что он сможет нормально их обрабатывать. Если это не так, следует создать исключение, содержащее полезную информацию.

Как только возникает исключение, оно всплывает в стеке. Вы должны определить слой обработки ошибок, который будет знать, что делать. Если пользовательский файл поврежден, объясните своему клиенту, что данные потеряны, и заново создайте чистый пустой файл.

Здесь важно вернуться в рабочее состояние и избежать распространения ошибок. Когда вы закончите с этим, вы можете работать с отдельными компонентами, чтобы сделать их более надежными.

Я не являюсь экспертом по объективным целям, но эта страница должна стать хорошей отправной точкой:

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


1

Забудьте свой конечный автомат. Здесь у вас серьезная многопоточность . Любая кнопка может быть нажата в любое время, и ваши внешние триггеры могут сработать в любое время. Кнопки, вероятно, все находятся в одном потоке, но триггер может буквально сработать одновременно с одной из кнопок или одним, несколькими или всеми другими триггерами.

Что вы должны сделать, это определить свое состояние в тот момент, когда вы решили действовать. Получить все кнопки и триггерные состояния. Сохраните их в локальных переменных. Исходные значения могут меняться каждый раз, когда вы на них смотрите. Затем действуйте в зависимости от ситуации. Это снимок того, как система выглядела в одной точке. Спустя миллисекунду это может выглядеть совсем по-другому, но при многопоточности нет реального текущего «сейчас», за которое вы можете держаться, только картинка, которую вы сохранили в локальных переменных.

Затем вы должны ответить на ваше сохраненное - историческое - состояние. Все исправлено, и у вас должно быть действие для всех возможных состояний. Он не будет учитывать изменения, сделанные между моментом, когда вы сделали снимок, и временем, когда вы отображаете результаты, но это жизнь в мире многопоточности. И вам может понадобиться использовать синхронизацию, чтобы ваш снимок не был слишком размытым. (Вы не можете быть в курсе, но вы можете приблизиться к получению всего вашего состояния с одного конкретного момента времени.)

Читайте о многопоточности. Тебе есть чему поучиться. И из-за этих триггеров я не думаю, что вы можете использовать множество часто используемых трюков для упрощения параллельной обработки («Рабочие потоки» и т. Д.). Вы не делаете «параллельную обработку»; Вы не пытаетесь использовать 75% из 8 ядер. Вы используете 1% всего ЦП, но у вас есть очень независимые, сильно взаимодействующие потоки, и вам потребуется много усилий, чтобы синхронизировать их и удержать синхронизацию от блокировки системы.

Тестирование как на одноядерных, так и на многоядерных машинах; Я обнаружил, что они ведут себя по-другому с многопоточностью. Одноядерные машины обнаруживают меньше многопоточных ошибок, но эти ошибки гораздо более странные. (Хотя многоядерные машины будут поражать вас, пока вы к ним не привыкнете.)

Еще одна неприятная мысль: это непросто проверить. Вам нужно будет сгенерировать случайные триггеры и нажатия кнопок и дать системе некоторое время поработать, чтобы увидеть, что произойдет. Многопоточный код не является детерминированным. Что-то может потерпеть неудачу один раз за миллиард пробежек, просто потому, что время отсрочено на наносекунду. Вставьте операторы отладки (с осторожными операторами if, чтобы избежать 999 999 999 ненужных сообщений), и вам нужно выполнить миллиард прогонов только для того, чтобы получить одно полезное сообщение. К счастью, в наши дни машины работают очень быстро.

Извините, что бросил все это на вас в начале вашей карьеры. Надеюсь, кто-нибудь придет с другим ответом и обойдёт все это (я думаю, что есть вещи, которые могут приручить триггеры, но у вас все еще есть конфликт триггер / кнопка). Если так, то этот ответ, по крайней мере, даст вам знать, что вам не хватает. Удачи.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.