JavaRush/Java блог/Random/Правила написания кода: сила правильных именований, хорош...
Константин
36 уровень

Правила написания кода: сила правильных именований, хорошие и плохие комментарии

Статья из группы Random
участников
Правила написания кода: сила правильных именований, хорошие и плохие комментарии - 1 Как часто вам приходилось разбираться в чужом коде? Когда вместо пары часов ты тратишь дни, чтобы просто разобраться в логике происходящего. Самое забавное, что для человека, писавшего данный код, тут всё ясно и весьма прозрачно. И это не мудрено: ведь совершенный или идеальный код — это весьма размытое понятие, ибо у каждого разработчика своё видение на мир и на код, соответственно, тоже. Не раз сталкивался с ситуацией, когда я и мой коллега смотрели на один и тот же код имели разное мнение насчет его корректности и чистоты. Правила написания кода: сила правильных именований, хорошие и плохие комментарии - 2Знакомое чувство, не правда ли? Тем не менее, есть некоторые моменты, проверенные временем, которых стоило бы придерживаться, что в итоге сыграет нам на руку, ведь если вы будете оставлять свой код в таком состоянии, в каком хотели бы сами хотели его получить, мир стал бы немножечко счастливей и чище. Правила написания кода: сила правильных именований, хорошие и плохие комментарии - 3В своей прошлой статье о правилах написания кода (а точнее, небольшом руководстве), мы немного пощупали рекомендации по написанию системы в целом и таких ее элементах как объекты, их интерфейсы, классы, методы и переменные. Там же я вскользь упоминал про правильные именования тех или иных элементов. Сегодня мне хотелось бы поговорить именно об этом, ведь правильные названия облегчают читаемость кода в разы. Закрывать же тему правильного кода будем с помощью размышлений и небольших примеров комментариев в коде — хорошо это или всё же не очень. Итак, давайте приступим.

Правильные именования

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

Именование интерфейсов

Интерфейсы, как правило, используют имена, начинающиеся с большой буквы и написаны в верблюжьем стиле (CamelCase). Раньше при написании интерфейса хорошей практикой считалось добавить префикс I для обозначения его как интерфейса (например, IUserService), но это выглядит весьма уродливо и отвлекает. В таких случая лучше писать без него (UserService), а к реализации его добавить -Impl (UserServiceImpl). Ну или на крайний случай, к его реализации добавить префикс С (СUserService).

Названия классов

Так же, как и у интерфейсов, имена пишутся с большой буквы и используют верблюжий стиль (CamelCase). Какой бы ни творился апокалипсис, как бы нb горели сроки, но никогда, запомните — никогда имя класса не должно быть глаголом! Имена классов и объектов должны быть существительными и их комбинациями (UserController, UserDetails, UserAccount, и так далее). Не следует снабжать имя каждого класса аббревиатурой данного приложения, так как это лишь добавит излишнюю сложность (например, у нас приложение User Data Migration, и мы к каждому классу добавим UDM — UDMUserDeatils, UDMUserAccount, UDMUserController).

Имена методов

Обычно названия методов начинаются с маленькой буквы, но и они юзают верблюжий стиль (СamelCase). Выше мы говорили про то, что имена классов никогда не должны быть глаголами. Тут же ситуация диаметрально противоположная: наименования методов как раз-таки должны быть глаголами или их сочетаниями с глаголами: findUserById, findAllUsers, createUser и так далее. При создании метода (как впрочем, и переменных и классов), чтобы не запутаться, используйте один подход в именовании. Например, для поиска пользователя метод можно написать как getUserById или findUserById. И ещё: не используйте в названиях методов юмор, ибо шуточку могут и не понять, как и то, что делает этот метод.

Названия переменных

В большинстве случаев имена переменных начинаются с маленькой буквы и тоже используют Сamelcase, не считая тех случаев, когда переменная — это глобальная константа. В таких случаях все буквы имени написаны в верхнем регистре и слова разделены нижним подчеркиванием — “_”. При именовании переменных для удобства можно использовать содержательный контекст. Иначе говоря, когда есть переменная как часть чего-то большего — например, firstName, lastName, status — в таких случаях можно добавить приставку, указывающую на объект, частью которого является данная переменная. Например: userFirstName, userLastName, userStatus. Ещё нужно избегать похожих имен для переменных, когда они имеют совершенно разную суть. Часто встречаемые антонимы для переменных:
  • begin/end
  • first/last
  • locked/unlocked
  • min/max
  • next/previous
  • old/new
  • opened/closed
  • visible/invisible
  • source/target
  • source/destination
  • up/down

Короткие имена переменных

Когда у нас есть переменные вида x или n или что-то вроде этого, мы сходу и не увидим намерения человека, писавшего код. Неочевидно, что делает метод n: он требует более вдумчивого осмысления (а это время время, время). Например, у нас есть поле — id ответственного пользователя, и вместо какого-нибудь имени типа — x или просто id, мы назовем эту переменную responsibleUserId, что сходу нам повышает читаемость и осмысленность. Тем не менее, короткие имена вида n имеют место быть в качестве локальных перемен небольших методов, где блок кода с этой переменой — всего лишь пара строк кода, и имя метода прекрасно описывает, что там происходит. Разработчик, увидев такую переменную, понимает её второстепенность и весьма ограниченную область видимости. По итогу есть некая зависимость от длины имени переменных: чем длиннее оно, тем более глобальная переменная и наоборот. Как пример, метод для поиска последнего сохраненного пользователя по дате:
public User findLastUser() {
   return findAllUsers().stream()
           .sorted((x, y) -> -x.getCreatedDate().compareTo(y.getCreatedDate()))
           .findFirst()
           .orElseThrow(() -> new ResourceNotFoundException("Any user doesn't exist "));
}
Тут мы используем короткие именования x и y для задания сортировки стрима, и забываем про них.

Оптимальная длина

Продолжим тему длины имён. Оптимальная длина имени — где-то между длиной имен maximumNumberOfUsersInTheCurrentGroup и n. То есть, слишком короткие страдают от недостатка смысла, а слишком длинные растягивают программу, не добавляя читабельности, и их просто лень каждый раз писать. Не учитывая вышеописанного случая, для переменных с коротким именем вида n нужно придерживаться длины примерно 8 -16 символов. Это не строгое правило: скорее как ориентир.

Малые различия

Не могу пройти мимо и малозаметных различий в именах, ведь это тоже плохая практика, так как можно просто напутать или потратить много лишнего времени на то, чтобы заметить незначительные различия в именах. Например, различие между InvalidDataAccessApiUsageException и InvalidDataAccessResourceUsageException беглым взглядом сложно обнаружить. Также часто дезинформация может возникнуть при использовании маленьких L и O, ведь их можно легко перепутать с 1 и 0: в некоторых шрифтах различие более очевидное, в некоторых — менее.

Смысловая часть

Нужно вкладывать смысловую часть в названия, но не переигрывать с синонимами, так как, к примеру, у UserData и UserInfo фактически один и тот же смысл, и придется немного поковыряться в коде, чтобы понять, какой конкретно объект нам нужен. Избегайте неинформативных слов, к примеру, firstNameString: к чему нам слово string? Разве может быть имя объектом типа даты? Конечно, нет: поэтому просто — firstName. Ещё в качестве примера хотелось бы отметить boolean переменные, например, — flagDelete. Слово flag не несёт никакой смысловой нагрузки. Более разумным было назвать — isDelete.

Дезинформация

Также хотелось бы сказать пару слов о неправильных именованиях. Допустим, у нас есть именование userActivityList, и при этом названный так объект имеет не тип List, а какой-нибудь другой контейнер или кастомный объект для хранения. Это может ввести рядового программиста в ступор: уж лучше назвать как-то вроде userActivityGroup или userActivities.

Поиск

Одним из недостатков коротких и простых имен является то, что их сложно искать в большом объёме кода, ведь что будет проще найти: переменную с именем name или NAME_FOR_DEFAULT_USER? Конечно же, второй вариант. Нужно избегать в названиях часто встречаемых слов (букв), так это будет лишь увеличивать количество найденных файлов при поиске, что не есть гуд. Хотелось бы напомнить, что за чтением кода программисты проводят больше времени, нежели за его написанием, так что с головой подходите к наименованию элементов вашего приложения. Но что если удачно назвать не получилось? Если название метода плохо описывает его функционал? Тут и выходит на сцену, наш следующий пункт — комментарии

Комментарии

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

Разновидности комментариев

  • юридические комментарии — комментарии, оставляемые в начале каждого файла с исходным кодом, по юридическим соображениям, как например:

    * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
    * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.

  • информативные комментарии — комментарии, представляющие пояснение к коду (предоставляющие дополнительную информацию или намерения данного участка кода.

    Как пример:

    /*
    * Объединяет пользователя из бд и пришедшего для обновления
    * Когда в requestUser поле пустое, оно заполняется старыми данными из foundUser
    */
    private User mergeUser(User requestUser, User foundUser) {
           return new User(
           foundUser.getId(),
           requestUser.getFirstName() == null ? requestUser.getFirstName() : foundUser.getFirstName(),
           requestUser.getMiddleName() == null ? requestUser.getMiddleName() : foundUser.getMiddleName(),
           requestUser.getLastName() == null ? requestUser.getLastName() : foundUser.getLastName(),
           requestUser.getAge() == null ? requestUser.getAge() : foundUser.getAge()
           );
           }

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

  • предупреждающий комментарий — комментарий, целью которого является предупредить других разработчиков о нежелательных последствиях какого-то действия (например, почему тест был помечен как @Ignore):

    // Слишком долго отрабатывает
    // Не запускайте, если не располагаете избытком времени
    @Ignore
    @Test
    public void someIntegrationTest() {
           ……
           }
  • TODO — комментарии, которые являются заметкой на будущее, что нужно будет сделать, но по какой-то причине нельзя сделать сейчас. Это неплохая практика, но всё же их нужно регулярно просматривать для удаления неактуальных, чтобы избежать нагромождения.

    Примером послужит:

    //TODO: Add a check for the current user ID (when will be created security context)
    
    @Override
    public Resource downloadFile(File file) {
           return fileManager.download(file);
           }

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

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

    Как пример, кусочек метода, заполняющий тестовую БД, некими скриптами:

    Stream.of(IOUtils.resourceToString("/fill-scripts/" + x, StandardCharsets.UTF_8)
           .trim()
           .split(";"))
           .forEach(jdbcTemplate::update);
    // Вызов trim() очень важен, убирает возможные пробелы в конце скрипта
    // чтобы при считке и разбивке на отдельные запросы не было пустых

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

    Пример вполне обычного метода обновления пользователя:

    /**
    * Обновляет передаваемые поля для пользователя по id.
    *
    * @param id  id обновляемого пользователя
    * @param user пользователь с заполненными полями для обновления
    * @return обновленный пользователь
    */
           User update(Long id, User user);

Плохие сценарии комментариев

Правила написания кода: сила правильных именований, хорошие и плохие комментарии - 7
  • бормочущий комментарий — комментарии, которые обычно пишут на скорую руку, смысл которых понятен только разработчику, писавшего их, так как только он видит ту ситуацию с теми нюансами, на которые он и ссылается.

    Рассмотрим данный пример:

    public void configureSomeSystem() {
           try{
           String configPath = filesLocation.concat("/").concat(CONFIGURATION_FILE);
           FileInputStream stream = new FileInputStream(configPath);
           }  catch (FileNotFoundException e) {
           //В случае отсутствия конфигурационного файла, загружается конфигурация по умолчанию
          }
    }

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

  • избыточный комментарий — комментарий, который не несёт смысловой нагрузки, так как и так понятно что происходит в заданном участке кода (он читается не проще, чем код).

    Смотрим пример:

    public class JdbcConnection{
    public class JdbcConnection{
       /**
        * Журнальный компонент, связанный с текущим классом
        */
       private Logger log = Logger.getLogger(JdbcConnection.class.getName());
    
       /**
        * Создаёт и возвращает connection с помощью входящих параметров
        */
       public static Connection buildConnection(String url, String login, String password, String driver) throws Exception {
           Class.forName(driver);
           connection = DriverManager.getConnection(url, login, password);
           log.info("Created connection with db");
           return connection;
       }

    Какой смысл таких комментариев, если мы и так всё прекрасно видим

  • недостоверные комментарии — комментарии, не соответствующие истине и лишь вгоняющие в заблуждение (дезинформирующие). Как например:

    /**
    * Вспомогательный метод, закрывает соединение со сканером, если isNotUsing истинно
    */
    private void scanClose(Scanner scan, boolean isNotUsing) throws Exception {
       if (!isNotUsing) {
           throw new Exception("The scanner is still in use");
       } scan.close();
    }

    Что в этом комменте не так? А то, что он немножко врёт нам, ведь соединение закрывается, если isNotUsing = false, но никак не наоборот, как нам вещает пометка.

  • обязательные комментарии — комментарии, которые считают обязательными (Javadoc), но кои по факту иногда бывают излишне нагромождающими, недостоверными и ненужными (нужно задуматься, а нужны ли здесь такие комментарии).

    Пример:

    /**
    *  Создание пользователя по переданным параметрам
    * @param firstName имя созданного пользователя
    * @param middleName среднее имя созданного пользователя
    * @param lastName фамилия созданного пользователя
    * @param age возраст созданного пользователя
    * @param address адресс созданного пользователя
    * @return пользователь который был создан
    */
    User createNewUser(String firstName, String middleName, String lastName, String age, String address);

    Смогли бы вы понять, что делает метод без этих комментариев? Скорее всего да, поэтому комментарии в этом случае стают бессмысленными.

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

    /**
    *  Записи ведутся с 09 января 2020;
    **********************************************************************
    *  09.01.2020  : Обеспечение соединения с БД с помощью Jdbc Connection;
    *  15.01.2020  : Добавление интерфейсов уровня дао для работы с БД;
    *  23.01.2020  : Добавление интеграционных тестов для БД;
    *  28.01.2020  : Имплементация интерфейсов уровня дао;
    *  01.02.2020  : Разработка интерфейсов для сервисов,
    *  согласно требованиям прописанным в user stories;
    *  16.02.2020  : Имплементация интерфейсов сервисов
    *  (реализация бизнес логики связанной с работой БД);
    *  25.02.2020  : Добавление тестов для сервисов;
    *  08.03.2020  : Празднование восьмого марта(Миша опять в хлам);
    *  21.03.2020  : Рефакторинг сервис слоя;
    */

    Когда-то этот проход был оправдан, но с появлением систем управления исходным кодом (например — Git), это стало лишним нагромождением и усложнением кода.

  • комментарии ссылки на авторов — комментарии, предназначением которых является, указание человека, писавшего код, чтобы можно было связаться и обсудить, как что и зачем:

    * @author  Bender Benderovich

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

  • закомментированный код — код, который был по тем или иным причинам закомментирован. Одна из самых плохих привычек, ибо вы закомментировали и забыли, а у других разработчиков просто не хватит храбрости его удалить (а вдруг это что-то ценное).

    //    public void someMethod(SomeObject obj) {
    //    .....
    //    }

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

  • неочевидные комментарии — комментарии, которые излишне сложно описывают что-либо.

    /*
        * Начать с массива, размер которого достаточен для хранения
        * всех байтов данных (плюс байты фильтра) с запасом, плюс 300 байт
        * для данных заголовка
        */
    this.dataBytes = new byte[(this.size * (this.deep + 1) * 2)+300];

    Комментарий должен объяснять код, а не сам нуждаться в объяснениях. А что тут? Что за «байты фильтра»? При чём здесь +1? Почему именно 300?

Если уж решили писать комменты, вот пара советов по их использованию:
  1. Используйте стили, которые будет легко поддерживать: поддерживать слишком причудливые и экзотические стили надоедает и съедает немало времени.
  2. Не используйте комментарии в конце строк, относящихся к одиночным строкам: получается большое нагромождение комментов, при этом трудно придумать выразительный комментарий для каждой строки.
  3. Придумывая комментарий, постарайтесь ответить на вопрос: «почему», а не «как».
  4. Избегайте сокращений. Как и говорил выше, нам не нужно объяснение для комментария: комментарий и есть объяснение.
  5. Можно использовать комментарии для пометки единиц измерения и диапазона допустимых величин.
  6. Располагайте комментарии близко к описываемому ими коду.
Как итог всё же хочется напомнить: лучшие комментарий — это отсутствие комментария, а вместо него — грамотное именование в приложении. Как правило, большинство времени мы будем уже работать уже с готовым кодом, с его поддержанием и расширением. Гораздо удобнее, когда данный код удобно читаем и понятен, ведь плохой код мешает, ставит те еще палки в колёса и спешка — его верный товарищ. И чем больше у нас плохого кода, тем больше падает производительность, поэтому нужно время от времени устраивать рефакторинг. Но если с самого начала стараться писать код, за который вас не захотят найти и убить следующие разработчики, то и рефакторить его нужно будет реже. Но всё же нужно будет, так как условия, требования к продукту постоянно меняются, дополняются навешивая дополнительные связи, и от этого не убежать. Напоследок оставлю пару интересных ссылок для ознакомления с данной темой тут, тут и тут Пожалуй, на этом у меня сегодня всё, спасибо всем кто дочитал)) Правила написания кода: сила правильных именований, хорошие и плохие комментарии - 8
Комментарии (8)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
kama1
Уровень 24
22 октября 2022, 06:15
🥳
Ян
Уровень 41
7 июня 2022, 21:08
//  *  08.03.2020  : Празднование восьмого марта(Миша опять в хлам);
Миши, отзовитесь)
LuneFox Java Developer в BIFIT Expert
2 мая 2022, 16:50
Читаю "Чистый код" Боба Мартина, и понимаю, что примеры в этой статье - слегка подредактированные примеры из его книги. "Списывай, но не 1 в 1" :)
Leftover
Уровень 39
2 ноября 2020, 21:28
Коммент не врет :)
/**
* Вспомогательный метод, закрывает соединение со сканером, если isNotUsing истинно
*/

private void scanClose(Scanner scan, boolean isNotUsing) throws Exception {
    if (!isNotUsing) {
        throw new Exception("The scanner is still in use");
    }
    scan.close();
}
Константин
Уровень 40
17 октября 2021, 16:02
Все же врет )
LuneFox Java Developer в BIFIT Expert
21 марта 2022, 20:20
Стоп... Если isNotUsing ложно, то выбрасывается исключение. Если истинно, то закрывается соединение со сканером. Всё верно, получается. Можно перевернуть if и будет яснее. А вот если бы было так,
if (!isNotUsing) {
    scan.close();
}
то было бы ложно уже :)
Константин
Уровень 40
22 апреля 2022, 09:36
Посмотрел еще раз. Пожалуй ты прав.
Хорс
Уровень 41
12 октября 2020, 17:11
Очень полезная и интересная статья. Просьба к автору подправить дублирование наречия "уже": "большинство времени мы будем УЖЕ работать УЖЕ с готовым кодом"