Перейти к основному содержимому

Чистый код в Java

· 16 мин. чтения

1. Обзор

В этом уроке мы рассмотрим принципы чистого кодирования. Мы также поймем, почему важен чистый код и как этого добиться в Java. Кроме того, мы посмотрим, есть ли какие-либо инструменты, которые могут нам помочь.

2. Что такое чистый код?

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

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

Любой дурак может написать код, понятный компьютеру. Хорошие программисты пишут код, понятный людям.

3. Почему мы должны заботиться о чистоте кода?

Написание чистого кода — это вопрос личных привычек, а не только мастерства. Как разработчик, мы растем благодаря опыту и знаниям с течением времени. Но мы должны задаться вопросом, почему мы все-таки должны инвестировать в разработку чистого кода? Мы понимаем, что другим, вероятно, будет легче читать наш код, но достаточный ли это стимул? Давай выясним!

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

  • Поддерживаемая кодовая база : Любое программное обеспечение, которое мы разрабатываем, имеет продуктивный срок службы и в течение этого периода требует внесения изменений и общего обслуживания. Чистый код может помочь в разработке программного обеспечения, которое легко изменять и поддерживать с течением времени.
  • Более простое устранение неполадок : программное обеспечение может вести себя непреднамеренно из-за множества внутренних или внешних факторов. Часто может потребоваться быстрый оборот с точки зрения исправлений и доступности. Программное обеспечение, разработанное с использованием принципов чистого кодирования , легче устранять .
  • Более быстрая адаптация : Программное обеспечение в течение всего срока его службы будет видеть, как многие разработчики создают, обновляют и поддерживают его, причем разработчики присоединяются к нему в разные моменты времени. Для поддержания высокой производительности требуется более быстрая адаптация , а чистый код помогает достичь этой цели.

4. Характеристики чистого кода

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

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

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

5. Чистый код в Java

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

5.1. Структура проекта

Хотя Java не навязывает какую-либо структуру проекта, всегда полезно следовать согласованному шаблону для организации наших исходных файлов, тестов, конфигураций, данных и других артефактов кода . Maven, популярный инструмент сборки для Java, предписывает определенную структуру проекта . Хотя мы можем не использовать Maven, всегда приятно придерживаться соглашения.

Давайте посмотрим на некоторые из папок, которые Maven предлагает нам создать:

  • src/main/java : для исходных файлов
  • src/main/resources : для файлов ресурсов, таких как свойства
  • src/test/java : для тестовых исходных файлов.
  • src/test/resources : для тестовых файлов ресурсов, таких как свойства

Подобно этому, существуют и другие популярные структуры проектов, такие как Bazel , предложенные для Java, и мы должны выбрать одну из них в зависимости от наших потребностей и аудитории.

5.2. Соглашение об именовании

Соблюдение соглашений об именах может значительно улучшить читабельность нашего кода и, следовательно, удобство сопровождения . Род Джонсон, создатель Spring, подчеркивает важность соглашений об именах в Spring:

«… если вы знаете, что что-то делает, у вас есть хороший шанс угадать имя класса Spring или интерфейса для него…»

Java предписывает набор правил , которых следует придерживаться, когда дело доходит до именования чего-либо в Java. Правильно сформированное имя не только помогает в чтении кода, но также многое говорит о намерениях кода. Возьмем несколько примеров:

  • Классы : класс с точки зрения объектно-ориентированных концепций является образцом для объектов, которые часто представляют объекты реального мира. Следовательно, имеет смысл использовать существительные для обозначения классов, достаточно их описывающих:
public class Customer {
}
  • Переменные : переменные в Java фиксируют состояние объекта, созданного из класса. Имя переменной должно четко описывать назначение переменной:
public class Customer {
private String customerName;
}
public class Customer {
private String customerName;
public String getCustomerName() {
return this.customerName;
}
}

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

5.3. Структура исходного файла

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

Давайте посмотрим, как должен выглядеть типичный порядок элементов в исходном файле:

  • Заявление о пакете

  • Операторы импорта

  • Весь статический импорт

  • Весь нестатический импорт

  • Ровно один класс верхнего уровня

  • Переменные класса

  • Переменные экземпляра

  • Конструкторы

  • Методы

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

Давайте посмотрим правильно сформированный исходный файл:

# /src/main/java/com/foreach/application/entity/Customer.java
package com.foreach.application.entity;

import java.util.Date;

public class Customer {
private String customerName;
private Date joiningDate;
public Customer(String customerName) {
this.customerName = customerName;
this.joiningDate = new Date();
}

public String getCustomerName() {
return this.customerName;
}

public Date getJoiningDate() {
return this.joiningDate;
}
}

5.4. Пробелы

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

Идея здесь состоит в том, чтобы ввести в код логические группы, которые могут помочь организовать мыслительный процесс при попытке прочитать его. Здесь нет единого правила, но есть общий набор руководящих принципов и неотъемлемое намерение сохранить удобочитаемость в центре этого:

  • Две пустые строки перед запуском статических блоков, полей, конструкторов и внутренних классов.
  • Одна пустая строка после сигнатуры метода, которая является многострочной.
  • Один пробел, отделяющий зарезервированные ключевые слова, такие как if, for, catch, от открытых скобок.
  • Один пробел, разделяющий зарезервированные ключевые слова, такие как else, catch из закрывающих скобок

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

5.5. Отступ

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

Давайте посмотрим на некоторые из важных критериев отступов:

  • Типичная лучшая практика — использовать четыре пробела, единицу отступа. Обратите внимание, что в некоторых рекомендациях вместо пробелов предлагается табуляция. Хотя здесь нет абсолютной передовой практики, ключом остается постоянство!

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

  • Наконец, поскольку многие выражения не помещаются в одну строку, мы должны последовательно их разбивать:

  • Вызов метода break после запятой

  • Разбить выражения перед оператором

  • Делайте отступы между строками для лучшей читабельности (мы здесь, в ForEach, предпочитаем два пробела)

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

List<String> customerIds = customer.stream()
.map(customer -> customer.getCustomerId())
.collect(Collectors.toCollection(ArrayList::new));

5.6. Параметры метода

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

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

Давайте посмотрим на пример этого:

public boolean setCustomerAddress(String firstName, String lastName, String streetAddress, 
String city, String zipCode, String state, String country, String phoneNumber) {
}

// This can be refactored as below to increase readability

public boolean setCustomerAddress(Address address) {
}

5.7. Жесткое кодирование

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

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

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

private int storeClosureDay = 7;

// This can be refactored to use a constant from Java

private int storeClosureDay = DayOfWeek.SUNDAY.getValue()

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

5.8. Комментарии к коду

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

Java допускает два типа комментариев: комментарии к реализации и комментарии к документации. У них разные цели и разные форматы. Давайте лучше их поймем:

  • Документация/комментарии JavaDoc

  • Аудитория здесь — пользователи кодовой базы.

  • Детали здесь, как правило, не зависят от реализации, больше внимания уделяется спецификации.

  • Обычно полезно независимо от кодовой базы

  • Реализация/Блок комментариев

  • Аудитория здесь — разработчики, работающие над кодовой базой.

  • Детали здесь зависят от реализации

  • Обычно полезно вместе с кодовой базой

Итак, как мы должны оптимально использовать их, чтобы они были полезными и контекстуальными?

  • Комментарии должны только дополнять код, если мы не можем понять код без комментариев, возможно, нам нужно его рефакторить.
  • Мы должны редко использовать блочные комментарии, возможно, для описания нетривиальных дизайнерских решений.
  • Мы должны использовать комментарии JavaDoc для большинства наших классов, интерфейсов, общедоступных и защищенных методов.
  • Все комментарии должны быть правильно оформлены с правильным отступом для удобочитаемости.

Давайте посмотрим на пример осмысленного комментария к документации:

/**
* This method is intended to add a new address for the customer.
* However do note that it only allows a single address per zip
* code. Hence, this will override any previous address with the
* same postal code.
*
* @param address an address to be added for an existing customer
*/
/*
* This method makes use of the custom implementation of equals
* method to avoid duplication of an address with same zip code.
*/
public addCustomerAddress(Address address) {
}

5.9. логирование

Любой, кто когда-либо прикасался к производственному коду для отладки, в какой-то момент жаждал большего количества журналов. Важность журналов для разработки в целом и обслуживания в частности невозможно переоценить .

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

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

Давайте посмотрим на пример описательной регистрации с правильным уровнем:

logger.info(String.format("A new customer has been created with customer Id: %s", id));

6. Это все?

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

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

6.1. ТВЕРДЫЙ

SOLID — это мнемоническая аббревиатура, основанная на пяти принципах, изложенных в ней для написания понятного и удобного в сопровождении программного обеспечения:

  • Принцип единой ответственности : каждый интерфейс, класс или метод, которые мы определяем, должны иметь четко определенную цель . По сути, в идеале он должен делать что-то одно и делать это хорошо. Это эффективно приводит к меньшим методам и классам, которые также можно тестировать.
  • Принцип открытости-закрытости : в идеале код, который мы пишем, должен быть открыт для расширения, но закрыт для модификации . Фактически это означает, что класс должен быть написан таким образом, чтобы не возникало необходимости его модифицировать. Однако он должен допускать изменения посредством наследования или композиции.
  • Принцип замещения Лискова : этот принцип гласит, что каждый подкласс или производный класс должен быть заменяемым для своего родителя или базового класса . Это помогает уменьшить связанность в кодовой базе и, следовательно, улучшить повторное использование.
  • Принцип разделения интерфейса : реализация интерфейса — это способ обеспечить определенное поведение для нашего класса. Однако классу не нужно реализовывать методы, которые ему не нужны . Это требует от нас определения меньших, более сфокусированных интерфейсов.
  • Принцип инверсии зависимостей : в соответствии с этим принципом классы должны зависеть только от абстракций, а не от их конкретных реализаций . Фактически это означает, что класс не должен нести ответственность за создание экземпляров для своих зависимостей. Скорее, такие зависимости должны быть внедрены в класс.

6.2. СУХОЙ И ПОЦЕЛУЙ

DRY расшифровывается как «Don's Repeat Yourself». Этот принцип гласит, что фрагмент кода не должен повторяться в программном обеспечении . Обоснование этого принципа заключается в уменьшении дублирования и повышении возможности повторного использования. Однако обратите внимание, что мы должны быть осторожны, принимая это слишком буквально. Некоторое дублирование действительно может улучшить читаемость кода и удобство сопровождения.

KISS расшифровывается как «Keep It Simple, Stupid». Этот принцип гласит, что мы должны стараться сделать код максимально простым . Это облегчает понимание и поддержку с течением времени. Следуя некоторым принципам, упомянутым ранее, если мы будем держать наши классы и методы сфокусированными и небольшими, это приведет к более простому коду.

6.3. TDD

TDD расшифровывается как «Разработка через тестирование». Это практика программирования, которая требует, чтобы мы писали любой код только в том случае, если автоматический тест дает сбой . Следовательно, мы должны начать с разработки дизайна автоматизированных тестов . В Java есть несколько фреймворков для написания автоматизированных модульных тестов, таких как JUnit и TestNG.

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

7. Инструменты для помощи

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

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

В экосистеме Java доступно несколько инструментов , которые снимают по крайней мере некоторые из этих обязанностей с рецензентов кода. Давайте посмотрим, что представляют собой некоторые из этих инструментов:

  • Средства форматирования кода. Большинство популярных редакторов кода Java, включая Eclipse и IntelliJ, позволяют выполнять автоматическое форматирование кода. Мы можем использовать правила форматирования по умолчанию, настраивать их или заменять их собственными правилами форматирования. Это заботится о многих соглашениях о структурном коде.
  • Инструменты статического анализа. Существует несколько инструментов статического анализа кода для Java, включая SonarQube , Checkstyle , PMD и SpotBugs . У них есть богатый набор правил, которые можно использовать как есть или настроить для конкретного проекта. Они отлично обнаруживают множество запахов кода , таких как нарушения соглашений об именах и утечка ресурсов.

8. Заключение

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

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

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