2014年1月15日 星期三

[連載]新創公司歷險記-2:搬開技術絆腳石、加速執行

        繼上次才寫到開頭的歷險記,第一週我先跟團隊達成『工作完成』定義的共識,避免錯誤的資訊混淆對實際狀況的掌握與判斷。

        但是,即使掌握正確的專案進度,避免浮報無知的樂觀,卻沒辦法讓實際的開發速度加快。採用了每週一輪的 spring,前兩週都無法達成目標,以當時的 deadline 來看,團隊的開發效率若沒有改善, 肯定無法對上有所交代。

        我仔細觀察造成開發效率低落的主要原因有幾項:
  1. 團隊浪費在 debug 的時間太多:寫好一個功能,卻要花費三四倍的時間在除錯?
  2. 技能與工作分配嚴重矛盾:個人專長無法與所負責的應用程式搭配。

關於 #1:

        團隊當時還是使用老派原始 ADO.net 的開發方法,徒手撰寫大量資料庫存取的 DAOData Access Object) 與 DTO(Data Transform Object),不但必須在資料庫改動時來回維護兩邊的接口,容易犯錯且工作量大,而且缺少強型別檢查,必須自己轉型,致使許多粗心大意的錯誤都要到執行階段才發現,令人不勝困擾。

        我立刻引進並介紹:LinqToSql 的技術,雖然跟 EntityFramework 相比,這已經是被汰換的技術,但由於我們資料庫的操作一律全部封裝到『預存程序』內,所以僅僅需要 Linq2Sql 自動產生 StoredProcedure 的代理界面,並且提供 strong-typed 的型別檢查,不但讓原本 DAO / DTO 的開發時間降低到趨近於 0只剩下拖拉資料庫物件到 VisualStudio 內約只需要五分鐘),更讓人為疏失消彌與無形,而且 IDE 自動幫我們在拖拉 code-gen 過程中,抓出『因為 table 改動造成既有的 spx 無法運作』的錯誤

        此技術的導入,省下團隊大量的開發時間,算是我至今採取最有幫助的手段之一。

        另外一個造成除錯時間過長的因素,是團隊在程式中不寫 log,也不擅長,大家都僅僅依賴 IDE 的中斷點和單步除錯來找問題,這種初學者除錯法尤其對 n-tier 架構的系統沒轍,而且測試出了問題後,需要再花費大量時間放置中斷點、重新啟動系統、重複剛才出錯的操作,來來回回嘗試數遍才會找到問題的根源
我剛剛打你的 service 怎麼沒有回傳資料?
應該是你沒有打進來吧?你檢查你打的 IP 位置對不對。
IP 正確啊,是你的程式沒有回傳結果吧?
你關掉你的防毒軟體或是防火牆再試試看,或許被擋掉了?
好,我關掉防毒軟體看看⋯⋯。

半小時後⋯⋯。

Fxxk!我就知道我有打,明明就是你的服務執行到一半出 exception 了,只會回 null 給我!還叫我把防毒軟體關掉!

        最後一句對話我戲劇化地渲染一下,我們其實是很溫柔的團隊,就算是踢皮球也是用很溫柔的方式把皮球緩緩地親手交付到對方手中。

        不過因為沒有 log,自己寫的服務連別人到底有沒有打進來都搞不清楚,只能瞎猜原因和踢皮球,這樣的對話當時屢見不鮮,嚴重拖累團隊開發的效率。

        我當時立即決定導入 log4net,強迫所有人都要在程式被外部呼叫的進入點包覆完整的 log 機制

Dear all:
目前系統已經開發至一個準備進入測試的階段, 請開始將您手上的系統加入 error log 的機制.
尤其以 MVC website 部分.
凡是會被外界呼叫的進入點, 例如所有的 Controller 的 Action 都要加上 log
範例如下:
using System.Reflection; 
using log4net;
public class XXXController {
    private static readonly ILog _logger = LogManager.GetLogger(typeof(XXXController));
    public ActionResult XXXAction(string[] args) {
        _logger.DebugFormat("Invoking {0}...", MethodInfo.GetCurrentMethod().Name);
        try {
            // Do something 整個邏輯都包住在裡面
        } catch (Exception ex) {
            _logger.Error(ex.Message, ex);
 
            throw; // 如果這個錯誤該讓外界知道
        }
 
        return View(); 
     }
}

        記得當時我開會上說過:
        「在正式營運的環境是沒機會讓你下中斷點,像現在這樣一步步找問題的!做 Production support 最怕兩件事:
        第一是手上在查問題的原始碼和 production 正在跑的不是同一個版本
        第二就是:『沒有 log』

        當時強制引入 log4net 之後,剛開始大家還是沒有看 log 的習慣,也搞不清楚 log 會寫到哪裡,從哪邊看、怎麼看,像是還不適應拔掉奶嘴(IDE 中斷點)的開發寶寶。幾個月後,如今只看 log 而完全不依賴 IDE 找問題的已經大有人在,在自己的開發機器以外的環境,也習慣看 log 和留下正確有意義的 log。這一步的效益雖然發酵地比較晚,但也在後期給團隊開發效率很大的幫助

        另外為了讓程式錯誤易於被察覺,盡可能透過 coding 技巧,將原本會在執行階段才出錯的錯誤,盡可能引導到編譯階段就發生。例如盡可能把大量的數值參數 int 的使用改為 enum,大量使用 generic 泛型編程來取代事後的轉型處理,還有...。

        當然執意使用強型別將 runtime binding 引導至 compilation time,會大幅降低程式執行時期的彈性,使得許多異動都必須演變成“改程式”來達到,而不是“改設定 / 改插件”,但是為了對付眼前層出不窮的失誤,這是必要之惡

        每當有人跳腳對別人說:「你昨天改了那個 DLL 內 XXX 類別,害我的程式今天就 build 不過。

        我就會很寬心地安撫他:「只要編譯時期能抓到的問題,哪怕是因此要從內改到外,都是小問題。我最怕的,是程式執行後遇到才爆發的 runtime error

        再來就是 CI 的建立。由了前面強型別的界面檢驗,讓每日午夜的 automation build 自動幫我們找到所有整合的 broken point,並作 automation deploy,讓團隊隨時都可以很輕易地進行 System Integration Test,避免『忘了拉別人最新的 code 下來跑,別人測沒問題,自己測試就是不行』這種偷走時間的鬼打牆。

        最後,由於我們的 n-tier 架構,系統溝通之間需要透過 socket 進行,這部分我們雖然採用了強大的 ZeroMQ 作為溝通管道,但是卻未經包裝成完善的 infrastructure,初期只用很原始的方式裸用,團隊必須自行處理 byte frame,缺乏組織過的資料封裝方式和完整的異常回報機制,導致開發雙方有大量口約定的協定必須處理,而且對於發生在彼岸的系統錯誤毫無所覺,所以後續我就全心投入 infrastructure 的建立,提供給團隊友善完整的開發基礎

        至於 #2 所提到,處理技能與負責項目的差距造成的困擾,就留到在下次再聊。 

沒有留言:

張貼留言