Глубинные преступления против проекта
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'а вычисляем зависимости вместо линейного списка, у нас появилась ещё одна переменная, которую по Глубокой Причине надо выставлять в явном виде и помнить про её существование всем остальным вокруг.
Если это всё немного обобщить, то преступления тут нескольких видов:
* Повышение цикломатической сложности
* Нарушение инвариантов
* Добавление паталогических связей (с особым преступлением, когда оказываются связаны вместе два компонента, которые раньше были независимы)
* Экспозиция сложности в контракты
* Повышение нестабильности сайд-эффектов.
... И я не имею ни малейшего представления как это можно заметить, кроме как на интуиции, и то, не всегда.
no subject
Date: 2023-06-08 05:57 pm (UTC)Хороший анализ. Эта фигня происходит вечно. Лекарство, как я понимаю - рефакторинг. Увеличение сложности должно приводить к какой-то регуляризации структуры. Но обычно все кончается большой жопой и желанием все переписать.
no subject
Date: 2023-06-09 06:18 am (UTC)Заметить мало, нужно еще иметь желание, возможность и способность отреагировать, а это возможно не всегда (подтверждением чему есть мой текущий проект, но то отдельная история).
no subject
Date: 2023-06-09 01:43 pm (UTC)... А с рефакторингом есть ещё такая жопа: если тебе сначала сделали (или ты сделал), а потом ты решил рефакторить, отложив на потом, то не получается. Потому что часть привнесённой сложности стала интегральной и её трудно убрать. Особенно, если на неё ещё что-то повисло, причём как на единственный метод что-то сделать.
Настоящий тру гуру путь - увидеть, что фича требует рефакторинга, сделать рефакторинг в процессе, или даже до реализации фичи (подготавливаю проект к тому, чтобы foobar перестал быть неявным синглтоном и стал атрибутом сепульки, или даже задумываюсь о том, почему у нас сепулька и foo - это разные сущности, хотя обе они делают одно и то же зависят от одного и того же, но делают это чуть-чуть по-разному).
Но это совершенный сверхмозг и высшие достижения. 90% времени - пошёл, принёс фичу и ложечку говна в общую бочечку кода, нати, читайте.
no subject
Date: 2023-06-09 01:48 pm (UTC)Нет, протекающие абстракции, это (во-первых объективная реальность, а во-вторых) только часть проблемы.
То, про что я говорю, это принесение несправедливой (незаслуженной) сложности. Она вполне может быть и в форме прочной абстракции, кстати. Но, например, абстракции неудобной (в которой мы сначала прогибаем всё под абстракцию, а потом разгибаем обратно в рабочее состояние).
Мне пока больше всего импонирует идея о паталогических связях. В этом случае можно явно тыкать в проблему: вот эта штука (строка кода) неявно зависит от изменения вот этой строки, потому что обе они оказываются зависящими от вот этой строки.
no subject
Date: 2023-06-09 03:34 pm (UTC)Дык. Я когда в Америку приехал, мне там дали хрень, добавить функциональности (покрыть тестами все существующие символы юникода); делов-то ничего, но мне дали уже "готовый код", написанный филологом (автором словаря какого-то африканского языка). Я приужахнулся, стал расчищать площадку. А Джон моему менеджеру пожаловался, что я его код починяю. А менеджер такой - "а зачем это ты делаешь?" А я такой (только что из Питера) - да там очень плохой код, надо расчистить место. А менеджер такой - "Ой! Так говорить в Америке нельзя!" (Сам он за пару лет до меня притащился из Швеции.) Ну нельзя так нельзя. Могу не говорить. А чо делать-то, надо что-то? Короче, меня оставили в покое, а с Джоном мы потом помирились.
Ну с тех пор вот так вот и поступаю - как считаю нужным.
no subject
Date: 2023-06-09 03:35 pm (UTC)see "Accidental Complexity".
no subject
Date: 2023-06-09 06:19 pm (UTC)Цена вопроса миллиарды (если компания просуществовала 20 лет это вполне ожидаемая цена) - могли бы и соломки с рефакторингом подстелить а встает он почему-то неожиданно для управленцев
https://ivypanda.com/essays/atampt-companys-wireless-self-destructs/
https://en.wikipedia.org/wiki/3Com
no subject
Date: 2023-06-09 07:48 pm (UTC)Но для этого надо, чтобы программисты работали в гильдиях, цехах, вроде ныне существующих медиков или юристов.
no subject
Date: 2023-06-12 10:55 am (UTC)Ну, на самом деле говорить "код плохой" для кода, который уже в продакшене, это ошибка. Он может не подходить под новую задачу или не соответствовать новым требованиям, но в целом, если речь идёт не про нарушение старых требований, код хороший, если работает.
... Я для себя давно поменял модель отношения к коду: когда ты работаешь со старым кодом для новых требований, разумеется, он не подходит к новым требованиям, и требуется адаптация (на самом деле в обе стороны - требования тоже не константные и могут подвинуться, если очень нужно). То есть вместо "ужас-ужас-ужас", "у нас новые требования и они расходятся со старым кодом".
no subject
Date: 2023-06-12 10:57 am (UTC)Это ж были тесты. Которые частенько ничего вообще не рапортовали. Ну как перегоревшая сигнальная лампочка - и в продакшене, и рапортует успех.
no subject
Date: 2023-06-12 10:59 am (UTC)И к тестам это тоже относится, они проверяли в одной реальности, а сейчас реальность уже другая. Тесты, кстати, по своим требованиям гниют даже быстрее кода, потому что человек закрывал одну проблему, а потом проблему убрали, и теперь тест либо не то проверяет, либо всегда успех, потому что поломать не может.
... И отдельная религия - это тестовые остнастки (фикстуры и т.д.). Они с одной стороны гниют из-за изменений продакшена, с другой стороны создают свои требования к тестам (которые вовсе не очевидны), с третьей их всё время норовят в тестах использовать "не так, как задумывалось"...
no subject
Date: 2023-06-12 11:00 am (UTC)В смысле, отказ что-то писать? Или я тебя не понял.
no subject
Date: 2023-06-12 12:30 pm (UTC)no subject
Date: 2023-06-12 12:44 pm (UTC)Ну, занимать позицию ты можешь любую, но в целом, в коммерческой разработке ценность ПО - вообще говоря, величина, зависящая от того, где этот софт будут применять. Если идеальную архитектуру не задеплоят, то грош цена этой архитектуре. Если лапша, от которой кровавые слёзы, делает своё дело, она работает, и ценна.
Дальше тут вопрос комфорта разработки и качества разработки, то есть профессиональный конфликт между разработчиками и менеджментом, который решается путём переговоров (у каждой стороны есть существенные и несущественные требования и между ними нужно найти компромисс).
Разработчики, посылающие менеджеров нахер, имеют возможность продолжать разработку только если им это позволяют. Менеджеры (как представители компании), которые срали на мнение разработчиков, имеют возможность продолжать использовать и развивать продукт, только если разработчики это поддерживают.
Каждая из сторон имеет свою правду и свой интерес, находящийся вне интереса другой стороны, и поиск компромисса - основа для успешной совместной работы.
Если одна сторона идёт на баррикады, то всё ломается. Всё, включая продуманную архитектуру и время на рефакторинг (которого остаётся 0, потому что отдел расформировали).
no subject
Date: 2023-06-13 11:00 am (UTC)Проблема с обоими вариантами в том, что требования будут реализованы силами других людей. Быстро, без переработки архитектуры и с ещё бо́льшим накоплением техдолга.