Android開源項目-編碼風格規範-Code Style Guidelines for Contributors[原創譯文]

Code Style Guidelines for Contributors

版本:Android 4.0 r1

英文原文:http://source.android.com/source/code-style.html

以下規則並非指導或推薦的性質,而是必須遵守的規定。如果不遵守這些規定,Android通常不會接受投稿。

已有的代碼未必全部遵守了這些規定,但是新的代碼全部都應該遵守。

在本文中

  撰稿人的編碼規範

   Java語言規範

不要忽略Exceptions

不要捕獲頂級Exception

不要使用Finalizer

使用完全限定名稱的Import

Java類庫規範

Java編碼規範

使用Javadoc標準的註釋

編寫簡短的方法

在標準的位置定義字段

限制變量的作用範圍

Import語句排序

使用空格進行縮進

遵守字段命名慣例

使用標準的大括號風格

限制代碼行長度

使用標準的Java Annotation

簡稱等同於單詞

使用TODO註釋

慎用日誌

保持一致

Javatest風格規範

遵守測試方法命名規範

Java語言規範

我們遵循標準的Java編碼規範,並加入了新的規則:

不要忽略異常

有時,完全忽略異常是非常誘人的,比如:

void setServerPort(String value) {

    try {

        serverPort = Integer.parseInt(value);

    } catch (NumberFormatException e) { }

}

絕對不要這麼做。也許你會認爲:你的代碼永遠不會碰到這種出錯的情況,或者處理異常並不重要,可類似上述忽略異常的代碼將會在代碼中埋下一顆地雷,說不定哪天它就會炸到某個人了。你必須在代碼中以某種規矩來處理所有的異常。根據情況的不同,處理的方式也會不一樣。

無論何時,空的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 {}語句塊中替換爲合適的值。

        void setServerPort(String value) {

            try {

                serverPort = Integer.parseInt(value);

            } catch (NumberFormatException e) {

                serverPort = 80;  // default port for server

            }

        }

· 捕獲異常並拋出一個新的RuntimeException。這種做法比較危險:只有確信發生該錯誤時最合適的做法就是崩潰,纔會這麼做。

        void setServerPort(String value) {

            try {

                serverPort = Integer.parseInt(value);

            } catch (NumberFormatException e) {

                throw new RuntimeException("port " + value " is invalid, ", e);

            }

        }

請記住,最初的異常是傳遞給構造方法的RuntimeException。如果代碼必須在Java 1.3版本下編譯,需要忽略該異常。

· 最後一招:如果確信忽略異常比較合適,那就忽略吧,但必須把理想的原因註釋出來:

        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.

            }

        }

不要捕獲頂級的Exception

有時在捕獲Exception時偷懶也是很吸引人的,類似如下的處理方式:

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異常。這種捕獲非常危險。這意味着本來不必考慮的Exception(包括類似ClassCastExceptionRuntimeException)被捲入到應用程序級的錯誤處理中來。這會讓代碼運行的錯誤變得模糊不清。這意味着,假如別人在你調用的代碼中加入了新的異常,編譯器將無法幫助你識別出各種不同的錯誤類型。絕大部分情況下,無論如何你都不應該用同一種方式來處理各種不同類型的異常。

本規則也有極少數例外情況:期望捕獲所有類型錯誤的特定的測試代碼和頂層代碼(爲了阻止這些錯誤在用戶界面上顯示出來,或者保持批量工作的運行)。這種情況下可以捕獲頂級的Exception(或Throwable)並進行相應的錯誤處理。在開始之前,你應該非常仔細地考慮一下,並在註釋中解釋清楚爲什麼這麼做是安全的。

比捕獲頂級Exception更好的方案:

· 分開捕獲每一種異常,在一條try語句後面跟隨多個catch 語句塊。這樣可能會有點彆扭,但總比捕獲所有Exception要好些。請小心別在catch語句塊中重複執行大量的代碼。

· 重新組織一下代碼,使用多個try塊,使錯誤處理的粒度更細一些。把IO從解析內容的代碼中分離出來,根據各自的情況進行單獨的錯誤處理。

· 再次拋出異常。很多時候在你這個級別根本就沒必要捕獲這個異常,只要讓方法拋出該異常即可。

請記住:異常是你的朋友!當編譯器指出你沒有捕獲某個異常時,請不要皺眉頭。而應該微笑:編譯器幫助你找到了代碼中的運行時(runtime)問題。

不要使用Finalizer

Finalizer提供了一個機會,可以讓對象被垃圾回收器回收時執行一些代碼。

優點:便於執行清理工作,特別是針對外部資源。

缺點:調用finalizer的時機並不確定,甚至根本就不會調用。

結論:我們不要使用finalizers。大多數情況下,可以用優秀的異常處理代碼來執行那些要放入finalizer的工作。如果確實是需要使用finalizer,那就定義一個close()方法(或類似的方法),並且在文檔中準確地記錄下需要調用該方法的時機。相關例程可以參見InputStream。這種情況下還是適合使用finalizer的,但不需要在finalizer中輸出日誌信息,因爲日誌不能因爲這個而被撐爆。

使用完全限定Import

當需要使用foo包中的Bar類時,存在兩種可能的import方式:

1.   import foo.*;

優點:可能會減少import語句。

1.   import foo.Bar;

優點:實際用到的類一清二楚。代碼的可讀性更好,便於維護。

結論:用後一種寫法來import所有的Android代碼。不過導入java標準庫(java.util.*java.io.*) 和單元測試代碼 (junit.framework.*)時可以例外。

Java類庫規範

使用Android Java類庫和工具存在一些慣例。有時這些慣例會作出重大變化,可之前的代碼也許會用到過時的模板或類庫。如果用到這部分過時的代碼,沿用已有的風格就是了(參閱Consistency)。創建新的組件時就不要再使用過時的類庫了。

Java編程規範

使用Javadoc標準註釋

每個文件的開頭都應該有一句版權說明。然後下面應該是package包語句和import語句,每個語句塊之間用空行分隔。然後是類或接口的定義。在Javadoc註釋中,應描述類或接口的用途。

package com.android.internal.foo;

 

import android.os.Blah;

import android.view.Yada;

 

import java.sql.ResultSet;

import java.sql.SQLException;

 

public class Foo {

    ...

}

每個類和自建的public方法必須包含Javadoc註釋,註釋至少要包含描述該類或方法用途的語句。並且該語句應該用第三人稱的動詞形式來開頭。

例如:

static double sqrt(double a) {

    ...

}

public String(byte[] bytes) {

    ...

}

如果所有的Javadoc都會寫成“sets Foo”,對於那些無關緊要的類似setFoo()getset語句是不必撰寫Javadoc的。如果方法執行了比較複雜的操作(比如執行強制約束或者產生很重要的副作用),那就必須進行註釋。如果“Foo”屬性的意義不容易理解,也應該進行註釋。

無論是public的還是其它類型的,所有自建的方法都將受益於Javadocpublic的方法是API的組成部分,因此更需要Javadoc

Android目前還沒有規定自己的Javadoc註釋撰寫規範,但是應該遵守Sun Javadoc約定

編寫簡短的方法

爲了把規模控制在合理範圍內,方法應該保持簡短和重點突出。不過,有時較長的方法也是合適的,所以對方法的代碼長度並沒有硬性的限制。如果方法代碼超過了40行,就該考慮是否可以在不損害程序結構的前提下進行分拆。

在標準的位置定義字段

字段應該定義在文件開頭,或者緊挨着使用這些字段的方法之前。

限制變量的作用範圍

局部變量的作用範圍應該是限制爲最小的(Effective Java29條)。使用局部變量,可以增加代碼的可讀性和可維護性,並且降低發生錯誤的可能性。每個變量都應該在最小範圍的代碼塊中進行聲明,該代碼塊的大小只要能夠包含所有對該變量的使用即可。

應該在第一次用到局部變量的地方對其進行聲明。幾乎所有局部變量聲明都應該進行初始化。如果還缺少足夠的信息來正確地初始化變量,那就應該推遲聲明,直至可以初始化爲止。

本規則存在一個例外,就是涉及try-catch語句的情況。如果變量是用方法的返回值來初始化的,而該方法可能會拋出一個checked異常,那麼必須在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 imports

2.   第三方庫(comjunitnetorg

3.   javajavax

爲了精確匹配IDE的配置,import順序應該是:

· 在每組內部按字母排序,大寫字母排在小寫字母的前面。

· 每個大組之間應該空一行(androidcomjunitnetorgjavajavax)。

原先次序是不作爲規範性要求的。這意味着要麼允許IDE改變順序,要麼使用IDE的開發者不得不禁用import自動管理功能並且人工維護import。這看起來比較糟糕。每當說起java規範,推薦的規範到處都是。符合我們要求的差不多就是選擇一個次序並堅持下去。於是,我們就選擇一個規範,更新規範手冊,並讓IDE去遵守它。我們期望:不必耗費更多的精力,用IDE編碼的用戶就按照這種規則去import所有的package

基於以下原因,選定了本項規則:

· 導入人員期望最先看到的放在最開始位置(android

· 導入人員期望最後纔看到的放在最後(java

· 風格讓人容易遵守

· IDE可以遵守

靜態import的使用和位置已經成爲略帶爭議的話題。有些人願意讓靜態import和其它import混在一起,另一些人則期望讓它們位於其它import之上或者之下。另外,我們還未提到讓所有IDE都遵守同一個次序的方法。

因爲大多數人都認爲這部分內容並不要緊,只要遵守你的決定並堅持下去即可。

使用空格進行縮進

我們的代碼塊縮進使用4個空格。我們從不使用製表符tab。如果存在疑惑,與前後的其它代碼保持一致即可。

我們用8個空格作爲換行後的縮進,包括函數調用和賦值。例如這是正確的:

Instrument i =

        someLongexpression_r(that, wouldNotFit, on, one, line);

而這是錯誤的:

Instrument i =

    someLongexpression_r(that, wouldNotFit, on, one, line);

遵守字段命名慣例

· public的、非static的字段名稱以m開頭。

· static字段名稱以s開頭。

· 其它字段以小寫字母開頭。

· public static final字段(常量)全部字母大寫並用下劃線分隔。

例如:

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個字符。

例外:import行可以超過限制,因爲很少有人會去閱讀它。這也簡化了編程工具的寫入操作。

使用標準的Java Annotation

Annotation應該位於Java語言元素的其它修飾符之前。 簡單的marker annotation@Override等)可以和語言元素放在同一行。 如果存在多個annotation,或者annotation是參數化的,則應按字母順序各佔一行來列出。

對於Java 內建的三種annotationAndroid標準的實現如下:

· @Deprecated:只要某個語言元素已不再建議使用了,就必須使用@Deprecated annotation。如果使用了@Deprecated annotation,則必須同時進行@deprecated Javadoc標記,並且給出一個替代的實現方式。此外請記住,被@Deprecated的方法仍然是能正常執行的。

如果看到以前的代碼帶有@deprecated Javadoc標記,也請加上@Deprecated annotation

· @Override:只要某個方法覆蓋了已過時的或繼承自超類的方法,就必須使用@Override annotation

例如,如果方法使用了@inheritdocs Javadoc標記,且繼承自超類(而不是interface),則必須同時用@Override標明覆蓋了父類方法。

· @SuppressWarnings@SuppressWarnings annotation僅用於無法消除編譯警告的場合。 如果警告確實經過測試不可能消除,則必須使用@SuppressWarnings annotation,以確保所有的警告都能真實反映代碼中的問題。

當需要使用@SuppressWarnings annotation時,必須在前面加上TODO註釋行,用於解釋不可能消除警告的條件。通常是標明某個令人討厭的類用到了某個拙劣的接口。比如:

// TODO: The third-party class com.third.useful.Utility.rotate() needs generics

@SuppressWarnings("generic-cast")

List<String> blix = Utility.rotate(blax);

如果需要使用@SuppressWarnings annotation,應該重新組織一下代碼,把需要應用annotation的語言元素獨立出來。

簡稱等同於單詞

簡稱和縮寫都視爲變量名、方法名和類名。以下名稱可讀性更強:

XmlHttpRequest

XMLHTTPRequest

getCustomerId

getCustomerID

class Html

class HTML

String url

String URL

long id

long ID

如何對待簡稱,JDKAndroid底層代碼存在很大的差異。因此,你幾乎不大可能與其它代碼取得一致。別無選擇,把簡稱當作完整的單詞看待吧。

關於本條規則的進一步解釋,請參閱Effective Java38條和Java Puzzlers68條。

使用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註釋是將來要做某事的格式,則請確保包含一個很明確的日期(200511月會修正),或是一個很明確的事件(在所有代碼整合人員理解了V7協議之後刪除本段代碼)。

慎用Log

記錄日誌會對性能產生顯著的負面影響。如果日誌內容不夠簡煉的話,很快會喪失可用性。日誌功能支持五種不同的級別。以下列出了各個級別及其使用場合和方式。

· ERROR: 該級別日誌應該在致命錯誤發生時使用,也就是說,錯誤的後果能被用戶看到,但是不明確刪除部分數據、卸裝程序、清除數據區或重新刷機(或更糟糕)就無法恢復。該級別總是記錄日誌。需要記錄ERROR級別日誌的事件一般都應該向統計信息收集(statistics-gathering )服務器報告。

· WARNING: 該級別日誌應該用於那些重大的、意外的事件,也就是說,錯誤的後果能被用戶看到,但是不採取明確的動作可能就無法無損恢復,從等待或重啓應用開始,直至重新下載新版程序或重啓設備。該級別總是記錄日誌。需記錄WARNING級別日誌的事件也可以考慮向統計信息收集服務器報告。

· INFORMATIVE: 該級別的日誌應該用於記錄大部分人都會感興趣的事件,也就是說,如果檢測到事件的影響面可能很廣,但不一定是錯誤。應該只有那些擁有本區域內最高級別身份認證的模塊才能記錄這些日誌(爲了避免級別不足的模塊重複記錄日誌)。該級別總是記錄日誌。

· DEBUG: 該級別的日誌應該用於進一步記錄有關調查、調試意外現象的設備事件。應該只記錄那些有關控件運行所必需的信息。如果debug日誌佔用了太多的日誌空間,那就應該使用詳細級別日誌(verbose)才更爲合適。

即使是發行版本(release build),該級別也會被記錄,並且需用if (LOCAL_LOG)if (LOCAL_LOGD)語句塊包裹,這裏的LOCAL_LOG[D]在你的類或子控件中定義。這樣就能夠一次性關閉所有的調試日誌。因此在if (LOCAL_LOG)語句塊中不允許存在邏輯判斷語句。所有日誌所需的文字組織工作也應在if (LOCAL_LOG)語句塊內完成。如果對記錄日誌的調用會導致在if (LOCAL_LOG)語句塊之外完成文字組織工作,那該調用就必須控制在一個方法內完成

還存在一些代碼仍然在使用if (localLOGV)。這也是可以接受的,雖然名稱不是標準的。

· VERBOSE: 該級別日誌應用於所有其餘的事件。該級別僅會在調試版本(debug build)下記錄日誌,並且需用if (LOCAL_LOGV)語句塊(或等效語句)包裹,這樣該部分代碼默認就不會編譯進發行版本中去了。所有構建日誌文字的代碼將會在發行版本中剝離出去,並且需包含在if (LOCAL_LOGV)語句塊中。

注意:

· 除了VERBOSE級別外,在同一個模塊中同一個錯誤應該儘可能只報告一次:在同一個模塊內的一系列層層嵌套的函數調用中,只有最內層的函數才返回錯誤;並且只有能爲解決問題提供明顯幫助的時候,同一模塊中的調用方纔寫入一些日誌。

· 除了VERBOSE級別外,在一系列嵌套的模塊中,當較低級別的模塊對來自較高級別模塊的非法數據進行檢測時,應該只把檢測情況記錄在DEBUG日誌中,並且只記錄那些調用者無法獲取的信息。特別是不需要記錄已經拋出異常的情況(異常中應該包含了全部有價值的信息),也不必記錄那些只包含錯誤代碼的信息。當應用程序與系統框架間進行交互時,這一點尤爲重要。系統框架已能正確處理的第三方應用程序,也不應該記錄大於DEBUG級別的日誌。僅當一個模塊或應用程序檢測到自身或來自更低級別模塊的錯誤時,才應該記錄INFORMATIVE及以上級別的日誌。

· 如果一個通常要記錄日誌的事件可能會多次發生,則採取一些頻次限制措施或許是個好主意,以防日誌被很多重複(或類似)的信息給撐爆了。

· 網絡連接的丟失可被視爲常見現象,也是完全可以預見的,不應該無緣無故就記錄進日誌。影響範圍限於應用程序內部的網絡中斷應該記錄在DEBUGVERBOSE級別的日誌中(根據影響的嚴重程度及意外程度,再來確定是否在發行版本中也記錄日誌)。

· 有權訪問的文件系統或第三方應用程序發起的系統空間滿,應該記錄大於INFORMATIVE級別的日誌。

· 來自任何未授信源的非法數據(包括共享存儲上的任何文件,或來自任何網絡連接的數據)可被視爲可預見的,如果檢測到非法數據也不應該記錄大於DEBUG級別的日誌(即使記錄也應儘可能少)。

· 請記住,對字符串使用+操作符時,會在後臺以默認大小(16個字符)緩衝區創建一個StringBuilder對象,並且可能還會創建一些其它的臨時String對象。換句話說,顯式創建StringBuilders對象的代價並不會比用'+'操作符更高(事實上效率還將會提高很多)。還要記住,即使不會再去讀取這些日誌,調用Log.v()的代碼也將編譯進發行版中並獲得執行,包括創建字符串的代碼。

· 所有要被人閱讀並存在於發行版本中的日誌,都應該簡潔明瞭、沒有祕密、容易理解。這裏包括所有DEBUG以上級別的日誌。

· 只要有可能,日誌就應該一句一行。行長最好不超過80100個字符,儘可能避免超過130160個字符(包括標識符)的行。

· 報告成功的日誌記錄絕不應該出現在大於VERBOSE級別的日誌中。

· 用於診斷難以重現事件的臨時日誌應該限於DEBUGVERBOSE級別,並且應該用if語句塊包裹,以便在編譯時能夠一次全部關閉。

· 小心日誌會泄漏隱私。應該避免將私人信息記入日誌,受保護的內容肯定也不允許記錄。這在編寫系統框架級代碼時尤爲重要,因爲很難預知哪些是私人信息和受保護信息。

· 絕對不要使用System.out.println() (或本地代碼中的printf())。System.out System.err會重定向到/dev/null,因此print語句不會產生任何可見的效果。可是,這些調用中的所有字符串創建工作都仍然會執行。

· 日誌的黃金法則是:你的日誌記錄不會導致其它日誌的緩衝區溢出,正如其他人的日誌也不會讓你的溢出一樣。

保持一致

我們的最終想法是:保持一致。如果你正在編寫代碼,請花幾分鐘瀏覽一下前後的其它代碼,以確定它們的風格。如果它們在if語句前後使用了空格,那你也應該遵循。如果它們的註釋是用星號組成的框框圍起來的,那也請你照辦。

保持風格規範的重點是有一個公共的代碼詞彙表,這樣大家就可以把注意力集中於你要說什麼,而不是你如何說。我們在這裏列出了全部的風格規範,於是大家也知道了這些詞彙。不過本地化的風格也很重要。如果你要加入的代碼和已存在的代碼風格迥異,那就會突然打破閱讀的節奏。請努力避免這種情況的發生。

Javatest風格規範

遵守測試方法命名規範

命名測試方法時,可以用下劃線來分隔測試的條件。這種風格可以讓測試的條件一目瞭然。

比如:

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

}

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章