對 Android 開發(fā)的一點思考

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)秀成為準則和習慣脏里,回歸最初的完美主義她我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市迫横,隨后出現(xiàn)的幾起案子番舆,更是在濱河造成了極大的恐慌,老刑警劉巖矾踱,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恨狈,死亡現(xiàn)場離奇詭異,居然都是意外死亡呛讲,警方通過查閱死者的電腦和手機禾怠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贝搁,“玉大人吗氏,你說我怎么就攤上這事±啄妫” “怎么了弦讽?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長关面。 經(jīng)常有香客問我坦袍,道長十厢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任捂齐,我火速辦了婚禮蛮放,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奠宜。我一直安慰自己包颁,他們只是感情好,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布压真。 她就那樣靜靜地躺著娩嚼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滴肿。 梳的紋絲不亂的頭發(fā)上岳悟,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天,我揣著相機與錄音泼差,去河邊找鬼贵少。 笑死,一個胖子當著我的面吹牛堆缘,可吹牛的內(nèi)容都是我干的滔灶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吼肥,長吁一口氣:“原來是場噩夢啊……” “哼录平!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缀皱,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤斗这,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后啤斗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涝影,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年争占,在試婚紗的時候發(fā)現(xiàn)自己被綠了燃逻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡臂痕,死狀恐怖伯襟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情握童,我是刑警寧澤姆怪,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響稽揭,放射性物質(zhì)發(fā)生泄漏俺附。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一溪掀、第九天 我趴在偏房一處隱蔽的房頂上張望事镣。 院中可真熱鬧,春花似錦揪胃、人聲如沸璃哟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽随闪。三九已至,卻和暖如春骚勘,著一層夾襖步出監(jiān)牢的瞬間铐伴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工俏讹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盛杰,地道東北人。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓藐石,卻偏偏與公主長得像,于是被迫代替她去往敵國和親定拟。 傳聞我的和親對象是個殘疾皇子于微,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354