以下的規(guī)則并不是普通的指導(dǎo)方針或者我們的推薦,而是嚴(yán)格的規(guī)范。不遵循這些風(fēng)格而構(gòu)建出來的 Android 軟件將不會(huì)被接受。
目前,并不是所有的代碼都符合規(guī)范,但是以后的代碼應(yīng)該向規(guī)范靠攏。
我們遵循標(biāo)準(zhǔn)的 Java 編碼規(guī)范,此外我們加入了一些新的規(guī)范
有時(shí)候,開發(fā)者會(huì)傾向于編寫完全忽視了異常的代碼,就像下面這樣:
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatExceptio e) { }
}
開發(fā)者絕不能像這樣。當(dāng)你覺得你的代碼不會(huì)出現(xiàn)這個(gè)錯(cuò)誤,或者處理這個(gè)錯(cuò)誤并不重要的時(shí)候,像上面的代碼那樣忽略了異常處理就相當(dāng)于在你的代碼中給以后接手的開發(fā)者埋下了地雷,他們總有一天會(huì)在這里遇到問題。你必須將代碼中所有的異常用一種規(guī)范化的方式處理好,具體的處理方式取決于情況的不同。
不管什么時(shí)候,如果代碼中存在空的捕獲語句,開發(fā)者應(yīng)當(dāng)感到毛骨悚然。不論如何,在捕獲語句中,必然存在應(yīng)當(dāng)執(zhí)行的操作,至少你應(yīng)該多考慮一下。在 Java 中,這種恐慌感是無法逃避的。 -James Gosling
可接受的處理方式(可以參照個(gè)人的喜好選擇)有:
void setServerPort(String value) throws NumberFormatException {
serverPort = Integer.parseInt(value);
}
void setServerPort(String value) throws ConfigurationException {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
throws new ConfigurationException("Port " + value + " is not valid.");
}
/** 設(shè)置端口號(hào),當(dāng) value 是一個(gè)無效數(shù)字時(shí), 使用 80 替代 */
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
serverPort = 80; // 服務(wù)器的默認(rèn)端口
}
}
運(yùn)行時(shí)異常。這種方式存在風(fēng)險(xiǎn):只有當(dāng)你確認(rèn)出現(xiàn)這種錯(cuò)誤的時(shí),令程序崩潰是最合適的處理方式,才能采用該方式。/** 設(shè)置端口號(hào),如果 value 是一個(gè)無效值,程序中斷。 */
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
throws new RuntimeException("port " + value + " is invalid, ", e);
}
}
請注意,原來的異常傳遞給了 RuntimeException 的構(gòu)造器。如果你的代碼必須在 Java 1.3 以下版本編譯,你需要省略掉異常發(fā)生的原因。
/** 如果 value 是無效的數(shù)字,將會(huì)使用原來的端口號(hào)。 */
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
// 文檔中會(huì)注明該方法將忽略掉用戶輸入的無效值
// serverPort 將不會(huì)發(fā)生改變
}
}
有些時(shí)候,為了偷懶,開發(fā)者將會(huì)像下面這樣捕獲異常并作出處理:
try {
someComplicatedIOFunction(); // 可能拋出 IOException
someComplicatedParsingFunction(); // 可能拋出 ParsingException
someComplicatedSecurityFunction(); // 可能拋出 SecurityException
// phew, made it all the way
} catch (Exception e) { // 我希望捕獲所有的異常
handleError(); // 一個(gè)普通的 handler
}
你不能這樣做。幾乎所有情況下都不適合捕獲一般性異常,因?yàn)槠渲邪ㄟ€包括了錯(cuò)誤異常,這是相當(dāng)危險(xiǎn)的。這意味著你并不期待的異常(包括運(yùn)行時(shí)異常,就像 ClassCastException)最后卻在應(yīng)用級(jí)別的錯(cuò)誤處理中被捕獲。它掩蓋了處理失敗的代碼,這意味著如果有人在您正在調(diào)用的代碼中添加了一個(gè)新類型的異常,編譯器無法告訴你需要以不同的方式處理該錯(cuò)誤。并且在幾乎全部情況下,你都不應(yīng)該以同種方式處理不同種類的異常。
這一規(guī)則有極少數(shù)例外:在某些測試代碼和頂級(jí)代碼中,你希望捕捉所有類型的異常(以防止他們在界面中顯示或者繼續(xù)進(jìn)行批處理作業(yè))。在這種情況下,你需要捕獲一般性異常并合適地處理錯(cuò)誤。這么做之前你需要謹(jǐn)慎考慮,然后在注釋中解釋為什么在這里這么做是安全的。
替代捕獲一般性異常的選項(xiàng):
在同一個(gè) try 語句之后設(shè)置多個(gè)分離的 catch 塊來處理不同的異常。這個(gè)方法可能不太方便,但是要比捕獲所有異常要好不少。需要注意的是,catch 塊中應(yīng)盡量避免代碼重復(fù)過多。
通過多個(gè) try 語句重構(gòu)代碼以使錯(cuò)誤處理的粒度更細(xì)小,把 IO 從 parsing 分離出來,在不同的情況中單獨(dú)處理錯(cuò)誤。
記?。寒惓J悄愕呐笥眩‘?dāng)編譯器告訴你還有異常沒有被捕獲的時(shí)候,不要皺眉。此時(shí)應(yīng)該微笑:編譯器令你能夠更加輕松地在自己的代碼中捕獲運(yùn)行時(shí)異常,是值得高興的事。
終結(jié)器是一種可以在對象被垃圾回收器回收時(shí),執(zhí)行一大塊代碼的方法。
優(yōu)點(diǎn):方便清理,特別是針對外部資源的時(shí)候。
缺點(diǎn):并不能保證終結(jié)器什么時(shí)候會(huì)被調(diào)用,甚至有時(shí)候它根本不會(huì)被調(diào)用。
判定:我們不應(yīng)使用終結(jié)器。在大多數(shù)情況下,你可以在終結(jié)器中執(zhí)行你需要的操作,并且能實(shí)現(xiàn)良好的異常處理。如果你一定需要它,定義一個(gè) close() 方法(或者是 like)并注明什么時(shí)候它應(yīng)該被調(diào)用。我們可以把 InputStream 當(dāng)做一個(gè)例子來說明一下。在這種情況下,它并不是一定要在終結(jié)器中輸出日志信息,但是,如果并不希望輸出太多日志信息的話,在終結(jié)器中輸出小段日志信息是很合適的方法。
當(dāng)你希望使用 foo 包中的 Bar 類的時(shí)候,你又兩種方法實(shí)現(xiàn):
第一種方式優(yōu)點(diǎn):可能會(huì)減少 import 語句的數(shù)量。 第二種方式優(yōu)點(diǎn):把要使用的類描述出來了,使得代碼在維護(hù)時(shí)更具可讀性。
判定:引用Android 代碼的時(shí)候使用第二種形式。Java 標(biāo)準(zhǔn)庫(java.util.*, java.io.*等)和單元測試代碼(junit.framework.*)是例外。
使用Android 中的 Java 庫和工具會(huì)帶來許多便利。有些情況下,這種便利性在一些重要方面發(fā)生了改變,因?yàn)榕f的代碼可能使用的是已經(jīng)棄用的模式或者資源庫。使用這樣的代碼時(shí),直接使用已有的模式是沒問題的。但是當(dāng)創(chuàng)建新的組件的時(shí)候,不要使用棄用的資源庫。
每個(gè)文件都會(huì)在其頂部放置版權(quán)聲明。然后是包的聲明和引用語句,用空行把不同作用的語句塊分離開來。接下來是類或者接口的聲明。在 Javadoc 標(biāo)準(zhǔn)注釋中,要描述類或者接口的作用。
/*
* Copyright (C) 2013 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 {
...
}
對于每個(gè)類和有價(jià)值的公共方法,你都應(yīng)該為之書寫一個(gè) Javadoc 注釋,該注釋至少包含一句關(guān)于類或方法作用的描述。并且這個(gè)描述應(yīng)該以第三人稱描述性謂詞開頭。
示例:
/** 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)
你并不需要為沒價(jià)值的 get 或者 set 方法書寫 Javadoc,比如,對于 setFoo() 方法,你在 Javadoc 中只能說設(shè)置了 Foo 的值。如果該方法所做的事情更加復(fù)雜(比如強(qiáng)制約束或者有一個(gè)重要的副作用),那你應(yīng)該寫一個(gè) Javadoc。假設(shè)之前的例子中,F(xiàn)oo 這個(gè)屬性的意思并不明確,那就該添加一個(gè) Javadoc。
你所寫的每一個(gè)方法,不論是公有的還是其他的,都將受益于 Javadoc。而公共函數(shù)是 API 的一部分,所以需要 Javadoc。
Android 目前并不強(qiáng)制開發(fā)者使用特定的 Javadoc 書寫風(fēng)格,但是你應(yīng)該遵循 How to Write Doc Comments for the Javadoc Tool 中的規(guī)范。
在可行的范圍內(nèi),方法應(yīng)該盡量向小型化和集中化靠攏。但是,也要認(rèn)識(shí)到,有些情況下較長的方法更加合適,所以對于方法的長度并不做硬性要求。如果某個(gè)方法的代碼超出了40行,那么應(yīng)該考慮一下是否能將它分解為小方法并且不影響到程序的結(jié)構(gòu)。
字段應(yīng)該在文件頂部定義,或者在即將使用它們的方法前定義。
局部變量的存在范圍應(yīng)該盡可能地低。這樣做將會(huì)增加代碼的可讀性和可維護(hù)性,并且還能降低出錯(cuò)的可能性。在嵌套的代碼塊中,每個(gè)變量應(yīng)該在能夠包含所有使用該變量的語句的最小的代碼塊中聲明。
局部變量應(yīng)該在第一次被使用時(shí)聲明。幾乎每個(gè)局部變量的聲明都應(yīng)該包括合理的初始化,如果你沒有沒辦法在此處獲得足夠的信息去初始化這個(gè)變量,那么你應(yīng)該將聲明推遲到你獲得足夠信息的地方。
這個(gè)規(guī)則的一個(gè)例外就是 try-catch 語句。如果一個(gè)變量初始化的時(shí)候,其初始值是一個(gè)拋出已檢查異常的方法的返回值,它就必須在 try 語句內(nèi)初始化。如果這個(gè)變量必須在 try 塊之外被使用,那么它就要在 try 塊之外聲明,盡管在那里它并沒有被合理地初始化:
// 實(shí)例化類 cl,表示某種集合
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");
}
// 運(yùn)用這個(gè)集合
s.addAll(Arrays.asList(args));
但即使是這種情況也能通過把 try-catch 語句封裝在一個(gè)方法中來避免:
Set createSet(Class cl) {
// 實(shí)例化的類cl,表示某種集合
try {
return (Set) cl.newInstance();
} catch(IllegalAccessException e) {
throw new IllegalArgumentException(cl + " not accessible");
} catch(InstantiationException e) {
throw new IllegalArgumentException(cl + " not instantiable");
}
}
...
// 運(yùn)用這個(gè)集合
Set s = createSet(cl);
s.addAll(Arrays.asList(args));
對于循環(huán)變量,除非有令人信服的理由,否則應(yīng)該在 for 語句中聲明:
for (int i = 0; i < n; i++) {
doSomething(i);
}
其他類型的循環(huán)變量:
for (Iterator i = c.iterator(); i.hasNext(); ) {
doSomethingElse(i.next());
}
引用語句的順序如下:
為了更好地符合 IDE 的設(shè)置,這些引用語句應(yīng)該是這樣的:
最初在順序的風(fēng)格上并沒有要求。也就是說 IDE 總是在改變這些順序,或者說使用 IDE 的開發(fā)人員不得不禁用自動(dòng)引用的特性然后手動(dòng)維護(hù)這些引用語句,這是相當(dāng)不好的。當(dāng)問及 Java 風(fēng)格的時(shí)候,開發(fā)者們喜歡的風(fēng)格是五花八門的。這和我們對于“盡量有序并且一致”的需求完全不同,所以我們選擇了一種風(fēng)格,更新了風(fēng)格指南,并且讓 IDE 來遵循它。我們希望,隨著用戶不斷使用 IDE 來編碼,最終所有的引用語句都能符合這種風(fēng)格。
這種風(fēng)格是這樣的:
靜態(tài)引用的使用和位置一直都存在爭議。有的人喜歡把靜態(tài)引用分散在已有的引用語句中,有些人則希望把它們放在其他所有引用語句的上方或者下方。此外,我們目前也沒有得出一種能夠讓所有 IDE 都遵循統(tǒng)一順序的方法。
由于多數(shù)人認(rèn)為這件事并不重要,所以你需要自行判斷,并盡可能保持一致。
我們使用 4 個(gè)空格來縮進(jìn),而不是使用 tab 鍵。對此感到困惑的話,請和你周圍的代碼保持一致。
語句換行的時(shí)候我們使用 8 個(gè)空格來縮進(jìn),包括方法的調(diào)用和賦值語句。例如,這樣是正確的:
Instrument i =
someLongExpression(that, wouldNotFit, on, one, line);
下面這樣則是不正確的:
Instrument i =
someLongExpression(that, wouldNotFit, on, one, line);
例子如下:
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;
}
花括號(hào)不單獨(dú)占一行,應(yīng)該放在和前面代碼同一行的位置。就像下面這樣:
class MyClass {
int func() {
if (something) {
// ...
} else if (somethingElse) {
// ...
} else {
// ...
}
}
}
我們需要在一個(gè)條件語句中使用括號(hào)。只有整個(gè)條件語句中只有一行代碼,你才可以把他放在一行而不使用括號(hào)。就如下面這樣是合法的:
if (condition) {
body();
}
而這樣也是可行的:
if (condition) body();
不過,那這樣也是可以的:
if (condition)
body(); // 不推薦這樣的風(fēng)格
你代碼的每一行的長度應(yīng)該限制在 100 個(gè)字符以內(nèi)。
為此我們做了很多討論,最終決定,一行的長度應(yīng)該限制在 100 個(gè)字符以內(nèi)。
例外1:如果一個(gè)注釋語句含有一個(gè)超過 100 個(gè)字符的簡單命令或者是一個(gè) URL,那么為了方便剪切和粘貼,你可以不在意行的長度。
例外2:引用語句可以不管這個(gè)限制,因?yàn)槿藗兒苌僭谝馑麄?。這樣也能簡化工具的編寫。
標(biāo)注應(yīng)該先于同一語言元素中的其他修飾符。簡單的標(biāo)注(如 @Override)可以和其他語言元素在同一行中列出。如果有多個(gè)標(biāo)注,或者參數(shù)化的標(biāo)注,他們應(yīng)該各自單獨(dú)占一行并按字母順序排列。
Java 預(yù)定義的三種標(biāo)注在Android 中的標(biāo)準(zhǔn)用法如下:
當(dāng)必須使用 @SuppressWarnings 標(biāo)注的時(shí)候,必須在其之前寫一個(gè) TODO 注釋來說明“無法消除”的情況。這通常會(huì)標(biāo)記出一個(gè)使用了一個(gè)比較尷尬的接口的違規(guī)類。例子如下:
// TODO: The third-party class com.third.useful.Utility.rotate() needs generics
@SuppressWarnings("generic-cast")
List<String> blix = Utility.rotate(blax);
當(dāng)需要 @SuppressWarnings 標(biāo)注的時(shí)候,應(yīng)該重構(gòu)代碼以隔離標(biāo)注中使用的軟件元素。
在為變量、方法以及類命名的時(shí)候,最好使用首字母縮略詞。這些名稱將會(huì)更具可讀性:
| Good | Bad |
|---|---|
| XmlHttpRequest | XMLHTTPRequest |
| getCustomerId | getCustomerID |
| class Html | class Html |
| String url | String URL |
| long id | long ID |
不論 JDK 還是Android 的代碼庫都使用了首字母縮略詞,因而他們是相當(dāng)一致的,因此,想使自己的代碼與他們一致是幾乎不可能的。請咬緊牙關(guān),盡量熟悉首字母縮略詞。
為代碼使用 TODO 注釋是暫時(shí),短期的解決方案,或者說是夠用的但是并不完美。
TODO 注釋必須用大寫的 TODO 開頭,后面跟一個(gè)冒號(hào):
// TODO: Remove this code after the UrlTable2 has been checked in.
或者:
// TODO: Change this to use a flag instead of a constant.
如果你的 TODO 語句是“在未來的某一時(shí)期做什么”的格式,請確保你包含了一個(gè)求具體的日期(2005年11月份)或者一個(gè)特定的事件(在所有產(chǎn)品開發(fā)者了解v7協(xié)議后刪除此代碼)。
當(dāng)日志記錄是必須的時(shí)候,它對性能有顯著的負(fù)面影響并且如果它不保持一定程度的簡潔性的話,就會(huì)迅速失去其有效性。測試系統(tǒng)提供五種不同級(jí)別的日志記錄:
if(LOCAL_LOG) 或者 if (LOCAL_LOGD) 代碼塊中來包含該日志。而在 LOCAL_LOG[D] 中則定義你的類或者子組件,故而要禁用所有這類記錄是有可能的。在 if(LOCAL_LOG) 塊中不應(yīng)當(dāng)含有執(zhí)行的邏輯,所有為日志而構(gòu)建的字符串也應(yīng)該放在 if(LOCAL_LOG) 塊中。如果日志調(diào)用會(huì)導(dǎo)致字符串的構(gòu)建發(fā)生在 if(LOCAL_LOG) 塊之外,那么不應(yīng)該把日志調(diào)用分散到函數(shù)調(diào)用中。還有些代碼仍然使用 if(localLOGV),這也是可接受的,盡管這個(gè)名稱并不規(guī)范。if(LOCAL_LOGV) 塊中。故而它可以在默認(rèn)情況下編譯,任何因此構(gòu)建的字符串在應(yīng)當(dāng)在 if(LOCAL_LOGV) 塊中出現(xiàn),并且在發(fā)行版中將會(huì)被剝離出來。注意:
System.out.println()(或者 printf() 原生代碼)。System.out 和 System.err 可以重定向到 /dev/null,所以你的打印語句不會(huì)有可見的影響。然而,所有銀這些調(diào)用而構(gòu)建的字符串仍然會(huì)被輸出。我們的部分想法:保持一致。如果你在編輯代碼,請花一點(diǎn)時(shí)間去觀察一下周圍人的代碼以確定你的風(fēng)格。如果他們在 if 語句的周圍使用空格,你也應(yīng)該這樣。如果他們的注釋是包含在用星號(hào)構(gòu)成的方塊里,你也要把注釋放在星號(hào)構(gòu)成的方塊里。
需要風(fēng)格指南的關(guān)鍵是有一個(gè)共同的代碼詞匯表,于是人們可以專注于你所說的內(nèi)容,而不是你說話的方式。我們提出了整體的風(fēng)格規(guī)范,所以人們知道這個(gè)專業(yè)詞匯表。但是局部的風(fēng)格也是很重要的,如果你在文件中加入的代碼看起來和周圍代碼明顯不同,那么讀者讀到這里的時(shí)候,這些代碼會(huì)使他們的節(jié)奏被打斷,這應(yīng)當(dāng)盡量避免。
當(dāng)為測試方法命名的時(shí)候,你可以使用下劃線從特定的測試情況中分離出要測試的東西。這種風(fēng)格可以更好地看出是在什么情況下進(jìn)行測試。
例如:
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))
}