Android 開發軟體架構思考以及經驗總結

井方哥發表於2016-12-29

一、萌芽

作為一隻程式設計經驗並不怎麼豐富的程式猿來講,我一直覺得架構師是一個比較神祕的職業,架構設計就更加的高大上了。經過今年的幾個專案,之前曾發文敘述我的從MVC到MVP專案重構實戰經驗,也曾說過我準備對目前手底下的專案進行重構。但是,前段時間,我改變了我的想法。開發模式的重構,僅僅只是換了一個套路,也許在重構的過程中對業務的邏輯進行了一次梳理,也是在基於前人的程式碼設計上進行了一些優化。但是,這遠遠還不夠,這不是我理想中的開發場景。在專案開發的過程中,也發現存在許多的問題,但是都是一些零散的問題,我很多時候希望能夠改變現狀,更加優雅地程式設計,然後實際的情況卻是陷入了迭代功能開發和bug修復的死迴圈。現在回過頭來想想,我理想中應該是開發應該是一種由規劃和設計指導的開發,那麼架構設計就顯的尤為重要了。

二、初識架構

1、閱讀《架構之美》之論架構

僅看完了《架構之美》的第一部分:論架構,對架構有了一個大概的認識。下圖是這部分的知識點概要:

書中很受啟發的概念:

  • 架構是一種折中,一種取捨。架構師要學會的就是平衡,質量與成本之間的平衡;
  • 架構師首先關注的不是系統的功能,而是滿足需求,滿足品質;
  • 架構設計要做的是讓關注點分離,並且對每個關注點進行設計一定的結構,該結構都有利於解答這一個關注點所定義的問題;
  • 好的架構應該是可以指導產品、開發、測試人員都對這一設計感到非常的舒適可靠,該設計覆蓋了所有該軟體系統相關利益的人員以及其中的關注點;
  • 只設計你知道需要的東西,多餘的設計是一種浪費;
  • 架構幾乎影響了該系統相關的所有的人和事,決定了該軟體系統是否健康。

2、分析行業內各個APP的架構演進

這裡僅僅通過Google搜尋各個app在架構演進方面的一些文章,從中分析他們為什麼要演進?怎麼演進?帶來了哪些好處?
簡單的整理如下:

(1)架構為什麼需要演進

  • 專案需求擴張,舊的架構不適應新的需求
  • 開發團隊人員增加,協作要求變高
  • 新技術引入
  • 更高的軟體質量要求

(2)他們是怎麼演進的

(3)帶來的好處

  • 進行了模組化的解耦,產品相對獨立,應對需求變化、技術更新更加靈活,團隊協作更加方便,並減少了許多是無用功,也給團隊留下了一些技術積累;
  • 進行了必要的統一規範,組織結構更加清晰,系統更加健康;
  • 引入了新的技術框架,,產品獲得更好的體驗;
  • 進行了系統的優化工作,軟體的品質更高,體驗更好;

3、Google搜尋關鍵字:架構設計

搜尋引擎對於我們來說是最棒的學習工具,我通過搜尋架構設計等關鍵字,閱讀了一些文章,並仔細研讀Keegan小鋼的部落格文章《小鋼的架構思考》系列。這幾篇文章在發表之初曾閱讀過,但是當時並不怎麼理解,大概是對架構還沒有一個大概的認識。在請教一位前輩的時候,他和說了對架構的一個理解,並再次推薦了這幾篇文章。所以我再次閱讀了好幾遍。下圖是文中關於架構設計的知識概要。

(1)知識概要

(2)個人小結

架構分為三個階段:規劃、設計、構建,每個階段架構的設計有不同職能。在規劃階段,考慮的是產品的需求、質量的需求,技術的可行性分析以及預研。在設計階段,考慮的如果將一個複雜的系統拆分,並設計如何進行組織這些拆分的模組。在構建階段,考慮的就是具體的實施問題,並且要保證一定的伸縮擴充套件性,因為架構是不斷演進的。文中引用了《軟體架構設計》一書的一個模型圖,我覺得有必要在此貼出來。最近也在思考軟體模組化的設計,模組化的設計也許各有理解,在此先不做討論。如下圖:

這張圖我起初理解不是很透徹,我曾嘗試自己去畫一些圖來表達我的一些想法。但是,當我再次回過頭看到這張圖的時候,才恍然大悟。
架構的設計可以從兩個維度來考慮,一是架構思維,二是架構原則。思維是我們的思考方式,是我們解決問題的方法。原則是我們思考問題的方向,是我們解決問題的一些標準。

三、架構的定義

對於架構的定義,業界都各有看法,也曾微信私信請教過一些行業內有豐富經驗的前輩。《軟體架構設計》一書則將架構定義總結為組成派和決策派:

  • 組成派:架構=元件+互動:軟體系統的架構將系統描述為計算元件及元件之間的互動。
  • 決策派:架構=重要決策集:軟體架構是在一些重要方面所作出的決策的集合。

keegan小鋼在《小鋼的架構思考:什麼是架構》文中提到:軟體架構是規劃、設計和構建軟體的過程和結果。《架構之美》一書在1.1.3架構的含義中提出:架構說明了設計和構建一個系統所使用的結構。《Software Architecture in Practice,Second Edition》中提出:一個程式或計算機的軟體架構是系統的一種結構或一組結構,它包含軟體元素、這些元素的外部可見的屬性,以及元素之間的關係。

本人並沒有經歷過大型的軟體系統,做過的也只是移動端App的開發,所以個人也只敢從移動端app的架構設計出發,給出我一個狹義的理解。我認為,移動端的app架構是一種基於產品和技術的進行統籌管理最終所形成的共識。可能大部分的app開發尤其是小團隊app的開發大多是由產品驅動的開發,需求來了,那就技術實現。需求變了,那就改。畢竟,應用層的開發是對業務負責的,必須保證正常的釋出。所以大多數的情況下,程式猿不得不在產品經理面前妥協,這樣對於開發人員的工作就會變的很被動。所以,就出現了程式猿和產品狗的撕逼笑談。這種現象的原因在於專案各個相關利益人員沒有對產品和技術達成共識,這正是移動端架構設計所要解決的問題。

四、產品

產品,是我們產品經理們設計的結果,也是開發人員開發的最終成果,是前後兩種人群的共同目標。作為軟體的架構設計者應該充分理解產品的設計理念,除了明白已經設計的功能業務,還得具有一定的預見,掌握產品的發展趨勢。下面主要從開發的角度來談一談產品。

1、產品設計(做什麼)

作為一名開發人員,我不能很專業的來談產品的設計,但是這裡我還是希望以一個開發人員的角度來講產品設計。什麼是產品設計,我覺得可以從以下幾個方面來思考。

(1)、使用者群體(什麼人用)

顧名思義,我們設計開發出來的產品最終是讓人用的。所以首先我們得定位產品的使用者是誰?使用者群體代表了我們產品的市場。所以產品做的好不好,最終市場說了算,使用者說了算。離開了使用者,不理解使用者,不注重使用者的體驗,一切都是無用功。

(2)、核心理念(要做什麼)

你知道你最每天累死累活一行一行敲出來的是什麼樣的產品嗎?可能對於我們開發人員很容易陷入到一個版本一個版本的迭代當中,這些永無止境的工作叫人忘記了思考,忘記了問下產品人員,我們最終是要做的什麼?一年後我們的產品將是個什麼樣的狀態,我們的最終願景是什麼?我們將會怎麼一步步去實現我們最終的願景?可能你會說,這些和我開發有什麼關係。接到需求,我把他開發出來實現不就ok了。然而,在我們的小組裡不乏有這些怨言,產品人員不斷的修改,我TM程式碼改過來改過去。我們的leader在這方面很強調我們開發人員應該擁有自己的主動權,可以反駁產品不合理的設計。但是,前提是你至少理解產品的核心理念,我們最終要做什麼。

2、迭代計劃(計劃怎麼做)

對於一個產品,使用者的需求是很多的,而且隨著時間不斷改變的。需求可以分兩種,一種是人性本能的需求,還有一種,是我們的產品催生的需求。這兩種需求都是合理的,也正是我們需要滿足使用者的。對於各式各樣的需求我們怎麼有計劃的去實現呢?在敏捷開發中,我們將這些需求放到一個“需求池”中,然後進行計劃安排在不同版本的迭代中。這個工作,不僅是產品人員去決定,開發人員也應該起一定的決策作用。產品人員需要從產品的角度去考慮,開發人員需要從技術實現的角度去考慮,最終的計劃應該是兩者的共同決策。特別注意的是,根據產品的特性,技術人員也應該提出技術方面的需求。合理的迭代計劃可以保證正常的開發節奏,完成迭代目標。

3、開發資源(用什麼做)

(1)、開發團隊配置(人)

在《人件》一書中提到了軟體開發中人的因素很重要,合理配置開發團隊是非常重要的。一個App的開發團隊至少需要5個角色,即產品、互動、UI、軟體、測試。不同角色也分不同的層次,比如軟體分初級、中級、高階。不同角色、不同層次合理搭配,才能夠獲得更高的工作效率,保證產品開發順利進行。

(2)、資料內容配置(物)

產品最終呈現給使用者的是資料,資料分兩種。一種是私有的資料,是由開發商自己生產的資料。一種是平臺自生的資料,是由使用者生產的。如果是自己生產的資料,就得考慮資料來源,資料的覆蓋率,資料的準確性,合法性等。如果是使用者生產資料,就得考慮使用者生產資料的動力、入口以及資料安全性、傳播性等。

(3)、開發投入預算(錢)

萬事俱備,只欠東風。完成一款app開發,需要一支專業的開發團隊,這裡的人力成本也是很高的。當然我這裡只談開發的預算,至於運營就不說了。我們得考慮,開發週期多長、需要多少人、後期維護怎麼辦。比如一個APP需要5個人開發,2個月的時間,開發兩個版本,按照每人1W的工資來計算的話,也需要 10W。這估計是最低階別的演算法了。所以,如果是創業公司,我們在組建開發團隊的時候,也得看看預算是多少,多少錢能辦多大事,當然如果是那些拿到投資無所謂的老闆來時得另算了,不過今年死的太多的公司以前都是大手筆。錢燒完了,也就沒有了。如果是大公司,可能不帶這麼摳門的。不過也應該去考慮,開發團隊會消耗公司多少資源,我們能否獲得相應或者更高的產出。

(4)、第三方資源

目前的開發而言,很多資源是可以尋找第三方合作的。比如,伺服器、雲端儲存、支付介面、登入介面、內容資料以及開發過程中的一些開源框架等等。我們需要選擇、商務談判直到整合到自己的APP中。

4、產品質量(做的怎麼樣)

(1)、使用者體驗

現在對於我們來說,使用者體驗是一個說爛了的詞。那是因為,使用者體驗真的很重要,決定了一個產品的成敗。產品開發完成後,最終到達使用者的手中。產品好不好,使用者說了算。哪些因素影響到使用者體驗呢?我想大概可以從5個角色各自的職責出發來看,產品的設計是否直達使用者痛點?互動是否符合人的喜好、習慣,UI是否讓使用者覺得舒適?軟體的效能好不好?軟體的缺陷是否多不多?

(2)、軟體效能

從技術的角度來講,我們可以通過軟體的效能來分析一個軟體產品的質量。今年許多的技術文章都在談效能優化,軟體的效能主要從軟體的啟動速度、流暢度、記憶體、功耗、流量、apk體積等幾個方面來評判。如果想做好一個應用,效能優化應該納入到日常的開發中持續進行。具體如何優化,這裡就不再多說了。

(3)、產品安全

產品的安全性可以從兩個角度來看,產品的生產商和產品的終端使用者。對於生產商而言,有許多的內容是需要受到法律保護的,有許多的敏感資訊,核心技術、網路介面等是不可以洩露的。對於使用者而言,我們肯定在本地或者伺服器儲存了大量的使用者資訊,比如賬號密碼,一些資訊一旦洩露將嚴重傷害到使用者的個人利益。所以,為了保護自己以及使用者利益,我們必須要生產一個安全可靠的產品。那麼對於一個應用端的開發者而言,我們的編譯出的apk最終會到使用者手中。所以,我們需要通過程式碼混淆、資料加密、許可權限制等一些技術手段來保護我們的應用。

(4)、質量評測

一個應用做的好不好,我認為可以主要從上述使用者體驗、軟體效能、產品安全三個維度來進行評判。那麼,我們該如何組織這些評判工作呢?我們有在進行這些工作嗎?就目前而言,我相信大多數的產品、開發、測試人員都或多或少的參與到這些工作當中,但是也許沒有將一些資料量化、沒有系統的組織這些工作。目前大部分的應用都整合了行為採集,產品的下載量、使用者的活躍度等也都是體現產品使用者體驗的主要引數。開發團隊內部一直在進行效能優化的工作,比如異常修復、bug修復、內容洩露,過度繪製,apk瘦身。我們也進行了程式碼混淆、資料加密、apk簽名加密的工作。但是,你知道你的產品質量如何嗎?相比同類產品來,你哪些做的好,哪些做的不好嗎?所以,我覺得將上述這些零碎的工作有系統的組織起來,將一些影響因素進行量化,讓我們更加清楚的瞭解我們的產品質量是一件非常有意義的事情。

5、風險規避

(1)、人力變動風險

人是善變的,尤其對於IT來說,人員的流動就更加的頻繁了,公司內部的調整,員工跳槽等等。所以,對於一直開發團隊,必須要考慮到人員變動的風險。如果,某某不在了,專案是否可以正常執行。開發團隊之間是否能夠交叉熟悉各自之間的業務。

(2)、上層決策風險

是否經歷過一個專案做到一大半業務被停掉了的情況?而這個時候,你做的是個半吊子。如果出現了這種情況,我們該怎麼辦?假設就在剛才你的老闆說你現在的專案不做了,那麼如何才能最大程度的挽回損失?如何進行專案的收尾工作?而不至於在專案又突然重啟的時候接收的是一個爛攤子。

(3)、專案延期風險

我們在專案開發的時候會進行評審,然後按照迭代計劃開發,但是在開發過程中一定會有許多問題影響我們的預期,比如需求變動、技術難題等等。專案延期在軟體專案的開發中是普遍存在的問題,對於某些迭代而言,可能並不對整個專案造成重大影響,但是這個問題是一定需要考慮的。並且,我們應該嚴格的掌控專案的進度,平衡這些問題,保證能夠按時交付產品。

(4)、軟體缺陷風險

我們應該隨時能夠提供一個穩定的版本,這是我們的leader所要求的。軟體的缺陷存在是正常的,我們不停的寫bug,也在不停的修改bug,對於那些隱藏很深的bug也許沒有讓測試測出來,最後流通到使用者的手中,這個時候我們如何完成緊急修復?如何快速響應能給到使用者一個穩定可靠的版本。這些是我們需要考慮的,任何時候,都應該有PlanB。

(5)、人為失誤風險

前段時間,公司內由於操作失誤,上架更新一個apk的時候不小心發錯了機型,導致使用該機型的使用者升級後程式無法使用。然後,由於這個機型缺少維護,找不到程式碼,僅僅只能找到一個apk檔案,然後只能考慮反編譯升級等等。我想,類似於這類的人為失誤還有很多,比如程式碼提交錯誤,整合路徑出錯等等。人總有一不小心的時候,所以,我們在設計的時候,應該將這些因素考慮進去,如何在出現失誤的時候主動警告,如何在使用者錯誤已經發生的時候啟動緊急方案,將不良影響降到最低。

6、產品交付

(1)、測試版本

在敏捷迭代開發中,我們基本上能夠一週提交兩個測試版本。我們開發一部分、修復一部分,都可以提交一個可測試的版本,這樣可以最大程度的降低開發風險,有利於軟體的穩定性。

(2)、灰度機制

如果你產品的使用者量夠大,這個時候釋出新的版本就得慎重考慮,使用者才是你的產品的檢驗員。目前基本都是使用灰度釋出的策略,先給少量的使用者釋出,看看使用者的反饋,而後逐步釋出給所有使用者。

(3)、版本管理

我們在開發過程中有許多的版本,也有很多分法。如debug和release版本,有的時候還需要給內容提供測試資料的data版本,還有的時候上一個版本還沒有正式釋出我們就需要開發下一個版本的功能。我們如何去管理各個版本的程式碼以及如何通過版本名來區分這些版本?我們需要制定一定的管理規範,並且這一規範是否在開發團隊中達成共識,就顯得非常重要。

五、技術開發

前面囉嗦了很多,終於寫到這裡了。對於一個開發人員來說,怎麼做才是我們的關鍵問題所在。只會Android開發,所以以下只討論Android。我主要從以下幾個方面來談一談怎麼做這個問題。

1、技術選型

(1)、 開發平臺

移動端的開發目前主要是兩大陣營Android、iOS,其他的就不多說了。

(2)、 開發工具

  • 編譯工具:Eclipse&Ant、AndroidStudio&Gradle,作為Android開發者,目前毫無疑問應該選擇AndroidStudio&Gradle;
  • 程式碼倉庫:Git 、SVN ,工具有海龜、AndroidStudio也整合了VCS;
  • Maven倉庫:可以使用nexus建立自己的maven私服;
  • 持續整合:Jinkens、Buildbot、Travis CI、Strider、Integrity;

(3)、 開發語言

Java、Kotin、Grovvy、SQL等等;

(4)、 開發模式

MVC、MVP、MVVM、clean等,各有優缺點,在此不做詳細說明;

(5)、 開源框架

都說了不要重複造輪子,因為你造的輪子不一定不人家的好用,對於我們開發者而言,有一件非常好的事情就是我們有太多的開源免費的第三方庫供我們使用,這樣給我們省去了大量的工作,做到更加高效的開發。但是,如何選擇,是否引入使我們需要考慮的一個問題。下面列出一些常用的第三方庫,更多請點選

  • 網路:OKHttp、 android-asyn-http、 volley、 Retrofit
  • 事件匯流排:otto、 EventBus
  • 依賴注入:Dagger、 RoboGuice、 ButterKnife
  • 圖片:FrescoGlidePicasso
  • 資料庫:GreenDao、 Ormlite、LitePal
  • Json解析: Gson、Jackson、 FastJson
  • 響應式程式設計: RxJava、 RxAndroid
  • 異常統計平臺:騰訊Bugly、Crashlytics
  • 效能優化: blockcanary、 leakcanary

(6)、 新興技術

軟體開發而言,新技術的發展相當迅速,然而我們實際落地到專案中卻需要很長的時間,因為新的技術剛出來一是需要學習成本,二是需要承擔新技術不夠成熟,存在缺陷帶來的一些風險。當然,我們應該積極的引入好的新的東西,跟得上時代的步伐才好。下面列舉的一些也許都算不上新的東西,但是也是近年來大家所追捧的新技術。

  • AndroidSupport:DataBinding、MaterialDesign等;
  • 混合開發:ReactNative、Hybrid、Weex等;
  • 程式語言:Java8、Kotlin
  • 熱修復:AndFix、HotFix、Tinker等;
  • 構建:InstantRun、Freeline

2、業務拆分

我們在進行業務拆分的時候,我認為可以將業務分成三類:

(1)、常用基礎業務

基礎業務主要是我們的app的一些基礎功能,像我們公司有BFC團隊給我們開發了檔案上傳下載、網路請求、行為採集、賬號系統等SDK,免除我們一些重複的勞動工作。怎麼去定義什麼業務才是基礎業務呢?我覺得可以這麼去區分。如果你的業務在行業普通的應用app都有需要,那麼這些這些就是具有普遍適用性的基礎業務。我們根據不同的功能進行拆分。

(2)、通用技術業務

通用技術業務我覺得是和自己app相關並且有技術性很強的業務,可能是你應用的核心技術部分,比如美顏這一類軟體的圖片處理,小猿搜題這類的圖片識別等就是一項通用技術型業務。通用技術業務的特點就是在和你同一類的app都會有需要使用的技術,我們可以根據不同的技術領域進行拆分。

(3)、特定功能業務

特定功能業務就是屬於你自己app的特定功能了,一般可以按照功能進行拆分成不同模組。比如說我目前的一鍵搜(類似於小猿搜題)主要有搜題、查單詞、翻譯三大功能。那麼就可以分拆為三大塊。搜題要經過拍照、框題、圖片處理、網路請求等步驟,每個步驟都可以看成一塊小業務,以此進行拆分。特定功能業務大部分僅適用於你自身的APP。

以上的說法僅從自身的經驗出發來進行描述,在我們實際的開發中可能會有一些特殊情況,或者有不同的拆分分方法。總之,業務的拆分還需要根據實際情況來。

3、架構設計(關注點分離、抽象)

(1)、核心概念

關注點分離

世上本沒有架構,關注點一分離就有了架構,我們將一個軟體系統的開發從多個維度將我們的工作進行拆分,對於每個領域進行設計,將各個領域有系統的組織起來,這種組織結構就是架構。然而如何將一個複雜的系統將關注點進行合理的分離,這個是非常有挑戰的。

抽象

抽象,這是在請教一位前輩時最後給我強調的一點。如果你對app是跟著互動走、一個頁面一個頁面寫的,那麼很顯然,你沒有對你的業務進行抽象,而只是在實現。作為java的設計思想也很強調抽象的概念。那抽象到底是什麼呢?抽象就是你要做什麼!更簡單的理解就是,寫interface而不是class。不知道大家有沒有這樣的經歷,在我們的MVP的開發當中,我們有個Model,也有一個IModel,但是我們寫完了Model才知道怎麼寫IModel,最後成了貼上複製的體力勞動。如果你是這麼做的,你可以自己思考下,假如我們先寫是IModel,而不是Model,那就是怎麼樣的體驗呢?這就是將你的業務進行抽象。在架構的設計當中,你只需要知道你要做些什麼?而不需要去過多的關注你具體怎麼去實現它,這才是設計。

(2)、設計思維

程式導向(Procedure Oriented)

眾所周知,在C語言的開發中,我們的邏輯大多是根據任務的流程走。這是程式導向的典型例子。程式導向關注的是工作的流程、一步一步的完成任務。

物件導向(Object Oriented)

Java語言作為物件導向開發的典型代表,這是我們所熟知的。我們將計算機按照人的思維來進行設計,每一個物件都持有自己的屬性,並且持有自己的操作方法。物件之間有繼承,組合等關係,通過組織這些關係來完成我們的程式。就像社會的人和物一樣,人與人之間各種複雜的關係組合完成了社會各項活動的運轉。

面向切面 (Aspect-Oriented)

面向切面是為彌補物件導向中的一些缺陷而生的,我們將某些功能封裝到一起,提供對外的介面,方便在任何地方呼叫。就如SharedPreferences, Json, Xml, File, Device, System, Log, 格式轉換等,這些通常會在until包裡邊。它就相當於一個橫截面,我們可以隨時面向這個橫截面完成操作,而自己的邏輯裡邊不再需要重複的設計。

面向服務

面向服務是將系統進行拆分,分成一個個獨立的程式或元件,並對外提供某一項服務。每項服務之間通過某種協議進行通訊,並進行分開部署,如HTTP,從而達到鬆耦合的目的。

以上四種思維重點在於看待問題的角度不同,不同的角度解決問題的方案就不一樣,當然各種角度各有優劣。那麼對於在android開發中是否都只是按照OOP原則來設計呢?很顯然不是。面對不同的需求,不同的場景,我們需要及時調整自己的思維,靈活運用,尋找最適合的角度,拿出最優的設計方案,這才是我們所追求的。

(3)、設計原則

高內聚

怎麼理解高內聚?我認為我們在拆分時某一細分領域只完成單一的功能,其內部的事情自己處理。從表面來看比如一個model的class,對外提供了一個介面,那麼他有一個輸入,一個輸出。單獨看這個介面而言,它是高內聚的。當然,其內部的組織結構有可能千差萬別,所以內聚的形式又各有不同。所以我們將他們分類為功能內聚、順序內聚、時間內聚等等。

低耦合

耦合指的是模組之間存在依賴關係,關係相互依賴就會相互制衡,這是必然的。所以,如果耦合度太高的話,將會導致牽一髮而動全身的後果,這個使我們不想看到的,也極大的影響的程式版本的迭代以及bug的修復。根據依賴關係的不同,我們分為了非直接耦合、資料耦合、內容耦合、開關耦合、控制耦合、外部耦合等等。我們要完成一個系統的開發,必須要將各個模組有效的組織起來,這種組織關係便無法避免存在了耦合,我們要做的是儘量減少這些依賴關係,尤其避免交叉依賴,將耦合度降低到最低,把我們的程式設計的更加的靈活。

適度設計

我們在設計的時候如果考慮不周,那麼設計不夠,不能滿足現有或者可預知的需求,從發展的眼光來看,會導致後期的開發中出現很多的問題。如果想的太多,很容易進行過度的設計,從而將一個簡單的系統設計的很複雜,那麼就給當前的開發將增加了許多無意義的工作,降低了開發效率。那麼怎樣的設計才是合理的設計呢?我認為能夠同時滿足現有的需求和可預知的需求,並且面對架構的調整時能夠很方便的進行擴充套件。這樣的設計,是非常好的設計。如何才能達到這樣的效果呢?我個人覺得在對系統進行設計時,關注點分離的顆粒度需要把握好,系統不過就是將不同單一小模組進行組織而已,那麼這些細小的模組就是架構設計的基礎,這就好比建房的那些磚頭。這些磚頭是什麼呢?他麼可以是是一個對外提供介面的公共方法,也可以是私有的內部方法,也可以是某某持用的成員變數。當然往大里看,他可以是某一個功能模組。在上述行業內各個app的架構演進中,都很強調進行模組化的改造。所以,分離好你的系統,才能夠靈活的組織起來,以不變應萬變。

(4)、設計方案

指導模型下圖在文中已經提到,這裡再次引入,因為這張圖對我的啟發真的很大,也表達出了我心之所想。面對一個複雜的系統,我們怎麼樣去分離,怎麼樣去組織,我認為這張圖已經傳達出了其中的精髓,所以我認為這是架構設計的指導模型,無論你是什麼MVC、MVP、MVVM之類的,都可以從中去理解。

模型分解

根據實踐開發中個人的理解,我將此圖再次進行了簡化如下:

橫向分塊

根據上圖的簡化模型,我們可以這麼理解,在橫向我們根據業務功能進行模組劃分。比如主題商店,我們可以分為桌布、鈴聲等等模組,每個模組間解耦。同時,在每一層的業務間再次進行分塊,比如桌布在資料層就有圖片的請求、載入、快取、裁剪處理等等。

縱向分層

接下來我們在對每個模組的業務根據職責分為展現層、業務層、資料層。資料層主要負責資料的獲取、封裝等工作,業務層主要更加上層的需要調配各資料層最終將資料返回給展現層,展現層的工作就是將資料展現在UI介面上,並且響應人的各種指令切換UI,操作新的資料。

介面通訊

在橫向來看,我們將業務進行了分塊,保證塊與塊之間相互之間沒有任何依賴,保證了絕對的解耦。從縱向來看,每個層級之間的依賴很明顯是無法避免的,所以我們可以保證上層僅依賴下層的介面,從而達到降低其耦合度的目的。

如上圖所示,通過上述橫向分塊、縱向分層、介面通訊這三大步驟之後,我們可以將一個系統進行了很好的分解,並得到一個理想模型。當然,這是一個理想的模型。在我們的實際開發中可能無法避免一些交叉等特殊情況,我們還需要從實際情況出發。但是有一點,我們可以保證介面的分離,已達到更低的耦合度的目的。

統一管理

統一管理,是對於我們的設計中有一些東西是需要統一管理起來的。通過上述原則,我們將一個複雜的系統進行了拆解,已達到架構設計中將關注點分離的目的。然而在實際的開發中,我們除了要進行業務的分拆,也需要對某些業務進行統一的管理。比如說一些模式的開關管理,比如說我們在進行網路請求時需要在測試環境和正式環境之間的切換,我們可以將這些模式切換的開關放到一個地方,方便我們進行管理,而不要去到各個地方去修改。再比如說我們的請求url地址,是否可以寫到一起進行統一的管理。還有在某些應用中會通過一箇中間人來進行統一管理資料的流通、頁面的跳轉,這也是一個可以嘗試的方案,詳細請看蘇寧易購移動端的架構優化實踐文中提到的模組管理器、Url跳轉管理器。統一管理的意思就是將分拆的某一類小的模組某一些特性放到某一處進行統一的管理。但是這樣會存在一個問題,比如前面舉例說到的統一開關管理,這造成了開關耦合,如何去避免呢?我覺得可以將開關預設寫到自己的模組裡邊,並公開出修改的介面,方便上層進行統一的修改,以達到統一管理的目的。這樣的話,即使這個模組拆離出來,也不會受到影響。但是,這樣的話,其安全性受到了一定的影響。架構設計總是這樣,你總需要選擇一個折中適合自己的方案。

我們通過上述橫向分塊、縱向分層的方法將一個系統切成不同的小塊,這些小塊負責某一單一的職責,然後通過介面將塊與塊之間進行了間接性的連線,依賴的是介面而不是例項,以弱化這種模組間通訊造成的耦合。當然,上述模型僅僅只是一個理想狀態的模型,如果是一個非常複雜的系統,那麼層級之間也能拆分出更多的層級。比如,在資料層,我們在MVP模式的開發下使用Model來完成,當Model層的業務變得非常複雜時,有部分人會考慮拆分出Data層放在最底層,最為最基礎的資料操作等。最後,為了方便我們對模組進行組合並進行管理,我們可以考慮在小模組中開放出介面,供上層進行統一的控制管理。最後,我想說的時,我們在進行業務分離拆解時可以考慮按照上述的方案來做,最終還得根據實際情況來進行設計。

4、開發實現

當完成我們的設計工作後,我們進入了開發編碼階段,在這個階段主要表達我們的設計,並最終取得實實在在的成果。當進入這個階段之前,我們的設計不能僅僅是一份文件,而應該是開發人員和架構設計者達成的某種共識。再好的設計,也需要獲得良好的表達和實現。下面主要談一談在實現過程中需要考慮的問題。

(1)、專案分包

專案的分包結構體現一個軟體的架構,我們在進行分包的時候總有一種困惑。因為我們存在多種分法,比如我們可以分為根據類的功能分為activity、fragment、adapter、util等,有的時候,我們又根據功能模組分,比如一鍵搜中有查單詞模組、有搜題模組,同時又存在網路請求,軟體升級等小的外圍通用功能模組。存在的問題就是模組之間又存在一些可以複用的東西,那麼我們進行拆分明顯出現了程式碼的冗餘。如果按照兩種方案同時分,那就肯定存在了架構的混亂。我們該如何達到這兩種的平衡?我認為,這個也需要更加專案的大小而來,如果是非常小的專案,也不存業務擴充套件的可能,我們就可以採用上述的第一種方案,簡單的分類就好。但是,對於較大的專案,我建議使用第二種方案。下面,我簡單列一個模型僅供參考:

+ app
    +main
        +com.jfg
            +common //常用基礎業務
                +util
                +wedget
                +base
            +function //通用技術業務
                +camera
                +sensor
            +moudule //特定功能業務
                +mouduleA
                    +model
                    +presenter
                    +view
                +mouduleB
                    +model
                    +presenter
                    +view
                +mouduleC
            +demo //主程式
                +app
                +activity

如上所示,我們根據開始的專案業務拆分分包如上,將常用的基礎業務放到common包裡邊,這個包在大多數情況是不變的,並且為app提供基礎性的服務,不過我們儘量不要放到這個common包裡邊,如果這個common包變得足夠大的時候,就一定要思考是不是該拆分了。因為common給人的感覺就是什麼都是,那就讓我們無法快速認知這個包所擔當的職責。我們可以這樣理解,common包是面向切面而設計的一些業務,但也不是絕對的。接下來我們先聊module這個包,實際這裡是將業務進行了模組化的分拆,如上我們拆分出了moudleA和moudleB,這兩者之間要求沒有任何的聯絡。但是,我們會存在一個問題,那就是moudleA和moudleB某些業務是一樣的,我們拆開顯得重複了許多體力活。這應該是大多數開發者面對的困擾,這種該怎麼去平衡呢?我是這麼考慮的。如果,moudle和moudleB存在重疊的業務,我們將這些業務提取到function包或者common包中,這樣降低了業務的層級。我們允許moudle包的各模組業務依賴於function和common為我們提供的基礎服務。為了更好的區分模組A和模組B雖然重疊但在邏輯上是各自屬於各自的,我們有兩種方法來做。第一種是將兩種業務進行一定的抽象,實現的過程還是放到各個moudle業務中。第二種方案定義兩個介面類,各自定義各自的介面。在具體的實現類中實現了這兩個介面類的方法,內部在進行相同的邏輯操作。這樣,對外看來,邏輯上moudleA和moduleB是分離的。總之,如何分包還得權衡利弊,儘量以一種思維來進行劃分,以避免設計混亂。

(2)、抽象介面

如果說在架構設計中抽象很重要,你可能有些迷糊,但是如果要你先寫interface或者abstract class 而不是class時,你就可能感覺得到抽象的意義。我們將一個系統分解成幾個大的模組,一個模組查分成不同的層級,每個層級再次拆分成不同的細節業務。最後,我們很清晰的知道我們要完成某一項功能需要做哪些事?對的,做哪些事就就是一個個介面,我們在編碼時先寫介面再寫實現有利於幫助我們對業務進行拆分和抽象。我們都知道做一件事情一般情況都需要提供一些條件,做完了會有返回結果。這些都可以在介面的設計中完成。我們需要注意是一個介面只做一件事情,如果有兩件事非常相似也要儘量拆分而不是合併。在介面命名方面做到見名知意,怎麼去評判,就是如果你的介面沒有註釋也同樣能讓人知道你的介面是做什麼的就好。

(3)、資料儲存

資料儲存常用的有SQLite、SharedPreference、檔案等,快取是否也可以算是一種。這裡想強調的就是要注意資料儲存的規範性以及安全性,如果是資料庫還有必要考慮其擴充套件性,如果不滿足需求將會需要進行升級。

(4)、效能管理

這裡源自於對效能優化的一點體會,對於服務端的開發我們很珍惜伺服器資源,應該是看的見的需要銀子買的。然而,對於客戶端的開發我們常常忽略了這一點。雖然手機裝置現在擁有大記憶體,但是如何寫出一個優秀的程式,效能也是一個非常重要的指標。效能優化處理,那是我們在更正錯誤,那麼之後應該是少犯錯誤。效能體驗不夠好,無非就是對機器裝置的記憶體、CPU、GPU資源無節制的使用,造成資源的浪費,當機器裝置無法承受時就會應用就會出現卡段、當機、異常等不良反應,嚴重影響了應用的體驗。我們要做的就是要有很強的效能管理意識,對於記憶體、CPU、GPU等資源按需借用,並做到有借有還,即用完後記得釋放資源。

(5)、特殊處理

我們在開發的過程中,總有那麼多問題並不是按照正常思路出牌的,這些得歸功於我們強大的測試團隊。不同的手法,就能得到不同的結果,然後就給了我們一堆的bug。所以,我們在軟體的開發中需要特別注意一些特殊情況的處理,這些最終往往還是邏輯上的死角。以下簡單總結了一些:

功能衝突

功能衝突可以分為兩種,一個是應用內部的功能衝突,二是應用之間的功能衝突。應用內衝突比如A功能和B功能都使用了某資原始檔,如果在同時使用就會出現問題,我們通常加同步鎖來防止這種衝突。應用外的衝突有很多,比如多媒體、鬧鐘、日曆、鈴聲、電話等都肯能引起這些衝突,比如你正在播放一段視訊,這個時候來了一個電話,那我該優先哪一個呢?還有當鬧鐘響起的時候,彈出一個介面是豎屏的,那麼他就會強制將當前的介面變為豎屏,而如果你這個時候如果是橫屏的話該怎麼辦呢?類似於這類還有很多,以後再細細總結。

極限操作

我們的測試人員喜歡對著某一個按鈕狂點、或者在機器上安裝無數的應用使記憶體爆滿,或者在磁碟裡邊塞滿各種檔案。這些場景雖然並不是理性的使用者所出現的,但實際也是程式的缺陷。所以,我們要注意對這些問題進行處理。

網路問題

不可用的網路,訊號很弱的網路,網路在wifi和流量之間切換,2G網路和4G網路,網路請求超時等都需要我們針對實際情況進行處理,比如切回到流量的時候進行下載是否有提醒使用者。這些處理也算是各個應用的標配了,就不再多說了。

為null處理

這應該是最常見的問題了,我們平時改bug或者從後臺異常抓取的大多數都是空指標異常。首先,我們得搞清楚為null的原因是什麼?然後我們需要進行為null的判斷,並警告。

(6)、Log列印

這裡把Log列印單獨拿出來是應為我覺得很需要重視。Log是用來幹嘛的?很顯然是用來幫助我們查詢問題的,然後我們大多數的情況下是問題來了再去加列印,並且TAG五花八門的,是有錯誤的地方用Log.d,而只是檢視資訊卻是用的Log.e,我們查問題的時候要去閱讀很多的程式碼邏輯,最後再定位到位置。我們在修復bug的時候花了大量的時候再閱讀程式碼,這個太影響工作效率了。如果我們在編碼之初就對Log有了很好的規範設計,有異常的地方就用Log.e,可能出現問題的地方就用Log.w等等,關鍵點的資訊用Log.i,臨時除錯的用Log.d這樣區分不是很好嗎?我們在控制檯一樣就能夠分辨自己需要的資訊。我們應該充分應用這個工具,幫助我們快速定位問題。

(7)、軟體重構

重構是因為業務的需要,也是因為對程式碼更高的質量要求,重構無處不在。我們不要為了重構而重構,也不要一直停滯不前,該重構時不重構,欠下太多的技術債務。重構有小到一個方法的重構,也有大到整個系統架構的重構。重構是軟體迭代升級的一個必要過程,也是我們能夠滿足當前需要,並且能夠適應未來的發展,獲得良好的擴充套件性的必要手段。重構並不難,也不是什麼大事,重點在於重構背後的目的、思想、設計是否清晰。

(8)、相容適配

相容適配的問題是我們開發一個頭疼的問題,Android裝置無法八門的螢幕尺寸、層次不齊的Android系統版本。除了進行鍼對性的處理,還得提醒產品設計人員在設計之初就得考慮相容性問題。

5、軟體測試

軟體測試是我們開發中非常重要的一到工序,除了能夠客觀的感應我們所開發的軟體質量水平,最終目的還是在於幫助開發人員修復軟體缺陷,提高軟體的質量。除了開發人員提交測試之前的自測,我們需要專業的測試人員來進行測試。人工測試的效率相對較低,所以我們應該考慮通過技術手段完成自動化的測試,如單元測試等。這樣有利於軟體的穩定,也同樣的有助於開發人員提高程式碼質量。

6、開發規範

(1)、設計一致

什麼樣的設計才是有規範的設計呢?我個人認為一個專案保持一致的設計思想就是有規範的設計。我們可以這樣去理解,在某一類的情況下,我們按照某一類似的方案來解決這一類問題。面對複雜的系統,我們一種設計思想也許無法滿足架構的需求,但是我們只需要明白,這種事情這麼幹,那種事情那麼幹,相互之間靈活的組合卻也不存在交叉,給人設計思路清晰的感覺。

(2)、編碼清晰

什麼樣的程式碼才是高質量的程式碼?我覺得結構簡單,邏輯清晰,一看就懂的程式碼就是一份高質量的程式碼。所以,我們可以拋開那些編碼規範、命名規範等等,重新去審視自己的程式碼,看看讀起來是不是很舒服。如果來了一個新同事,對你對專案一無所知,是否能夠很快速的理解你的思維?最後幾點有必要提出,一是慎用設計模式,二是儘量少寫文件註釋,三是遵循Java的物件導向六大設計原則。我想這三點就是編碼的原則,遵守這些原則,形成一種習慣,自然而然就是一種規範。

(3)、文件有效

什麼樣的文件才是有效的文件,我認為能說明核心問題的文件就是有效的文件。作為軟體開發人員,我們沒有耐心去閱讀一份複雜囉嗦的文件。文件只需要給我們呈現程式碼無法說明的問題,幫助我們快速理解專案結構,備忘重點問題,追溯歷史記錄。文件的形式又很多,比如我們Git的提交記錄、tag,專案結構圖、核心功能流程圖等。總之,文件是給人看的,以儘量少的文件來說明核心問題就好。

7、日常工作

作為一名合格的軟體開發人員,我們有許多的日常工作,如Bug修復、異常處理、Monkey、效能優化、程式碼質量改善等待。這些事情並一定要求你每天都要做,但是每天都得關注,做到心中有數。如此,才能夠培養一個很好的程式設計習慣,寫出優秀的程式碼,優秀的程式。

六、統籌規劃

1、開發驅動

一個專案的正常運轉一定是由某一方主導專案的進行,不斷的提出產品需求,並組織專案成員分工合作,完成產品的開發交付,以下是兩種驅動開發模型。

TDD:測試驅動開發(Test-Driven Development)

測試驅動開發指的是由測試主導開發的工作,從產品的使用出發,對產品的功能提出測試要求,組織專案中各個角色完成開發任務。

BDD:行為驅動開發(Behavior Driven Development)

敏捷開發便是行為驅動的開發,更加強調產品的設計,鼓勵專案中各個角色提出自己對專案開發的觀點,已獲得更優秀的產品功能,完成產品的開發。

2、敏捷開發

下圖引自網路上的一張關於敏捷開發的圖片,目前我們團隊基本也是根據這個模型進行敏捷開發的。我覺得在敏捷開發中更加強調的各個角色之間的隨時溝通和快速響應,我們並不對整個系統進行一份完整而詳細的設計,而是進行階段的設計開發工作,並謀求不斷的迭代更新。在敏捷開發的過程中,普遍存在的問題就是溝通不及時、產品變動大,所以如何動態的進行統籌管理變的非常重要。我們通過需求評審、互動評審、視覺評審、Bug評審等由各個有關角色的人員參加評審會議,共同完成決策,以保證軟體開發的順利進行。不得不說,這個過程中還是存在許多的問題,溝通的問題,產品變動的問題,產品功能細節開發過程的中流程性的問題等等,在此不詳細說了,後期有時間再進行總結。

3、達成共識

敏捷開發中有一個特點就是,產品開發決策是由專案各個角色成員共同完成的,各個角色領域的問題又是由該角色自身最終拍板。那麼隨之而來的就出現了一個問題,各個角色所決定的問題又被其他的角色所制衡。比如說,產品經理設計的功能在開發人員而言存在技術難題,互動的設計存在邏輯上的漏洞。我想,這些問題是一個產品不能按照預期時間完成的真實原因。那麼,哪一個角色來統籌管理這些問題呢?通常,我們有架構師、專案經理、老闆等等角色來對專案進行把控和管理。

軟體架構的設計來源於產品的需求,並決定了產品的最終形態。然而處於敏捷開發中的我們而言,產品的需求變化很快,同時要求我們開發人員能夠快速響應這種變化。那麼,如何保持軟體的整體架構和產品的設計同步更新就顯的非常重要。

對於開發者而言,我們首先要對專案有一個整體的認知,隨時掌握產品的動態。這些包括產品的設計、迭代計劃、開發資源、質量要求、交付要求、風險規避方案等。然後根據產品的需求變化,動態調整自己的軟體架構,這些工作包括,技術選型、架構設計、程式碼重構、文件更新等,以適應新的需求,並把這種架構共享給相關人員以達成共識。這種共識,是需要對產品和技術進行統籌規劃才能夠完成的。也只有專案各個相關利益人員能夠達成這種共識,我們的溝通才會更有效,才能做出各方人員都滿意的產品。

七、總結

寫到最後,我回過頭來看看,才發現對架構的整體認識站在了決策派的一邊,即這種共識便是在產品規劃、設計、開發階段一些重要方面所作出的決策的集合。這些決策是我們專案各個角色成員通過計劃會、評審會、迭代會等達成的共識,並最終表現在我們的產品上。產品、互動、UI、開發、測試,每個角色對自己所在領域負責的同時也需要隨時掌握其他領域的資訊,以便自己做出正確的決策。但是在對軟體技術實現的架構設計上,我就偏向了組成派,將軟體系統的業務進行了拆分和組合,已達成系統運轉。我必須承認,此刻我的認識還是不夠通透,在本文中各個方面大多還是泛泛而談,每個問題深入下去還有許多可以去研究的,希望能夠在以後的工作能夠有更深刻的領悟。

八、 參考文獻

後記

經過這段時間對架構的學習和思考,我發現這些理論不僅可以運用在軟體架構中,同樣也適用於工作、生活當中。試著將我們的工作任務進行分離,減少每項任務之間的交叉,避免東一榔頭西一棒子,有條理的完成各項工作。這樣一來,你的工作目標是清晰的,也更加容易的完成既定目標。同樣的,我們可以通過這一理論還規劃自己的人生。將我們的事業、家庭、興趣愛好分離,再對每一項分離出更細的關注點,每個關注點在不同的階段定下自己小目標。這樣,我們對自己就可以有了更清晰的認識,認識自己已經擁有什麼?最終想要什麼?計劃怎麼做?當前怎麼做?當然,並不是每一步都是能夠按照既定目標走的,我們需要不斷的更新對自己的認識,做出更加合適的決策。

相關文章