Wednesday, August 31, 2016

vim 的插件 vim-go 的 tutorial

vim-go 的 tutorial  裡頭有很多有趣、有用的技巧。取出一些我覺得比較好用的。

Import 的技巧
:GoImportAs ff fmt
:GoImport strings

Edit 的技巧
if 表示函數的內部 inside a function
dif = delete inside a function
yif = yank   inside a function
vif = visual mode inside a function

af 表示整個函數 -> a function
daf = delete a function
yaf = yank a function
vaf = visual mode a function


Trace 程式碼的技巧
對 channel 的理解
 :GoChannelPeers  選擇某個channel,顯示它的send/receive/def

對 variable 的理解
 :GoReferrers     選擇某個變數,顯示所有被使用的地方。

對於 type 的理解
 :GoImplements    選擇某個類別,顯示它實作的介面。(一個有搭配函數的類別就很可能是有實作介面的類別)
 :GoDescribe      選擇某個類別,顯示所有使用它的函數。(用使用範例來"描述"一個類別)

對於 error 的理解
 :GoWhicherrs     選擇某個錯誤,顯示它可能包含的錯誤資訊。

對 func 的理解
 :GoCallees       選擇某個區域變數(該變數的型態是函數),顯示它可以對應到的函數。
 :GoCallstack     選擇某個函數顯示它被呼叫時的call stack
 :GoCallers       選擇某個函數的定義,顯示它被呼叫的位置

Refactor 的技巧
將區段的程式碼抽取出來,變成函數
 :GoFreevars      區塊選擇一段程式碼之後,顯示這段程式碼裡的輸入變數(input variables)
 :GoRename        修改一個變數的命名,自動修改多個位置。


註:在新版的 vim-go ,已經用 guru 取代了 oracle 。所以如果沒有看到 oracle ,不用再去找了。

log 與 error handling --- 讓錯誤看得出來是錯誤

公司的同事 mike 前幾天跟我講了幾句話,讓我受用無窮:

  1. 很多程式設計師寫出了幽靈程式碼,總是抓不出錯。這是因為在撰寫程式時,就不夠嚴謹。
  2. 要如何寫出嚴謹的程式碼呢? 遇到異常(exception)或是錯誤(error),有辦法處理,就要設法處理。沒有辦法處理時,也要寫在規格(也就是註解),至少讓後來維護的人知道這邊有個「坑」


於是我歸納了一些要寫出嚴謹的 golang 程式,可以注意的重點:

  1. 不要亂用 log level ,要小心地使用 log level 。比方說,在 golang 裡, log.Fatal 就會呼叫 exit(1) ,這個就絕對不可以亂用。有時候,網路上找到的 code snippet 就用了 log.Fatal。如果直接不加思索的照抄。那程式就會異常終止了。
  2. log level 和 log message 要好好地搭配使用: 比方說,如果引入了 logrus 這套 log level 的函式庫。對於不會影響程式正常運作的 log level 就有 info, warn, error 三種。既然已經用了 warn 的 log level ,其實 log message 就該避開使用 error  這個關鍵字。
  3. 儘量在程式裡處理所有的函數傳回的 error 。要做到這件事,有一個 golang tool 可以使用: errcheck 。這個工具可以檢查出,所有傳回錯誤,錯誤卻沒有被處理的函數。如果有用 vim-go 的話,可以下指令 :GoErrCheck  

Thursday, August 18, 2016

依賴注入 - dependency injection

寫單元測試時,常常會遇到一問題,原始的程式碼本身的結構,難以加入單元測試。

最近我遇到了一個問題,我寫的程式會呼叫 git clone ,而 git clone 在網路環境不佳時,甚至會執行超過24小時,將程式的 go routine 整個卡住。所以解法就是需要將 golang 的外部指令呼叫,改成有 timeout 的。

要加上這個 timeout 並不難,困難點是,要如何測試這個功能?因為其實要模擬出 git clone 長時間執行的環境,並不是很簡單的事。

解法是這樣子:
加上這個功能時,必須將這個「為指令加上 timeout 功能」實作成一個獨立的函數,使它的功能獨立,並不「依賴」於 git clone 。換言之,「 git clone 」對應的指令,會成為這個函數的輸入變數(input argument),由外部「注入」。於是這個功能就可以獨立地來寫單元測試來測。測試它的時候,就可以用「 sleep 500 」對應的指令,來做為它的輸入變數(input argument)

原始程式碼的修改
新增的程式碼和單元測試

除錯的利器 - tcpdump

開發程式,我最常用來除錯的技巧主要就是幾種:

  1. 寫單元測試 
  2. 查 log 檔

然而,上述兩種技巧都有一些限制。

單元測試的話,如果原本的程式寫的很差,我自己新寫的部分有時也很難測,要先重構舊的程式才能寫單元測試。查 log 檔的話,如果一些 bug 是在 production 環境才出現,也很難取得完整的 log ,因為在 production 環境, loglevel 通常是設定成 info 而已。

也因此,在 production 環境之下,用 tcpdump 有時可以取得比 log 還更完整的資訊。

Wednesday, August 17, 2016

例外處理




寫程式有一段時間了,有一些習慣問題,卻一直沒有深刻的認識。直到真的寫出 bug 以後,才會理解。

上方的例子,就是一個不好的例子。 GetCurrPluginVersion() 的傳回值,其實應該要有兩個資料型態,一個應該要用 string ,另一個應該要用 error 。而上頭的例子,只用一個 string 就裝了兩個不同資料型態的資料。這樣子讓使用 GetCurrPluginVersion() 的使用者,很容易潛意識地主觀認為,這個函數是一定會成功的,不會有 error 。於是,真的有 error 時,就一直穿透,直到很後期才發現。