Я видел не во сне, а наяву атакующие корабли, пылающие под четырьмя вложенными if-else, и лучи CI с кучей сканирований у ворот Тангейзера, вызывающие лютую боль разработчиков. Меня зовут Максим, и я техлид в Газпромбанке. То, что вы сейчас увидите, выросло из внутреннего стайлгайда, к которому мы пришли через тернии многочисленных код-ревью и разработанных сервисов. Я постарался собрать здесь все основные и просто интересные грабли, которые нам попадались, и показать решения с примерами и обзором возможных трудностей в процессе внедрения. Моё понимание чистого кода: это исходники, каждый файл которых, например, связан с одной зоной ответственности — доменом; методы и классы в нём лаконичны, не расползаются на пять вложенных циклов с ветвлениями. Качественный же код вбирает в себя свойства чистого кода, дополняет их вниманием к техническому исполнению и тестируемостью классов по отдельности. В примерах будет код на Kotlin, однако это не означает, что практики нельзя применить к другим языкам. Руководство подойдёт даже для YAML-программистов. Попробуйте после прочтения материала посмотреть на какой-нибудь код ролей Ansible, и наверняка обнаружите простор для улучшения. Ваш код должен выглядеть и читаться как основная документация. Это не значит, что аналитическую и бизнес-документацию надо выбросить в мусорку. Такие материалы должны дополнять тесты и кодовую документацию как спецификация для сверки. Как достичь такого «самодокументированного» кода? Начните с постепенного введения Type-Driven Development и тестов бизнес-логики. В случае с Kotlin: делайте sealed class и в нём описывайте все возможные результаты выполнения метода через классы и object’ы. Вот пример:   Type Driven в лучах ValueObject   Суть в этом — класс сам себя тестирует: Нюансы: Внедрение Type-Driven Development сопряжено с пусть и небольшими, но затратами. Требует перемен в складе мышления. Не совсем понятно, что в Type-Driven Development делать с исключениями. Но мы к этому ещё вернёмся. Что лучше — писать логи в какой-то файл где-то на файловой системе или отправлять их в stdout? Отправлять логи строками в Elasticsearch или потратить время на изучение distributed tracing и структурированных логов? Спроектированный с умом мониторинг сокращает время обратной связи и упрощает поиск причины неполадок там, где нет дебаггера под рукой. Как можно внедрить логи с метриками на примере незамысловатого класса-прослойки для чтения файла. Подключить сбор метрики: alphaMetrica (productId, route).В коде это будет выглядеть так: Всё должно быть прозрачно, очевидно и настраиваемо. Особенности: Делать логи надо такими же читаемыми, как и код, т. е. избегать println («123»). Метрики лучше готовить вместе с инженерами эксплуатации, иначе можно по незнанию включить в них персональные данные и просто чувствительную информацию пользователей, например токены или пароли. Или включить в label-метрики URL запроса и таким образом размножить их в Prometheus. В любом из двух случаев вы получите по шапке от соседних команд или руководства. Доменно-специфичные языки появились сравнительно давно, один из первых популяризаторов DSL — это язык Groovy. С помощью DSL-конструкций можно элегантно и декларативно решать разные задачи, но есть и подводные камни в виде оверхеда лямбд или сложностей проектирования. И да, если вы пытаетесь лечить бойлерплейт в бизнес-логике приложения с помощью DSL или иных декларативных решений, например data-классами в Python (которые с type hints), дважды подумайте.  Лучше делайте то, что нужно, здесь и сейчас, а не парите в абстракциях. Или же посмотрите в сторону рефакторинга бизнес-кода, если ваш код становится универсальным. Как использовать фичи ЯП и не нарываться на ошибки или code smell? Говоря опять про Kotlin, функции-расширения — это очень здорово, мы в Газпромбанке ими активно пользуемся. И идея с композицией вместо наследования в data-классах мне нравится — всё как Джошуа Блох советует. Но у каждого языка есть и свои сложные синтаксические решения. Inline-методы с noinline- и crossinline-аргументами тому пример. Или ООП в Python, свойства и геттеры-сеттеры. Как и с DSL, если дело дошло до синтаксических приёмов в бизнес-логике, то остановитесь и задумайтесь, как вы оказались в таком положении. Как можно делать: Возможные проблемы: Велик соблазн пользоваться языком по полной». Речь идёт о null, None, nil, в общем, о нулевых значениях. В принципе, здесь всё просто: не надо, просто не надо писать код с null. Мы решительно не используем null. Только Either<T, ERR>, только хардкор от мира ФП! Или sealed classes (проще говоря, классы с приватным конструктором), в конце концов. Но ещё лучше инкапсулировать данные в классы с бизнес-логикой. Если вам трудно перейти к полному отсутствию null, начните с полумер, другими словами, внедрите null-safety, Optional’ы и null-checking через «?.». Всяко лучше, чем ничего! Возможный нюанс только один — плохое взаимодействие с внешними библиотеками. Но это решаемо через прослойки и функции-расширения опять же. Шаблоны проектирования полезны. Мы используем различные паттерны в процессах дизайна кода. Вот лишь часть: ФП, монады, railway-oriented programming (когда код последователен и не перескакивает из места на место). А также мы отказались от исключений. Как? Да просто — через те же sealed classes и Either для описания ошибок как обычных доменных объектов! А не каких-то исключений, которые обрабатываются где-то далеко по стеку. Вот пример кода с бизнес-логикой, которую можно расширять. И код легко читать. Смотри какое элегантное решение: Плюс этого решения с TDD + DDD в том, что он надёжен и прост. Мне, как лиду, легко его контролировать, а разработчику остаётся просто бежать по этому заботливому коридору к ЧистоКоду в конце тоннеля с электроовцами. Сложности: Трудно перестроить свои мозги под мышление паттернами, думать тем же ФП и монадами. Перевести всю команду на новую «поваренную книгу» - думал будет труднее, но очень много позитивных отзывов. Опытные разработчики повидавшие еще Великих Гигантов отмечают эту историю как элегантной кодописью. Мы в каком-то смысле поклоняемся постулатам The Twelve-Factor App.  Один из них: хранить средозависимые настройки в переменных окружения, — в этом случае нет риска случайно закоммитить файл конфигурации (что я очень не люблю).  Другой: не использовать группы настроек, поскольку так можно получить комбинаторный взрыв из сочетаний, — про логику слияния и переопределения параметров я вообще молчу. Поэтому не усложняйте себе жизнь — используйте самое простое решение, environment variables! Суть: вся конфигурация контурозависимая, выносится и просовывается через переменные окружения. Пример конфига интеграции: И в тестах:   Всё, что может помешать внедрению такого подхода, это технические ограничения фреймворка или рантайма. При открытии какого-то перечня веб-фреймворков из абстрактного awesome-list вам может показаться — вот оно, в этом фреймворке решены все проблемы человечества!  Не обнадёживайте себя и не забывайте, что внедрение такого фреймворка может обернуться адом для всей команды — у неё может быть свой уже сложившийся стиль.  Ни одно решение от этого не застраховано. Да, опенсорс прекрасен, и разные фреймворки по-разному потрясающие! Просто они далеко не всегда решают задачи бизнеса. Наша команда буквально живёт идеей TDD: мы начинаем код с юнит-тестов, затем пишем интеграционные BDD-тесты бизнес-логики и добавляем иногда щепотку end-to-end-тестов — собственно, всё согласно пирамиде тестирования. Не рожку мороженого — пирамиде! Писать с TDD проще, чем кажется: просто попробуйте писать сначала гипотетические тесты перед кодом. Если не получается — подумайте, каким кодом вы бы хотели пользоваться. Конвейеры автосборки — это просто чудо. С ними можно легко задеплоить сервис на тестовую среду, выполнить тесты и провести проверки кода через SAST или линтинг. Конечно, это не так легко — например, взять те же тесты: среди них могут быть плавающие, флапающие тест-кейсы.  Как бороться: запускайте в CI только небольшой, самый стабильный набор тестов и постепенно расширяйте его по мере развития пайплайна. С SAST надо быть осторожным, поскольку при слишком агрессивных проверках он может не помогать команде, а работать во вред процессам. Если в целом, то советую начинать CI/CD с автотестов и потом уже внедрять линтеры, анализ кода и анализ зависимостей. Не налегайте слишком на оптимизации в сервисе, ведь вы можете навредить качеству кода, причём сильно. Могу предложить делать оптимизации красиво — так, чтобы это соответствовало гайдлайнам и best practices фреймворка, но для этого нужен навык. При этом некоторые оптимизации можно сделать, даже немного приукрасив кодовую базу: например, паттерны пагинации, batch processing или потоковой обработки могут и оптимизировать код, и сделать его в чём-то понятнее.   Это всё наша команда извлекла из своих уроков жизни и программирования. А какие приёмы и советы знаете вы? Приглашаем в комментарии!  Software Craftsmanship энтузиаст