如何寫出一份「有理有據令人信服」的Android項目設計文檔

在大廠,寫得一手好文檔是一個很是吃香的技能。這可不僅是一個錦上添花的東西,而是不少工程師晉升,打造本身話語權的武器。 我這兩年在組內的深入體會就是,大部分厲害的高級工程師(不包括那些純混日子靠資歷晉升的人),寫文檔的能力一點也不含糊,很能抓住上級和項目的G點。web

可能有人會以爲,我技術牛逼就好了,爲啥還要提升寫文檔的能力,有這功夫我還不如多看看源碼分析?這是一些初級或者剛入門的工程師的廣泛的困惑。這是由於大部分剛剛入行的朋友有一個很深的誤區,就是他們覺得作軟件工程是一個和計算機打交道的工做,其實否則。軟件工程不僅是和代碼打交道,更重要的是和人打交道,是一份社會性質很強的工做。在大部分公司裏面,尤爲是大廠,牽涉到的人,組,都是很是很是多的。在小廠,人與人之間交流意見和設計能夠口口相傳,心照不宣,可是一旦人開始多了,就只能靠文檔了。除非你能夠厲害到一我的把全部代碼擼完,否則仍是最好老老實實的夯實本身寫文檔的能力。面試

若是你有寫技術博客的習慣,那麼恭喜你,相信你已經對如何抓住文檔受衆的技巧有所瞭解了。這對你在大廠生存有很大的幫助。若是沒有也不要傷心,這篇文章就是爲你精心設計的。sql

在這篇文章裏,我會大體的把一份安卓的項目設計文檔的骨架,和一些我工做中實際遇到的正反例都列出來,方便你們之後在工做中實踐。數據庫

設計文檔的結構

一個好的項目設計文檔,其實有必定的模板能夠參考的,不過無論模板怎麼變,大體都須要有如下幾個大框架編程

  1. 項目背景
  2. 項目術語
  3. 技術挑戰
  4. 完成要求

4.1. App性能要求 (可選)後端

4.2. App Size 要求 (可選)bash

  1. 現有架構(可選)
  2. 建議架構

6.1. 引入的第三方框架/SDK的簡介 (可選)服務器

  1. 開發時間線
  2. 其餘可選架構(可選)
  3. 參考文獻

咱先從項目背景開始聊websocket

項目背景

若是你們面試次數夠多,應該會有聽過一個叫STAR原則的東西,就是介紹本身項目的時候要遵循Situation(背景)->Target(目標)->Action(行動/作法)->Result(結果)這樣的順序,儘可能作到簡潔。網絡

一樣的,項目背景的介紹就是對應了這個STAR原則的S,也能夠說是項目的動機,爲何要作這個項目。

這個背景和動機能夠是一個產品產生的動機。好比說抖鷹的產品經理髮現競品快腳發提供了一個新的視頻濾鏡,並且這個濾鏡在競品快腳 中迅速攀升到用戶熱度的第一位了,基於咱們在產品的數據分析中blalala。。。因而咱們也要作這個濾鏡。這就是一個簡潔明瞭的項目背景。固然這個背景也能夠是一個純技術方面的問題,好比架構的升級等等,固然若是是架構的升級,那須要在背景裏面簡單的介紹現有架構的大概的一些侷限性(咱們下文會提到)。

本人閱讀過的一些經典反例就是,背景介紹的第一句話上來就開始直接飆產品/公司內部的一些黑話,好比某個sqlite 的 database的某一個col有問題啊,或者是公司內部的一個SDK的限制等等。這些都是技術細節,不是項目大背景。提早把這些細節說出來是無法在第一段就抓住讀者的眼球的,這會讓讀者失去仔細觀看全文的熱情,致使最後你的設計文檔可能收不到任何有意義的反饋。

項目術語

這一部分就更重要了。項目術語這個部分必需要儘量的把設計中涉及到的:

  1. 新引用的SDK/框架
  2. 項目以前沒用過的語言
  3. 項目/公司內部工具,服務
  4. 產品自己的組件Component.

都過一遍,尤爲是對一些剛剛進組的朋友,這對他們會有很大的幫助。不少剛剛入職的朋友初來乍到,可能也不太敢在研討會上問問題,閱讀沒有項目術語的文檔對他們能夠說是直接勸退的。做爲一個往高級工程師方向努力的朋友們,擴大本身在組內影響力也是一個相當重要的點,若是你的設計文檔能夠對初級工程師/剛剛進組的朋友更友好,那麼你已經成功了一半了。不少在組裏面待了好久的老鳥會懶得在產品自己的組件Component 解釋太多,由於他們想固然的會以爲這是一個他本身天天都接觸的組件沒有必要解釋。這實際上是不太好的,由於你的文檔不是給本身看的,而是給其餘組員,甚至老闆(老闆不少狀況下是不瞭解產品的技術細節的)。

好比你在新的項目中打算使用GraphQL這個查詢語言和相應的框架。那麼最好的作法是先在術語環節介紹一下:

  1. GraphQL -> 是一種針對圖狀數據進行查詢特別有優點的查詢語言
  2. GraphQL Query-> 一種相似於HTTP GET的GraphQL 請求,用來查詢後端數據
  3. GraphQL Mutation-> 一種相似於HTTP POST 的GraphQL請求,用來修改後端數據
  4. GraphQL Subscription-> 一種創建在客戶端和後端之間的長連接,用來監聽後端數據變化請求,大部分GraphQL框架用websocket來實現

有了這上面的介紹,相信你在接下來設計細節說到Query/Mutation的時候就不會有人懵逼了。

技術挑戰

這個環節就比較簡單了,把該項目的技術難點都列舉出來,可是有一個問題要切記:

不要貼源碼!不要貼源碼!不要貼源碼!

不少朋友,包括在寫博客的時候都是一言不合直接複製粘貼源碼,這樣的作法是很是讓人討厭的,說白了就是偷懶,連精煉一下源碼,哪怕作一份僞代碼加comment的功夫都不願下。仍是那句話,文檔是寫給別人看的,不是寫給本身的。

這裏我用KunMinX juejin.im/user/58ab0d… 大哥的博客裏面的僞代碼作正面 例子,你們若是看到這一份安卓事件分發的源代碼 (KunMinX 大哥若是你看到了以爲不想本身的例子被放進個人文章,請聯繫我,會及時刪掉並替換,在這裏先感謝你):

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }


複製代碼

是否是瞬間沒有任何興趣了?請注意我尚未所有都複製上來,就只是一小段而已。

再看KunMinX大哥的精簡版僞代碼:

juejin.im/post/5d3140…

是否是瞬間就豁然開朗了。

寫文檔也是這樣,若是我是審查者看到上來就貼項目內部的源代碼的設計文檔,對不起,我會直接打零分,貼源代碼的設計文檔,像極了初中的時候爲了湊八百字而生硬的加排比句的做文同樣,看起來很豐滿,其實都是骨頭沒有肉。當你本身想鑽研技術的時候,一行行的研究源碼是沒毛病的,可是若是你是要分享給他人的時候,千萬別直接複製粘貼。

完成要求

這個環節也很少作介紹,這是和公司/產品內部的需求有關係。好比你作結構的修改,作完以後是否會影響到原有的開發流程,若是有,是否會嚴重的影響,這些都是須要列出來的。

現有架構和建議架構

一提到架構,不少人都會以爲很虛,感受無從下手。其實在這方面,流程圖和組件通訊圖都是很好的幫手。有時候可能本身會以爲無從寫起,可是其實只要把流程圖/組件通訊圖一畫,其實就豁然開朗了。

這裏我以我司的一個最近剛剛開源的移動開發框架 Amplify(aws.amazon.com/cn/amplify/)爲例。

假如我在個人最新設計中提議使用這麼一個新的框架,那麼首先我得闡明這個框架是作什麼的(PS:這是我本身總結的,和公司文案無關):

Amplify Mobile sdk 給客戶端提供了一套離線應用解決方案,它包括了離線存儲,和服務端數據增量更新,還有身份驗證,日誌發送等等移動端所需的功能。該框架以GraphQL語言爲基礎,經過WebSocket保持和服務器端的實時鏈接,還有基於時間戳的增量/全量更新保持客戶端和服務端的數據一致。

好了,那大家組的高級工程師可能會問,那這個Amplify Mobile SDK內部是大概怎麼實現離線還有和服務器端數據一致性的呢?

這個時候組件通訊圖就派上用場了。話很少說,先上圖 (這裏咱們用 arcentry.com/app/ 來作示範,這個工具提供了不少AWS相關的服務組件圖,比較好上手)。

同時,讓我來以一個設計者的角度來講明這個架構圖大概內容:

  1. 在Amplify Android SDK的架構設計上,每當用戶在客戶端進行數據操做(CRUD)的時候,Amplify都會經過Data 組件把用戶本地的數據先進行修改(Model DataBase),在修改數據的同時,會把每一次CRUD操做進行序列化,存儲在另外一個Mutation數據庫裏面。
  1. Amplify Android SDK的Engine組件經過Observer模式,註冊了一個數據源變化的觀察者,若是有新的Mutation,Engine就會從Mutation數據庫將Mutation取出併發送到API組件,API組件再將其封裝成一個GraphQL的Mutation 請求發送至後端
  1. 圖中的左邊的區域爲客戶端,右邊爲後端

有了組件通訊圖,描述架構就變成了看圖說話,小學四年級咱就學過了,很是輕鬆!

從以上的圖和描述中,咱們隊友們就應該知道,數據存儲在Sqlite 數據庫內,同時保存了數據自己和對數據操做的序列化對象,而且他們也會有更多的問題,好比說

  1. 既然有Model數據庫,咱們怎麼定義客戶端的model,model長啥樣,是Amplify有工具自動生成,仍是必須咱們手寫?
  2. 既然是先寫入數據庫再和服務端更新,萬一網絡鏈接暫時不可用怎麼辦?Amplify怎麼處理數據不一致?

這些都是文檔閱讀者在閱讀完你寫的簡明易懂的架構簡介以後會問的問題,是一個順其天然的事情,當他們問到這裏的時候,你應該感到高興而不是緊張懼怕,由於這說明你們把你的文檔讀進去了,而不是敷衍和不耐煩。能讓閱讀者和做者產生互動的技術文檔,是好文檔!

有了架構圖,再加入一個流程圖,就更棒了。這裏我會用 www.plantuml.com/ 做爲師範工具來構建流程圖。

仍是以Amplify爲例子。既然咱們決定使用Amplify了,那使用Amplify先後咱們的代碼和架構會發生很大的變化麼?

假如咱們的產品是一款點餐的軟件,咱們的Model(數據模型)是一道一道的菜,同時菜自己能夠修改相應的元數據,好比辣的程度,是否加入了配菜等等。每當咱們把菜加入到購物車的時候,不一樣設備同一帳號的軟件的購物車應該出現相同的菜品。

在使用Amplify以前,咱們都是手動存入本身設置好的數據庫,而後立刻發送給服務端,來更新購物車的。

在使用Amplify以後,咱們不須要存進本身的數據庫了,而是直接面向Amplify的Model編程

若是你們以爲對比還明顯,咱再來一個一刀切式的對比,把兩幅圖放在一塊兒,再使用中間切割的方式:

經過上面這個對比圖,閱讀者能夠很清晰的看到,在現有的設計中,咱們徹底沒有修改Adapter和View之間的通訊方式和流程順序,僅僅是修改了Adapter和數據源的操做,從原來的Adapter修改本地數據庫和發送網絡請求兩手一把抓,變成了如今僅需向Amplify SDK修改模型Model數據。

配上組建通訊圖和流程圖,可讓你的文檔不僅是有枯燥的文字,使閱讀者有更大的想象空間,加上和原有架構的對比,高級工程師看了也會直呼你是老司機。

開發時間線

開發時間線通常須要和產品經理協商,可是一個很重要的小技巧是,當你設計你的開發時間線的時候,最好是經過功能/產品發版的時間進行倒推,算時間線。

好比,我要2020年10月一號正式發版,那麼假設咱們Beta內測須要兩個周,Beta bug修復一個周,QA測試內測版release兩個周,那麼咱們開發的Code Freeze日期就定下來,大概是八月26號左右。有了Code Freeze日期,設計,開發週期就有了:

開發項目 日期
正式發版 10/01/2020
Beta bug修復 09/24/2020
Beta QA 09/10/2020
Beta 發佈 08/27/2020
Code Freeze 08/26/2020
開發 07/26/2020
設計/文檔 07/15/2020
技術選型調研 07/01/2020

其餘可選架構

有時候,對於同一個項目,同一個功能,還有其餘的第三方類庫或者結構可用,那麼最好也要列舉出來,同時好比各自的優劣勢,這是給你選用的架構的很好的背書。這裏就不列舉例子了。這個環節也能夠參考以前講過的架構描述方法。

參考文獻

是的,雖然咱不是寫論文,可是確定多多少少有引用到一些文章,技術博客,哪怕是第三方類庫的官方簡介,也都要放在文末,以供其餘組員參考。同時這也是一個霸氣的結尾,"老子調研的這麼辛苦這麼盡責,看了這麼多文獻,你好意思反對麼?",此時無聲勝有聲。。。。。

最後。。。。

其實寫文檔就像寫做文同樣,是一件很是消耗時間,而且須要積累的過程。我記得大二考雅思和GRE的時候,寫做都是拿最低分的一part,當時的大學英語老師和我說,寫做就是一個輸入和輸出的關係,你須要有100%的輸入,纔可能有10%的輸出。要看不少,練不少,纔可能有你練和看的那10%的成果,是一件很是辛苦的事情。可是在公司裏面,寫好了文檔真的是一件對職業發展很是有利的事情。在谷歌的朋友曾經和我說,他在Android Support Libray組工做(如今是AndroidX了),由於support library太複雜,並且須要很強的backward compatible(向後兼任)的設計,因此常常性的是改幾行代碼,寫1000字的文檔和申請,在谷歌,寫文檔成了高級工程師常規操做,我相信大部分大廠也都是同樣的。因此但願你們都能多寫,多練,多拿反饋,不要懼怕一開始被人批評或者吐槽,這些都是你的墊腳石。

相關文章
相關標籤/搜索