Thursday, October 12, 2017

在 Golang 裡使用類似 Clojure 的 with-redefs 技巧

在寫 Golang 的 unit test 的時候,我覺得很不方便的一點就是沒有辦法像 clojure 一樣,利用 with-redefs 將 symbol 重新定義,讓本來有外部依賴的函數可以很快地被測試。但是,公司的資深工程師 Mike 教了我一招。

基本的概念就是這樣子:
1. 在 Golang 裡,可以被重新定義的東西就是 global variable 。
2. 如果要測的某個函數,它本身沒有寫好依賴注入之類的東西,又恰好呼叫了一個有 complex dependency 的函數。就把這個有 complex dependency 的函數用 global variable 來加上一層間接性。

Tuesday, October 3, 2017

curl, netcat, API testing

最近常常在測試一些同事寫的 API ,整理了一下自己覺得好用的小技巧:
(1) mock server
有時候想要看一下,自己生成的 curl 到底是產生怎麼樣的 http request ,產生怎樣的 body ,就需要先架一個簡單的 mock server 。

#如果需要看 http POST 會需要用 netcat
sudo nc -l 8000     #開啟一個 8000 port 的 tcp listener
# Python v2.7
python -m SimpleHTTPServer
# Python 3
python -m http.server 8000

(2) 快速生成 curl 指令
如果是對於既有的 web application 的話,可以利用 chrome 來取得。
Ctrl + Shift + i -> Network -> Copy as cURL 

(3) curl 常用的參數
-s  =>  silent, quiet mode 
-d =>  用來指定要放進 http request 的 body 的內容。
           用了 -d 選項之後,預設的 Content-Type 是 application/x-www-form-urlencoded
           如果要使用客制化的選項的話,要再加上 -H 參數。
-F =>  用了 -F 選項之後,預設的 Content-Type 是 multipart/form-data
           這個選項通常是用於「大量的資料」或是 binary data
-H => 用來指定 http  header 。常見的用法有:
           指定客戶端送出的資料格式                          -H 'Content-Type: application/json'
           告訴伺服器「客戶端想接受的資料格式」  -H 'Accept: application/json'

(4) 從 server side 的 source code 來理解
常見的 server side framework 可以處理的 http content type 通常是下列四種:
  • application/json
  • application/xml
  • application/x-www-form-urlencoded
  • multipart/form-data
(5) 由 server side 的 source code 來猜測, client side 的 curl 該如何產生?
若 server side 是用類似 parseForm, formValue 之類的函數來處理。curl 會是如下的形式:

curl -d 'title=標題&content=測試'  http://localhost:8000/

Tuesday, September 26, 2017

awk style 的 shell script

有一段時間我很喜歡寫一種特定形式的 shell script ,我自己稱之為 awk style shell script 。它通常是長成這個樣子:

這種風格的程式碼,我之前寫得很爽,因為覺得寫起來很快,字數、行數都少。後來回頭看的時候,雖然覺得有些優點、也有些缺點,卻也不知其所以然,最近終於想出原因了。

這種風格它有幾項特色:
1.  主要使用 awk
2.  awk 指令用 unix pipe 串接

仔細分析這種風格使用的程式語言的概念:
1. 每一行的 awk 指令,可以視為是函數呼叫,而且是「高階函數」(higher order function)  。是高階函數是因為 awk 指令都隱含了一個迴圈、每一行就是對應一次的迴圈操作。
2. 每一行的 awk 指令裡的部分運算內容,可以視為是傳遞進入高階函數裡的運算,換言之,它們可以視為是匿名函數
3. unix pipe 之間,是通過有特定格式「字串流」 (string stream) 來傳遞資料。 其『特定格式』是表格的形式,以斷行字元 (\n) 來分隔 row,以空白字元 (\s) 來分隔 column

有了上述的分析,要講出優點就容易多了:
1. 因為使用了高階函數的概念,要寫的 boilerplate code 就變少了,寫起來自然快。
2. 因為使用了匿名函數,一方面減少了 boilerplate code ,還省去了函數命名的心理負擔。
3. 使用有特定格式的字串流來傳遞資料,因為特定格式通常可以描述輸出所需要的完整資料,程式就可以變成「單向的資料流」。程式要復用的時候,通常就是截取資料流之中,可以復用的部分來復用。
此外,這種使用字串流的風格與 C style 的程式設計最大的差異點在於: 「C style 的程式設計風格是只要是不會被函數修改的資料,都盡量不要放進函數的輸入、輸出。」這是因為執行效能考量,減少不必要的拷貝。付出的代價則是,往往為了輸入輸出引數的一點點變化,要寫許多重覆的程式、或是套接 (adapter) 的程式碼,因而減少了開發效率。

4. Unix pipe 雖然不是惰性求值 (lazy evaluation) ,而是 buffered queue ,但是使用 unix pipe 寫出的 shell script 卻是惰性求值的風格:『不需要管資料的長度』。

另一方面,也有缺點,但是要克服缺點,就需要使用 python/clojure 之類的語言才容易克服了。
1. 每一行 (row) 的字串資料對應的 awk 指令,其實就是 functional programming 裡的 map/filter 操作。改成用 map/filter/reduce 來寫,表現力不會打折扣,可讀性還會提高。
2. 匿名函數如果要復用時,還是給它取個名稱比較好。
3. 使用特定格式的字串流來傳遞資料,不如使用 list 或是 hash map 來傳遞資料。後者的表現能力更強、而且還可以夾帶型別資訊

Thursday, September 21, 2017

快速測試 clojure library

在開發 clojure 的時候,有時候會想要快速地測試一個新的 library ,但是,只是快速地測試,卻得要為這個 library 做 jar 的下載、並且在 project.clj 裡設置路徑,也太麻煩了。有沒有比較簡單的方式呢? 可以用一個 leiningen 的 plugin 來辦到這件事。

<安裝>
修改 (~/.lein/profiles.clj) :
{:user {:plugins [[lein-try "0.4.1"]]}}
<實測>  測試 cheshire 這個 json library
vagrant@owl-docker:~/workspace/src/github.com/clo/test$ lein try cheshire
nREPL server started on port 53192 on host 127.0.0.1 - nrepl://127.0.0.1:53192
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_144-b01
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (require '[cheshire.core :as json])
nil
user=> (json/generate-string {:foo "bar" :baz 5})
"{\"foo\":\"bar\",\"baz\":5}"
user=>

Datomic 學習/使用心得

之前在研究 object-relation impedance mismatch 的時候,發現了 Datomic 這個資料庫,據說這個資料庫可以有效地迴避這個 object-relation impedance mismatch ,於是我找到機會後就來用看看。

學習的過程大概是這樣子:
1.  下載 datomic-free 的 docker 來用,並且寫 clojure 程式將資料塞入資料庫。
2.  註冊 datomic  的網站,取得 datomic 的 web console
3.  上一個 learn datalog today 的網站練習 Datomic 的 Query 怎麼寫。

踩過的坑:
(1) group-by
本來一度以為,Datomic 沒有支援 group-by 的語法。後來仔細查才發現,原來是 implicit 。而且要搭配 :with 語句來控制 group-by 的行為。
範例:
    [:find ?i (avg ?r)
     :with ?u
     :where  [?e :movie/rating ?r]
                  [?e :movie/id ?i]
                  [?e :user/id ?u]]
相當於
select movie_id, avg(rating * 1.0) from RATINGS group by movie_id;

實際測試中,我用電影資料庫,放了 100000 筆的電影評分資料 (user id, movie id, rating)。結果用 Datomic query 做 group-by average 花費了 3000 milliseconds。但是 H2 database 卻只用了 500 milliseconds

( 註:做上述的實驗時,我對 Datomic 頗不熟,也因此沒有對 Datomic 設定 index 。然而, H2 database 卻有設定 index ,所以其實這個比較並不公平。由於電影資料的特性,其實可以考慮將 :/user/id:movie/id 這兩個 schema 都設定為 :db/unique )

(2) transact-async
本來我照範例是用 transact ,結果當有大量資料一開始要匯入時,資料庫就發生 timeout exception 了。查了論壇後,才發現因為 transact 函數內建的 timeout 是十秒。所以論壇上建議,遇到大量匯入或是資料庫在高負載時,要用 transact-async 。

If a system is under heavy load (e.g. an import job) then the default transaction timeout can be too short, and you should use transact-async so you can apply your own timeout logic.

Wednesday, September 13, 2017

clojure 實務技巧


  1. Application 讀取一般檔案
    使用 clojure.java.io/resource ,可以從 classpath 來讀取檔案。檔案放在 /resources 資料夾下即可。
  2. 「尋找 collections 符合某個條件的 item ,找到後傳回第一個」(find-first)
    stackoverflow 列舉了數種寫法 
    (a) filter/ first          -> 最容易理解
    (b) some
    (c) reduce/ reduced  -> 效能最好
  3. lein repl 啟動時發生 timeout
    解決方案: 在 project.clj 裡修改 repl 的 timeout
    :repl-options {
                 ;; If nREPL takes too long to load it may timeout,
                 ;; increase this to wait longer before timing out.
                 ;; Defaults to 30000 (30 seconds)
                 :timeout 120000
                 }
  4. 「讀取 linux 的 /proc 系統檔」
    需要使用一些特別的技巧,因為 /proc 下的檔案和一般的檔案不同,要先做一些轉換。
    (slurp (java.io.FileReader. "/proc/cpuinfo"))
  5. 將字串轉換成整數值
    儘量不要輕易地使用 read-string 這個函數。用比較單純的  (Integer/parseInt "123")
  6. here document
    clojure 沒有 here document 的語法。如果要使用的字串裡頭包含了太多需要逸脫 (escape) 的字元,官方建議的作法是把字串放在 /resources 下的檔案裡,用讀檔的方式取得。
  7. clojure 數值型態 suffix 的意義
    (type 1N)  => clojure.lang.BigInt
    (type 1)     => java.lang.Long
    (type 1.0)  => java.lang.Double
    (type 1M) => java.math.BigDecimal

Saturday, September 9, 2017

lexical binding v.s. dynamic binding

在 clojure 語言裡,變數可以是 lexical binding 也可以是 dynamic binding 。前者用於大多數的情況,因為這樣子寫,一眼就可以看出函數的依賴。後者多半用於一些 global 變數,而且是底層的函數才有在使用的 global 變數。這些 global 的變數,因為使用了 dynamic binding ,上層的函數就不需要一層又一層地去傳遞「變數的綁定」到最內層去。

範例在這邊:

javascript 的變數是採用 lexical binding 。但是, this 這個變數會隨著程式的運行,而指向不同的物件,this 顯然就是 dynamic binding 的變數。甚至 javascript 也提供了 bind 函數,可以用來明確地將函數與某個特定的物件做綁定。

有一種說法: 「Javascript 也可以算是一種 Lisp 的方言」。算不算是,自然是見仁見智。然而,如果我們從這個 lexical binding 和 dynamic binding 的角度來切入的話,確實是有特別的相似之處:因為都同時支持了兩種綁定方式。