Четкое структурирование тестируемого модульного кода с растущими условиями бизнес-требований

Я пытаюсь осмыслить самый простой способ написания кода для чего-то вроде системы регистрации типа Yelp или FB, но
с компонентом проверки тоже. Я использую PHP.

Основным требованием может быть:
Регистрироваться:
если это типа «здание», мне нужно

  1. посмотрите фактическое здание по его UUID, чтобы получить его первичный ключ, который мы запишем при регистрации, чтобы позже включить их в отчеты
    (зарегистрировался в здании ‘123 Main Street’).
  2. в настоящее время они не могут быть проверены ни к чему
  3. они должны быть в Х метрах от помещения
  4. если они не проходят X метров помещения, должна быть строка исключения для регистрации, чтобы объяснить, почему.

если это типа «комната», мне нужно

  1. найдите фактическую комнату по ее UUID, чтобы получить ее первичный ключ, который
    мы войдем в систему с регистрацией, чтобы позже включить их в отчеты
    (зарегистрирован на «123 Main Street -> Комната 212»).
  2. они должны быть проверены в здании уже
  3. их уже нельзя заселить в комнату

Также есть этап «отъезда», о котором я упоминаю, чтобы вы могли понять, что это не совсем то, что Yelp, когда вы просто говорите
«Я был здесь». Это не одно действие, чтобы просто зарегистрироваться; за ним последует «проверка», о которой я здесь не буду рассказывать.
для краткости. Мой вопрос касается создания более чистых контроллеров, методов и шаблонов проектирования для структурирования кода, который
удовлетворяет вышесказанному.

Итак, запрос отправляет json, обращается к приложению (Slim), отправляется на контроллер для метода (в данном случае Post). У нас есть
их GPS-координаты, UUID здания / комнаты, в которую они проверяются, их ID (через аутентификацию) и тип
регистрации, здания или комнаты. Что-то вроде:

{
"latitude": "33.333", // these only matter if it is 'building'
"longitude": "-80.343", // these only matter if it is 'building'
"buildingUuid": "ff97f741-dba2-415a-a9e0-5a64633e13ad", // could also be 'roomUuid' ...
"type": "building", // or 'room'
"exceptionReason": null
}

У меня есть все возможности проверить их связь с координатами здания, посмотреть идентификатор здания и т. Д. Я
ищет идеи о том, как сделать этот код простым, понятным и тестируемым. С одной стороны, вы можете увидеть, как мой
операторы switch (дальний блок кода) вышли из-под контроля, но я не знаю, как еще это структурировать. Это
суть вопроса
.

Без каких-либо требований: (код Feaux)

switch($type) {
case 'building':
$model = new \Namespace\Models\Building($this->container);
break;

case 'room':
$model = new \Namespace\Models\Room($this->container);
break;

default:
throw new \InvalidArgumentException("Type {$type} not understood");
}

$model->checkIn(); // polymorphic method that all 'type' classes will have, based on an interface implemented

Я пытаюсь избегать операторов switch, но в какой-то момент мне нужно что-то использовать, чтобы выяснить, какие создать экземпляр,
право? Полиморфизм здесь не помогает, так как я понимаю, что он помогает многим операторам переключения, потому что я еще не
есть объект.

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

(код Feaux)

switch ($type) {
case 'building':
$model = new \Namespace\Models\CheckInBuilding($this->container);
$alreadyInTest = new \Namespace\Models\Buildings\BuildingTestIn($this->container);
$building = new \Namespace\Models\Buildings\Building($this->getData('buildingUuid'), $this->container);

if (!$building) {
throw new Exceptions\FailBadInput('Building not found: ' . $this->getData('buildingUuid'));
}

$model->setBuilding($building); // building object used for these properties: ID, coordinates for the lat/long 'nearby' check
break;

case 'room':
$model = new \Namespace\Models\CheckInRoom($this->container);
$alreadyInTest = new \Namespace\Models\Rooms\RoomTestIn($this->container);
$room = new \Namespace\Models\Rooms\Room($this->getData('roomUuid'), $this->container);

if (!$room) {
throw new Exceptions\FailBadInput('Room not found: ' . $this->getData('roomUuid'));
}

$model->setRoom($room); // room object used for these properties: ID
break;

default:
throw new \InvalidArgumentException("Type {$type} not understood");
}

$model->setAlreadyInTest($alreadyInTest);
$model->setData($this->getData());
$model->checkIn();
//
// Now, for 'building|room', this has all the data internal to check:
//   - if they are nearby (but only if in building)
//   - to test if they are already logged in
//   - to log the primary key ID with the request
//

//
// In addition, and not covered here, if they are not in range of the building, they can still check-in, but need to
// type a short reason why they are doing a check-in and not in nearby range ('GPS broken, etc', who knows...). So,
// would I add yet another class, instantiated here so it can be mocked in a test, and passed in empty, so it could
// eventually be used to insert an exception reason (in a different table than the check-in table)?
//

На основе шаблонов дизайна, если мне нужно добавить еще $typeЯ просто делаю это здесь, что кажется довольно хорошим. Но…
это в контроллере … он злоупотребляет статусом переключателя … и кажется хрупким / хрупким из-за всех разных
вещи, которые создаются и передаются.

У меня есть DIC, но я не знаю, что это проясняет ситуацию.

Хотя это и прояснит код здесь, я не хочу создавать экземпляры каких-либо классов в реальных моделях (например, я не хочу
для создания объекта «hereInTester» внутри объекта), потому что я хочу, чтобы это было тестируемым, и делать это похоже на
сделало бы намного более сложным тестирование / макет.

Сказав это, тесты имеют ту же проблему. Существует так много катания на этих различных требованиях и как проверить
им, что тесты не очень изолированы. Я могу издеваться над объектами InInest и Building / Room, чтобы изолировать
Метод checkIn и тестирование здания / комнаты по отдельности, но чтобы протестировать их все вместе, как в интеграционном тесте, теперь я
Риск недетерминированных тестов, потому что я считаю, что это грязный подход.

Моей последней мыслью было бы что-то вроде этого, но я слишком переживаю за то, что не откормлю контроллер: (код feaux)

switch($type) {
case 'building':
$alreadyInTest = new \Namespace\Models\Buildings\BuildingTestIn($this->container);
if($alreadyInTest->isIn()) {
throw new \InvalidArgumentException('You are already checked in to a building');
}

$building = new \Namespace\Models\Buildings\Building($this->getData('buildingUuid'), $this->container);

if (!$building) {
throw new Exceptions\FailBadInput('Building not found: ' . $this->getData('buildingUuid'));
}

$model = new \Namespace\Models\Building($this->container);
break;

case 'room':
$alreadyInTest = new \Namespace\Models\Rooms\RoomTestIn($this->container);

if($alreadyInTest->isIn()) {
throw new \InvalidArgumentException('You are already checked in to a room');
}

$room = new \Namespace\Models\Rooms\Room($this->getData('roomUuid'), $this->container);

if (!$room) {
throw new Exceptions\FailBadInput('Room not found: ' . $this->getData('roomUuid'));
}

$model = new \Namespace\Models\Room($this->container);
break;

default:
throw new \InvalidArgumentException("Type {$type} not understood");
}

$model->setData($this->getData());
$model->checkIn();

Опять же, я чувствую, что должен абстрагировать эти два if / throws (для каждого случая) в другом месте, но это, похоже, не делает этого
более простой (также: я признаю, что это не сложный пример … пока), и контроллер не является более тонким
в любом примере. Я чувствую, что последний пример для меня более понятен. Суть для меня в том, что каждый раз что-то будет
добавлено, это будет означать добавление еще к инструкции switch. Я думаю, что полиморфная система будет лучше, но так как
для проверки требований потребуются внешние классы, которые я должен был бы создать и передать тонну
объекты в любом случае, что делает его таким же сложным. Создание экземпляров классов внутри каждого объекта регистрации не так тестируемо. я
Я подумал, что, возможно, шаблоны Chain of Responsibility или Command могут быть полезны, но, похоже, не совсем подходят.

Я хожу по кругу.

Итак, один из них — лучший способ, или я мог бы сделать что-то лучше?

1

Решение

Задача ещё не решена.

Другие решения

Других решений пока нет …