Паттерн Input-Do-Output


Уточнение Single Responsibility Principle

Мне нравится принцип единственной ответственности. Чувствуется в нём что-то глобальное, основательное. Что мне не нравится — так это отсутствие понимания, что же он, чёрт возьми, конкретно означает. Ну вот что конкретно мне нужно сделать, чтобы его соблюсти? Как на пальцах мне понять, что вот тут ответственность одна, а там несколько?

Чёткого понятного ответа я так и не нашёл (не факт, что он существует).

Но зато нашёл несложное эвристическое правило, позволяющее заметно снизить сложность методов.

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

Что такое получение

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

Методом исключения — это все способы добыть данные кроме двух: получить их через аргументы метода или поля объекта.

Что такое отправка

Если первый пункт ясен, то по второму вопросов тоже не будет. Всё то же самое, только в обратном направлении.

Единственное движение данных, которое не является отправкой — передать из метода через возвращаемое значение. Точка.

По моему твёрдому убеждению, присваивание переменной любого значения тоже является отправкой (вопрос: является ли получением чтение значения из переменной? — нет, на мой взгляд не является).

И наконец — преобразование

Тут вообще ничего конкретного сказать невозможно. Были одни данные, стали другие — вот и преобразование. Любое вычисление и любая обработка. Самое главное, что в моменты вычисления или преобразования мы не получаем данные из внешних источников — только через аргументы функций и поля. И, соответственно, никуда не передаём. И даже не присваиваем. Только возвращаем из метода.

Почему всё так?

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

Сегодня мы получаем данные из апи, а завтра захотим с целью отладки грузить их из файла.

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

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

Любые эволюции кода нам доступны, если только мы изначально разделили транспортировку данных и их изменение.

Что значит «должны заключаться в отдельных методах»

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

В тот момент, когда мы делаем метод для преобразования, мы не думаем о том, откуда возьмутся исходные данные, и куда они потом денутся — только преобразование, и всё.

Дальше мы делаем один или несколько методов для загрузки данных в память. Из базы, из файла, из апи — как душе угодно (или заказчику). Аналогично метод/методы для отправки результатов: в файл, в базу, на стандартный вывод или ещё дальше.

В конце концов закономерно возникает вопрос: а где то место, в котором мы будем всю эту красоту вызывать?

Методы-контроллеры

И тут нам на помощь приходит схема MVC, которая сама по себе тоже является воплощением Single Resposibility Principle… и вообще, погоди-ка, мы же что-то такое только что обсуждали!

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

Только MVC делит, условно говоря, целые слои приложения, а IDO — всего лишь отдельные методы.

Что делает контроллер? Заводит внешние данные в систему (?), запускает обработку (model), затем куда-то отдаёт (view). Controller — Model — View.

Если убрать лишний пафос, то этой же работой может заниматься даже самый незначительный метод, и он будет ничем иным, как контроллером. Input — Do — Output.

Что дальше