Java Clean Code: как сделать код читаемым и красивым

Привет, Хабр!  

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

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

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

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

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

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

Появились разные стандарты, проверка синтаксиса и внутренние договорённости команд разработки.

Сейчас существует несколько основных правил, соблюдение которых обеспечивает чистоту кода:

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

Стандарты стиля кода. Для Java существует стандарт оформления кода — Java Code Conventions, который подробно описывает правила написания кода. В некоторых компаниях действуют собственные стили, но ключевое требование одно: придерживаться единого формата. Это позволяет обеспечить единообразие и упростить его чтение, будь то ваш собственный код или код коллеги.

Модульность. Модули позволяют разделить программу на отдельные функциональные блоки, каждый из которых решает свою узкую задачу. Чем меньше блок кода, тем легче его понять. Функция длиной в 10 строк гораздо удобнее для восприятия, чем та, что занимает 150 строк.

Иерархическая структура. Четкая иерархия упрощает понимание структуры проекта и облегчает поиск нужных элементов. Благодаря этому сразу видно, где находится нужная функция.

Соблюдение соглашений. Важно следовать принятым правилам команды или стандартам, таким как Java Code Conventions. Это создаёт единое пространство разработки и улучшает взаимопонимание среди членов команды. 

Со временем возникла потребность в дополнительных подходах, которые могли бы улучшить качество кода. Появились принципы проектирования, такие как, например, KISS, DRY, YAGNI, APO и BDUF. Эти принципы широко используются, их часто проверяют на собеседованиях, поскольку они стали основой для качественной разработки. Каждый из этих принципов имеет своё значение, но важно помнить, что они представляют собой лишь рекомендации. Всегда оценивайте, насколько тот или иной принцип применим к вашему проекту. Некоторые из них пришли в разработку из других областей, и их интерпретация может варьироваться в зависимости от контекста. Тем не менее, знание базовых принципов и следование им помогает создавать устойчивый и поддерживаемый код, что особенно важно в долгосрочных проектах.

Первый принцип, который мы обсудим, — это KISS. Аббревиатура расшифровывается как Keep It Short and Simple, хотя в IT-сообществе встречается и другая трактовка: Keep It Simple, Stupid. Этот принцип зародился в 1960-х годах в ВМС США. Основная идея заключается в том, что чем проще система, тем она эффективнее и надёжнее. Чем больше деталей и сложностей мы добавляем, тем выше вероятность поломок и нарушений работы.

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

Вторая интерпретация, популярная в IT, переводится как «Будь проще, глупец». Она акцентирует внимание на том, что простота и ясность в коде очень важны. Когда код лаконичен и прямолинеен, его легче читать, объяснять и поддерживать. Избегайте добавления множества условий и сложных конструкций, ведь это только осложнит дальнейшую работу с кодом. 

Давайте посмотрим пример.

91b6a0d1ef5537e31dcefbf25e68bd30.png

У нас есть небольшой Java-класс, основной задачей которого является формирование отчёта по списку задач. Нужно вывести название каждой задачи, её приоритет и текущий статус. Для этого используется три метода. 

Первый метод создаёт текстовую строку, содержащую три параметра: название задачи, её статус и приоритет. Далее следуют еще два метода. Один из них получает статус задачи. Другой проверяет, является ли задача приоритетной, исходя из значения булевского флага, и возвращает либо high, либо low. Затем собирается полный текст отчёта, который формируется в цикле. Хотя код кажется простым, он всё же перегружен. Поэтому его можно переписать более лаконично.

5d43f23f0fa1e63b466766f97bda70c2.png

Теперь текст стал более понятным. Мы сократили лишние подробности и оставили только ключевые моменты. Создаются три переменные, каждая из которых содержит необходимую информацию. Терминальный оператор используется непосредственно в строке. Затем эти переменные объединяются в итоговый текст. Однако этот код можно дополнительно упростить, сделав его ещё более читаемым. Например, можно избавиться от некоторых переменных.

3fa8da7c588f402817e0a6d074a4625e.png

Мы получили следующий результат. Код стал намного проще и понятнее. Он выводит текст с тремя параметрами, каждый из которых последовательно вычисляется. Сравнивая прежний вариант с новым, видим значительное упрощение структуры. Мы решили задачу напрямую, без введения дополнительных усложнений. Нет вывода констант или лишних сущностей, что делает код более лаконичным. Все необходимые данные берутся прямо из переменной task. Теперь разработчику легче ориентироваться в структуре программы, поскольку все операции выполняются последовательно и логично. В отличие от предыдущего варианта, когда приходилось искать связи между несколькими методами, новый код воспринимается интуитивно. Даже в простых примерах такие улучшения делают код более удобным для понимания и поддержки.

Вторая практика называется YAGNI, что означает You Aren’t Gonna Need It («Вам это не потребуется»). Суть данного подхода заключается в отказе от написания функциональности, которая изначально не предусмотрена требованиями. Если определённая функция не указана в техническом задании, её не стоит реализовывать. Основная идея сводится к тому, что код пишется исключительно для текущих нужд, а любые дополнительные возможности добавляются только тогда, когда они действительно необходимы.

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

Третий подход, который мы рассмотрим, называется APO (Avoid Premature Optimization) — избегайте преждевременной оптимизации.

Что это значит? Иногда, обнаружив проблему в коде, возникает желание немедленно её исправить. Например, если кто-то нарушает принцип KISS, возникает соблазн улучшить код. Казалось бы, это полезное действие, но на практике это может оказаться преждевременной оптимизацией. Проблема заключается в том, что вы пытаетесь решить вопрос, который пока не возник. Возможно, этот код больше никогда не потребуется изменять, и ваши усилия окажутся напрасными. 

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

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

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

Сделаем промежуточные выводы.

3c2551cd28b455b64b697b6991fb2864.png

Мы рассмотрели три основных принципа, которые являются наиболее известными и важными, однако ими часто пренебрегают. Давайте рассмотрим еще один — DRY (Don’t Repeat Yourself), означающий «Не повторяйся». 

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

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

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

8810ae95fed348126b7360ebb928f883.png

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

Первый метод проверяет, не пустая ли строка и содержит ли она символ »@». Это базовая проверка, обеспечивающая минимальный уровень валидации. Со временем добавляется второй метод, который проверяет email при регистрации. Сначала он убеждается, что пользователь ввёл правильный адрес, а затем проверяет, не занят ли этот email другим пользователем. 

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

e38b45aaf0b61eb0589752a88d18c3d4.png

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

Давайте рассмотрим другой пример.

2ebf331c8d7f48c6a77115475c0cae79.png

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

acbb5565b6d3ab0d460a5c1ebaa69e15.png

Мы заметили, что код наших методов очень похож, разница только в том, что в одном случае у нас стоит двойное равно, а в другом случае — не равно. Мы понимаем, что это одно и то же, за исключением одного символа. Исходя из принципа DRY, который гласит «не повторяйся», мы решили переписать код вот так.

ce1eafde06d13185f09d0079d21eeda4.png

Теперь у нас появился единый метод под названием filterNumbers. Вызывая этот метод, мы передаём ему сами числа и соответствующий предикат. 

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

Название метода filterNumbers намекает на фильтрацию, но как именно она происходит — неясно. Этот пример показывает, что применение принципа DRY привело к нарушению принципа KISS: код стал сложнее, чем был ранее. Мы добавили ненужные усложнения, такие как предикаты, хотя могли обойтись без них. Важно помнить, что иногда стремление к одному принципу может конфликтовать с другим. Следует находить баланс и оценивать код в целом, чтобы избежать излишней сложности. 

Последний принцип — BDUF (Big Design Up Front), что переводится как «Глобальное проектирование прежде всего». Название говорит само за себя: не стоит пренебрегать этапом проектирования. Прежде чем приступать к реализации, важно тщательно продумать, что именно вы собираетесь делать. 

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

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

Ну, а теперь краткие выводы.

a4f4d65b3fe0f37a2f8ee39b26ff1799.png

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

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

© Habrahabr.ru