本篇文章關注於編程實踐中的相關流程設計內容,內容來源本身過去的工做總結。編程
越複雜流程,越容易出錯。爲了減小出錯的狀況,須要提取而且封裝通用邏輯,用一個易於理解的名字來對外提供服務。在業務不一樣抽象層級上,幹各自職責對應的事情。緩存
先後臺交互的流程越多,須要維護的狀態就越多,出現問題的機率就越大,所以在不影響主要功能的前提下,流程能簡化就儘可能簡化,那些被簡化的路徑,在某些異常場景下,會對業務有必定的影響,具體影響到什麼程序,在上線前,誰也很差說。可經過數據埋點獲得上線後的真實數據,以供權衡。網絡
針對複雜業務時,不能一上手就開始寫,要先設計好框架和總體流程。對於一些需求上面沒有明確的交互細節,通常按照通用交互細節來就能夠。框架
對於具體子功能的實現上,須要細緻考慮,較好的實踐方法是逐條列出來,在每種狀態或狀況下,什麼樣的輸入獲得什麼樣的輸出。函數
在複雜程序執行過程當中,須要維護多個狀態,隨着業務的改變,在處理於狀態關聯的事件時,思惟負擔會愈來愈重重,很容易顧此失彼。
爲了更好的控制複雜度,簡化邏輯,須要對狀態進行分級管理,將每一個級別的狀態和對應處理分層化。對外統一狀態操做接口,每一個模塊中,都經過統一狀態接口來管理狀態。測試
每個常量數字,在業務上都要有依據來源,要麼是經驗值,要麼是業務規則規定的。字體
任何失敗重試類操做,都要考慮對後臺的影響。由於網關只會轉發請求,大量冗餘的請求會給後臺形成巨大壓力,可能會影響正常業務流程。對於失敗重試,必定要設置定時間隔以及重試次數,這裏的失敗要區分類型,若是是業務操做類的失敗,業務層直接提示出錯便可。若是是其餘類型的失敗,底層須要作好重試策略,直到重試策略返回失敗,纔可認爲是失敗,此過程對上層是透明的。優化
針對開發回發的數據,要進行有效性校驗才能繼續往下走,在有些業務場景中,須要對不一樣類型的失敗分配不一樣類的錯誤碼,以便外部處理。好比讀取配置信息這個功能,可能的出錯就有如下幾種狀況:this
若是由於上述種種緣由,致使讀取配置文件失敗,那要作好默認配置的處理,根據產品要求,對不一樣的異常狀況,作出對應的處理,使用默認配置繼續運行,仍是彈框提示用戶等。設計
在對後臺返回來的格式化數據進行解析時,必定要考慮對應字段不存在的狀況下該如何處理。無論後臺返回什麼類型的錯誤數據,都不能崩潰。
仔細分析業務流程,避免沒必要要的依賴和操做,相關的業務邏輯,要聚合到一塊兒。
分析問題的思路要不斷在實踐中打磨,反思,總結。本身第一時間想到的方案,每每還有很大的優化空間,那麼,從什麼角度去思考優化呢?在開發實踐中,逐漸明確兩個大的方向,在此記錄一下
從底層往上層思考 : 底層是最基礎的數據層,業務流程的數據是否是能夠往下移動到底層,而後調用底層明確性語義接口函數來實現上層業務,而不是把一堆判斷邏輯都交由業務層去作。
從上層往底層思考 : 在業務層和底層數據之間,要有一個間接層,對上提供較爲高級的功能,對下封裝最底層數據,提供更高層次的接口。
類似的業務處理採用類似的辦法,不要一堆條件,範圍A-C的用這樣的處理方法,業務範圍D-F的用那樣的處理方法,其餘類型業務用其餘的處理方法。這樣一來,業務和對應的錯誤處理分散在各處,後期維護很容易出錯。
一個類,如需向其餘類得到相關信息,不要依賴於類與類之間的繼承組織關係,嘗試經過this
來進行強制轉換來獲取,由於這種層級關係在將來可能會發生變更,而這種層級關係的變更,不會通知使用者,所以在實現相似需求時,須要將對外部的依賴關係儘量的下降,建議採用消息的方式,每層規定好消息的職責,一層一層向上傳遞請求,直到某一層給與響應爲止。
一致性,指的是對於UI元素和代碼命名的一致性,同一邏輯元素的命名一致性,它在UI層、中間層、網絡層以及後臺接口中的核心詞彙要保持一致,這樣增長代碼的可讀性。
一致性體如今資源管理上,同一份資源,誰申請,誰就負責釋放。誰增長引用記數,誰就負責減小。
在一些通用功能的設置上,保持代碼一致性很重要。好比設置控件字體,原生系統會提供一套接口,自繪部分也會提供一套接口,那麼外部在使用時,就會有疑惑,設置字體是用原生接口仍是自繪接口呢?他們會不會相互影響呢?在前期設計時,同一類功能,只提供一套接口較好。
類似操做可以歸集到一塊兒的,必定要歸集到一塊兒,讓邏輯聚合,而不是處處分散。
好比,你修改了A函數中的A分支,A函數的B分支沒有改動。外部O1模塊使用了A函數的B分支,O2模塊使用了A函數的A分支,那麼你的本次修改涉及到的影響範圍是哪些呢?O2模塊確定是要測的,O1模塊需不須要提測?答案是須要的,從廣義上來看,任何調用A函數的模塊都要重測一片。即便本次修改沒有涉及到分支,也要去測試。
在嘗試修改後臺程序時,要注意與以前系統的兼容性。
在修改一個涉及範圍比較大的問題時,若是涉及的地方太多了,爲了最終問題的解決和測試方面的平滑過渡,須要將問題分治,按照功能需求模塊來統計彙總,而後,將問題列表按模塊分配給各自負責人,由他們去進一步往下分散進行修改,這樣能夠保證大型問題的平滑過渡,爲開發和測試提供緩衝時間段。
好比本次修改點可分爲如下幾個集合,
集合1:涉及到哪些方面
集合2:涉及到哪些方面
集合3:涉及到哪些方面
對於層級調用,資源的申請和釋放,要特別注意一致。這一點,特別容易遺漏。
好比說,先壓入緩存,再發出請求,那麼,清理緩存的時機,應該要覆蓋發出請求後的每一條執行路徑。
7.1 發出請求失敗
7.2 發出請求成功,響應成功
7.3 發出請求成功,響應失敗
一段業務代碼,正確的路徑只有一條,但錯誤的路徑有不少條,怎麼處理在這個過程當中有可能出現的錯誤,就很顯的功力所在了。
若是遇到一些須要特殊處理的地方,優先把大部分狀況放在主幹邏輯上,在額外的分支中處理額外。
凡是涉及到網絡請求的,若是中途關閉了頁面,必定要注意請求資源的清理操做,這樣在從新打開頁面,不會收到上一次無效的響應。
沒有profile的優化都是瞎扯淡,一旦開始重構,就要指定好優化目標,全部的相關修改都聚焦在目標上,不能跑偏了。優化前要分析現狀、制定方案和可量化的目標,完成優化後,須要提取相關的數據,總結對比可量化的分析優化成果。後臺重構須要配置A/B方案,對於可能會有變化的模塊或者業務,或者A、B方案很差取捨的,可採用後臺配置採用哪一個方案,當重構的版本上線時,先保留舊的模塊,若是有問題,能夠在後臺中配置切回到舊的模塊,這種思想同能夠同灰度發佈一塊兒使用(5W,10W)
在進行組件的重構時,須要遵循最小影響範圍來作,一處重構,儘可能隻影響到這個業務,不影響到其餘業務,保證可控性和可測試性。
對於接手舊項目,裏面用到的第三方庫,若是有新版本的接口庫或者更好的第三方接口庫,該如何抉擇?
當老舊代碼中有不少相互交纏的邏輯,須要改動多處才能改好詞Bug,很差評估改動影響的範圍,這個時候,爲了最大限度的兼容老舊代碼,不能繼續在老舊代碼的基礎上繼續填坑、挖坑,若是選取其中一個簡單的界面,用清晰簡單的邏輯去實現,而後在逐步替換老舊模塊,小步慢跑的改進。
當須要在原有功能上面增長新的功能時,要特別注意一點,就是原有功能所使用的指令和流程不能更改。好比查詢A產品的天數信息,原有的指令只支持一種產品的查詢,如今其餘界面有同時查詢多種產品的狀況,而原有指令只實現了查詢單個產品信息, 那麼,下面有兩種解決方法:
優勢 缺點 方法一:修改原有指令,使其支持多條信息的查詢 增長指令的功能 需修改原有查詢單條產品的頁面,增長了測試成本 方法二:不改動原有指令,新增支持多條信息查詢指令 不會影響已有功能 相似功能有多處實現,查多條包含查一條的功能
由於查詢一條產品信息屬於查詢多條產品信息的特例,爲了可維護性,採用方法一較好。
有A、B、C三個帳號,有ID1,ID2,ID3,ID4四個功能,每一個帳號容許的功能以下:
A--> ID1, ID3 B--> ID2, ID3 C--> ID4
如今要設計一個權限判斷的函數,給定功能ID和帳號,返回是否容許執行此操做判斷?
一種是從上往下,從入口ID和各個可操做ID段來判斷,優勢是直觀,簡單,缺點是對擴展性很差,將來要增長新的入口時,須要修改多出
方法一: 從上到下,邏輯簡單直接,可擴展性差
bool IsAllow(Account acnt, int nFunId) { if (A == acnt) { if (nFunId == ID1 || nFunId == ID3) { return TRUE; } } else if (B == acnt) { if (nFunId == ID2 || nFunId == ID3) { return TRUE; } } else if (C == acnt) { if (nFunId == ID4) { return TRUE; } } return FALSE; }
另外一種方法是從下往上,將每一個交易ID和可以操做條件集合在一塊兒,這種方式的擴展性和可讀性都很是好,推薦使用。
struct TIDInfo { int nId; vector<Account> vAllowAcntType; // 該ID要求的帳號屬性 bool IsAllow(Account acnt) // 給定帳號是否存在特定屬性 { return vAllowAcntType.find(acnt) != vAllowAcntType.end(); } } vector<TIDInfo> allIdInfo; // 全部功能ID集合 bool IsAllow(Account acnt, int nFunId) { for (int i = 0 ; i < allIdInfo.size(); ++i) { if (allIdInfo[i].nId == nFunId) { return allIdInfo[i].IsAllow(acnt); } } return FALSE; }