供貢獻者使用的 AOSP Java 代碼樣式

此頁面上的代碼樣式是向 Android 開源項目 (AOSP) 貢獻 Java 代碼的嚴格規則。捐款的Android平台不遵守這些規則一般是不能接受的。我們認識到並非所有現有代碼都遵循這些規則,但我們希望所有新代碼都符合這些規則。請參閱關於編碼為一個更具包容性的生態系統的術語使用,並避免的例子。

始終如一

最簡單的規則之一是保持一致。如果您正在編輯代碼,請花幾分鐘時間查看周圍的代碼並確定其樣式。如果代碼使用周圍的空間if條款,你也應該這樣。如果代碼註釋周圍有小星星框,讓你的註釋也有小星星框。

擁有風格指南的重點是擁有一個通用的編碼詞彙,這樣讀者就可以專注於你在說什麼,而不是你是如何說的。我們在這裡展示了全局樣式規則,以便您了解詞彙,但本地樣式也很重要。如果您添加到文件中的代碼看起來與它周圍的現有代碼截然不同,那麼讀者在閱讀它時就會失去節奏。盡量避免這種情況。

Java語言規則

Android 遵循標準的 Java 編碼約定以及下面描述的附加規則。

不要忽略異常

編寫忽略異常的代碼可能很誘人,例如:

  void setServerPort(String value) {
      try {
          serverPort = Integer.parseInt(value);
      } catch (NumberFormatException e) { }
  }

不要這樣做。雖然您可能認為您的代碼永遠不會遇到這種錯誤情況或者處理它並不重要,但忽略這種類型的異常會在您的代碼中創建地雷,以便其他人有一天觸發。您必須以有原則的方式處理代碼中的每個異常;具體處理視情況而定。

只要有人有他們應該有一個讓人毛骨悚然的感覺。有一定時候,它實際上是做正確的事情空的catch子句,但至少你得想想,在Java中,你無法逃避的令人毛骨悚然的感覺。 “ -詹姆斯·高斯林

可接受的替代方案(按優先順序)是:

  • 將異常拋出給方法的調用者。
      void setServerPort(String value) throws NumberFormatException {
          serverPort = Integer.parseInt(value);
      }
    
  • 拋出一個適合您的抽象級別的新異常。
      void setServerPort(String value) throws ConfigurationException {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new ConfigurationException("Port " + value + " is not valid.");
        }
      }
    
  • 正常處理的錯誤,並在替換為適當的值catch {}塊。
      /** Set port. If value is not a valid number, 80 is substituted. */
    
      void setServerPort(String value) {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            serverPort = 80;  // default port for server
        }
      }
    
  • 捕獲異常,並拋出的新實例RuntimeException 。這是危險的,所以只有當您確定如果發生此錯誤時才執行此操作,正確的做法是崩潰。
      /** Set port. If value is not a valid number, die. */
    
      void setServerPort(String value) {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new RuntimeException("port " + value " is invalid, ", e);
        }
      }
    
  • 作為最後的手段,如果您確信忽略異常是合適的,那麼您可以忽略它,但您還必須以充分的理由評論原因。
    /** If value is not a valid number, original port number is used. */
    
    void setServerPort(String value) {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            // Method is documented to just ignore invalid user input.
            // serverPort will just be unchanged.
        }
    }
    

不要捕獲通用異常

在捕獲異常並執行以下操作時很容易變得懶惰:

  try {
      someComplicatedIOFunction();        // may throw IOException
      someComplicatedParsingFunction();   // may throw ParsingException
      someComplicatedSecurityFunction();  // may throw SecurityException
      // phew, made it all the way
  } catch (Exception e) {                 // I'll just catch all exceptions
      handleError();                      // with one generic handler!
  }

不要這樣做。在幾乎所有情況下,這是不恰當的追趕通用的ExceptionThrowable (最好不要Throwable ,因為它包含Error例外)。這是危險的,因為它意味著你永遠預料的異常(包括運行時異常就像ClassCastException )被捲進應用級的錯誤處理。它掩蓋了代碼的故障處理屬性,這意味著如果有人在您調用的代碼中添加了新類型的異常,編譯器不會指出您需要以不同方式處理錯誤。在大多數情況下,您不應該以相同的方式處理不同類型的異常。

此規則的罕見例外是測試代碼和頂級代碼,您希望在其中捕獲各種錯誤(以防止它們出現在 UI 中,或保持批處理作業運行)。在這種情況下,你可能趕上一般Exception (或Throwable ),並適當處理錯誤。但是,在執行此操作之前請仔細考慮,並在註釋中解釋為什麼在這種情況下它是安全的。

捕獲通用異常的替代方法:

  • 分別趕上每個異常作為多catch塊的一部分,例如:
    try {
        ...
    } catch (ClassNotFoundException | NoSuchMethodException e) {
        ...
    }
  • 重構您的代碼以使用多個 try 塊進行更細粒度的錯誤處理。從解析中拆分 IO,並在每種情況下分別處理錯誤。
  • 重新拋出異常。很多時候你無論如何都不需要在這個級別捕獲異常,只需讓方法拋出它即可。

請記住,例外是您的朋友!當編譯器抱怨你沒有捕捉到異常時,不要皺眉。微笑!編譯器只是讓您更輕鬆地捕獲代碼中的運行時問題。

不要使用終結器

終結器是一種在對像被垃圾回收時執行一段代碼的方法。雖然終結器可以方便地清理(尤其是外部資源),但不能保證終結器何時會被調用(甚至根本不會被調用)。

Android 不使用終結器。在大多數情況下,您可以改用良好的異常處理。如果你絕對需要一個終結,定義close()正好方法(等)和文件時要調用的方法需求(見的InputStream的例子)。在這種情況下,從終結器打印一條簡短的日誌消息是合適的,但不是必需的,只要它不會淹沒日誌。

完全合格的進口

當你想使用類Bar從包foo ,有兩種可能的方式來導入它:

  • import foo.*;

    可能會減少導入語句的數量。

  • import foo.Bar;

    使使用的類一目了然,並且代碼對於維護者來說更具可讀性。

使用import foo.Bar;用於導入所有 Android 代碼。一個明確的例外是Java標準庫取得( java.util.*java.io.*等)和單元測試代碼( junit.framework.* )。

Java 庫規則

有使用 Android 的 Java 庫和工具的約定。在某些情況下,約定在重要方面發生了變化,舊代碼可能會使用已棄用的模式或庫。使用此類代碼時,可以繼續現有樣式。但是,在創建新組件時,切勿使用已棄用的庫。

Java 風格規則

使用 Javadoc 標準註釋

每個文件的頂部都應該有一個版權聲明,然後是 package 和 import 聲明(每個塊由一個空行分隔),最後是類或接口聲明。在 Javadoc 註釋中,描述類或接口的作用。

/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.foo;

import android.os.Blah;
import android.view.Yada;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Does X and Y and provides an abstraction for Z.
 */

public class Foo {
    ...
}

每類和你寫必須包含至少一個句子描述什麼類或方法做了Javadoc註釋平凡的公共方法。這句話應該以第三人稱描述性動詞開頭。

例子

/** Returns the correctly rounded positive square root of a double value. */

static double sqrt(double a) {
    ...
}

或者

/**
 * Constructs a new String by converting the specified array of
 * bytes using the platform's default character encoding.
 */
public String(byte[] bytes) {
    ...
}

您不必為瑣碎的get和set方法,如寫的Javadoc setFoo()如果所有的Javadoc會說是“集富”。如果該方法做了一些更複雜的事情(例如強制執行約束或具有重要的副作用),那麼您必須記錄它。如果屬性“Foo”的含義不明顯,您應該記錄它。

您編寫的每個方法,無論是公共的還是其他方式,都會從 Javadoc 中受益。公共方法是 API 的一部分,因此需要 Javadoc。 Android不執行特定的風格寫的Javadoc註釋,但你應該遵循說明如何編寫適用於Javadoc工具的文檔註釋

編寫簡短的方法

在可行的情況下,保持方法小而集中。我們認識到長方法有時是合適的,因此對方法長度沒有硬性限制。如果一個方法超過40行左右,考慮是否可以在不破壞程序結構的情況下分解它。

在標準位置定義字段

在文件頂部或緊接使用它們的方法之前定義字段。

限制變量範圍

將局部變量的範圍保持在最低限度。這提高了代碼的可讀性和可維護性,並降低了出錯的可能性。在包含變量所有用途的最裡面的塊中聲明每個變量。

在首次使用局部變量時聲明局部變量。幾乎每個局部變量聲明都應該包含一個初始值設定項。如果您還沒有足夠的信息來合理地初始化變量,請推遲聲明,直到您這樣做為止。

例外是 try-catch 語句。如果一個變量是用拋出已檢查異常的方法的返回值初始化的,則它必須在 try 塊內初始化。如果必須在 try 塊之外使用該值,則必須在 try 塊之前聲明它,在那裡它還不能被合理地初始化:

// Instantiate class cl, which represents some sort of Set

Set s = null;
try {
    s = (Set) cl.newInstance();
} catch(IllegalAccessException e) {
    throw new IllegalArgumentException(cl + " not accessible");
} catch(InstantiationException e) {
    throw new IllegalArgumentException(cl + " not instantiable");
}

// Exercise the set
s.addAll(Arrays.asList(args));

但是,您甚至可以通過在方法中封裝 try-catch 塊來避免這種情況:

Set createSet(Class cl) {
    // Instantiate class cl, which represents some sort of Set
    try {
        return (Set) cl.newInstance();
    } catch(IllegalAccessException e) {
        throw new IllegalArgumentException(cl + " not accessible");
    } catch(InstantiationException e) {
        throw new IllegalArgumentException(cl + " not instantiable");
    }
}

...

// Exercise the set
Set s = createSet(cl);
s.addAll(Arrays.asList(args));

除非有令人信服的理由,否則在 for 語句本身中聲明循環變量:

for (int i = 0; i < n; i++) {
    doSomething(i);
}

for (Iterator i = c.iterator(); i.hasNext(); ) {
    doSomethingElse(i.next());
}

訂單導入語句

導入語句的順序是:

  1. 安卓進口
  2. 從第三方進口( comjunitnetorg
  3. javajavax

為了完全匹配 IDE 設置,導入應該是:

  • 每個分組內按字母順序排列,大寫字母在小寫字母之前(例如,Z 在 a 之前)
  • 各主要類別之間的空行分隔( androidcomjunitnetorgjavajavax

最初,對排序沒有樣式要求,這意味著 IDE 要么總是改變排序,要么 IDE 開發人員必須禁用自動導入管理功能並手動維護導入。這被認為是不好的。當被問及 Java 風格時,首選風格千差萬別,歸根結底是 Android 需要簡單地“選擇順序並保持一致”。所以我們選擇了一種風格,更新了風格指南,並讓 IDE 遵守它。我們希望當 IDE 用戶處理代碼時,所有包中的導入都將匹配此模式,而無需額外的工程工作。

我們選擇了這種風格:

  • 人們想看看第一的進口往往是在頂部( android )。
  • 人們想看看至少進口往往是在底部( java )。
  • 人類可以很容易地遵循這種風格。
  • IDE 可以遵循這種風格。

將靜態導入置於與常規導入相同的順序的所有其他導入之上。

使用空格縮進

我們對塊使用四 (4) 個空格縮進,從不使用製表符。如有疑問,請與周圍的代碼保持一致。

我們使用八 (8) 個空格縮進進行換行,包括函數調用和賦值。

受到推崇的

Instrument i =
        someLongExpression(that, wouldNotFit, on, one, line);

不建議

Instrument i =
    someLongExpression(that, wouldNotFit, on, one, line);

遵循字段命名約定

  • 非公開,非靜態字段名稱開頭m
  • 靜態字段名稱以s
  • 其他字段以小寫字母開頭。
  • 公共靜態最終字段(常量)是ALL_CAPS_WITH_UNDERSCORES

例如:

public class MyClass {
    public static final int SOME_CONSTANT = 42;
    public int publicField;
    private static MyClass sSingleton;
    int mPackagePrivate;
    private int mPrivate;
    protected int mProtected;
}

使用標準大括號樣式

將大括號放在與它們之前的代碼相同的行上,而不是放在它們自己的行上:

class MyClass {
    int func() {
        if (something) {
            // ...
        } else if (somethingElse) {
            // ...
        } else {
            // ...
        }
    }
}

我們需要在條件語句周圍使用大括號。例外:如果整個條件(條件和主體)放在一行中,您可以(但沒有義務)將它們全部放在一行中。例如,這是可以接受的:

if (condition) {
    body();
}

這是可以接受的:

if (condition) body();

但這是不可接受的:

if (condition)
    body();  // bad!

限制線長

代碼中的每行文本的長度最多為 100 個字符。雖然許多討論已經包圍這條規則,決定仍然是100個字符是以下情況除外最大:

  • 如果註釋行包含示例命令或長度超過 100 個字符的文字 URL,為了便於剪切和粘貼,該行可能會超過 100 個字符。
  • 導入線可能會超過限制,因為人類很少看到它們(這也簡化了工具編寫)。

使用標準 Java 註釋

註釋應位於同一語言元素的其他修飾符之前。簡單的標記註釋(例如, @Override )可以在與所述語言元件的同一行中列出。如果有多個註釋或參數化註釋,請按字母順序每行列出一個。

Java 中三個預定義註解的 Android 標準做法是:

  • 使用@Deprecated註釋時使用的註釋元素氣餒。如果你使用@Deprecated標註,你還必須有一個@deprecated Javadoc標記,它應該命名一個替代實現。另外,請記住,一個@Deprecated方法仍然應該工作。如果您發現有一個舊的代碼@deprecated Javadoc標記,添加@Deprecated註解。
  • 使用@Override註釋每當一個方法覆蓋從超類的聲明或實施。例如,如果您使用@inheritdocs Javadoc標記,並從中類(而不是接口),還必須標註該方法覆蓋父類的方法。
  • 使用@SuppressWarnings僅情況下它是不可能消除的警告註釋。如果警告通過這個“不可能消除”測試中, @SuppressWarnings註釋必須使用,以確保所有的警告反映了代碼的實際問題。

    @SuppressWarnings註釋是必要的,它必須與一個前綴TODO註釋,解釋了“不可能消除”狀態。這通常會標識具有笨拙接口的違規類。例如:

    // TODO: The third-party class com.third.useful.Utility.rotate() needs generics
    @SuppressWarnings("generic-cast")
    List<String> blix = Utility.rotate(blax);
    

    @SuppressWarnings需要註解,重構代碼以分離其中註解應用軟件元件。

將首字母縮略詞視為單詞

在命名變量、方法和類時將首字母縮寫詞和縮寫詞視為單詞,以使名稱更具可讀性:

好的壞的
xmlHttp請求XMLHTTP請求
獲取客戶 ID獲取客戶ID
類 Html類 HTML
字符串網址字符串網址
長 ID長身份證

由於 JDK 和 Android 代碼庫在首字母縮略詞方面不一致,因此幾乎不可能與周圍的代碼保持一致。因此,始終將首字母縮寫詞視為單詞。

使用 TODO 註釋

使用TODO註釋的代碼,是暫時的,短期的解決方案,或不夠好,但並不完美。這些意見應包括字符串TODO全部大寫,後面跟一個冒號:

// TODO: Remove this code after the UrlTable2 has been checked in.

// TODO: Change this to use a flag instead of a constant.

如果你的TODO的形式是“在未來的日子做些什麼”,確保你要么包含特定日期(“修正2005年11月”)或特定事件(“畢竟生產攪拌機了解協議V7中刪除此代碼。” )。

謹慎記錄

雖然日誌記錄是必要的,但如果不保持合理簡潔,它會對性能產生負面影響並失去其用處。日誌記錄工具提供五種不同級別的日誌記錄:

  • ERROR :使用時,一些致命的已經發生了,那就是,什麼都會有用戶可見的後果,會不會不刪除一些數據,卸載應用程序,擦數據分區,或者重新刷新整個設備(或更糟)恢復。始終記錄此級別。這證明在一些日誌記錄問題的ERROR水平都不錯的候選人上報給統計收集服務器。
  • WARNING :當事情嚴重,意想不到的事情發生使用,也就是東西,將有用戶可見的後果,但很可能通過執行一些明確的行動,從等待或重新啟動應用程序一路重新下載是不會丟失數據可恢復應用程序的新版本或重新啟動設備。始終記錄此級別。這證明在日誌記錄問題的WARNING級別可能也被考慮到,統計信息收集服務器報告。
  • INFORMATIVE :使用要注意的是一些有趣的事情發生了,那就是,當檢測到這種情況是有可能產生廣泛的影響,但不一定是一個錯誤。這樣的條件應該只由認為它是該域中最權威的模塊記錄(以避免非權威組件重複記錄)。始終記錄此級別。
  • DEBUG :用於進一步注意到發生了什麼,可能是相關的調查和調試意外行為的設備上。僅記錄收集有關組件發生情況的足夠信息所需的內容。如果您的調試日誌占主導地位,那麼您應該使用詳細日誌記錄。

    這個電平被記錄甚至在發布版本,並且需要由一個包圍if (LOCAL_LOG)if LOCAL_LOGD)塊,其中LOCAL_LOG[D]在你的類或子組件被定義,以使得有禁用所有這樣的日誌記錄的可能性.因此,必須有在不活性邏輯if (LOCAL_LOG)塊。所有日誌字符串建設也需要放在裡面if (LOCAL_LOG)塊。如果它會導致串建設採取的以外的地方不要重構記錄呼出到一個方法調用if (LOCAL_LOG)塊。

    有一些代碼,仍然說if (localLOGV)這也被認為是可以接受的,儘管名稱是非標準的。

  • VERBOSE :使用了一切。上調試版本和應該由一個包圍該電平僅登錄if (LOCAL_LOGV)塊(或等效物),以便它可以被默認編出來。任何字符串建築被剝離的發布版本和需求出現在裡面if (LOCAL_LOGV)塊。

筆記

  • 在給定的模塊,比其他VERBOSE級別,錯誤只應一次如果可能的話報導。在一個模塊內的單個函數調用鏈中,只有最裡面的函數應該返回錯誤,並且同一模塊中的調用者應該只添加一些日誌記錄,如果這對隔離問題有顯著幫助。
  • 在鏈模塊,比在其它VERBOSE水平,當下級模塊檢測無效的數據從更高級別的模塊來,較低級別的模塊應該只記錄此情況向DEBUG日誌,且僅當日誌記錄提供調用者無法獲得的信息。具體來說,不需要記錄拋出異常的情況(異常應該包含所有相關信息),或者記錄的唯一信息包含在錯誤代碼中。這是造成已正確由框架處理應該不會觸發記錄比更高的第三方應用程序框架和應用程序,和條件之間的互動尤其重要DEBUG水平。應該在觸發記錄的唯一情況INFORMATIVE級別或更高級別是當一個模塊或應用程序在其自己的水平檢測到錯誤或從一個較低的水平來。
  • 當通常證明某些日誌記錄合理的情況可能多次發生時,實施某種速率限制機制以防止日誌溢出許多相同(或非常相似)信息的重複副本可能是一個好主意。
  • 網絡連接的丟失被認為是常見的並且是完全可以預期的,不應無故記錄。有一個應用程序內的後果網絡連接的損失應在記錄DEBUGVERBOSE級別(取決於是否後果是嚴重的不足和意外足以在一個發布版本被記錄)。
  • 在第三方應用程序可以訪問或代表第三方應用程序訪問的文件系統上擁有完整的文件系統不應以高於 INFORMATIVE 的級別記錄。
  • 無效的數據從任何非置信源來(包括在共享存儲的任何文件,或通過網絡連接來的數據)被認為是預期的和不應該在水平觸發任何測井高於DEBUG的所檢測到時,它是無效的(甚至然後記錄應盡可能限制)。
  • 當所使用String對象,則+操作者隱式地創建一個StringBuilder與默認的緩衝區大小(16個字符)和潛在的其他臨時的實例String對象。因此,明確創建StringBuilder對象不是不是依賴於默認比較貴+運營商(可以是很多更有效)。請記住,調用代碼Log.v()被編譯並執行發行版本,包括建設中的字符串,即使日誌沒有被讀取。
  • 任何旨在供其他人閱讀並在發布版本中可用的日誌記錄都應該簡潔而不神秘,並且應該是可以理解的。這包括所有的日誌記錄到DEBUG水平。
  • 如果可能,請保持單行登錄。最多 80 或 100 個字符的行長度是可以接受的。如果可能,避免長度超過大約 130 或 160 個字符(包括標籤的長度)。
  • 如果登錄成功的報導,從來沒有在各級使用它高於VERBOSE
  • 如果您使用的臨時記錄來診斷這是難以重現的問題,保持它在DEBUGVERBOSE水平,然後用它括如果塊是允許在編譯時禁用它。
  • 小心日誌中的安全漏洞。避免記錄私人信息。尤其要避免記錄有關受保護內容的信息。這在編寫框架代碼時尤其重要,因為提前知道哪些是私人信息或受保護的內容,哪些不是。
  • 切勿使用System.out.println()printf()本機代碼)。 System.outSystem.err重定向到/dev/null ,讓您的打印報表沒有明顯的影響。但是,為這些調用發生的所有字符串構建仍然會被執行。
  • 日誌記錄的黃金法則是,您的日誌可能不會不必要地將其他日誌推出緩衝區,就像其他人可能不會將您的日誌推出一樣。

Javatests 風格規則

遵循測試方法命名約定並使用下劃線將正在測試的內容與正在測試的特定案例分開。這種樣式可以更輕鬆地查看正在測試的案例。例如:

testMethod_specificCase1 testMethod_specificCase2

void testIsDistinguishable_protanopia() {
    ColorMatcher colorMatcher = new ColorMatcher(PROTANOPIA)
    assertFalse(colorMatcher.isDistinguishable(Color.RED, Color.BLACK))
    assertTrue(colorMatcher.isDistinguishable(Color.X, Color.Y))
}