17 年畢業(yè)開始工作到現(xiàn)在已快兩個年頭姜胖,在實際項目開發(fā)的過程中誉帅,我對 Android 開發(fā)有了一些自己的思考。本著碰撞才會有火花右莱、討論才會進步的理念蚜锨,我把對 Android 開發(fā)的一點思考分享出來,真誠的希望可以有不同的觀點慢蜓,在糾結(jié)反駁之中得到最優(yōu)解亚再,共同進步。
最初的時候晨抡,你是否是一個完美主義者氛悬,不容忍任何一點 warning 與嘆號则剃,if 必有 else,switch 必有 default如捅,即使 else 和 default 中確實什么也不用處理棍现,你也會添加一個 //do nothing 注釋,表示這里的邏輯是經(jīng)過充分考慮的镜遣,下次閱讀程序時己肮,告訴別人也告訴自己,這里的確什么也不用處理悲关,可以快速跳過谎僻。
我想大多數(shù)開發(fā)者,都是經(jīng)歷過這種心態(tài)的寓辱,然后在繁忙的版本迭代中艘绍、在趕著回家的加班時、在愈來愈發(fā)的對自己的薪水不滿時讶舰、在一次又一次看到團隊中別人得過且過的代碼時鞍盗,漸漸的,就可能對“生活”妥協(xié)跳昼,丟掉了完美主義般甲。
然而如果你有更高的追求,就要勇敢的戰(zhàn)勝自己的感性鹅颊。
使用 IntDef敷存、StringDef
平時特常用的 View.setVisibility() 方法使用 IntDef 來規(guī)定參數(shù)的可選項,可以試想一下堪伍,假如沒用 IntDef 會怎么樣锚烦?對于初學者來說,可能要稍微閱讀一下源碼或查下資料才能知道 setVisibility 有哪些參數(shù)可以設置帝雇。你可能會覺得沒什么差涮俄,因為你很清楚 setVisibility 方法有哪些參數(shù)可以設置。但若是程序中新增的一個方法呢尸闸?比如你新接觸一個模塊彻亲,某個界面有若干個跳轉(zhuǎn) Action,你得先找到定義這些 Action 的地方吮廉,而若一不小心將這些 Action 分散寫在不同的地方苞尝,那對后面的維護和拓展可能就是一個災難。
建議凡是符合語義的邏輯宦芦,都必須用 IntDef宙址、StringDef 來約束,它比枚舉節(jié)省內(nèi)存调卑,性能更優(yōu)抡砂,其 RetentionPolicy.SOURCE 表示此注解只在源碼中存在大咱,編譯時會剔除。你可以在 Android Studio 的 Live Templates 中添加 IntDef注益、StringDef 寫法:
使用精準表達的變量類型
比如你需要聲明一個變量來表示某個功能是否啟用徽级,譬如控制你的 App 是否展示廣告,并且可以通過服務端在線下發(fā)開關來控制聊浅,如果沒有接收到下發(fā)的開關,就根據(jù)地區(qū)來決定是否展示现使。
這種情況下你會使用什么類型的變量低匙?
你可能會想到使用一個 int 類型變量來控制,然后需要給這個變量加上注釋:
// 0:展示碳锈; 1:不展示顽冶; 2:未接收到在線開關,需要根據(jù)地區(qū)決定是否展示
private int mShouldShowAd;
以后每當改動到這部分邏輯售碳,都需要查看一下這個變量數(shù)值對應的含義强重,隨著時間的推移和代碼量的增多,在此邏輯之上可能堆積了很多代碼贸人,然后就會出現(xiàn)各種各樣的問題间景,別人可能在不存在的邏輯分支做了一些事:
if (mShouldShowAd == 0) {
//do something
} else if (mShouldShowAd == 1) {
//do something
} else if (mShouldShowAd == 2) {
//do something
} else {
//do something...
}
甚至可能對這個變量賦值 [0,2] 區(qū)間之外的數(shù)值! 你可能對這個變量的意義很了解也絕不會用錯艺智,但你不能保證他人不會出現(xiàn)上面所說的荒唐的用法倘要,因為這個變量類型并不能很精準的表達它的語義,也沒有任何約束性十拣。
我們可以怎樣改善這種難維護封拧、有風險的代碼?
- 可以使用 IntDef 規(guī)定這個變量的取值
- 可以換成 Boolean 類型夭问,用 null 表示未獲取到在線開關泽西,恰好的表達語義并且易讀、易維護
使用盡可能少的變量
舉個例子:
mDebug = BuildConfig.DEBUG;
if (mDebug) {
Log.d(TAG, "...");
}
你是否寫過這樣的邏輯缰趋?明明已經(jīng)存在了一個可以直接使用的變量條件捧杉,你仍然要重新定義。這個例子邏輯還十分簡單埠胖,此變量是 final 類型的糠溜,不會出錯。而如果是非 final 類型的變量直撤,那就是強行增加了一個賦值聯(lián)動的邏輯非竿,埋下了隱患,后續(xù)如果出了問題谋竖,白白的增加了定位問題的路徑與復雜度红柱。
實際開發(fā)中我們可能自己都意識不到使用了不必要的變量承匣,比如我們的服務端接口一般會有多個接口環(huán)境,那你的代碼可能是這樣的:
//是否是測試環(huán)境
private static boolean sIsApiHostTest;
//是否是beta環(huán)境
private static boolean sIsApiHostBeta;
//正式環(huán)境host
private static String sApiHost = "http://api.com/";
//測試環(huán)境host
private static String sApiHostTest = "http://test.api.com/";
//beta環(huán)境host
private static String sApiHostBeta = "http://beta.api.com/";
/**
* 是否是測試環(huán)境
*/
public static boolean isApiTest() {
return sIsApiHostTest;
}
/**
* 是否是beta環(huán)境
*/
public static boolean isApiBeta() {
return sIsApiHostBeta;
}
/**
* 獲取接口域名
*/
public static String getApiHost() {
if (isApiTest()) {
return sApiHostTest;
} else if (isApiBeta()) {
return sApiHostBeta;
} else {
return sApiHost;
}
}
這樣看起來好像沒什么問題锤悄,只要維護好 sIsApiHostTest韧骗、sIsApiHostBeta 這兩個變量就行了。如果后面又添加了一個環(huán)境呢零聚?又添加了三四個環(huán)境呢袍暴?是不是還要維護多個變量?這個邏輯可以通過減少變量來改善:
//當前環(huán)境host
private static String sCurApiHost;
//正式環(huán)境host
private static String sApiHost = "http://api.com/";
//測試環(huán)境host
private static String sApiHostTest = "http://test.api.com/";
//beta環(huán)境host
private static String sApiHostBeta = "http://beta.api.com/";
/**
* 是否是測試環(huán)境
*/
public static boolean isApiTest() {
return sApiHostTest.equals(sCurApiHost);
}
/**
* 是否是beta環(huán)境
*/
public static boolean isApiBeta() {
return sApiHostBeta.equals(sCurApiHost);
}
/**
* 獲取接口域名
*/
public static String getApiHost() {
return sCurApiHost;
}
再加上 StringDef 就完美了:
@StringDef({ApiHost.sApiHost, ApiHost.sApiHostTest, ApiHost.sApiHostBeta})
@Retention(RetentionPolicy.SOURCE)
public @interface ApiHost {
//正式環(huán)境host
String sApiHost = "http://api.com/";
//測試環(huán)境host
String sApiHostTest = "http://test.api.com/";
//beta環(huán)境host
String sApiHostBeta = "http://beta.api.com/";
}
//當前環(huán)境host
@ApiHost
private static String sCurApiHost = ApiHost.sApiHost;
/**
* 是否是測試環(huán)境
*/
public static boolean isApiTest() {
return ApiHost.sApiHostTest.equals(sCurApiHost);
}
/**
* 是否是beta環(huán)境
*/
public static boolean isApiBeta() {
return ApiHost.sApiHostBeta.equals(sCurApiHost);
}
/**
* 獲取接口域名
*/
@ApiHost
public static String getApiHost() {
return sCurApiHost;
}
/**
* 設置接口域名
*/
@ApiHost
public static void setApiHost(@ApiHost String apiHost) {
sCurApiHost = apiHost;
}
不知道你有沒有感受到易讀性隶症、可維護性政模、拓展性都蹭蹭蹭的往上漲呢?
單一數(shù)據(jù)源
同時接受多個數(shù)據(jù)源數(shù)據(jù)的邏輯相比只接受一個數(shù)據(jù)源的數(shù)據(jù)需要考慮時序性等問題蚂会,要復雜很多淋样。打個比方,可以把數(shù)據(jù)源當作你的直接上級胁住,上級會不定時的分配任務給你做趁猴,如果你有多個上級,一個讓你做任務 A彪见,一個讓你做任務 B儡司,且 A 需要在 B 之前完成,你要怎么辦企巢?兩個上級都讓你做任務 A枫慷,但是只用做一次,你要怎么辦浪规?
在安卓中較為典型的場景就是同時加載網(wǎng)絡和本地緩存數(shù)據(jù)到 UI 上或听,你的 UI 上展示的數(shù)據(jù)來自不同的地方,你需要考慮不同數(shù)據(jù)源之間如何協(xié)作笋婿。谷歌推出的 Jetpack 開發(fā)指南上推薦我們使用單一數(shù)據(jù)源誉裆,假如你的網(wǎng)絡數(shù)據(jù)也需要緩存的話,那你的實現(xiàn)邏輯應該是這樣:
- 加載網(wǎng)絡數(shù)據(jù)缸濒,返回后插入到本地
- 統(tǒng)一從本地取數(shù)據(jù)展示到 UI 上
這點和上面說的“使用盡可能少的變量”有相通之處足丢,都是盡量規(guī)避使用多個條件變量對程序產(chǎn)生影響的邏輯。
職責分離
強烈建議什么類里就干什么事庇配,別把邏輯都揉到一塊兒斩跌,這樣隨著代碼量的增加,會愈發(fā)的難以維護捞慌,到最后就變成一顆存在重大隱患的地雷耀鸦,看見就頭疼。
舉個例子,比如你要自定義一個 View袖订,那就像系統(tǒng)控件一樣氮帐,只負責一個控件該負責的事,處理一下渲染洛姑、展示上沐,把手勢交互通過接口開放出來,把數(shù)據(jù)的獲取寫在數(shù)據(jù)倉庫中楞艾。這樣如果數(shù)據(jù)展示出了問題参咙,可以很快的定位到是數(shù)據(jù)獲取出了問題,還是渲染展示出了問題硫眯;如果這個控件的渲染展示是經(jīng)過驗證的昂勒,之后就幾乎不用改動此控件,至少你有機會可以將你的自定義 View 寫的像系統(tǒng)的控件一樣穩(wěn)定舟铜。
這里再推薦一下谷歌的 Jetpack - MVVM 全家桶,MVC 真的是不易讀奠衔、難維護谆刨、問題多、很簡陋归斤。
回歸最初的完美主義
希望你我可以戰(zhàn)勝感性痊夭,不向“生活”妥協(xié),讓優(yōu)秀成為準則和習慣脏里,回歸最初的完美主義她我。