Удаление зависимостей из структуры диаграммы состояний - PullRequest
2 голосов
/ 13 сентября 2011

У меня много проблем с проектом, над которым я сейчас работаю. Проекту более 10 лет, и он основан на одной из тех коммерческих платформ C ++, которые были очень популярны в 90-х годах. Проблема с государственными чартами. Каркас обеспечивает довольно распространенную реализацию шаблона состояния. Каждое состояние является отдельным классом с действием при входе, действием в состоянии и т. Д. Существует переключатель, который устанавливает текущее состояние в соответствии с полученными событиями.

Дьявол скрыт в деталях. Этот проект огромен. Это что-то около 2000 KLOC. Определенно слишком много диаграмм состояний (я видел циклы for, реализованные с использованием диаграмм состояний). Более того, фреймворк позволяет встроить диаграмму состояний в другую диаграмму состояний, поэтому существует множество диаграмм состояний с семью или даже более уровнями вложенности. Поскольку диаграммы состояний выполняются в разных потоках, и можно отправлять события между диаграммами состояний, у нас много проблем с синхронизацией (и большой беспорядок в интерфейсах).

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

Итак, я прошу помощи: Знаете ли вы какие-нибудь книги / статьи / магические артефакты, которые могут помочь мне это исправить? Я хотел бы, по крайней мере, отделить как можно больше кода от диаграммы состояний, не вводя никаких скрытых зависимостей, и поддерживать отдельный код поддерживаемым, тестируемым и повторно используемым.

Если у вас есть предложения, как с этим справиться, пожалуйста, дайте мне знать.

Ответы [ 3 ]

1 голос
/ 13 сентября 2011

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

Я бы начал с этих двух пунктов, так как они решат большинство ваших проблем параллелизма, просто решив их. То, что вам нужно определить, это:

  1. Каковы ваши внешние, асинхронные события для системы? Это единственные вещи, которые должны определять переходы состояний, а не вещи, которые происходят во время обработки событий. Событие может вызвать 0 или 1 переходы состояний. Получив список этих переходов состояний, вы можете восстановить действительные состояния вашей системы. Если вам известны диаграммы состояния UML, это было бы идеальное время для того, чтобы набросать одну из них в программе построения диаграмм не только для себя (хотя это вам очень поможет), но и для всех, кто в будущем должен вернуться к проект. Как вы узнали, это происходит.
  2. Теперь, когда вы знаете, что такое состояния, перечислите в коде состояния, которых быть не должно. Обычно это означает, что что-то может быть «функционально разложено». Вместо объекта состояния для каждого из них, вероятно, все, что нужно, - это отдельная функция. Это сократит значительную нагрузку на объекты состояния и приведет к огромной очистке кода.
  3. Теперь пришло время заняться этими ужасными заявлениями переключателей, которые вы упомянули. Если они действительно основаны на состоянии, вам не нужно вообще. Вместо этого вы сможете напрямую вызывать конечный автомат.

Что-то вроде:

myStateMachine->myEvent();

и он должен работать без какого-либо переключателя. Но обратите внимание, это может иметь место даже для некоторых объектов, которые не работают с асинхронными событиями. Это также указывает на то, где вы можете просто использовать наследование, чтобы получить тот же эффект. Если у вас есть:

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

case type2:
  doSomethingElse();
  break;
}

обычно правильный метод ООП - создать два фактических типа Type1, Type2, оба получаются из abstract base TypeBase, с виртуальным методом doSomething (), который делает то, что вам нужно. Это полезно потому, что это означает, что вы можете «закрыть» обработку (в смысле открытого / закрытого принципа) и при этом расширять функциональность, добавляя новые производные типы по мере необходимости (оставляя его открытым для расширения). Это сохраняет ошибки, как сумасшедшие, потому что позволяет разработчикам получить доступ к этим операторам switch, которые могут быть довольно уродливыми и запутанными, вместо этого инкапсулируя каждое отдельное поведение в отдельных классах.

4 - Теперь посмотрите, чтобы исправить проблемы с нитями. Определите все объекты, используемые из нескольких потоков. Сделай список. Теперь, как они используются? Некоторые из них всегда используются вместе? Начните делать группы. Цель здесь состоит в том, чтобы найти уровень инкапсуляции, который лучше всего подходит для этих объектов, разделить объекты на отдельные классы, которые управляют их собственной синхронизацией, выяснить атомарный уровень фактических «транзакций» для объектов и создать методы классов, которые разоблачить эти значимые транзакции, скрытые за кулисами с соответствующими мьютексами, условными переменными и т. д.

Возможно, вы говорите: «Это звучит как большая работа! Почему все это вместо того, чтобы просто писать все над собой?» Хороший вопрос! :) Причина на самом деле проста: если вы собираетесь делать все самостоятельно, это те шаги, которые вы должны делать в любом случае. Вы должны определить свои состояния, свой динамический полиморфизм и получить контроль над многопоточными транзакциями. Но если вы начнете с существующего кода, у вас также будут все те невысказанные бизнес-правила, которые никогда не были документированы и могут привести к неожиданным ошибкам. Вам не нужно доводить все до конца - если вы подозреваете, что это ошибка, обсудите логику с людьми, которые работали с системой в прошлом (если есть), QA или с кем-либо, кто мог бы идентифицировать ошибки, и посмотрите, действительно ли это должны быть перенесены. Но вам нужно на самом деле оценить, какие ошибки есть, так или иначе, или вы не можете кодировать то, что действительно требовало кодирования.

В конце концов, это ручной процесс, который является частью разработки программного обеспечения. Существуют инструменты CASE, которые могут помочь составить диаграммы состояний и даже опубликовать их в коде, есть инструменты рефакторинга, подобные тем, которые встречаются во многих IDE, которые могут помочь перемещать код между функциями и классами, и аналогичные инструменты, которые могут помочь определить потребности потоков , Тем не менее, эти вещи не должны быть подобраны для одного проекта. Их нужно изучать на протяжении всей вашей карьеры, подбирая их и изучая их глубже за годы работы, поскольку они являются частью работы инженера-программиста. Они не делают это для вас. Вам все еще нужно знать, почему и как, и они просто помогают сделать это более эффективно.

0 голосов
/ 14 сентября 2011

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

0 голосов
/ 13 сентября 2011

Звучит так, будто твоя лучшая ставка (глоток!) Может начаться с нуля, если она так ужасно сломана, как и ты.Есть ли документация?Не могли бы вы начать создавать какое-то более разумное программное обеспечение на основе документов?

Если полная перезапись невозможна (а в моем опыте их нет), я бы попробовал кое-что из следующего:

  1. Если у вас его еще нет, нарисуйте архитектурную картину всей системы.Выясните, как все элементы должны предполагаться для совместной работы, и это поможет вам разбить систему на потенциально управляемые / тестируемые части.
  2. Есть ли у вас какие-либо требования или план тестирования вместо?Если нет, можете ли вы написать один и начать внедрять модульные тесты для различных частей кода / функциональности, которые уже существуют?Если вы можете сделать это, вы можете начать рефакторинг, не нарушая того, что работает в данный момент.
  3. Как только вы немного разбили вещи, начните встраивать свои модульные тесты в интеграционные тесты, которые объединяют больше функций.

Я сам их не читал, ноЯ слышал хорошие вещи об этих книгах, которые могут дать несколько советов, которые вы можете использовать:

  1. Рефакторинг: улучшение дизайна существующего кода (серия Object Technology) .
  2. Эффективная работа с устаревшим кодом (Роберт С. Мартин)

Удачи!: -)

...