[Перевод] 10 ошибок, приводящих к оверинжинирингу ПО

Несколько вещей гарантированно будут увеличиваться со временем: расстояния между звёздами, энтропия вселенной и бизнес-требования к ПО. Многие статьи пишут «Не усложняйте!», но не пишут почему или как это сделать. Вот вам 10 ясных примеров. 1. Инженерам виднее
Мы, инженеры, считаем себя умнейшими людьми. Ну, поскольку мы создаём разные штуки. И эта ошибка часто приводит к оверинжинирингу. Если вы спланировали и построили 100 модулей — Бизнес всегда попросит у вас 101-ый, о котором вы никогда не задумывались. Если вы соберётесь с силами и решите 1000 проблем — они придут к вам и выложат на стол 10 000 новых. Вы считаете, что у вас всё под контролем, а на самом деле вы даже не представляете, в каком направлении вас завтра поведёт дорога.
image
За мои 15 лет работы программистом я ещё ни разу не видел, чтобы Бизнес выдал законченные и стабильные раз и навсегда требования к ПО. Они всегда меняются, расширяются. И это природа бизнеса, а не ошибки людей, управляющих им.

Мораль: Казино (бизнес) всегда побеждает

2. Повторное использование кода
Когда Бизнес подкидывает нам всё больше и больше требований (как и ожидается), мы иногда говорим себе: «Ок, давайте попробуем сгруппировать и обобщить всё, что только можно!».
image
Вот почему большинство MVC-систем заканчиваются Большой Моделью или Жирным Контроллером. Но, как мы уже выяснили, бизнес-требования никогда не прекратят добавляться, а значит это путь в никуда. На самом деле нам нужно реагировать на это вот так:
image

Пример: однажды для одного из заказчиков мы разрабатывали модуль профиля пользователя. Начали с классического CRUD-контроллера, ну потому что это же просто профиль пользователя и там всё должно быть просто. Закончилось это всё реализацией 13 различных сценариев создания и заполнения этого профиля: регистрация через социальную сеть, обычная, упрощённая, быстрая с последующим редактированием, совсем иначе выглядящая для подсайтов, и т.д. Они имели между собой очень мало общего. Аналогично отличаются и, например, сценарии «просмотр заказа» и «редактирование заказа».
Попробуйте для начала разделить бизнес-функциональность вертикально, прежде чем делить её горизонтально. Это даже делает более простой задачу разделение монолита на микросервисы. Иногда и вовсе уже становится всё-равно монолит у вас или сервисы. В противном же случае становится всё сложнее и сложнее изменять части вашей системы.

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

Совет: Возьмите некоторую видимую пользователю часть вашей программы (форму\страницу\команду). Сколько переключений контекста понадобиться программисту, чтобы понять весь код, связанный с ней?

3. Обобщим вообще всё
(Несколько перекликается с предыдущим пунктом, но иногда случается и независимо от него)
  • Нужно подсоединиться к базе данных? Давайте напишем Обобщённый Адаптер.
  • Сделать запрос? Обобщённый Запрос.
  • Передать ему параметр? Обобщённый Параметр.
  • Собрать несколько параметров во что-то? Обобщённый Builder.
  • Отразить полученный результат во что-то? Обобщённый Data Mapper.
  • Обработать запрос пользователя? Обобщённый Запрос.
  • Что-то выполнить? Обобщённый Исполнитель.
  • и т.д.

Иногда инженеров заносит. Вместо решения бизнес-проблем они тратят время для выбора правильного родительского класса. А ответ прост.
image

Архитектура ПО всегда играет в догонялки с требованиями бизнеса. Так что даже если вы с помощью какой-то магии найдёте идеальную абстракцию сегодня, в комплекте с ней сразу будет идти срок её годности — см. пункт №1 выше — бизнес всегда побеждает. Так что лучшая характеристика качества сборки вашей архитектуры — как быстро она может быть разобрана. Есть отличная статья о том, как писать код, который будет легко удалить, а не легко изменить.
Мораль: Дублирование кода лучше, чем неверная абстракция

Более того, дублирование кода иногда помогает найти ту самую правильную абстракцию. Потому, что когда вы видите несколько частей системы, использующий одинаковый код одинаковым образом — вы можете понять, что их объединяет. Качество Абстракции определяется качеством её слабейшего звена. Дублирование кода позволяет взглянуть на разные ситуации под разными углами и увидеть границы Абстракции чётче.

4. Написание обёрток для всех внешних библиотек
Существует практика написания обёрток (врапперов) для каждой используемой внешней библиотеки (в основном из-за следования стилю основного продукта, иногда из-за причин, описанных в предыдущих двух пунктах). Очень популярный подход в энтерпрайз-программировании. Программисты на динамических языках вроде Node/Ruby/Python просто добавляют библиотеку и начинают её использовать. Но Энтерпрайз Разработчик на это пойти не может — ему необходимо создать обёртку. А потом ещё обёртку над обёрткой. Возможно, это имело какой-то смысл в 2000-ых, когда большинство библиотек представляли собой мешанину, да и вообще открытого кода было не так много.

Но сегодня уже 2016-ый год. Внешние библиотеки улучшились на порядок. Они стали просто фантастичны. Скорее всего они написаны отличными программистами и оттестированы лучше вашего основного кода. Они имеют внятный API. В них можно встроить логирование. Вам не нужно тратить своё время на написание обёрток вокруг и так уже хорошего кода. Кроме того, большинство обёрток — совершенно бессмысленны. Их интерфейс очёнь тесно связан с лежащей ниже библиотекой, часто просто в виде отражения функций «один к одному». Если в будущем библиотека измениться, большинство кода с использованием данной обёртки также придётся изменить. Создание «агностической» обёртки, способной остаться неизменной даже, например, при полной замене оборачиваемой библиотеки на другую — нетривиальная задача. Такие задачи иногда ставятся с мыслью о потенциальной «конфигурабельности» общего решения, сама идея которого будет рассмотрена чуть ниже.

Мораль: обёртки должны быть исключениями, а не нормой

5. Слепое применение метрик качества кода
Бездумное следования концепциям качества кода (вроде того, что все переменные должны быть объявлены как «private final», каждый класс должен иметь интерфейс) не сделает ваш код АВТОМАТИЧЕСКИ хорошим. Посмотрите, если ещё не видели, на энтерпрайз-версию FizzBuzz или Hello World. Прорва кода. На микро-уровне каждый класс следует принципам SOLID и использует отличные паттерны. Если вы натравите на этот код какой-нибудь инструмент анализа качества кода — он скажет, что это прекрасный продукт, прямо загляденье. Но если вы сделаете шаг назад — то сразу увидите каким кошмаром является эта программа, всего лишь печатающая «Fizz Buzz».

Мораль: всегда делайте шаг назад и смотрите на общую картину.

Аналогично, автоматические средства хорошо определяют тестовое покрытие кода, но совершенно ничего не говорят о том, тестируете ли вы то, что нужно, или нет. Они могут измерить производительность в некотором тесте, но ничего не скажут насколько хорошо ваша программа обрабатывает данные в другом случае. Все они смотрят на микро-уровень: что делает этот класс\метод\строка. И только Человек смотрит на общую картину.

Выучите другой язык программирования и попробуйте другие подходы для решения уже известных вам задач. Это сделает вас существенно лучшим программистом. Не зацикливайтесь на чём-то одном.

5.1. Слои сандвича

Давайте возьмём некоторый тесно связанный функционал и разделим его на 10–20 слоёв, где каждый слой не имеет никакого смысла без остальных. Ну, потому что мы хотим реализовать концепцию «Тестируемого кода», или «Принцип единой ответственности», или назовите это ещё как-нибудь модно. В прошлом это делалось через наследование. Класс А наследуется от Б, который наследуется от С и т.д.
image
Сегодня люди делают всё то же самое, кроме того, что каждый класс теперь представляется парой интерфейс/класс и инжектируется на следующий слой, потому что у нас же SOLID.
image
Мораль: концепции требуют вдумчивого применения. Их нельзя применять просто везде и всегда, как инструменты.

6. Синдром Новинки
Изучили дженерики. Теперь у нас простой «HelloWorldPrinter» станет «HelloWorldPrinter».
Не нужно использовать дженерики, когда реально необходимая форма будет всегда инстанциироваться одними и теми же типами данных. Параметров достаточно в большинстве случаев.

Изучили паттерн Стратегия. Теперь все операторы «if» будут стратегией.
Почему?

Научились писать DSL. Теперь у нас DSL будет везде и для всего.
Ну я даже не знаю…

Обнаружили моки. Теперь у нас будет замокано всё вдоль и поперёк.
Да ну как же…

Метапрограммирование — шикарная вещь, давайте используем здесь и здесь!
Объясни зачем…

Методы расширения\концепты\что-то ещё — хорошая штука, давайте будем использовать кругом!
А давайте не будем!

Мораль: ничего не надо применять везде и всегда. И раздел «Мораль» тоже не надо бы писать в каждый пункт.

7. «X–сть»
  • Конфигурабельность
  • Безопасность
  • Масштабируемость
  • Поддерживаемость
  • Расширяемость

Расплывчато. Неизменно. Трудно поспорить.
Пример 1: Давайте создадим CMS, чтобы клиент смог сам добавлять поля вот в эту форму.
Результат: Клиенты никогда не будут ею пользоваться. Когда понадобятся — они найдут разработчика и озадачат его. Возможно вместо полноценной CMS стоило набросать короткую инструкцию о том, как быстренько добавить поле.

Пример 2: Давайте спроектируем большой слой для работы с базами данных, для упрощения «Конфигурабельности». Мы должны получить возможность сменить базу данных правкой одного конфиг-файла.
Результат: За 10 лет работы я видел лишь один проект, где понадобилось менять одну базу данных на другую по объективным причинам. И, когда до этого дошло дело, то «правкой одного конфиг-файла» дело совсем не обошлось. Была масса сопутствующей работы. Несовместимость, пробелы в функционале. А ещё однажды клиент попросил на перевести ПОЛОВИНУ наших моделей данных в новую NoSQL базу данных. У нас был «магический файл» с подключением базы данных, но во-первых, только реляционной, а во-вторых для всех данных, а не для половины. Возможно все эти пляски с конфигурабельностью имеют смысл, когда речь идёт о чём-то вроде Оракловской базы данных, которая стоит как найм 20-ти программистов — там и правда удобно иметь возможность переключиться на что-то другое при необходимости. Но для современных баз данных, всё что нам необходимо — это простой набор вертикальных DAO-классов вместо широкого горизонтального слоя ORM. Не существует единой модели, удачно сочетающей в себе SQL и NoSQL, так что, возможно, нам действительно стоит их разделять, используя в каждом случае то одно, то другое, вместо того, чтобы пытаться совместить несовместимое и городить ужасные неверные абстракции.

Пример 3: Мы построили систему OAuth-авторизации для энтерпрайз-клиентов. Для администраторов этой системы нас попросили добавить ещё один уровень — авторизацию администраторов через Google OAuth. «Потому, что должно быть безопасно». Если кто-то взломает нашу OAuth-авторизацию, нужно чтобы хотя бы учётки администраторов остались недоступны. Google OAuth — надёжная штука, так что возражать тут вроде бы особо нечего.
Результат: Тому, кто захочет взломать данную систему совсем не нужно пробиваться через OAuth-слой. Можно найти уязвимость в чём-нибудь попроще (такие всегда есть). В итоге все затраты на поддержку двух уровней OAuth (а они проходили насквозь через всю систему) дали примерно никаких результатов. Лучше было потратить это время на устранение обычных уязвимостей.
image
Мораль: Не принимайте все эти характеристики с окончанием на »-сть» как неизменную данность. Они имеют свою цену — так что чётко определяйте Сценарий\Историю\Использование.

8. Велосипедостроение
Это всегда круто в начале. Но через несколько лет это уже будет Груз Наследия.
Пара примеров:
  • Свои библиотеки (HTTP, мини-ORM, кеширование, конфигурация)
  • Свои фреймворки (CMS, обработка событий, многопоточность, фоновые задачи)
  • Свои инструменты (система сборки, система деплоя)

Что тут плохого:

  • Тривиальные, казалось бы, задачи на самом деле требуют приличных знаний и глубокого погружения в предметную область. Какой-нибудь «запускатель процессов» требует понимания менеджмента процессов в ОС, работы демонов, системы ввода\вывода и ещё кучи всего. CMS — это не просто что-то, подставляющие данные в шаблоны — там есть зависимости, проверки, визарды, обобщения и т.д. Самая простая на вид библиотека может иметь нетривиальную функциональность.
  • Всё это нужно ещё и поддерживать, обновлять.
  • Если вы выложите код в открытый доступ — всем будет плевать. Ну, может, кроме тех, кто работал над этим кодом раньше.
  • Люди, которые разбираются в этом коде — рано или поздно уйдут. А кроме них в этой реализации не разбирается никто в мире.
  • Внедрение в проект уже существующих библиотек и фреймворков, заточка их под ваши нужды требует время прямо сейчас. Но изобретение собственных велосипедов требует куда больше времени в долгосрочной перспективе.

Мораль: Переиспользуйте. Заимствуйте хорошее. Пересматривайте свои решения.
Если вы всё-же решили строить свой велосипед — по крайней мере делайте его по принципу «внутреннего опенсорса» в своей компании. Объясните людям, зачем это нужно. Покажите первый набросок. Предложите им использовать или улучшать это. И подумайте 10 раз, стоит ли продолжать строить этот велосипед, если даже ваши коллеги не выскажут своего одобрения.

9. Развивайте свой код
Как только вы реализовали что-нибудь определённым образом — все остальные начинают это использовать в том виде, в каком это реализовано. Никто не задумывается, правильно ли это. Пока ваш код работает — это «хороший код». Люди начинают применять его даже для задач, которые он изначально не должен был решать. Получается иногда хорошо, а иногда очень плохо. И тут нужно не бояться брать и менять ваш код, совершенствуя его под текущие задачи. Здоровые программные системы всегда живут, меняются. Нездоровые — лишь дополняются. Если кусок кода не видел коммитов очень давно — с ним что-то не так, он «попахивает». Каждая часть кода должна развиваться. Вот отличная статья, описывающая это.
Вот как команды работают над задачами, а вот как они должны это делать, Каждый День:
image

Мораль: рефакторинг — это часть любой разработки. Никакой код не остаётся неизменным.

10. Неверные оценки сроков
Часто мы видим, как неплохие вроде бы команды или отдельные кодеры вдруг выдают откровенно слабый продукт. Мы смотрим на кодовую базу и удивляемся — «Как же так, неужели это было действительно создано этой командой\человеком, а я же считал их\его такими умными\умным!». Качество требует не только умения, но и времени. Даже умные разработчики часто переоценивают свои возможности. И вдруг обнаруживают себя перед горящим дедлайном, мучительно вписывающих в код ужасный костыль, дающий шанс как-то уложиться в сроки.

Мораль: неверная оценка сроков наносит вред качеству вашего проекта ещё до того, как написана первая строка кода.

Комментарии (1)

  • 25 июля 2016 в 12:10

    0

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

    Статья о том, как растут из миддлов в сеньоры.

© Habrahabr.ru