Статьи

Скажите, не спрашивайте в случае веб-службы

Это пост, не зависящий от языка: он действителен для каждого объектно-ориентированного императивного языка, такого как Java, C #, PHP.

Начнем с самого начала: адаптер веб-службы

У меня было требование: скачивать сообщения из определенной группы в LinkedIn для анализа их контента. Пока все хорошо: для любой интеграции с моими приложениями я следую Шестиугольной Архитектуре .

По словам Кокберна , гексагональная архитектура имеет ряд портов (обычно интерфейсы, определенные на языке), где вы можете подключить адаптеры, чтобы приложение действительно работало. Постоянство имеет дело с интерфейсами репозитория; интеграция веб-сервисов с интерфейсом WebServiceName. В моем случае реализация — это объект, который запрашивает API-интерфейс LinkedIn через HTTP.

Остальная часть приложения зависит только от интерфейсов портов : тестирование становится проще, поскольку для новых тестовых случаев вы можете подключить Test Double для веб-службы, которая способна возвращать все фальшивые ответы XML (или JSON), которые вам нужны.

С этой привычкой я начал тест-драйв этого дизайна:

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

Подводя итоги: теперь мой код зависит от интерфейса, и я могу протестировать большую его часть с заглушкой, возвращающей поддельные ответы XML. На самом деле я просто поместил один объект перед ним, чтобы проанализировать XML, поэтому с этого момента остальная часть системы будет работать с Plain Old YourLanguage Objects . Правильно? Правильно.

Вы можете увидеть проблему еще?

Разве добытчики не были злыми? Я издеваюсь или заглушаюсь?

Да, методы получения объектов, содержащих логику, не являются моим определением чистого кода. Однако, когда я пишу адаптеры, я заполняю их классами: getPosts (), getUsers (), getComments () …

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

Я смотрел презентацию на Ruby (чтобы писать с помощью Java-приложения, как программист на PHP … Я говорил вам, что эта статья не зависит от языка) под названием «Почему вы не получаете фиктивные объекты?».

Выделенная проблема заключалась в том, что мы часто пишем макеты, которые становятся заглушками, и мы проверяем переданные им аргументы и результат вычислений:

$mock = $this->getMock();
$mock->expects($this->once)
     ->method('whatIsThis')
     ->with(42)
     ->will($this->returnValue("The answer"));
...bit of work to obtain $response from a real object composing the mock...
$this->assertEquals("According to Deep Thought, 42 is The answer", $response);

Основанная на состоянии (утверждение) и основанная на поведении проверка ( с (42) ) в то же время становятся запахом для меня . Этот вид тестов также приводит к тестированию и производству кода, которые являются зеркальными.

В нашем примере LinkedIn я должен проверить параметры ( groupId == 23 и postsToFetch == 10 или что-то еще), и что класс, составляющий службу, выполняет свою работу (возвращая объекты домена Post, содержащие правильные значения, проанализированные из XML).

Голливудские объекты

Также была интересная дискуссия о голливудских объектах в списке рассылки GOOS . Голливудские объекты были определены как гипотетические объекты, которые не имеют геттеров, а просто методы с обратными вызовами:

mainObject.getField();
//becomes:
mainObject.tellField(objectWhoNeedsIt);

Недостающий элемент в этом обсуждении (я, возможно, пропустил это, даже если кто-то написал), заключался в том, что objectWhoNeedsIt был передан в стек, а tellField () — это уровень косвенности, который я нахожу бесполезным в большинстве случаев.

Но если objectWhoNeedsIt передается в конструктор mainObject, он становится соавтором. Вызов метода становится просто mainObject.tellField ().

Мое новое решение

Теперь, когда я обновил свои идеи по составлению объектов, давайте попробуем исключить метод получения в LinkedInService:

Inverted Dependency Inversion … хорошее имя. PostsParser, в свою очередь, передает свои вновь созданные объекты Post кому-либо еще, и так далее, и так далее. Нет необходимости в геттерах ScribeLinkedInService, и мне даже не нужна заглушка для сервиса; все тестовые двойники, которые я использую, все еще являются имитацией моих собственных типов, но исключая услугу.

Повторите со мной:

Объектно-ориентированное программирование основано на объектах, передающих сообщения друг другу, а не на вызовах методов.

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

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

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

Почему я издеваюсь реже, чем могу?


Инструменты, которые мы используем, оказывают глубокое (и коварное!) Влияние на наши привычки мышления и, следовательно, на наши мыслительные способности.
— Дейкстра

I originally used only JUnit and PHPUnit for TDD: the absence of a mocking framework in the first and of decent matchers in the second has lead me to favor state-based verification with lot of assert*() even when mocks proved equally capable. As a consequence, I wrote a lot of getters instead of following the Tell, Don’t Ask principle. State-based verification is also how I learned (and taught) TDD, since it is the most immediate method.

But now I have transformed my LinkedIn code: it’s easier to test with mocks, and it’s easier for me to substitute objects.