Как стать автором
Обновить

Чистый код — красивая архитектура. А работает ли это?

Уровень сложностиПростой
Время на прочтение12 мин
Количество просмотров4.5K
Всего голосов 34: ↑28 и ↓6+33
Комментарии31

Комментарии 31

Если копнуть глубже: как вы решаете конфликт между SOLID-принципами (например, Single Responsibility) и необходимостью проектировать горизонтально масштабируемые компоненты в высоконагруженных системах? Например, микросервисы с избыточными абстракциями могут порождать latency или сложности в orchestration.

Есть ли у вас примеры, где оптимизация архитектурных границ (через DDD, event-driven и т.п.) оказалась критичнее, чем рефакторинг «грязного» кода?

Нет у автора статьи примеров и нет возможности копнуть грубже. Это ГПТ-творчество. Возможно, привлекали человеческого редактора к сведению текста или умеют работать с ароматами, когда собирают цельную статью...

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

Напримерр, в Java-сервисе Netflix (GS2) при переносе с m5.4xlarge на m5.12xlarge ожидаемый линейный рост не подтвердился — фактическое улучшение оказалось существенно ниже. При микроархитектурном анализе выяснилось, что имеет место false sharing : соседние поля в объекте конкурировали за одну кеш-линию, генерируя постоянные инвалидации и просадки throughput. Добавиление 56-байтный padding – и voilа: ≈3.5× рост RPS и заметное снижение как средней, так и 99-го процентиля задержки.

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

Другой пример — AWS Local Zones. Netflix перенёс часть latency-sensitive логики ближе к студиям аниматоров, и теперь художники получают однозначные миллисекундные задержки при интерактивном рендеринге графики — чего невозможно добиться при кросс-региональном вызове. При этом внутри объединённых процессов соблюдаются чёткие DDD-bounded contexts и event-driven обмен, так что поддерживаемость и масштабируемость не страдают.

и снова видно, что инфрастр. решение зачастую даёт больший выигрыш, чем идеальная SRP-архитектура, особенно в условиях real-time.

Так что именно такой микс «умных» границ и продуманных инженерных приёмов зачастую сильнее тысячи правок Code Smells. Этот ответ подпитан свежей кружкой кофе и старыми добрыми статьями тех-блогов :)

У нас в команде был показательный кейс: полгода вылизывали код, дробили сервисы по SOLID, выносили всё в общие библиотеки — в итоге получили «идеальный» монолит в распределённой обёртке. Каждый микросервис блестел чистотой, но при попытке масштабировать систему упёрлись в адские cross-service зависимости. Например, сервис нотификаций вызывал 5 других сервисов через цепочку RPC, чтобы собрать данные для письма — латенси ползла в ..

Пришлось экстренно пересматривать архитектуру: слепить несколько сервисов обратно в рамках bounded context, разрешить контролируемое дублирование DTO и убить «общие» библиотеки. Код внутри сервисов стал менее «чистым» (например, появились god-классы для локальной агрегации данных), но общая система задышала.

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

Какое отношение SOLID имеет к распределенной архитектуре? SOLID это про организацию кода внутри одного приложения, а не про микросервисы.

Ну вообще можно рассматривать микросервисы как классы (они инкапсулируют догику, обмениваются сообщениями с другими микросервисами, даже инстансов бывает несколько). Так что солид можно применять и к ним в том числе, например, для инверсий зависимостей, ну вы поняли :)

Сейчас DDD это уже база для любой разработки, что монолит что микросервисов. Неправильное разделение на контексты действительно породит кучу проблем, поэтому в DDD уделяется этому большое внимание и все специалисты говорят не делать наносервисы. Мы тоже обожглись, когда разделяли монолит решили "на будущее" сразу сделать Уведомления + Email сервис (для отправки писем) + Telegram Service (для уведов напрямую). В итоге если сразу правильно все продумали, то не плодили бы эти лишние сервисы, потому что по-сути все это контекст уведомлений и не нужно было ничего лишнего добавлять. SOLID / KISS/ DRY при этом не противоречат DDD, просто надо правильно интерпретировать эти методологии

SOLID как раз про уменьшение зависимостей. Общие библиотеки и запрет дублирования DTO часто ведут к нарушению принципа открытости-закрытости.

У меня есть другой пример. Допустим, есть идея стартапа которая должна покорить мир в ближайшем будущем. Мы набираем команду разработчиков которые профессионально подходят к своему делу, пишут документацию с первого дня, рисуют схемы, делают код-ревью, разносят код на модули и библиотеки, покрытие тестами приближается к 100%. Надеюсь ни для кого не секрет, что это всё занимает чуть больше времени чем просто реализация через спагетти-код. В итоге может получится, что когда распрекрасно-чистый код дойдёт до продакшена, идея потеряет свою актуальность. Еще может стать, что кто-то чуть менее принципиальный уже давно в проде и захватил весь рынок. К чему это всё, к тому, что в реальной жизни самурайский принцип не подходит и цель важнее пути.

Мартин Фаулер приводил своё видение данного вопроса. Сначала скорость написания методом говнокода действительно выше, но потом она сравнивается с чистым кодом и начинает безбожно отставать. Его мнение, что период до пересечения занимает меньше месяца.

скорости можно добиться урезанием фич, а не качества.

Где же он живет - легендарный удобный для всех баланс между неудобствами от отсутствия правил и неудобствами от необходимости их соблюдения...

Слова вы правильные говорите, но вот как на пример "хорошего" кода посмотришь, так сомневаться начинаешь.

  • Аннотации отстутствуют.

  • Наименования функций совершенно не отражают их функциональность. Начиная с первых двух.

  • Если считать, что "хороший" код, это результат рефакторинга "плохого", так функция process_string возвращает list[int], который хоть и сортируется, как str, но в результат попадает как все тот же list[int], а в исходном варианте у вас там str.

  • Ну и выделять в функцию одну строку кода, это уже перебор.

Ну и выделять в функцию одну строку кода, это уже перебор.

Это норм. Вы так описываете бизнес логику. Умножение интов это низкоуровневая операция , а функция уже уровнем выше.

Если бы это был класс, от которого можно было бы отнаследоваться, или если бы две первые функции были методами другого класса, передаваемого, как параметр в данный, тогда да, а так неоправданное усложнение. Тогда, кстати и к именам претензии бы не было.

Спасибо за ссылку на исследование. Любопытно как авторы пытаются дать определения "чистому коду" или измерить читаемость аж 8 математическими методами. Склонен считать, что лучше всего получилось у Ann Campbell с ее метрикой "cognitive complexity". Хотя "сложность" (машинную) и "трудность" (человеческую) часто путают и смешивают.

Хочется верить, дедлайны - это основная причина говнокода, но у меня на проекте это точно не так.

Проект начинался в режиме стартапа с очень ограниченными временными и трудовыми ресурсами. Мы работали быстро и делали много. Оценка задачам давалась с квантованием в 30 минут. Как уж тут без говнокода...

Прошло 11 лет. Народу на проекте стало больше. Начальство сменилось на разных уровнях более чем по 3 раза. Никто уже не требует былых темпов, да и не осталось никого из тех, кто помнит, как тут было принято работать раньше. Квантование оценки задачи - день. Ничего с нуля придумывать почти никогда не надо. Еще одна кнопка, еще одна форма, еще один HTTP API. Работа - не бей лежачего. Думаете, стало говнокода меньше? Нет. Только через code review и спасаемся.

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

Если бы опрос провели на моем проекте, я бы таким результатам не поверил. Люди пишут говнокод потому что или не могут отрефлексировать, или надеются, что code review будет выполнять не очень бдительный коллега.

У всех понятные названия и лаконичные docstrings. Такой код читается без напряжения


Автору на заметку. "Читаемость" функции означает в том числе и то, что в месте использования то, что делает функция, понятно без необходимости лезть во внутренности.

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

Итого, чтобы понять код на более высоком уровне абстракции, мне нужно будет держать в голове документацию на все используемые методы. В контексте цитаты сверху я это назову "чтение с напряжением". К примеру, название multiplyByTwo выдаёт детали реализации, но в месте использования хотя бы понятно, что происходит.

В целом стиль статьи сочту "неинженерным" от слова совсем: аргументация недалеко уходит от "я так примерно почувствовал". Про измеряемые характеристики кода можно почитать, к примеру, в "Чистой архитектуре" Мартина: входящие/исходящие завивимости, устойчивость, абстрактность. В книге "Фундаментальный подход к программной архитектуре" Марка Ричардса и Нила Форда дополнительно можно почитать про коннасценцию различных видов.

В данном контексте именно такие имена, как у автора, подходят лучше. Оно говорит нам не «здесь нужно число умножить на 2» (почему? почему именно на 2? это требование алгоритма?), а «здесь нужно обработать число некоторым способом в соответствии с тех.заданием, какое бы оно ни было». В данном случае это умножение на 2; было бы другое требование — реализовали бы другой алгоритм.

Спасибо за комментарий. Наводит на мысль, что факт того, что название функции мной и вами интерпретируется по разному, и назначение функции надо додумывать, говорит не в пользу качества этого названия. Полагаю, приведённая вами формулировка (отсутствующая в статье и коде примера) прекрасно бы выразилась в названии "somehow_process_number".

Я уже писал. Тогда нужно было делать класс, чтобы или от него отнаследоваться, или операции с элементами списка выносить в другой класс.

Это правильно, если у нас сложная система с многими (или ожидаемо многими в обозримой перспективе) вариантами обработки. Тогда мы сделаем иерархию классов и применим стратегию. А в простом случае устраивать себе «паттерны головного мозга» — оно того не стоит.

Кроме всего прочего, к сожалению, глаз замыливается.

Когда долго копаешься в каком-то куске кода, глаз видит то, что в этом коде имелось ввиду. А не то, что на самом деле написано.

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


Хотел добавить но нервы не выдерживают когда стал вспоминать сколько дерьма за консалтингом разгреб.

Это ребята шурупы вколачивают а гвозди вкручивают...


Я как-то работал в одной конторе, которая делала WiFi чип (и, к сожалению, не сделала. В смысле, не довела до товарного вида).

Начинал я с ними, как контрактор, и написал по контракту линуксный драйвер ихнего WiFi чипа. Для венды у них какой-то драйвер был, но такой, они не рассчитывали вывести его в production.

Меня тем временем взяли в штат, а для венды заказали у третьесторонней конторы драйвер промышленного качества. И дали им мой линуксный драйвер, ну, просто в качестве документации.

Потом от них пришел результат. В общем, вместо того, чтобы написать нормальный вендовый драйвер, они решили мой под венду перенести. Проблема в том, что в линуксе, в ядре, почти всё, что приходит к драйверу, приходит на контексте какого-нибудь процесса (что примерно соответствует IRQL = PASSIVE_LEVEL в виндовсной терминологии). Т.е., можно использовать мьютексы, примитивы ожидания и т.п. И я этим активно пользовался. А в венде практически все обращения к драйверу идут на контексте, который в венде называется IRQL = DISPATCH_LEVEL, и примерно соответствует линуксному interrupt bottom half. И блокироваться там нельзя от слова совсем. А они этого совсем-совсем не понимали.

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

Пришлось потом очень долго за ними это разгребать.

вспомнился один проект... Ставили кастомизированный, если память не подводит, radius сервер у провайдера спутникового ТВ. Программка через спутниковый канал подмешивала инструкции для приемника тв какие передачи записать. А звонили клиенты по dial up, чтобы инструкции передать, провайдеру тв. OpenTV и сеттап боксы то еще убожество были, ansi c в чистом виде, многих привычных функций нет, отладка по шнурку, десяток моделей, у самой слабой меньше мега памяти... Шел 2003й год.

Отвлекся. Так вот, взяли проект радиуса в котором код "был как персик", на 5± но документация дрянная - его подрехтовали. Заказчику же слили документацию с другого опенсорс радиуса, у которого документация "была как персик" а код смотреть невозможно, спагетти.

Как до Мартина программировали и системы строили - загадка)))

С осторожностью и пониманием, что если когда программируешь, не думать, придется в 10 раз больше думать, когда отлаживаешь

Также. Он не придумал ничего нового, кроме названий.

Чистый код — красивая архитектура. А работает ли это?

Да, работает, если на проекте трудятся люди, которые знают, что это такое и зачем оно нужно.

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий