CodeGym /Java Blog /Toto sisi /編碼規則:正確名稱、好的和壞的註釋的力量
John Squirrels
等級 41
San Francisco

編碼規則:正確名稱、好的和壞的註釋的力量

在 Toto sisi 群組發布
編碼規則:正確名稱、好的和壞的評論的力量 - 1您有多少次不得不深入研究別人的代碼?你可能會花兩天的時間來簡單地理解正在發生的事情的邏輯,而不是兩個小時。有趣的是,對於編寫代碼的人來說,一切都是清晰且完全透明的。這並不奇怪:畢竟,完美代碼是一個非常模糊的概念,因為每個開發人員對世界和代碼都有自己的看法。我不止一次遇到這樣的情況,我和一位同事查看相同的代碼,但對其正確性和整潔性有不同的看法。編碼規則:正確名稱的力量,好的和壞的評論 - 2聽起來很熟悉,不是嗎?儘管如此,還是有一些經過時間考驗的原則需要遵守。最終,它們會對您有利,因為如果您將代碼保持在您自己希望接收它的狀態,那麼世界會變得更快樂、更清潔。在我們之前的文章中(或者更確切地說,小指南)關於編碼規則,我們對編寫一個系統作為一個整體及其組成部分,如對象、接口、類、方法和變量,有了一些建議。在同一篇文章中,我不經意地提到了某些元素的正確命名。我今天想談談這個,因為正確的名稱使代碼更容易閱讀。我們將通過一些反思、代碼註釋的小示例以及對這是否好的考慮來結束正確代碼的主題。好吧,讓我們開始吧。

正確的名字

正確的名稱可以提高代碼的可讀性,從而減少熟悉代碼所需的時間,因為當方法的名稱大致描述了它的功能時,使用方法會容易得多。代碼中的一切都由名稱(變量、方法、類、對象、文件等)組成,因此在創建正確、乾淨的代碼時這一點變得非常重要。基於以上所述,名稱應該傳達含義,例如變量存在的原因、作用、用途等。我會不止一次地指出,對變量最好的註釋是給它起一個好名字。編碼規則:正確名稱的力量,好的和壞的評論 - 3

來自電視劇“夏洛克”(2010-2017)

命名接口

接口的名稱通常以大寫字母開頭,並以 CamelCase 書寫。在編寫接口時,添加前綴“I”以將其指定為接口(例如,IUserService)曾經被認為是一種很好的做法,但這看起來很醜陋並且讓人分心。在這種情況下,最好省略前綴 (UserService) 並將“Impl”作為後綴添加到其實現的名稱中(例如 UserServiceImpl)。或者,作為最後的手段,在實現的名稱中添加一個“C”前綴(例如 CUserService)。

類名

就像接口一樣,類名也是大寫的並且使用 CamelCase。我們是否面臨殭屍末日並不重要,末日是否臨近也無關緊要 - 永遠,永遠,永遠不要讓類名成為動詞!類名和對象名必須是名詞或複合名詞(UserController、UserDetails、UserAccount 等)。您不應將應用程序的縮寫附加到每個類名稱的末尾,因為那樣只會增加不必要的複雜性。例如,如果我們有一個用戶數據遷移應用程序,那麼請不要在每個類中添加“UDM”,即 UDMUserDetails、UDMUserAccount、UDMUserController。

方法名稱

通常,方法名以小寫字母開頭,但它們也使用駝峰式(camelCase)。上面,我們說過類名永遠不應該是動詞。這裡的情況正好相反:方法的名稱應該是動詞或動詞短語:findUserById、findAllUsers、createUser 等等。在創建方法(以及變量和類)時,請使用一致的命名約定以避免混淆。例如,要查找用戶,可以將方法命名為 getUserById 或 findUserById。還有一件事:不要在方法名稱中使用幽默,因為其他人可能無法理解這個笑話。結果,他們可能無法理解該方法的作用。

變量名

在大多數情況下,變量名以小寫字母開頭,也使用駝峰式命名,除非變量是全局常量。在這種情況下,名稱的所有字母都以大寫形式書寫,單詞之間用下劃線(“_”)分隔。為方便起見,您可以在命名變量時使用有意義的上下文。換句話說,當變量作為更大的事物的一部分存在時,例如 firstName、lastName 或 status。在這種情況下,您可以添加一個前綴來指示該變量所屬的對象。例如:userFirstName、userLastName、userStatus。當變量具有完全不同的含義時,您還應該避免使用相似的名稱。以下是一些常用於變量名的反義詞:
  • 開始/結束
  • 第一個/最後一個
  • 鎖定/解鎖
  • 最小/最大
  • 下一個/上一個
  • 老新
  • 打開/關閉
  • 可見/不可見
  • 來源/目標
  • 來源/目的地
  • 上/下

短變量名

當我們有 x 或 n 之類的變量時,我們不會立即看到編寫代碼的人的意圖。n 做什麼並不明顯。弄清楚這一點需要更仔細的思考(這意味著時間、時間、時間)。例如,假設我們有一個字段代表負責用戶的 ID。我們將此變量命名為“responsibleUserId”,而不是像 x 或簡單的 id 這樣的變量名稱,這會立即提高可讀性和信息內容。也就是說,像 n 這樣的短名稱在小方法中作為局部變量佔有一席之地,其中涉及該變量的代碼塊只有幾行長,而方法名稱完美地描述了那裡發生的事情。看到這樣的變量,開發人員明白它是次要的,並且範圍非常有限。因此,範圍對變量名的長度有一定的依賴性:名稱越長,變量越全局,反之亦然。例如,這是一種按日期查找最後保存的用戶的方法:

public User findLastUser() {
   return findAllUsers().stream()
           .sorted((x, y) -> -x.getCreatedDate().compareTo(y.getCreatedDate()))
           .findFirst()
           .orElseThrow(() -> new ResourceNotFoundException("No user exists"));
}
這裡我們使用短命名變量 x 和 y 對流進行排序,然後我們忘記它們。

最佳長度

讓我們繼續名稱長度的話題。最佳名稱長度介於 n 和 maximumNumberOfUsersInTheCurrentGroup 之間。換句話說,短的名字會缺乏意義,而太長的名字會拉長程序而不會增加可讀性,而且我們也懶得每次都寫。除了上面描述的具有短名稱(如 n)的變量的情況外,您應該堅持大約 8-16 個字符的長度。這不是一個嚴格的規則,只是一個指導方針。

小差異

我不能不提一下名字上的細微差別。這也是一種不好的做法,因為這些差異可能只會讓人感到困惑,或者需要花費大量額外時間才能注意到它們。例如,InvalidDataAccessApiUsageException 和 InvalidDataAccessResourceUsageException 之間的區別很難一目了然。使用小寫字母 L 和 O 時也經常會出現混淆,因為它們很容易被誤認為是 1 和 0。在某些字體中,差異更明顯,而在某些字體中則更小。

意義

我們需要使名稱有意義,但不能通過同義詞產生歧義,因為例如 UserData 和 UserInfo 實際上具有相同的含義。在這種情況下,我們將不得不深入研究代碼以了解我們需要哪個特定對象。避免使用不能傳達有用信息的詞語。例如,在 firstNameString 中,為什麼我們需要 String 這個詞?這真的是 Date 對象嗎?當然不是。所以,我們簡單地使用 firstName。我還想提一下布爾變量。例如,使用一個名為 flagDeleted 的布爾值。旗幟這個詞沒有任何意義。稱其為 isDeleted 更為合理。

虛假信息

我還想談談不正確的命名約定。假設我們有一個名為 userActivityList 的變量,但這個對像不是 List,而是一些其他容器類型或自定義存儲對象。這可能會使普通程序員感到困惑:最好將其稱為 userActivityGroup 或 userActivities。

搜索

簡短名稱的缺點之一是它們很難在大量代碼中找到——“name”或“NAME_FOR_DEFAULT_USER”哪個更容易找到?當然是第二種選擇。我們應該避免在名稱中經常遇到的單詞(字母),因為它們只會增加搜索時匹配文件的數量,這是不好的。我想提醒您,程序員花在閱讀代碼上的時間多於編寫代碼,因此在命名應用程序的元素時要聰明。但是,如果找不到好名字怎麼辦?如果一個方法的名稱沒有很好地描述它的功能怎麼辦?這是評論進入舞台的地方。

評論

編碼規則:正確名稱的力量,好的和壞的評論 - 4沒有什麼比中肯的評論更好的了,但是沒有什麼比空洞的、過時的或錯誤的評論更能使模塊混亂的了。它們可以是一把雙刃劍,不是嗎?不過,您不應將評論視為明確的好事,而應將其視為次要的罪惡。畢竟,註釋本質上是一種補償代碼中未明確表達的想法的方法。例如,如果方法本身太令人困惑,我們會使用它們以某種方式傳達方法的本質。在這種情況下,正確地重構代碼比編寫描述性註釋更好。註釋越舊,註釋越差,因為代碼往往會增長和演變,但註釋可能保持不變。自評論創建以來經過的時間越長,它就越有問題。不准確的評論比根本沒有評論要糟糕得多,因為它們具有混淆性和欺騙性,給人以錯誤的期望。即使我們有非常棘手的代碼,我們也應該重寫它而不是註釋它。

評論類型

  • 法律註釋——出於法律原因在每個源文件開頭的註釋,例如:

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

  • 信息性註釋——代表代碼解釋的註釋(提供附加信息或闡述給定代碼部分的意圖)。

    例如:

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

    在這種情況下,您可以不加註釋,因為方法名稱及其參數以及非常透明的功能很好地描述了它們自己。

  • 警告註釋——註釋旨在警告其他開發人員有關操作的不良後果(例如,警告他們為什麼測試被標記為@Ignore):

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

  • TODO — 註釋是關於將來需要完成但由於某種原因現在無法完成的事情的註釋。這是一個很好的做法,但應定期審查此類評論,以刪除不相關的評論並避免混亂。

    一個例子是:

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

    這裡我們注意到一個事實,即我們需要添加執行下載操作的用戶(我們將從安全上下文中提取其 ID)與執行保存操作的用戶的比較。

  • 強化評論——強調某個情況的重要性的評論,乍一看似乎微不足道。

    例如,考慮一個用一些腳本填充測試數據庫的方法:

    
    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
    

  • Javadoc 註釋— 描述特定功能的 API 的註釋。可能有最有用的評論,因為文檔化的 API 更容易使用。也就是說,它們也可能像任何其他類型的評論一樣過時。所以,永遠不要忘記,對文檔的主要貢獻不是通過註釋,而是通過好的代碼。

    下面是一個相當常見的更新用戶方法的示例:

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

差評

  • 喃喃自語的評論——通常是匆忙寫下的評論,其含義只有編寫它們的開發人員才能理解,因為只有他或她才能感知評論所指的微妙情況。

    考慮這個例子:

    
    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 
          }
    }
    

    誰加載這些設置?他們已經加載了嗎?此方法是否應該捕獲異常並加載默認設置?出現的問題太多,只能通過深入研究系統的其他部分來回答。

  • 冗餘註釋——不帶有任何語義負載的註釋,因為代碼的給定部分中發生的事情非常清楚。換句話說,註釋並不比代碼更容易閱讀。

    讓我們看一個例子:

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

    這樣的評論有什麼意義?他們解釋的一切都已經很清楚了。

  • 不可靠的評論——不真實且僅具有誤導性的評論(虛假信息)。例如,這裡有一個。

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

    這個評論有什麼問題?事實上,它對我們有點謊言,因為如果 isNotUsing 為假,連接將關閉,反之亦然,正如評論告訴我們的那樣。

  • 強制性評論——被認為是強制性的評論(例如 Javadoc 評論),但實際上有時會過度堆積並且不可靠且不必要(您需要考慮是否真的需要這些評論)。

  • 例子:

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

    如果沒有這些註釋,您是否能夠理解該方法的作用?很可能,是的,所以評論在這裡變得毫無意義。

  • 日誌評論——有時在每次編輯模塊時添加到模塊開頭的評論(類似於更改日誌)。

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

    這種方法曾經是合理的,但隨著版本控制系統(例如 Git)的出現,它變成了不必要的代碼混亂和復雜化。

  • 作者評論——旨在表明編寫代碼的人的評論,以便您可以聯繫他/她並討論如何、什麼和為什麼,例如:

    
    * @author Bender Bending
    

    再一次,版本控制系統準確地記住了誰在什麼時候添加了任何代碼,所以這種方法是多餘的。

  • 註釋掉的代碼——出於某種原因被註釋掉的代碼。這是最糟糕的習慣之一,因為發生的事情是你註釋掉了一些東西然後忘了它,然後其他開發人員就沒有勇氣刪除它(畢竟,如果它是有價值的東西怎麼辦?)。

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

    結果,註釋掉的代碼像垃圾一樣堆積起來。在任何情況下你都不應該留下這樣的代碼。如果你真的需要它,不要忘記版本控制系統。

  • 不明顯的評論——以過於復雜的方式描述某事的評論。

    
    /*
        * 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];
    

    評論應該解釋代碼。它本身不應該需要解釋。那麼這裡出了什麼問題?什麼是“過濾字節”?“+ 1”是什麼意思?為什麼正好是 300?

如果您已經決定寫評論,這裡有一些提示:
  1. 使用易於維護的樣式:維護過於花哨​​和異國情調的樣式既煩人又耗時。
  2. 不要使用引用單行的行尾註釋:結果是一大堆註釋。更重要的是,很難為每一行想出一個有意義的評論。
  3. 撰寫評論時,請嘗試回答“為什麼”的問題,而不是“如何”的問題。
  4. 避免刪節信息。正如我上面所說,我們不需要對評論進行解釋:評論本身就是解釋。
  5. 您可以使用註釋來記錄單位和值範圍。
  6. 將註釋放在它們描述的代碼附近。
最後,我還是要提醒你,最好的註釋不是註釋,而是在整個應用程序中使用巧妙的命名。通常,大部分時間我們將使用現有代碼,對其進行維護和擴展。當這段代碼易於閱讀和理解時會方便得多,因為糟糕的代碼是一個障礙。這就像在工作中投擲一把扳手,匆忙是它的忠實夥伴。糟糕的代碼越多,性能下降的越多。這意味著我們需要時不時地進行重構。但是,如果從一開始您就嘗試編寫不會導致下一個開發人員想要找到並殺死您的代碼,那麼您就不需要經常重構它。但這仍然是必要的,因為產品的條件和要求會隨著新的依賴項和連接的添加而不斷變化。好吧,我想這就是我今天的全部內容。感謝所有讀到這裡的人 :)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION