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

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

Стаття з групи Random UA
учасників
Правила написання коду: сила правильних імен, хороші та погані коментарі. Як часто вам доводилося розумітися на чужому коді? Коли замість кількох годин ти витрачаєш дні, щоб просто розібратися в логіці того, що відбувається. Найцікавіше, що для людини, яка писала цей код, тут все ясно і дуже прозоро. І це не дивно: адже досконалий чи ідеальний код — це дуже розмите поняття, бо кожен розробник має своє бачення на світ і код, відповідно, теж. Неодноразово стикався з ситуацією, коли я і мій колега дивабося на один і той же код мали різну думку щодо його коректності та чистоти. Правила написання коду: сила правильних імен, хороші та погані коментарі.Знайоме почуття, чи не так? Тим не менш, є деякі моменти, перевірені часом, яких варто було б дотримуватися, що в результаті зіграє нам на руку, адже якщо ви залишатимете свій код у такому стані, в якому хотіли б самі хотіли його отримати, світ став би трішки щасливішим і чистіше. Правила написання коду: сила правильних імен, хороші та погані коментарі.У своїй минулій статтіпро правила написання коду (а точніше, невеликому посібнику), ми трохи помацали рекомендації щодо написання системи в цілому та таких її елементах як об'єкти, їх інтерфейси, класи, методи та змінні. Там же я побіжно згадував про правильні іменування тих чи інших елементів. Сьогодні мені хотілося б поговорити саме про це, адже правильні назви полегшують читання коду у рази. Закривати ж тему правильного коду будемо за допомогою роздумів та невеликих прикладів коментарів у коді — добре це чи все ж таки не дуже. Отже, давайте приступимо.

Правильні іменування

Правильні назви покращують читабельність коду, відповідно заощаджуючи час на ознайомлення, адже значно простіше використовувати метод, коли назва приблизно описує його функціонал. Так як у коді все складається з назв (змінні, методи, класи, об'єкти, файли і т. д.), цей пункт стає дуже важливим при створенні правильного, чистого коду. Виходячи з вищесказаного, ім'я має передавати сенс, чому, наприклад, змінна існує, що вона робить і як використовується. Ще не раз зазначу, що найкращим коментарем для опису змінної є її правильне ім'я. Правила написання коду: сила правильних імен, хороші та погані коментарі.

Найменування інтерфейсів

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

Назви класів

Так само, як і в інтерфейсів, імена пишуться з великої літери та використовують верблюжий стиль (CamelCase). Який би не робився апокаліпсис, як би не горіли терміни, але ніколи, запам'ятайте — ніколи ім'я класу не повинно бути дієсловом! Імена класів та об'єктів повинні бути іменниками та їх комбінаціями (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? Звісно ж, другий варіант. Потрібно уникати в назвах слів (букв), що часто зустрічаються, так це буде лише збільшувати кількість знайдених файлів при пошуку, що не є гуд. За читанням коду програмісти проводять більше часу, ніж за його написанням, так що з головою підходите до найменування елементів вашої програми. Але якщо вдало назвати не вдалося? Якщо назва методу погано описує його функціонал? Тут і виходить на сцену, наш наступний пункт — коментарі

Коментарі

Правила написання коду: сила правильних імен, хороші та погані коментарі.Немає нічого кращого за доречний коментар, але і ніщо не захаращує модуль так, як беззмістовні, застарілі або помилкові коментарі. Палиця з двома кінцями, чи не так? Все ж таки не варто ставитися до коментарів як до однозначного добра: скоріше як до меншого зла. Адже коментар за своєю суттю — це компенсація невдало вираженої думки в коді. Наприклад, ми використовуємо їх, щоб якось донести суть методу, якщо він виявився надто заплутаним. У такій ситуації краще грамотно відрефакторити код, ніж писати позначки, що описують. Чим давніший коментар, тим гірше, адже код має властивість розростатися і еволюціонувати, а коментар може залишитися тим самим, і чим далі, тим сумнівніші ці замітки. Неточні коментарі набагато гірші, ніж їх відсутність, адже вони спантеличують і обманюють, даючи помилкові очікування.

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

  • юридичні коментарі - коментарі, що залишаються на початку кожного файлу з вихідним кодом, з юридичних міркувань, як наприклад:

    * 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);

Погані сценарії коментарів

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

    Розглянемо цей приклад:

    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. Розташовуйте коментарі близько до описуваного ними коду.
Як результат все ж таки хочеться нагадати: найкращі коментар - це відсутність коментаря, а замість нього - грамотне найменування в додатку. Як правило, більшість часу ми вже працюватимемо вже з готовим кодом, з його підтримкою та розширенням. Набагато зручніше, коли цей код зручно читаємо і зрозумілий, адже поганий код заважає, ставить ще палиці в колеса і поспіх — його вірний товариш. І чим більше у нас поганого коду, тим більше падає продуктивність, тому потрібно час від часу влаштовувати рефакторинг. Але якщо з самого початку намагатися писати код, за який вас не захочуть знайти та вбити наступні розробники, то й рефакторити його потрібно буде рідше. Але все ж таки потрібно буде, оскільки умови, вимоги до продукту постійно змінюються, доповнюються навішуючи додаткові зв'язки, і від цього не втекти.тут , тут і тут Мабуть, на цьому у мене сьогодні все, дякую всім хто дочитав)) Правила написання коду: сила правильних імен, хороші та погані коментарі.
Коментарі
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
Для цієї сторінки немає коментарів.