Глубинные преступления против проекта
Jun. 8th, 2023 05:06 pmОдно из самых сложных и повреждающих действий в проекте - повышение сложности проекта без должной отдачи.
Когда человек сталкивается с новым требованием в проекте, он открывает код, ищет, куда что дописать. Находит. Дописывает. Фича готова. Если проект достаточно покрыт тестами, то старые фичи/юскейсы не сломаны. Задача сделана, можно сказать, на отлично.
... Но в глубине...
В каждом месте, где он дописал, изменение, грубо говоря, можно свести к:
* всегда делать что-то (добавлять timestamp к сообщению). Сложность не изменилась, точнее, изменилась на несколько строчек. С точки зрения необоснованной сложности вопросов нет. Могут быть вопросы по дублированию кода (если одна и та же строчка получения/форматирования времени появилась в нескольких местах).
* Делать что-то по условию (если у нас есть фичефлаг 'timestamp', добавлять timestamp). Был линейный код, теперь у нас ветвление. Маленькое или большое - но ветвление. В декартовом произведении состояний приложения всё удвоилось.
* Делать что-то по условию в ветвях условий. Если это одно и то же, то это просто дублирование кода и предыдущий случай, если же это разные действия, то мы получаем нарушение инварианта, в котором, возможно, есть не покрытые тестами ветки.
* Добавить трекинг фичефлага или "режима", или "глобальной переменной". Теперь весь (или существенная часть) кода параметризирована, и, возможно, имеет необходимость отвечать на вопрос "что делать в этой ситуации" даже там, где фича не нужна или не имеет смысла.
* Вызывать распространяющиеся изменения (например, фичефлаг - линейный код в месте использования, но новый код вызывает расползающиеся изменения в компонентах, которые зависили от старого кода, включая "накладывание" эффектов идущих по разным ветвям кода на другой код, который вынужден вместо одного "да/нет" уметь реагировать на комбинацию действий разных ветвей кода (А: да, Б: нет; А: да, Б: да, А: нет, Б: да, А: нет, Б: нет).
* Вызвать появление нескольких версий интерфейсов или нарушение контрактов интерфейсов из-за появления вариантивности из-за фичи, включая необходимость думать о ковариантности или контр-вариантности (паталогический случай: когда в код передают сущность (коллбэк, замыкание), ожидающую объект с фичей или без фичи как параметр); особо тут обстоит вопрос с контрактами для внешних потребителей.
* Вызывать нелинейные сайд-эффекты, проявляющиеся через нетипизированные изменения среды исполнения (блокировки из-за чрезмерно активной записи, переполнения, etc).
И всё это выглядит как feature done, но последствия для проекта совершенно разные.
А вот заметить это глубинное преступление очень трудно. Feature done? Ну да, пришлось добавить matrix, ну да, мы теперь с помощью if'а вычисляем зависимости вместо линейного списка, у нас появилась ещё одна переменная, которую по Глубокой Причине надо выставлять в явном виде и помнить про её существование всем остальным вокруг.
Если это всё немного обобщить, то преступления тут нескольких видов:
* Повышение цикломатической сложности
* Нарушение инвариантов
* Добавление паталогических связей (с особым преступлением, когда оказываются связаны вместе два компонента, которые раньше были независимы)
* Экспозиция сложности в контракты
* Повышение нестабильности сайд-эффектов.
... И я не имею ни малейшего представления как это можно заметить, кроме как на интуиции, и то, не всегда.
Когда человек сталкивается с новым требованием в проекте, он открывает код, ищет, куда что дописать. Находит. Дописывает. Фича готова. Если проект достаточно покрыт тестами, то старые фичи/юскейсы не сломаны. Задача сделана, можно сказать, на отлично.
... Но в глубине...
В каждом месте, где он дописал, изменение, грубо говоря, можно свести к:
* всегда делать что-то (добавлять timestamp к сообщению). Сложность не изменилась, точнее, изменилась на несколько строчек. С точки зрения необоснованной сложности вопросов нет. Могут быть вопросы по дублированию кода (если одна и та же строчка получения/форматирования времени появилась в нескольких местах).
* Делать что-то по условию (если у нас есть фичефлаг 'timestamp', добавлять timestamp). Был линейный код, теперь у нас ветвление. Маленькое или большое - но ветвление. В декартовом произведении состояний приложения всё удвоилось.
* Делать что-то по условию в ветвях условий. Если это одно и то же, то это просто дублирование кода и предыдущий случай, если же это разные действия, то мы получаем нарушение инварианта, в котором, возможно, есть не покрытые тестами ветки.
* Добавить трекинг фичефлага или "режима", или "глобальной переменной". Теперь весь (или существенная часть) кода параметризирована, и, возможно, имеет необходимость отвечать на вопрос "что делать в этой ситуации" даже там, где фича не нужна или не имеет смысла.
* Вызывать распространяющиеся изменения (например, фичефлаг - линейный код в месте использования, но новый код вызывает расползающиеся изменения в компонентах, которые зависили от старого кода, включая "накладывание" эффектов идущих по разным ветвям кода на другой код, который вынужден вместо одного "да/нет" уметь реагировать на комбинацию действий разных ветвей кода (А: да, Б: нет; А: да, Б: да, А: нет, Б: да, А: нет, Б: нет).
* Вызвать появление нескольких версий интерфейсов или нарушение контрактов интерфейсов из-за появления вариантивности из-за фичи, включая необходимость думать о ковариантности или контр-вариантности (паталогический случай: когда в код передают сущность (коллбэк, замыкание), ожидающую объект с фичей или без фичи как параметр); особо тут обстоит вопрос с контрактами для внешних потребителей.
* Вызывать нелинейные сайд-эффекты, проявляющиеся через нетипизированные изменения среды исполнения (блокировки из-за чрезмерно активной записи, переполнения, etc).
И всё это выглядит как feature done, но последствия для проекта совершенно разные.
А вот заметить это глубинное преступление очень трудно. Feature done? Ну да, пришлось добавить matrix, ну да, мы теперь с помощью if'а вычисляем зависимости вместо линейного списка, у нас появилась ещё одна переменная, которую по Глубокой Причине надо выставлять в явном виде и помнить про её существование всем остальным вокруг.
Если это всё немного обобщить, то преступления тут нескольких видов:
* Повышение цикломатической сложности
* Нарушение инвариантов
* Добавление паталогических связей (с особым преступлением, когда оказываются связаны вместе два компонента, которые раньше были независимы)
* Экспозиция сложности в контракты
* Повышение нестабильности сайд-эффектов.
... И я не имею ни малейшего представления как это можно заметить, кроме как на интуиции, и то, не всегда.