安卓官方編碼規範

Java 語言規則

Android 遵循標準 Java 編碼規範以及下文所述的其他規則。

請勿忽略異常

開發者可能會傾向於編寫完全忽略異常的代碼,例如:

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

千萬不要這樣做。雖然您可能認爲自己的代碼永遠不會遇到這種錯誤,或者無需費心處理這種錯誤,但像上例那樣忽略異常會在您的代碼中埋下隱患,這種錯誤總有一天會被他人觸發。您必須有原則地處理代碼中的每個異常;具體處理方式因情況而異。

無論何時,只要遇到空的 catch 子句,就應該保持警惕。當然,在某些時候,空的 catch 語句確實沒什麼問題,但至少你得想一想。在 Java 中,你怎麼小心都不爲過。-James Gosling

可接受的替代方案(按優先順序排列)包括:

  • 將異常拋給方法調用者。
    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); } }

    注意:原始異常會傳遞到 RuntimeException 的構造函數。如果您的代碼必須採用 Java 1.3 進行編譯,則必須忽略表示原因的異常。

  • 最後一種方案:如果您確信忽略異常是合適的處理方式,那麼您可以忽略異常,但您必須添加備註以充分說明理由:
    /** 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! }

千萬不要這樣做。幾乎所有情況下都不適合捕獲常規異常或 Throwable(最好不要捕獲 Throwable,因爲它包含 Error 異常)。這樣做非常危險,因爲這意味着系統會在處理應用級錯誤期間捕獲到您從未預料到的異常(包括 ClassCastException 之類的 RuntimeException)。它掩蓋了代碼的故障處理屬性,也就是說,如果有人在您所調用的代碼中添加了一種新類型的異常,編譯器不會幫助您意識到您需要採取不同的方式來處理該錯誤。在大多數情況下,您不應以相同的方式處理不同類型的異常。

這條規則的特例是:在測試代碼和頂級代碼中,您希望捕獲所有類型的錯誤(以防它們顯示在界面中或者以便一直進行批處理作業)。在這些情況下,您可以捕獲常規異常(或 Throwable)並適當地處理錯誤。但在這樣做之前,請務必三思,然後添加備註以說明爲何在此處執行這類操作是安全之舉。

捕獲常規異常的替代方案:

  • 在單個 try 之後將每個異常作爲單獨的 catch 塊分別進行捕獲。這樣做可能顯得比較笨拙,但仍比捕獲所有異常更可取。請注意,不要在 catch 塊中過多地重複使用代碼。

  • 通過多個 try 塊重構您的代碼,使得錯誤處理過程更精細。從解析中分離出 IO,然後分別處理每種情況下的錯誤。

  • 重新拋出異常。很多時候,您無需在該級別捕獲異常,只需讓相應方法拋出異常即可。

請謹記:異常是您的朋友!當編譯器抱怨您沒有捕獲異常時,別悶悶不樂!您應該微笑:因爲編譯器讓您能夠更加輕鬆地捕獲代碼中的運行時錯誤。

請勿使用終結器

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

Android 不使用終結器。在大多數情況下,您可以通過良好的異常處理流程實現終結器功能。如果您的確需要終結器,請定義一個 close() 方法(或類似方法),並註明需要調用該方法的確切時間(有關示例,請參閱 InputStream)。這種情況下,可以(但並非必須)在終結器中輸出簡短的日誌消息,前提是不會輸出大量日誌消息。

完全合格的導入

當您想要使用 foo 包中的 Bar 類時,可以使用以下兩種方式導入:

  • import foo.*;

    可能會減少 import 語句的數量。

  • import foo.Bar;

    明確指出實際使用了哪些類,而且代碼對於維護者來說更易讀。

使用 import foo.Bar; 導入所有 Android 代碼。在 Java 標準庫(java.util.*java.io.* 等)和單元測試代碼 (junit.framework.*) 中創建顯式異常。

Java 庫規則

使用 Android 的 Java 庫和工具需要遵守相關規範。在某些情況下,具體規範發生了一些重大變化,舊代碼可能使用的是已棄用的模式或庫。使用此類代碼時,可以繼續遵循現有樣式。不過,在創建新組件時,請不要再使用已棄用的庫。

Java 樣式規則

使用 Javadoc 標準備註

每個文件都應該在頂部放置版權聲明,其後是 package 和 import 語句(各個塊之間用空行分隔),最後是類或接口聲明。在 Javadoc 備註中說明類或接口的作用。

/* * Copyright (C) 2015 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 方法(如 setFoo()),您無需編寫 Javadoc,要寫也不過是“設置 Foo”。如果該方法執行更復雜的操作(例如強制實施約束條件或具有重大副作用),那麼您必須添加備註。如果屬性“Foo”的意思不明確,您也應該添加備註。

您所編寫的每一種方法(無論是公開方法還是其他方法)都將受益於 Javadoc。公開方法是 API 的一部分,因此需要 Javadoc。Android 目前並不強制要求採用特定樣式來編寫 Javadoc 備註,但建議您參照如何爲 Javadoc 工具編寫文檔備註中的說明。

編寫簡短方法

在可行的情況下,儘量編寫短小精煉的方法。我們瞭解,有些情況下較長的方法是恰當的,因此對方法的代碼長度沒有做出硬性限制。如果某個方法的代碼超出 40 行,請考慮是否可以在不破壞程序結構的前提下對其拆解。

在標準位置定義字段

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

限制變量的作用域

儘可能縮小局部變量的作用域。這樣做有助於提高代碼的可讀性和可維護性,並降低出錯的可能性。每個變量應該在包含變量所有使用場合的最內層的塊中進行聲明。

局部變量應該在首次使用時聲明。幾乎每個局部變量聲明都應該包含一個初始化程序。如果您還沒有足夠的信息來合理地初始化某個變量,請推遲到信息充足時再進行聲明。

try-catch 語句是例外情況。如果通過一個會拋出受檢異常的方法的返回值來初始化變量,則必須在 try 塊中進行初始化。如果該值必須在 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()); }

爲 import 語句排序

import 語句的順序爲:

  1. 導入 Android 包

  2. 導入第三方包(comjunitnetorg

  3. java 和 javax

要完全符合 IDE 設置,導入順序應爲:

  • 每個分組內按字母順序排序,其中大寫字母開頭的語句位於小寫字母開頭的語句前面(例如 Z 在 a 前面)。

  • 每個主要分組(androidcomjunitnetorgjavajavax)之間用空行隔開。

最初對於語句順序並沒有樣式要求,這意味着 IDE 經常會改變順序,或者 IDE 開發者必須停用自動導入管理功能並手動維護導入語句。這樣相當不方便。當提及 Java 樣式時,開發者們喜歡的樣式五花八門,最終針對 Android 簡單歸結爲“選擇一種兼容一致的排序方式”。因此我們選擇了一種樣式,更新了樣式指南,並讓 IDE 遵循該指南。我們希望 IDE 用戶在編寫代碼時,系統對所有軟件包的導入都符合此模式,無需再進行額外的工程處理。

這種樣式是按以下原則選取的:

  • 用戶希望先看到的導入往往位於頂部 (android)。

  • 用戶最不希望看到的導入往往位於底部 (java)。

  • 用戶可以輕鬆遵循的樣式。

  • IDE 可以遵循的樣式。

靜態導入的使用和位置一直都存在爭議。有些人希望靜態導入穿插在其他導入語句之間,而有些人更希望其位於其他所有導入語句的上方或下方。此外,我們還沒有確定如何讓所有 IDE 都使用同一種順序。由於許多人認爲這個問題不太重要,因此您只需在保持一致的前提下自行決定即可。

使用空格縮進

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

我們使用八 (8) 個空格來縮進自動換行,包括函數調用和賦值。正確示例如下:

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

錯誤示例如下:

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

遵循字段命名規範

  • 非公開且非靜態字段的名稱以 m 開頭。

  • 靜態字段的名稱以 s 開頭。

  • 其他字段以小寫字母開頭。

  • 公開靜態 final 字段(常量)爲全部大寫並用下劃線連接 (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 個字符的示例命令或文字網址,那麼爲了便於剪切和粘貼,該行可以超過 100 個字符。
  • 導入語句行可以超出此限制,因爲用戶很少會看到它們(這也簡化了工具編寫流程)。

使用標準 Java 註釋

註釋應該位於同一語言元素的其他修飾符之前。簡單的標記註釋(例如 @Override)可以與語言元素列在同一行。如果有多個註釋或參數化註釋,則應各佔一行並按字母順序排列。

Java 中 3 個預定義註釋的 Android 標準做法如下:

  • @Deprecated:在不建議使用註釋元素時,必須使用 @Deprecated 註釋。如果您使用 @Deprecated 註釋,則還必須爲其添加 @deprecated Javadoc 標記,並且該標記應該指定一個替代實現方案。另外請注意,@Deprecated 方法應該仍然可以使用。如果您看到帶有 @deprecated Javadoc 標記的舊代碼,請添加 @Deprecated 註釋。
  • @Override:當某個方法替換了超類中的聲明或實現時,必須使用 @Override 註釋。例如,如果您使用 @inheritdocs Javadoc 標記,並且派生於某個類(而非接口),則必須再爲方法添加 @Override 註釋,說明該方法替換了父類的方法。
  • @SuppressWarnings:@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 註釋時,您應該重構代碼以分離出需要使用該註釋的軟件元素。

將首字母縮寫詞視爲字詞

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

良好 不佳
XmlHttpRequest XMLHTTPRequest
getCustomerId getCustomerID
class Html class HTML
String url String URL
long id long ID

由於 JDK 和 Android 代碼庫在首字母縮寫詞上非常不一致,幾乎也不可能與周圍的代碼保持一致。因此,請務必將首字母縮寫詞視爲字詞。

使用 TODO 備註

爲代碼使用 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 協議後移除此代碼”)。

謹慎使用日誌記錄

雖然日誌記錄非常有必要,但對性能卻有明顯的負面影響,如果不能保持一定程度的簡潔性,就會迅速失去其實用性。日誌記錄工具提供以下 5 種不同級別的日誌記錄:

  • ERROR:在出現極其嚴重的情況時使用。例如,某些事件會導致用戶可見的後果,如果不明確刪除某些數據、卸載應用、清除數據分區或重寫整個設備(或更糟),則無法恢復。系統一直會記錄此級別的日誌。一般情況下,最好向統計信息收集服務器報告能夠說明 ERROR 級別的一些日誌記錄情況的問題。
  • WARNING:在出現比較嚴重和意外的情況時使用。例如,某些事件會導致用戶可見的後果,但是通過執行某些明確的操作(從等待或重啓應用,一直到重新下載新版應用或重新啓動設備)可在不丟失數據的情況下恢復。系統一直會記錄此級別的日誌。可以考慮向統計信息收集服務器報告能夠說明 WARNING 級別的一些日誌記錄情況的問題。
  • INFORMATIVE:用於記錄大多數人感興趣的信息。例如,當檢測到某種情況會造成廣泛的影響時,儘管不一定是錯誤,系統也會記錄下來。這種情況應該僅由一個被視爲該領域最具權威性的模塊來記錄(避免由非權威組件重複記錄)。系統一直會記錄此級別的日誌。
  • DEBUG:用於進一步記錄設備上發生的可能與調查和調試意外行爲相關的情況。您應該只記錄收集有關組件的足夠信息所需的信息。如果您的調試日誌是主要日誌,那麼您可能應採用 VERBOSE 級別的日誌記錄。

    系統會記錄此級別的日誌(即使在發佈版本中),並且周圍要有 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 級別或更高級別日誌記錄的唯一情況是,模塊或應用在其自身級別或更低級別檢測到錯誤。
  • 當事實證明某些日誌記錄可能會發生多次時,最好實施一種頻率限制機制來防止出現具有相同(或非常相似)信息的大量重複日誌副本。
  • 失去網絡連接屬於完全在預期之內的常見情況,沒必要記錄下來。如果失去網絡連接後導致在應用內出現某種後果,則應該記錄爲 DEBUG 或 VERBOSE 級別(具體取決於後果是否足夠嚴重以及足夠意外,足以記錄在發佈版本中)。
  • 如果在第三方應用可訪問或代表第三方應用的文件系統上擁有完整的文件系統,則不應該記錄高於 INFORMATIVE 級別的日誌。
  • 來自任何不受信任來源(包括共享存儲空間中的任何文件或通過任何網絡連接獲取的數據)的無效數據被視爲符合預期,在被檢測到無效時不應觸發高於 DEBUG 級別的任何日誌記錄(甚至應該儘可能地限制日誌記錄)。
  • 請注意,在對 String 使用 + 運算符時,它會隱式創建一個具有默認緩衝區(大小爲 16 個字符)的StringBuilder,還可能會創建其他臨時 String 對象。例如,顯式創建 StringBuilder 並不比依賴默認的“+”運算符成本更高(實際上可能更高效)。請注意,即使沒有讀取日誌信息,調用 Log.v() 的代碼也會在發佈版本中進行編譯和執行,包括編譯字符串。
  • 任何供其他人閱讀並且在發佈版本中提供的日誌記錄都應當簡潔明瞭、合理易懂。這包括一直到 DEBUG 級別的所有日誌記錄。
  • 在內容有意義的情況下儘可能使日誌記錄在一行之內。一行長度在 80 到 100 個字符內是完全可以接受的,應當儘可能避免長度超過 130 或 160 個字符(包括標記的長度)。
  • 絕不能使用高於 VERBOSE 級別的日誌記錄報告成功事件。
  • 用於診斷難以重現的問題的臨時日誌記錄應採用 DEBUG 或 VERBOSE 級別,並且應當包裹在 if 塊中,以便在編譯期間將其完全停用。
  • 請務必謹慎,避免在日誌中泄露安全方面的信息。應避免提供個人信息,且必須避免提供有關受保護內容的信息。這在編寫框架代碼時尤爲重要,因爲事先無法輕易得知哪些是個人信息或受保護的內容,哪些不是。
  • 請勿使用 System.out.println()(或針對原生代碼使用 printf())。System.out 和 System.err 會重定向到 /dev/null,因此您的 print 語句不會產生可見效果。不過,爲這些調用編譯的所有字符串仍會得以執行。
  • 日誌記錄的黃金法則是,您的日誌不一定要將其他日誌排擠出緩衝區,正如其他日誌不會這樣對您的日誌一樣。

保持一致

總而言之:保持一致。如果您正在修改代碼,請花幾分鐘時間看一下週圍的代碼並確定其樣式。如果該代碼在 if 語句周圍使用空格,那麼您也應該這樣做。如果代碼備註的周圍是用星號組成的小方框,您也應該將備註放在這樣的小方框內。

制定樣式規範的目的是整理出通用的編碼詞彙表,以便人們可以專注於您所說的內容,而不是您表達的方式。我們在此提出整體樣式規則,讓用戶都知道這一詞彙表,但局部樣式也很重要。如果您添加到文件的代碼看起來與其周圍的現有代碼明顯不同,那麼當讀者讀到此處時,這些代碼會打亂他們的節奏。請儘量避免這種情況。

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)) }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章