1. 程式人生 > >設計方案考量:準則與細則

設計方案考量:準則與細則

沒有完美的方案。所有方案都有利弊,在於適用場景以及權衡取捨。Think Deeper, Design Better.

設計溝通

問題發起者,建立文件說明痛點,採用方案能夠得到的優點與可能的弊端,利弊權衡。


明確痛點

每一個需求/優化/重構,總能追溯到某個痛點。痛點主要有如下:

  • 功能訴求: 競爭對手有拼團功能,賺了好多錢好多粉絲,我也要有!【新功能】

  • 穩定性優化: 時不時出現xx報錯,真是影響生意!同時多個大流量匯出,系統波動有點大啊!【穩定性】

  • 效能提升:怎麼這麼慢啊 ! 這麼多訂單,得處理到什麼時候?【響應速度與吞吐量】

  • 維護成本: 這方案得佔雙倍的儲存資源,還有兩個同步,理解起來多費勁!這個報錯,沒法看出問題在哪裡,還得再打個日誌看看。商家在等著修復問題,真急人!【資源/時間】

  • 彈性: 明年訂單量要增加5倍,現在這個方案貌似扛不住啊!【容量擴充套件】

  • 資料: 這待發貨訂單數顯示為2,怎麼點進去沒訂單?【對比分析有困惑】

  • 體驗: 要做完一個批量操作,要好多步驟,還容易出錯,真耗費時間啊!【步驟繁瑣,易錯】

  • 及時: 更新一個內容,要馬上生效,而不需要重新修改程式碼部署系統。【即時更新】

  • 安全: 啊啊,不小心把DB/重要檔案目錄資料刪除了!【安全性提醒】

  • 擴充套件:實現一個需求,要改這麼多程式碼?

  • 重構: 要實現新的業務需求,真沒法改了!非得動大手術了!

痛點是否足夠痛? 避免為了解決/優化問題而解決/優化問題,避免為了嘗試新技術而引入新技術。一定是為了解決痛點。因為事情是做不完的,顧此則失彼,要對做的事情進行仔細規劃。明確痛點,才能真正對症下藥,藥到病除。


設計準則

自然清晰

  • 設計自然、直觀、清晰;

  • 沒有拐彎抹角的地方。

  • 域的劃分清晰。

  • 分層清晰。

  • 所見即所得與形式的一致性。


案例:

  • API 巢狀、繼承導致 API 使用很迷暈。【反例】

  • 要匯出零售總店的網店訂單,需要傳 head_shop_id, 並將 shop_id 置為空。Workaround 方案。【反例】

  • 將大量邏輯放在一個類裡。程式碼分層不清晰。【反例】

  • 使用策略模式分離輸出標準報表和自定義報表的報表欄位列表。


容錯處理

  • 減少了錯誤發生的可能性。

  • 錯誤發生時,更安全友好地處理。

  • 不會因為次要區域性影響整體。

  • 減少了故障可能性,或可能故障級別。

  • 故障發生時,能夠更快更安全地處理和恢復正常。


案例:

  • 對每個訂單的處理進行異常捕獲打日誌,且對每個訂單的每個欄位的處理進行異常捕獲打日誌。避免某個訂單的某個欄位錯誤影響該訂單的其他欄位的匯出,或者避免某個訂單的資料錯誤,影響了其他訂單的匯出。【容錯、隔離】

  • 重試機制。

  • 針對可能導致故障的點,進行重點防護。


擴充套件能力

  • 底層模型統一。

  • 核心簡潔而穩定,外圍可擴充套件。

  • 適當地分離關注點,更容易組合和組織關注點。

  • 元件化、配置化,通過增減外掛來支援需求。


彈性擴充套件

  • 當業務量增長時,可以自然應對而無需額外改動。

  • 可以即時加機器,解決臨時高併發吞吐量問題。

  • 可以即時減機器,去掉不必要的資源空閒。


維護成本

  • 減少了儲存資源佔用。

  • 減少了多處同步。

  • 能更快速地定位問題,大幅減少了排查和解決問題的時間(秒/分鐘/小時/天)。


案例:

  • 更明顯的錯誤原因指明和建議措施,利於快速定位問題和解決。


可複用

  • 以小見大,從一個需求點看到一類需求。

  • 建立可重複使用的方法、機制和流程,更容易地解決相似問題。


案例:

  • 建立一個可複用的 HBase 詳情獲取外掛,來解決匯出商品編碼的問題;同時又能為其他欄位匯出需求所使用。


配置化

  • 解決一個需求時,建立相應的配置,當後續可能發生細節變更時,只需要修改配置即可即時生效。


案例:

  • 工程裡的列舉的配置化,避免每次加一個都要修改程式碼釋出系統。

  • 當要針對不同業務過濾可配置的欄位列表時,使用配置來指定要過濾的欄位列表。


一勞常逸

  • 建立良好的約定,解決一次,出問題只追溯源頭。


案例:

  • 零售訂單的導購員姓名取下單表的擴充套件欄位XXX 。建立這個約定後,推進和完善各個場景下這個欄位的落庫。


依賴弱化

  • 減少了不必要的依賴(API,apollo,NSQ, KV 等),或者至少不引入新的依賴。

  • 對外部依賴進行降級或熔斷。


案例:

  • 訂單詳情介面去除對粉絲介面的依賴。

  • 訂單匯出任務完成後,直接更新DB裡的任務記錄,不再依賴訊息中介軟體。


最小複雜

  • 總是首先尋找簡單、改動最小、比較徹底的方案。

  • 複雜度衡量: 少量順序程式碼 < 一些條件分支程式碼 < 增加少量apollo配置 < 增加DB < 增加DB和快取 < 增加一個模組。


舉一反三

  • 發現一處,解決多處類似的問題,而不是發現一個解決一個。


案例:

  • 訂單詳情介面的商品圖片URL欄位未輸出,可以藉此梳理下還有哪些欄位需要輸出。因為每改一次的釋出成本很大。


整合能力

  • 發現多個需求點的關聯,綜合考慮和解決,避免來一個解決一個,導致解決方案比較鬆散。


權衡取捨

  • 沒有完美的方案,只有合適的權衡取捨。

  • 針對不同的場景,衡量收益和代價。

  • 要綜合思考,避免線性思考;避免為了解決一個次要的問題引入更大的問題。

優先順序:穩定性 > 清晰性 > 靈活性。


通常可以認為:

  • 如果能夠達到建立新功能、避免故障、彈性擴充套件、大幅降低維護成本、可複用, 其收益將是非常高的,此時,增加少許依賴、複雜度,其實是可以接受的。

  • 為擴充套件留下實現空間,但可以暫時不實現。

  • 一勞常逸/舉一反三,相比只是解決當前問題,更有價值;多往前走一步。

  • 自然清晰,是非常不容易達到的;但很值得為之一步步接近。


設計細則

設計細則是在準則的基礎上,針對具體事項、場景而建議採用的方案。

  • 文件說明: 當設計變更涉及較大變動時,建立文件說明改動點及緣由。

  • API :繼承不可超過兩層,避免巢狀;避免將不相關的東西混雜在 API 引數中;避免將底層實現細節暴漏在API 引數與傳參中。

  • 分層: 提煉出一系列關注點,分離到不同的語義層次,分離到多個類的單一職責中。

  • 元件化: 將工程裡的程式碼與功能實現抽象為元件介面與實現。

  • 策略模式: 使用策略模式分離同一個介面的不同實現,並根據場景選擇適宜的實現。

  • 外掛流程: 如果流程是可變的,那麼將單個流程節點變成可配置的外掛,並進行編排。

  • 啟動檢查: 當應用啟動時,載入所有必要元件,任一不滿足時及時報錯退出,避免錯上加錯。

  • 使用切面: 當多個功能要複用同一個前置或後置邏輯時,使用切面來實現這些前置或後置邏輯。

  • 受控執行緒池: 切忌在應用裡動態建立單個執行緒或執行緒池;使用全域性受控的執行緒池來執行任務。

  • 過載函式: 使用過載函式建立適合的工具類。

  • 無狀態: 除非必要,不要在例項間共享狀態;不要讓請求的處理結果依賴於某個狀態。

  • 快速失敗: 當前置要件不滿足時,快速失敗勝於自以為的智慧容錯處理。