Я нахожусь в проекте распределенной системы, написанном на Java, где у нас есть несколько классов, которые соответствуют очень сложным объектам реального мира. Эти объекты имеют множество методов, соответствующих действиям, которые пользователь (или некоторый другой агент) может применить к этим объектам. В результате эти занятия стали очень сложными.
Системный подход к общей архитектуре привел к тому, что многие виды поведения были сконцентрированы на нескольких классах и множестве возможных сценариев взаимодействия.
В качестве примера и для простоты и ясности, скажем, что Robot и Car были классами в моем проекте.
Итак, в классе Robot у меня было бы много методов по следующей схеме:
- спать(); isSleepAvaliable ();
- бодрствования (); isAwakeAvaliable ();
- ходить (направление); isWalkAvaliable ();
- стрелять (направление); isShootAvaliable ();
- turnOnAlert (); isTurnOnAlertAvailable ();
- turnOffAlert (); isTurnOffAlertAvailable ();
- перезарядки (); isRechargeAvailable ();
- выключить(); isPowerOffAvailable ();
- stepInCar (Car); isStepInCarAvailable ();
- stepOutCar (Car); isStepOutCarAvailable ();
- саморазрушение(); isSelfDestructAvailable ();
- умереть(); isDieAvailable ();
- является живым(); isAwake (); isAlertOn (); getBatteryLevel (); getCurrentRidingCar (); getAmmo ();
- ...
В классе автомобилей это было бы похоже:
- включить(); isTurnOnAvaliable ();
- выключи(); isTurnOffAvaliable ();
- ходить (направление); isWalkAvaliable ();
- заправлять (); isRefuelAvailable ();
- саморазрушение(); isSelfDestructAvailable ();
- аварии (); isCrashAvailable ();
- isOperational (); Ison (); getFuelLevel (); getCurrentPassenger ();
- ...
Каждый из них (Робот и Автомобиль) реализован как конечный автомат, где некоторые действия возможны в некоторых состояниях, а некоторые нет. Действия изменяют состояние объекта. Методы действия выдают, IllegalStateException
когда вызываются в недопустимом состоянии, и isXXXAvailable()
методы сообщают, возможно ли действие в данный момент. Хотя некоторые из них легко выводятся из состояния (например, в состоянии сна, бодрствование доступно), некоторые - нет (чтобы стрелять, он должен быть бодрствующим, живым, иметь боеприпасы и не ездить на машине).
Кроме того, взаимодействия между объектами тоже сложны. Например, автомобиль может содержать только одного пассажира робота, поэтому, если другой попытается въехать, должно быть выдано исключение; Если машина падает, пассажир должен умереть; Если робот мертв в транспортном средстве, он не может выйти, даже если с самим автомобилем все в порядке; Если робот находится внутри автомобиля, он не может войти в другой, прежде чем выйти; и т.п.
В результате, как я уже сказал, эти занятия стали действительно сложными. Что еще хуже, есть сотни возможных сценариев, когда робот и автомобиль взаимодействуют. Кроме того, большая часть этой логики требует доступа к удаленным данным в других системах. В результате юнит-тестирование стало очень трудным, и у нас много проблем с тестированием, одно из которых приводит к порочному кругу:
- Установки тестовых сценариев очень сложны, потому что они должны создать значительно более сложный мир для тренировок.
- Количество тестов огромно.
- Тестовый набор занимает несколько часов.
- Наш тестовый охват очень низкий.
- Код тестирования, как правило, пишется на несколько недель или месяцев позже, чем код, который они тестируют, или никогда.
- Многие тесты тоже не пройдены, в основном из-за того, что требования тестируемого кода изменились.
- Некоторые сценарии настолько сложны, что во время установки они терпят неудачу по таймауту (мы настраивали тайм-аут в каждом тесте, в худшем случае - 2 минуты, и даже если этот тайм-аут был продолжительным, мы убедились, что это не бесконечный цикл).
- Ошибки регулярно проскальзывают в производственную среду.
Этот сценарий «Робот и машина» является чрезмерным упрощением того, что мы имеем на самом деле. Очевидно, что эта ситуация не управляема. Итак, я прошу помощи и предложений: 1, уменьшить сложность занятий; 2. Упростить сценарии взаимодействия между моими объектами; 3. Уменьшите время тестирования и количество кода, который будет проверен.
РЕДАКТИРОВАТЬ:
Я думаю, что я не был ясен о государственных машинах. Робот сам по себе является конечным автоматом с состояниями «спящий», «бодрствующий», «перезарядка», «мертвый» и т. д. Автомобиль - это еще один конечный автомат.
РЕДАКТИРОВАТЬ 2: В случае, если вам интересно, что на самом деле представляет собой моя система, взаимодействующие классы - это такие вещи, как Сервер, IP-адрес, Диск, Резервное копирование, Пользователь, SoftwareLicense и т. Д. Сценарий «Робот и машина» - это как раз тот случай, который я обнаружил. это было бы достаточно просто, чтобы объяснить мою проблему.
источник
Ответы:
Государственный шаблон дизайна может быть полезна, если вы не используете его.
Основная идея в том , что вы создаете внутренний класс для каждого отдельного государства - так , чтобы продолжить ваш пример,
SleepingRobot
,AwakeRobot
,RechargingRobot
иDeadRobot
все были бы классов, реализует общий интерфейс.Методы
Robot
класса (например,sleep()
andisSleepAvaliable()
) имеют простые реализации, которые делегируют текущему внутреннему классу.Изменения состояния реализуются путем замены текущего внутреннего класса на другой.
Преимущество этого подхода состоит в том, что каждый из классов состояний значительно проще (так как он представляет только одно возможное состояние) и может быть независимо протестирован. В зависимости от языка вашей реализации (не указан), вы все равно можете ограничиться размещением всего в одном файле, или вы можете разбить вещи на более мелкие исходные файлы.
источник
Я не знаю ваш код, но на примере метода «сна» я предположу, что он похож на следующий «упрощенный» код:
Я думаю, что вы должны делать различие между интеграционными и юнит-тестами . Написание теста, который выполняется на всей машине, - это, безусловно, большая задача. Написание меньших модульных тестов, которые проверяют, правильно ли работает ваш метод сна, проще. На этом этапе вам не нужно знать, было ли должным образом обновлено состояние машины или «машина» правильно отреагировала на тот факт, что состояние машины было обновлено «роботом» ... и т. Д., Вы получите его.
Учитывая приведенный выше код, я бы издеваться на «machineState» объект и мой первый тест был бы:
Мое личное мнение таково, что написание таких маленьких модульных тестов должно быть первым делом. Вы написали, что:
Выполнение этих небольших тестов должно быть очень быстрым, и вам не нужно ничего инициализировать заранее, как ваш «сложный мир». Например, если это приложение на основе контейнера IOC (скажем, Spring), вам не нужно инициализировать контекст во время модульных тестов.
После того, как вы охватите значительный процент вашего сложного кода с помощью модульных тестов, вы можете начать создавать более трудоемкие и более сложные интеграционные тесты.
Наконец, это может быть сделано независимо от того, является ли ваш код сложным (как вы сказали, что он есть сейчас), или после того, как вы произвели рефакторинг.
источник
Я читал раздел «Происхождение» статьи в Википедии о принципе разделения интерфейсов , и мне напомнили об этом вопросе.
Я процитирую статью. Проблема: «... один основной класс Job… толстый класс с множеством методов, специфичных для множества разных клиентов». Решение: «... слой интерфейсов между классом Job и всеми его клиентами ...»
Ваша проблема, кажется, перестановка той, что была у Xerox. Вместо одного толстого класса у вас есть два, и эти два толстых класса разговаривают друг с другом вместо большого количества клиентов.
Можете ли вы сгруппировать методы по типу взаимодействия, а затем создать интерфейсный класс для каждого типа? Например: классы RobotPowerInterface, RobotNavigationInterface, RobotAlarmInterface?
источник