JavaRush /בלוג Java /Random-HE /כללי קוד: כוחו של השם הנכון, הערות טובות ורעות

כללי קוד: כוחו של השם הנכון, הערות טובות ורעות

פורסם בקבוצה
כללים לכתיבת קוד: כוחו של מתן שמות נכון, הערות טובות ורעות - 1 באיזו תדירות נאלצת להבין את הקוד של מישהו אחר? כאשר, במקום כמה שעות, אתה מבלה ימים רק כדי להבין את ההיגיון של מה שקורה. המצחיק הוא שלמי שכתב את הקוד הזה הכל ברור ושקוף מאוד. וזה לא מפתיע: אחרי הכל, קוד מושלם או אידיאלי הוא מושג מאוד מעורפל, כי לכל מפתח יש את החזון שלו לגבי העולם והקוד, בהתאמה. לא פעם נתקלתי במצב שבו אני ועמיתי הסתכלנו על אותו קוד והיו לנו דעות שונות לגבי נכונותו ונקיונו. כללים לכתיבת קוד: כוחו של מתן שמות נכון, הערות טובות ורעות - 2זו תחושה מוכרת, לא? עם זאת, יש כמה נקודות שנבדקו בזמן שצריך להקפיד עליהן, שבסופו של דבר יעבדו לטובתנו, כי אם תשאיר את הקוד שלך במצב שבו אתה עצמך תרצה לקבל אותו, העולם יהיה קצת יותר שמח מְנַקֶה. במאמר האחרוןכללים לכתיבת קוד: כוחו של מתן שמות נכון, הערות טובות ורעות - 3 שלנו על כללי כתיבת קוד (או ליתר דיוק, מדריך קטן), נגענו מעט בהמלצות לכתיבת המערכת כולה ומרכיביה כמו אובייקטים, ממשקים, מחלקות, שיטות ומשתנים. שם הזכרתי בקצרה את השם הנכון של אלמנטים מסוימים. היום הייתי רוצה לדבר בדיוק על זה, כי שמות נכונים הופכים את קריאת הקוד להרבה יותר קלה. נסגור את נושא הקוד הנכון בעזרת הרהורים ודוגמאות קטנות של הערות בקוד – האם זה טוב או לא כל כך טוב. אז בואו נתחיל.

מתן שמות נכון

שמות נכונים משפרים את קריאות הקוד, ובהתאמה חוסכים זמן בהיכרות, מכיוון שהרבה יותר קל להשתמש בשיטה כשהשם מתאר בערך את הפונקציונליות שלו. מכיוון שכל דבר בקוד מורכב משמות (משתנים, מתודות, מחלקות, אובייקטי קבצים וכו'), נקודה זו הופכת חשובה מאוד בעת יצירת קוד נכון ונקי. בהתבסס על האמור לעיל, השם אמור להעביר את המשמעות של מדוע, למשל, קיים משתנה, מה הוא עושה וכיצד הוא משמש. אציין שוב ושוב שההערה הטובה ביותר לתיאור משתנה היא שמו הנכון. כללים לכתיבת קוד: כוחו של מתן שמות נכון, הערות טובות ורעות - 4

ממשקי שמות

ממשקים משתמשים בדרך כלל בשמות שמתחילים באות גדולה ונכתבים באותיות גמל (CamelCase). פעם זה היה נוהג טוב בעת כתיבת ממשק להקדים אותו עם I כדי לייעד אותו כממשק (למשל IUserService), אבל זה די מכוער ומסיח את הדעת. במקרים כאלה עדיף לכתוב בלעדיו (UserService), ולהוסיף -Impl (UserServiceImpl) למימושו. ובכן, או כמוצא אחרון, הוסף את הקידומת C (CUserService) ליישום שלו.

שמות כיתות

בדיוק כמו ממשקים, השמות באותיות גדולות ומשתמשים בסגנון גמל (CamelCase). לא משנה איזה סוג של אפוקליפסה מתרחשת, לא משנה כמה מהירים המועדים הם, אבל לעולם אל תזכרו, השם של כיתה לעולם לא צריך להיות פועל! שמות מחלקות ואובייקטים חייבים להיות שמות עצם והשילובים שלהם (UserController, UserDetails, User Account וכן הלאה). אין לספק את השם של כל מחלקה עם הקיצור של האפליקציה הזו, מכיוון שזה רק יוסיף מורכבות מיותרת (לדוגמה, יש לנו אפליקציית User Data Migration, ואנו נוסיף UDM לכל מחלקה - UDMUserDeatils, UDMUserAccount, UDMUserController ).

שמות שיטות

בדרך כלל שמות השיטות מתחילים באות קטנה, אבל הם משתמשים גם בסגנון גמל (CamelCase). למעלה דיברנו על העובדה ששמות כיתות לא צריכים להיות פעלים. כאן המצב הפוך בתכלית: שמות השיטות חייבים להיות פעלים או צירופיהם עם פעלים: findUserById, findAllUsers, createUser וכו'. בעת יצירת שיטה (כמו גם משתנים ומחלקות), כדי למנוע בלבול, השתמש בגישת שמות אחת. לדוגמה, כדי למצוא משתמש, השיטה יכולה להיכתב בתור getUserById או findUserById. ועוד משהו: אל תשתמשו בהומור בשמות של שיטות, כי הם עלולים לא להבין את הבדיחה, כמו גם מה השיטה הזו עושה.

שמות משתנים

ברוב המקרים, שמות משתנים מתחילים באות קטנה ומשתמשים גם באותיות Camelcase, למעט מקרים בהם המשתנה הוא קבוע גלובלי. במקרים כאלה, כל אותיות השם נכתבות באותיות גדולות והמילים מופרדות בקו תחתון - "_". בעת מתן שמות למשתנים, אתה יכול להשתמש בהקשר משמעותי מטעמי נוחות. במילים אחרות, כאשר יש משתנה כחלק ממשהו גדול יותר - למשל, firstName, lastName, status - במקרים כאלה ניתן להוסיף קידומת המציינת את האובייקט שהמשתנה הזה הוא חלק ממנו. לדוגמה: userFirstName, userLastName, userStatus. אתה גם צריך להימנע משימוש בשמות דומים למשתנים כאשר יש להם משמעויות שונות לחלוטין. אנטונימים נפוצים למשתנים:
  • להתחיל/סיום
  • ראשון אחרון
  • נעול/לא נעול
  • מינימום מקסימום
  • הקודם הבא
  • ישן חדש
  • נפתח/נסגר
  • גלוי/בלתי נראה
  • מקור/יעד
  • מקור/יעד
  • למעלה למטה

שמות משתנים קצרים

כשיש לנו משתנים כמו x או n או משהו כזה, אנחנו לא רואים מיד את הכוונה של האדם שכתב את הקוד. לא ברור מה עושה שיטה n: היא דורשת חשיבה יותר מחושבת (וזה זמן, זמן, זמן). לדוגמא, יש לנו שדה - המזהה של המשתמש האחראי, ובמקום איזה שם כמו 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 כדי להגדיר את מיון הזרם, ולשכוח מהם.

אורך אופטימלי

בואו נמשיך את נושא אורך השם. אורך השם האופטימלי הוא איפשהו בין אורך השם המקסימליNumberOfUsersInTheCurrentGroup לבין n. כלומר, קצרים מדי סובלים מחוסר משמעות, וארוכים מדי מותחים את התוכנית מבלי להוסיף קריאות, והם פשוט מתעצלים לכתוב אותם בכל פעם. אם לא לוקחים בחשבון את המקרה הנ"ל, עבור משתנים עם שם קצר כמו n, אתה צריך לשמור על האורך של כ-8 -16 תווים. זה לא כלל קפדני: יותר קו מנחה.

הבדלים קטנים

אני לא יכול להתעלם מהבדלים עדינים בשמות, כי זה גם מנהג רע, מכיוון שאתה יכול פשוט להתבלבל או לבזבז זמן נוסף על הבדלים קלים בשמות. לדוגמה, קשה לזהות את ההבדל בין InvalidDataAccessApiUsageException ל-InvalidDataAccessResourceUsageException. כמו כן, מידע שגוי יכול להתעורר לעתים קרובות בעת שימוש ב-L ו-O קטנים, מכיוון שניתן לבלבל ביניהם בקלות עם 1 ו-0: בחלק מהגופנים ההבדל ברור יותר, באחרים פחות.

חלק סמנטי

אנחנו צריכים להכניס את החלק הסמנטי לשמות, אבל לא לשחק יותר מדי עם מילים נרדפות, שכן, למשל, ל-UserData ול-UserInfo יש למעשה אותה משמעות, ונצטרך לחפור קצת יותר לעומק בקוד כדי להבין איזה אובייקט ספציפי אנחנו צריכים . הימנע ממילים לא אינפורמטיביות, למשל, firstNameString: למה אנחנו צריכים את המילה מחרוזת? האם שם יכול להיות אובייקט מסוג תאריך? כמובן שלא: לכן, פשוט - FirstName. כדוגמה, אני רוצה להזכיר משתנים בוליאניים, למשל, flagDelete. למילה דגל אין שום משמעות סמנטית. זה היה הגיוני יותר לקרוא לזה - 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
    @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 контекста) с тем, кто сохранил.

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

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

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

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

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

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

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

כללים לכתיבת קוד: כוחו של מתן שמות נכון, הערות טובות ורעות - 7
  • бормочущий комментарий — комментарии, которые обычно пишут на скорую руку, смысл которых понятен только разработчику, писавшего их, так How только он видит ту ситуацию с теми нюансами, на которые он и ссылается.

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

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

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

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

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

    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();
    }

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

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

    Пример:

    /**
    *  Creation пользователя по переданным параметрам
    * @param firstName Name созданного пользователя
    * @param middleName среднее Name созданного пользователя
    * @param lastName фамorя созданного пользователя
    * @param age возраст созданного пользователя
    * @param address 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  : Рефакторинг сервис слоя;
    */

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

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

    * @author  Bender Benderovich

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

  • קוד תגובות הוא קוד שהוצא מסיבה זו או אחרת. אחד ההרגלים הגרועים ביותר, כי הגבת את זה ושכחת, ולמפתחים אחרים פשוט אין את האומץ למחוק את זה (מה אם זה משהו בעל ערך).

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

    כתוצאה מכך, הוא מצטבר כמו זבל. בשום פנים ואופן אין להשאיר קוד כזה. אם אתה באמת צריך את זה, אל תשכח את מערכת בקרת הגרסאות.

  • הערות לא ברורות הן הערות שמתארות משהו בצורה מורכבת שלא לצורך.

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

    הערה צריכה להסביר את הקוד, לא צריך הסבר עצמו. מה פה? מהם "בתים סינון"? מה הקשר של 1+ לזה? למה בדיוק 300?

אם תחליט לכתוב הערות, הנה כמה טיפים לשימוש בהן:
  1. השתמשו בסגנונות קלים לתחזוקה: שמירה על סגנונות מפוארים ואקזוטיים מדי יכולה להיות מעצבנת וגוזלת זמן.
  2. אל תשתמשו בהערות בסוף שורות המתייחסות לשורות בודדות: כך נוצרת ערימה גדולה של הערות, וקשה להעלות הערה אקספרסיבית לכל שורה.
  3. בעת יצירת הערה, נסה לענות על השאלה "למה" ולא "איך".
  4. הימנע מקיצורים. כפי שאמרתי לעיל, אין אנו צריכים הסבר להערה: ההערה היא ההסבר.
  5. ניתן להשתמש בהערות כדי לסמן את יחידות המדידה ואת טווח הערכים המקובלים.
  6. הצב הערות קרוב לקוד שהם מתארים.
כתוצאה מכך, אני בכל זאת רוצה להזכיר לכם: ההערות הטובות ביותר הן היעדר הערה, ובמקום זאת, שם תקין באפליקציה. ככלל, רוב הזמן כבר נעבוד עם קוד מוכן, תחזוק והרחבתו. זה הרבה יותר נוח כאשר הקוד הזה קל לקריאה ולהבנה, כי קוד גרוע מפריע, מכניס חישור לגלגלים, והחיפזון הוא בן לוויתו הנאמן. וככל שיש לנו יותר קוד גרוע, כך הביצועים יורדים יותר, אז אנחנו צריכים לבצע מחדש מעת לעת. אבל אם כבר מההתחלה תנסה לכתוב קוד שעבורו המפתחים הבאים לא ירצו למצוא אותך ולהרוג אותך, אז תצטרך לשחזר אותו בתדירות נמוכה יותר. אבל זה עדיין יהיה הכרחי, שכן התנאים והדרישות למוצר משתנים כל הזמן, בתוספת חיבורים נוספים, ואין מנוס מכך. לבסוף , אשאיר לכם כמה קישורים מעניינים כדי להכיר את הנושא הזה כאן , כאן וכאן אני מניח שזה הכל בשבילי היום, תודה לכל מי שקרא)) כללים לכתיבת קוד: כוחו של מתן שמות נכון, הערות טובות ורעות - 8
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION