一年前偶然的機會參與了公司的重點專案,需要長時間出差,開發團隊規模在20人左右,時間緊迫。在異地,少了公司技術團隊的支持,遠端溝通不便,許多事情顯得較為困難,遇到問題往往需要自己摸索、自己解決。有句話說,一個開發團隊有時就像一台引擎,一旦啟動,就能有成果與產出。但如果方向出現偏差,引擎越跑越遠,可能收不住腳,最終會導致專案失控。幸運的是,我們這個專案順利上線,成功完成電商大促,並順利交付。但其中的一些風險點和經驗教訓仍值得拿出來總結。
通常開發經理或架構師會早於開發人員介入專案,了解專案需求、進行系統分析、做相關技術選型、制定開發計畫與開發規範。在技術規格說明階段,開發經理或架構師要協調所有開發人員,指定技術規範,並與開發人員保持溝通,確保開發人員理解自己負責的模組或子系統,並按照架構意圖實現各項功能。
基本上每個公司都有一份這樣的文件(如果沒有,基本上可以考慮跳槽),這份文件通常與專案無關,比如命名規範、註解規範、SQL規範等等。此外,要統一JDK,包括本地開發環境與伺服器環境;定好專案名稱、包名、資料庫名稱、表名,以及是否每個表需要通用欄位(如version樂觀鎖版本號)等等。
這裡重點強調兩個地方,工程規範和包名目錄規範。
工程規範:要明確定義每個工程模組的邊界,尤其在分佈式系統中,這一點尤為重要。開發人員要非常理解框架的層次結構,比如什麼時候該定義DTO,什麼時候該定義Domain。如果這個沒搞清楚,專案在整個生命週期中會越來越龐大,問題一旦暴露出來簡直就是災難。
包名目錄規範:包目錄規範要儘可能從頂層開始,開發人員的包目錄權限要儘可能低,簡單來說就是儘可能讓開發人員少去建包,這一點很像日本的外包專案,目的就是統一規範。此外,要避免兩個工程模組出現相同的包路徑,這樣在引用時極有可能出現衝突。
系統分解後,要定義好元件(子系統或模組)的邊界與職責,這是專案初期開發人員最關心的問題,如果這個沒有定義清楚,後續系統將面臨重構的風險。
比如基礎數據,並非所有基礎資料、配置信息都要放到基礎數據模組中,只有跨系統、跨服務、跨模組的基礎資料、配置信息才屬於基礎數據管理範疇。此外,基礎數據服務的介面需要具有一定的通用性,儘可能減少針對某個系統、服務、模組開放的特殊介面;且不允許基礎數據服務依賴上層服務。
專案初期,除了像工具類等公共模組可能被打包成Release版本,其他一般都是SNAPSHOT版本,當專案陸續上線後,比如在分佈式系統中,RPC調用需向客戶端系統提供jar包引用,版本控制的重要性就會凸顯。這裡推薦閱讀一篇文章《語義化版本2.0.0》,是由Gravatars創辦者兼GitHub共同創辦者Tom Preston-Werner建立的語義化版本規範。
根據專案的開發模型,確定代碼主幹(trunk)、分支(branches)、基線(tags)的關係,以及相關環境的發布流程規範。
在分佈式系統開發中,沒有自動構建簡直無法想像。通常一個專案有很多子專案部署在數十台甚至數百台伺服器上。如果沒有自動構建,開發人員可能要花費大量時間在服務打包發布上,一旦需要立即發布一個測試bug,都需要很長時間,這會讓整個團隊精疲力竭。
生產環境中的專案禁止將日誌輸出到Console中。專案代碼中必須通過Logger對象來輸出日誌與異常,禁止使用system.out.println等方法,禁止使用e.printStackTrace來輸出異常。日誌需要通過日期、大小兩個維度來分割文件,避免文件過大無法打開。生產環境中禁止開啟DEBUG日誌級別,若確實需要排查,可以針對特定類別開啟,但不能將整個環境設為DEBUG,否則可能導致系統因文件鎖定而卡死。
在分佈式業務處理系統中,系統存在大量跨服務調用,並且需要對不同類型的異常進行不同級別的處理。隨著系統的擴展,處理方式需適應不同類型的異常,並且做到跨服務的異常統一定義,並能快速定位跨服務異常的源頭與原因。一般可通過定義全系統統一的異常編碼,並定義其產生原因,達到識別系統目的。
批量更新必須使用提供的統一批量更新方法,對性能有很大提升。若有非常特殊場景無法使用,則必須經過評審,否則可能成為性能瓶頸。
如簡訊服務、郵件服務等基礎服務要優先安排開發,因為幾乎每個模組都有可能涉及。
如果系統中使用了多線程,不要隨意開闢線程,儘可能使用統一線程池,並封裝公共的調用方法以及返回結果。
11.1 避免長時間鎖定事務:避免產生鎖定多行資料的長時間事務,儘可能將事務拆解或改為異步處理。
11.2 分佈式事務:專案中儘可能不透過資料庫層面來實現分佈式事務來保證資料一致性,而是透過異步、補償、冪等等方式來實現資料最終一致性。
快取常常被忽視,等到專案後期遇到性能瓶頸時才發現需要重構,此時代價已非常大。因此需要及早確定快取方案,是用Ehcache?Guava?還是Redis?並讓開發人員落實。
memcached/redis快取環境規劃,必須定義好資料結構使用規範,比如不能讓所有人都使用key/value方式存儲,最終導致快取環境髒亂。
及早確定是在物理上做共享磁碟,還是使用分佈式文件系統。
編寫單元測試要成為一種習慣,且單元測試應該是沒有副作用的。定義良好的單元測試在多次運行時,如果沒有其他條件變化,每次都應產生完全相同的結果。比如說,在資料庫中通常會插入一些假數據,然後在測試中驗證這些數據,但這種方式的測試不可靠,因為資料庫可能會變化。可以在單元測試過程中使用內存資料庫,或每次單元測試的數據都自動生成並在最後自動刪除。
許多同事害怕寫單元測試的一個主要原因是依賴太多(遠端服務調用、Redis、Webservice等)。如果一個服務因為種種原因掛掉,那麼這個測試就會失敗。要解決這個痛點,可以引入mock對象來滿足這些需求,而Mockito就是這樣的一個框架,用Mockito來模擬相關行為,不用費力去準備各種依賴環境,這樣只需專注於業務邏輯即可。