Худшие практики разработки и архитектуры

image

Я собрал худшее из худшего! Оказалось, что хороших практик — море, и разбираться в них долго, а вот плохих, реально плохих, — считаные единицы.

Понятно, что плохие практики не отвечают на вопрос: «А как делать-то?» —, но они помогают быстро разобраться в том, как не делать.

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

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

Это если команда одна. А если разработчики на пятом проекте новые, то начинается самое весёлое — этот сталактит надо ещё прочитать.

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

Поток лавы


Итак, дамы и господа, позвольте представить: первый на нашей сцене — поток лавы! В целом про него я уже примерно рассказал, и вы представляете, откуда он такой берётся и что делает. Обратите внимание: паттерн подкупающе хорош в кратковременной перспективе и становится плох только на дальней дистанции. И смертельно плох на очень дальней. Поэтому на всякий случай давайте формально отмечу основное.

Симптомы:

  • Непонятно откуда взявшиеся переменные.
  • Не относящиеся к задаче фрагменты кода.
  • Очень странное покрытие документацией, много выглядящего важным за её пределами.
  • Архитектура слеплена из предыдущих трёх архитектур.
  • «Да кто её будет смотреть, просто подключайте к проекту!»
  • Устаревшие интерфейсы.

Что будет дальше:, а дальше либо всё это разбирать, либо разработчики возьмут ветку целиком и унесут дальше, потому что её слишком сложно рефакторить, но она работает. Через три итерации код невозможно будет задокументировать во внятные сроки и в него нельзя будет быстро внести изменения.

В API-driven-подходе + Domain-Driven Development такого в принципе не должно случаться, а мы хотим думать, что придерживаемся этих идей. Точнее, архитекторы уверены, что придерживаемся, а дальше уже как пойдёт в конкретных командах.

Как и в других случаях ниже, тут возможно отдельное развлечение — прогулка по минному полю, когда вы не знаете, какой фрагмент кода как точно работает. И некоторые, казалось бы, очевидные и прямые решения приводят к интересным для отладки последствиям. Если к вам подходит более опытный разработчик и говорит: «Ты так не делай, сейчас расскажу, как обойти, эта функция вообще опасная, с ней лишний раз не связывайся» — это как раз оно.

Blob


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

Эта штука перестаёт быть в полной мере объектно-ориентированной и возвращает нас в мрачные времена расцвета программирования. Собственно, на практике я встречал такие конструкции только в банковском легаси. Всё, что было примерно после нулевых, уже проектировали с головой независимо от того, объектное или функциональное там программирование. Даже старые монолиты могут быть разделены на функции так, что получается модульная структура, легко переживающая изменения (она только релизится вместе, а разрабатывается независимо).

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

Думаю, что несильно ошибусь, если скажу, что у каждого банка из топ-10 в России есть система, которой больше 15 лет и в которой есть штуковина размером с фреймворк для бизнес-логики, вокруг которой аккуратно расставлены обработчики и разные внешние интерфейсы. Сама же система не поддаётся членению и не может быть переписана в ближайшие годы.

Непрерывное устаревание


Здесь всё просто: нужно смотреть, что сейчас новое и правильное из технологий, и внедрять то, что было новым хотя бы пару лет назад. Ну или не внедрять, но осознанно, понимая, что дальше произойдёт с вашим кодом.

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

Заканчивается это либо тем, что система умирает от старости и больше становится не нужна, либо рефакторингом, больше похожим на переписывание с нуля.

Функциональная декомпозиция


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

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

Вот простой пример функциональной декомпозиции:

Выяснять плюсы и минусы ООП тут не планируем, это лишь простая демонстрация усложнения понимания в процедурном решении.

Процедурная версия расчёта кредита для клиента.

«Плохой» вариант:
image

Объектно-ориентированная версия расчёта кредита для клиента.

«Хороший» вариант:
image

Распутывание такого кода, когда он на всём проекте, — отличное приключение. Обычно такая практика случается, когда на проекте — несколько джунов, и нет никого, кто выступил бы архитектором. Или когда разработчики быстро клепают MVP, понимая, что завтра этот код полетит в корзину, а в итоге он используется годами. Иногда я вижу такое в коде аутсорс-компаний, которые берегут время разработчиков.

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

Лодочный якорь


Это когда вам досталось много кода, и весь этот код бесполезен в рамках задачи. Я такое видел после слияний-поглощений компаний, когда из двух систем компаний в итоге делают одну. Или когда вам достаётся код без базы данных, часть логики реализована в базе — и удачного вам сбора требований и попыток доработки.

В общем, так бывает, что у вас есть что-то не просто неиспользуемое, а ещё и мешающее. Такой код надо вырезать целиком как можно быстрее.

Золотой молоток


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

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

Я встретился с золотым молотком в момент, когда Кафка стала хорошо протестированной шиной. А у нас в проекте везде применялась интеграция через IBM MQ. Пришлось потратить много времени на защиту нового решения интеграции и убедить коллег в её преимуществах для нашей очередной интеграционной задачи.

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

Важно понимать: это обоснованное желание остаться в рамках старой технологии, или «синдром утёнка», когда первая изученная технология кажется лучшей.

Спагетти-код


Вообще-то это группы объектов, написанные для конкретного бизнес-процесса, но не предназначенные для использования в другом. На практике это скопипащенные куски кода, которые в плюс-минус одинаковом виде встречаются раз так 5–10 минимум в разных модулях.

Пример спагетти-кода из Linux SCSI driver (с небольшими изменениями) начала нулевых:
wait_nomsg:
        if ((inb(tmport) & 0x04) != 0) {
                goto wait_nomsg;
        }
        outb(1, 0x80);
        udelay(100);
        for (n = 0; n < 0x30000; n++) {
                if ((inb(tmport) & 0x80) != 0) {        /* bsy ? */
                        goto wait_io;
                }
        }
        goto TCM_SYNC;
wait_io:
        for (n = 0; n < 0x30000; n++) {
                if ((inb(tmport) & 0x81) == 0x0081) {
                        goto wait_io1;
                }
        }
        goto TCM_SYNC;
wait_io1:
        inb(0x80);
        val |= 0x8003;          /* io,cd,db7  */
        outw(val, tmport);
        inb(0x80);
        val &= 0x00bf;          /* no sel     */
        outw(val, tmport);
        outb(2, 0x80);
TCM_SYNC:
/* ... */
small_id:
        m = 1;
        m <<= k;
        if ((m & assignid_map) == 0) {
                goto G2Q_QUIN;
        }
        if (k > 0) {
                k--;
                goto small_id;
        }
G2Q5:                   /* srch from max acceptable ID#  */
        k = i;                  /* max acceptable ID#            */
G2Q_LP:
        m = 1;
        m <<= k;
        if ((m & assignid_map) == 0) {
                goto G2Q_QUIN;
        }
        if (k > 0) {
                k--;
                goto G2Q_LP;
        }
G2Q_QUIN:   /* k=binID#,       */

Что надо знать: хотя бы принципы функционального программирования. А в идеале — ООП и архитектурные принципы типа DDD.

Это тот самый код, который имеет очень яркую региональную принадлежность, и если вы аутсорсите, например, в Индию, то встречаться с ним придётся довольно часто.

Копипаста


Сначала были только книги и журналы. Код из них, конечно, можно было перепечатать, но к катастрофам это не приводило. Появился Google (как первый удобный поисковик), и разработчики стали много гуглить. Непонятные куски кода стали появляться в проектах. «Эта штука делает то-то, и не трогайте внутри» стало нормой. Но такими кусками закрывались какие-то дыры, где компетенций не хватало. Появился StackOverflow, и код часто стал состоять из таких скопированных кусков, а разработчики только криво сшивали их между собой. И, наконец, вершиной копипасты стал ChatGPT4 (и аналоги), который умеет сшивать разные фрагменты кода между собой лучше бездумного копирования.

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

И отдельно — про грибы


Это немного не про разработку, но последствия ощущаются и в коде. Есть такая история — «Mushroom management», когда вместо задачи даются требования. Причём частями. По-русски это называется «Сиди в темноте и ешь навоз», что очень точно описывает основные принципы выращивания грибов. Так вот, если так поступать с разработчиками, то вас ждёт море интересного на приёмочных тестах и при интеграциях. Особенно при интеграциях.

Пока всё


В эту статью не попали, например, такие антипаттерны, как Poltergeists, потому что слабо тянет на антипаттерн, Тупик (Dead End), потому что это следствие рассмотренных выше, Непрерывное устаревание и Лодочный якорь, Кладж ввода (Input Kludge) или Слепая вера (Blind faith), потому что он должен автоматически решаться тестами, и другие, которые не относятся напрямую к разработке (или мало распространены с точки зрения автора), но при желании читателей рассмотрим и остальные.

Такие плохие практики называются «антипаттерны» в книге «Банды четырёх» «Шаблоны проектирования», где как раз описываются практики хорошего программирования. Хорошие практики назвали «паттернами», а противоположные — «антипаттернами». Про антипаттерны можно посмотреть ещё вот здесь:


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

© Habrahabr.ru