CodeGym /Blog Java /Random-PL /Zasady kodowania: siła poprawnych nazw, dobrych i złych k...
John Squirrels
Poziom 41
San Francisco

Zasady kodowania: siła poprawnych nazw, dobrych i złych komentarzy

Opublikowano w grupie Random-PL
Zasady kodowania: siła poprawnych nazw, dobre i złe komentarze - 1Jak często musiałeś grzebać w czyimś kodzie? Zamiast dwóch godzin, możesz spędzić dwa dni, aby po prostu zrozumieć logikę tego, co się dzieje. Zabawne jest to, że dla osoby, która napisała kod, wszystko jest jasne i całkowicie przejrzyste. Nie jest to zaskakujące: w końcu doskonały kod to bardzo mgliste pojęcie, ponieważ każdy programista ma własną wizję świata i kodu. Nie raz spotkałem się z sytuacją, gdy wraz ze współpracownikiem patrzyliśmy na ten sam kod i mieliśmy różne opinie co do jego poprawności i czystości.Zasady kodowania: siła poprawnych nazw, dobre i złe komentarze - 2Brzmi znajomo, prawda? Mimo to istnieją pewne sprawdzone zasady, których należy przestrzegać. Ostatecznie będą one dla Ciebie korzystne, bo jeśli pozostawisz swój kod w takim stanie, w jakim sam chciałbyś go otrzymać, to świat stałby się trochę szczęśliwszy i czystszy. W naszym poprzednim artykule(a raczej mały przewodnik) na temat zasad kodowania, dostaliśmy trochę wyczucia zaleceń dotyczących pisania systemu jako całości i jego części składowych, takich jak obiekty, interfejsy, klasy, metody i zmienne. W tym samym artykule mimochodem wspomniałem o prawidłowym nazewnictwie niektórych elementów. Chciałbym dziś o tym porozmawiać, ponieważ poprawne nazwy sprawiają, że kod jest wielokrotnie łatwiejszy do odczytania. Temat poprawnego kodu zakończymy kilkoma refleksjami, małymi przykładami komentarzy w kodzie i rozważeniem, czy jest to dobre, czy nie. Cóż, zacznijmy.

Poprawne nazwy

Poprawne nazwy poprawiają czytelność kodu, skracając tym samym czas potrzebny na zapoznanie się z kodem, ponieważ użycie metody jest znacznie łatwiejsze, gdy jej nazwa z grubsza opisuje jej funkcjonalność. Wszystko w kodzie składa się z nazw (zmienne, metody, klasy, obiekty, pliki itp.), więc ten punkt staje się bardzo ważny podczas tworzenia poprawnego, czystego kodu. W oparciu o powyższe, nazwa powinna przekazywać znaczenie, na przykład, dlaczego zmienna istnieje, co robi i jak jest używana. Nieraz zauważę, że najlepszym komentarzem do zmiennej jest nadanie jej dobrej nazwy.Zasady kodowania: siła poprawnych nazw, dobre i złe komentarze - 3

z serialu "Sherlock" (2010-2017)

Nazewnictwo interfejsów

Interfejsy zwykle mają nazwy zaczynające się od dużej litery i są zapisane w CamelCase. Podczas pisania interfejsu uważano za dobrą praktykę dodawanie przedrostka „I” w celu oznaczenia go jako interfejsu (na przykład IUserService), ale wygląda to dość brzydko i rozprasza. W takich przypadkach lepiej pominąć przedrostek (UserService) i dodać „Impl” jako przyrostek do nazwy jego implementacji (np. UserServiceImpl). Ewentualnie w ostateczności dodać przedrostek „C” do nazwy implementacji (np. CUserService).

Nazwy klas

Podobnie jak interfejsy, nazwy klas są pisane wielkimi literami i używają CamelCase. Nie ma znaczenia, czy stoimy w obliczu apokalipsy zombie, nie ma znaczenia, czy koniec jest bliski — nigdy, nigdy, nigdy nazwa klasy nie powinna być czasownikiem! Nazwy klas i obiektów muszą być rzeczownikami lub rzeczownikami złożonymi (UserController, UserDetails, UserAccount itd.). Nie należy przyklejać skrótu aplikacji na końcu nazwy każdej klasy, ponieważ spowodowałoby to tylko niepotrzebną komplikację. Na przykład, jeśli mamy aplikację do migracji danych użytkownika, prosimy nie dodawać „UDM” do każdej klasy, tj. UDMUserDetails, UDMUserAccount, UDMUserController.

Nazwy metod

Zwykle nazwy metod zaczynają się od małej litery, ale używają również stylu camel case (camelCase). Powyżej powiedzieliśmy, że nazwy klas nigdy nie powinny być czasownikami. Tutaj sytuacja jest odwrotna: nazwy metod powinny być czasownikami lub frazami czasownikowymi: findUserById, findAllUsers, createUser i tak dalej. Podczas tworzenia metody (a także zmiennych i klas) używaj spójnej konwencji nazewnictwa, aby uniknąć nieporozumień. Na przykład, aby znaleźć użytkownika, metoda może mieć nazwę getUserById lub findUserById. I jeszcze jedno: nie używaj humoru w nazwach metod, bo inni mogą nie zrozumieć żartu. W rezultacie mogą nie zrozumieć, co robi ta metoda.

Nazwy zmiennych

W większości przypadków nazwy zmiennych zaczynają się od małej litery i również używają camelCase, z wyjątkiem sytuacji, gdy zmienna jest stałą globalną. W takich przypadkach wszystkie litery nazwy są pisane wielkimi literami, a słowa są oddzielone podkreśleniem („_”). Dla wygody podczas nazywania zmiennych można używać zrozumiałego kontekstu. Innymi słowy, gdy zmienna istnieje jako część czegoś większego, na przykład imię, nazwisko lub status. W takich przypadkach można dodać przedrostek wskazujący obiekt, do którego należy dana zmienna. Na przykład: imię użytkownika, nazwisko użytkownika, status użytkownika. Należy również unikać podobnych nazw dla zmiennych, gdy mają one zupełnie inne znaczenie. Oto kilka często spotykanych antonimów używanych w nazwach zmiennych:
  • początek/koniec
  • pierwszy/ostatni
  • zablokowany/odblokowany
  • minimum maksimum
  • Następna poprzednia
  • stare nowe
  • otwarte/zamknięte
  • widoczne/niewidzialne
  • cel źródłowy
  • źródło/przeznaczenie
  • Góra dół

Krótkie nazwy zmiennych

Kiedy mamy zmienne takie jak x lub n lub coś w tym rodzaju, nie od razu widzimy intencje osoby, która napisała kod. Nie jest oczywiste, co robi n. Zrozumienie tego wymaga dokładniejszej kontemplacji (a to oznacza czas, czas, czas). Załóżmy na przykład, że mamy pole reprezentujące identyfikator odpowiedzialnego użytkownika. Zamiast jakiejś nazwy zmiennej typu x lub po prostu id, nazwiemy tę zmienną „responsibleUserId”, co od razu poprawia czytelność i zawartość informacji. To powiedziawszy, krótkie nazwy, takie jak n, mają miejsce jako zmienne lokalne w małych metodach, gdzie blok kodu obejmujący tę zmienną ma zaledwie kilka linii, a nazwa metody doskonale opisuje, co się tam dzieje. Widząc taką zmienną programista rozumie, że ma ona drugorzędne znaczenie i ma bardzo ograniczony zakres. W rezultacie zasięg ma pewną zależność od długości nazwy zmiennej: im dłuższa nazwa, tym bardziej globalna zmienna i odwrotnie. Oto przykład metody znajdowania ostatniego zapisanego użytkownika według daty:

public User findLastUser() {
   return findAllUsers().stream()
           .sorted((x, y) -> -x.getCreatedDate().compareTo(y.getCreatedDate()))
           .findFirst()
           .orElseThrow(() -> new ResourceNotFoundException("No user exists"));
}
Tutaj używamy zmiennych o krótkich nazwach x i y do sortowania strumienia, a potem o nich zapominamy.

Optymalna długość

Kontynuujmy temat długości nazwy. Optymalna długość nazwy wynosi od n do maksimumNumberOfUsersInTheCurrentGroup. Innymi słowy, krótkie nazwy cierpią z powodu braku znaczenia, podczas gdy zbyt długie nazwy wydłużają program bez zwiększania czytelności, a my jesteśmy po prostu zbyt leniwi, aby je zapisywać za każdym razem. Poza przypadkiem opisanym powyżej dla zmiennych o krótkiej nazwie typu n, należy trzymać się długości około 8-16 znaków. To nie jest ścisła zasada, tylko wskazówka.

Małe różnice

Nie mogę nie wspomnieć o subtelnych różnicach w nazwach. Jest to również zła praktyka, ponieważ te różnice mogą być po prostu mylące lub wymagać poświęcenia dużo dodatkowego czasu, aby je zauważyć. Na przykład różnica między InvalidDataAccessApiUsageException a InvalidDataAccessResourceUsageException jest trudna do zauważenia na pierwszy rzut oka. W przypadku używania małych liter L i O często dochodzi do zamieszania, ponieważ można je łatwo pomylić z 1 i 0. W niektórych czcionkach różnica jest bardziej oczywista, w innych mniej.

Znaczenie

Musimy nadać nazwom znaczenie, ale nie tworzyć dwuznaczności za pomocą synonimów, ponieważ na przykład UserData i UserInfo mają w rzeczywistości to samo znaczenie. W takim przypadku musielibyśmy zagłębić się w kod, aby zrozumieć, jakiego konkretnego obiektu potrzebujemy. Unikaj słów, które nie przekazują przydatnych informacji. Na przykład, dlaczego w firstNameString potrzebujemy słowa String? Czy to naprawdę może być obiekt Date? Oczywiście nie. Więc po prostu używamy imienia. Chciałbym również wspomnieć o zmiennych boolowskich. Jako przykład weźmy wartość logiczną o nazwie flagDeleted. Słowo flaga nie ma żadnego znaczenia. Rozsądniej jest nazwać to isDeleted.

Dezinformacja

Chciałbym również powiedzieć kilka słów o nieprawidłowych konwencjach nazewnictwa. Załóżmy, że mamy zmienną o nazwie userActivityList, ale zamiast listy, ten obiekt jest innym typem kontenera lub niestandardowym obiektem pamięci masowej. Może to zmylić przeciętnego programistę: lepiej nazwać to czymś w rodzaju userActivityGroup lub userActivities.

Szukaj

Jedną z wad krótkich i prostych nazw jest to, że trudno je znaleźć w dużej części kodu — co byłoby łatwiejsze do znalezienia: „nazwa” czy „NAME_FOR_DEFAULT_USER”? Druga opcja, oczywiście. Powinniśmy unikać często spotykanych słów (liter) w nazwach, ponieważ zwiększą one tylko liczbę pasujących plików podczas wyszukiwania, co nie jest dobre. Pragnę przypomnieć, że programiści spędzają więcej czasu na czytaniu kodu niż na jego pisaniu, dlatego mądrze nazywaj elementy swojej aplikacji. Ale co, jeśli po prostu nie można znaleźć dobrej nazwy? Co jeśli nazwa metody nie opisuje dobrze jej funkcjonalności? W tym miejscu na scenę wkraczają komentarze.

Uwagi

Zasady kodowania: siła poprawnych nazw, dobre i złe komentarze - 4Nie ma nic lepszego niż trafny komentarz, ale nic nie zaśmieca modułu tak jak bezsensowne, nieaktualne lub fałszywe komentarze. Mogą być mieczem obosiecznym, prawda? Nie należy jednak traktować komentarzy jako jednoznacznie dobrego, a raczej jako mniejsze zło. W końcu komentarz jest zasadniczo sposobem na zrekompensowanie myślenia, które nie jest wyraźnie widoczne w kodzie. Na przykład używamy ich, aby w jakiś sposób przekazać istotę metody, jeśli sama metoda okaże się zbyt zagmatwana. W takiej sytuacji lepiej jest poprawnie zrefaktoryzować kod niż pisać noty opisowe. Im starszy komentarz, tym gorszy, ponieważ kod rozwija się i ewoluuje, ale komentarze mogą pozostać takie same. Im więcej czasu minęło od utworzenia komentarza, tym bardziej może budzić wątpliwości. Niedokładne komentarze są znacznie gorsze niż ich brak, ponieważ wprowadzają w błąd i wprowadzają w błąd, dając fałszywe oczekiwania. I nawet jeśli mamy bardzo skomplikowany kod, powinniśmy go przepisać, a nie komentować.

Rodzaje komentarzy

  • Komentarze prawne — Komentarze na początku każdego pliku źródłowego ze względów prawnych, na przykład:

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

  • Komentarze informacyjne — Komentarze stanowiące wyjaśnienie kodu (dostarczające dodatkowych informacji lub wyjaśniające przeznaczenie danej sekcji kodu).

    Na przykład:

    
    /*
    * Combines the user from the database with the one passed for updating
    * When a field in requestUser is empty, it is filled with old data from 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()
           );
           }
    

    W tym przypadku można obejść się bez komentarzy, gdyż nazwa metody i jej parametry w połączeniu z bardzo przejrzystą funkcjonalnością dobrze opisują same siebie.

  • Komentarze ostrzegawcze — Komentarz mający na celu ostrzeżenie innych programistów o niepożądanych konsekwencjach działania (na przykład ostrzeżenie o tym, dlaczego test został oznaczony jako @Ignore):

    
    // Takes too long to run
    // Don't run if you don't have a lot of time
    @Ignore
    @Test
    public void someIntegrationTest() {
           ……
           }
    

  • DO ZROBIENIA — Komentarze, które są notatką o czymś, co należy zrobić w przyszłości, ale z jakiegoś powodu nie można zrobić teraz. Jest to dobra praktyka, ale takie komentarze należy regularnie przeglądać, aby usunąć nieistotne i uniknąć bałaganu.

    Przykładem może być:

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

    Tutaj zwracamy uwagę na fakt, że musimy dodać porównanie użytkownika wykonującego operację pobierania (którego identyfikator wyodrębnimy z kontekstu bezpieczeństwa) z tym, który wykonał operację zapisu.

  • Uwagi wzmacniające — Komentarze podkreślające wagę okoliczności, która na pierwszy rzut oka może wydawać się nieistotna.

    Jako przykład rozważ fragment metody, który wypełnia testową bazę danych niektórymi skryptami:

    
    Stream.of(IOUtils.resourceToString("/fill-scripts/" + x, StandardCharsets.UTF_8)
           .trim()
           .split(";"))
           .forEach(jdbcTemplate::update);
    // The trim() call is very important. It removes possible spaces at the end of the script
    // so that when we read and split into separate requests, we don't end up with empty ones
    

  • Komentarze Javadoc — Komentarze opisujące interfejs API dla określonych funkcji. Są prawdopodobnie najbardziej przydatne komentarze, ponieważ udokumentowany interfejs API jest znacznie łatwiejszy w obsłudze. To powiedziawszy, mogą być również nieaktualne, jak każdy inny rodzaj komentarzy. Nigdy więc nie zapominaj, że główny wkład w dokumentację to nie komentarze, ale dobry kod.

    Oto przykład dość powszechnej metody aktualizowania użytkownika:

    
    /**
    * Updates the passed fields for a user based on its id.
         *
    * @param id id of the user to be updated
    * @param user user with populated fields for updating
    * @return updated user
    */
           User update(Long id, User user);
    

Złe komentarze

  • mruczący komentarz — Komentarze, które zwykle są pisane w pośpiechu i których znaczenie jest zrozumiałe tylko dla programisty, który je napisał, ponieważ tylko on dostrzega zniuansowaną sytuację, do której odnosi się komentarz.

    Rozważ ten przykład:

    
    public void configureSomeSystem() {
           try{
           String configPath = filesLocation.concat("/").concat(CONFIGURATION_FILE);
           FileInputStream stream = new FileInputStream(configPath);
           } catch (FileNotFoundException e) {
           // If there is no configuration file, the default configuration is loaded 
          }
    }
    

    Kto ładuje te ustawienia? Czy zostały już załadowane? Czy ta metoda ma wyłapywać wyjątki i ładować ustawienia domyślne? Pojawia się zbyt wiele pytań, na które można odpowiedzieć jedynie poprzez zbadanie innych części systemu.

  • Zbędne komentarze — Komentarze, które nie mają żadnego ładunku semantycznego, ponieważ to, co dzieje się w danej sekcji kodu, jest całkowicie jasne. Innymi słowy, komentarz nie jest łatwiejszy do odczytania niż kod.

    Zobaczmy przykład:

    
    public class JdbcConnection{
    public class JdbcConnection{
       /**
        * The logger associated with the current class
        */
       private Logger log = Logger.getLogger(JdbcConnection.class.getName());
    
       /**
        * Creates and returns a connection using the input parameters
        */
       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;
       }
    

    Jaki jest sens takich komentarzy? Wszystko, co wyjaśniają, jest już doskonale jasne.

  • Niewiarygodne komentarze — Komentarze, które są nieprawdziwe i jedynie wprowadzają w błąd (dezinformacja). Na przykład, oto jeden.

    
    /**
    * Helper method. Closes the connection with the scanner if isNotUsing is true
    */
    private void scanClose(Scanner scan, boolean isNotUsing) throws Exception {
       if (!isNotUsing) {
           throw new Exception("The scanner is still in use");
       } scan.close();
    }
    

    Co jest nie tak z tym komentarzem? Fakt, że trochę nas okłamuje, w tym, że połączenie jest zamknięte, jeśli isNotUsing jest fałszywe, a nie odwrotnie, o czym informuje nas komentarz.

  • Obowiązkowe komentarze — Komentarze, które są uważane za obowiązkowe (np. komentarze Javadoc), ale w rzeczywistości czasami piętrzą się nadmiernie, są niewiarygodne i niepotrzebne (trzeba się zastanowić, czy te komentarze są rzeczywiście potrzebne).

  • Przykład:

    
    /**
    * Create a user based on the parameters
    * @param firstName first name of the created user
    * @param middleName middle name of the created user
    * @param lastName last name of the created user
    * @param age age of the created user
    * @param address address of the created user
    * @return user that was created
    */
    User createNewUser(String firstName, String middleName, String lastName, String age, String address);
    

    Czy byłbyś w stanie zrozumieć, co robi ta metoda bez tych komentarzy? Najprawdopodobniej tak, więc komentarze stają się tutaj bezcelowe.

  • Komentarze w dzienniku — Komentarze, które czasami są dodawane na początku modułu za każdym razem, gdy jest on edytowany (coś w rodzaju dziennika zmian).

    
    /**
    * Records kept since January 9, 2020;
    **********************************************************************
    * 9 Jan 2020: Providing a database connection using JDBC Connection;
    * 15 Jan 2020: Adding DAO-level interfaces for working with the database;
    * 23 Jan 2020: Adding integration tests for the database;
    * 28 Jan 2020: Implementation of DAO-level interfaces;
    * 1 Feb 2020: Development of interfaces for services,
    * in accordance with the requirements specified in user stories;
    * 16 Feb 2020: Implementation of service interfaces
    * (implementation of business logic related to the work of the database);
    * 25 Feb 2020: Adding tests for services;
    * 8 Mar 2020: Celebration of International Women's Day (Terry is drunk again);
    * 21 Mar 2020: Refactoring the service layer;
    */
    

    Takie podejście było kiedyś uzasadnione, ale wraz z pojawieniem się systemów kontroli wersji (na przykład Git) stało się niepotrzebnym bałaganem i komplikacją kodu.

  • Komentarze autorskie — Komentarze, których celem jest wskazanie osoby, która napisała kod, dzięki czemu można się z nią skontaktować i przedyskutować, jak, co i dlaczego, np.:

    
    * @author Bender Bending
    

    Po raz kolejny systemy kontroli wersji dokładnie pamiętają, kto dodał jakikolwiek fragment kodu i kiedy, więc takie podejście jest zbędne.

  • Skomentowany kod — Kod, który został skomentowany z tego czy innego powodu. To jeden z najgorszych nawyków, ponieważ zdarza się, że komentujesz coś i zapominasz o tym, a potem inni programiści po prostu nie mają odwagi tego usunąć (w końcu co, jeśli to coś wartościowego?).

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

    W rezultacie wykomentowany kod gromadzi się jak śmieci. W żadnym wypadku nie należy pozostawiać takiego kodu. Jeśli naprawdę tego potrzebujesz, nie zapomnij o systemie kontroli wersji.

  • Komentarze nieoczywiste — Komentarze, które opisują coś w nadmiernie skomplikowany sposób.

    
    /*
        * Start with an array large enough to store
        * all the data bytes (plus filter bytes) with a cushion, plus 300 bytes
        * for header data
        */
    this.dataBytes = new byte[(this.size * (this.deep + 1) * 2)+300];
    

    Komentarz powinien wyjaśniać kod. Sam w sobie nie powinien wymagać wyjaśnienia. Więc co tu jest nie tak? Co to są „bajty filtra”? O co chodzi z tym „+1”? Dlaczego dokładnie 300?

Jeśli już zdecydowałeś się pisać komentarze, oto kilka wskazówek:
  1. Używaj stylów, które są łatwe w utrzymaniu: utrzymywanie stylów, które są zbyt fantazyjne i egzotyczne, jest irytujące i czasochłonne.
  2. Nie używaj komentarzy na końcu wiersza, które odnoszą się do pojedynczych wierszy: wynikiem jest duży stos komentarzy. Co więcej, trudno wymyślić sensowny komentarz do każdej linijki.
  3. Tworząc komentarz, staraj się odpowiedzieć na pytanie „dlaczego”, a nie „jak”.
  4. Unikaj skróconych informacji. Jak powiedziałem powyżej, nie potrzebujemy wyjaśnienia komentarza: sam komentarz jest wyjaśnieniem.
  5. Możesz użyć komentarzy, aby zanotować jednostki i zakresy wartości.
  6. Umieść komentarze blisko kodu, który opisują.
Na koniec chcę jeszcze przypomnieć, że najlepszym komentarzem jest brak komentarza, a raczej umiejętne nazewnictwo w całej aplikacji. Z reguły przez większość czasu będziemy pracować z istniejącym kodem, utrzymując go i rozszerzając. O wiele wygodniej jest, gdy ten kod jest łatwy do odczytania i zrozumiały, ponieważ zły kod jest przeszkodą. To jak wrzucenie klucza do robót, a pośpiech jest jego wiernym towarzyszem. A im więcej mamy złego kodu, tym bardziej spada wydajność. Oznacza to, że od czasu do czasu musimy dokonać refaktoryzacji. Ale jeśli od samego początku będziesz próbował napisać kod, który nie spowoduje, że kolejni programiści będą chcieli cię znaleźć i zabić, nie będziesz musiał go tak często refaktoryzować. Ale nadal będzie to konieczne, ponieważ warunki i wymagania produktu stale się zmieniają wraz z dodawaniem nowych zależności i połączeń. Cóż, to chyba wszystko dla mnie na dziś. Dziękuję wszystkim, którzy doczytali do tego momentu :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION