關於這個,我可以分享一個我實際上做出來的side project: Rocket(https://github.com/dannypsnl/rocket)
最開始我的想法很簡單,只是想知道後端的那些路由框架是怎麼做的,當時我只會regex所以實作就只是一堆regex,用一個迴圈去跑regexp.MatchString
想當然爾這是個很爛的實作,如果我沒有記錯的話當時同樣功能的路由執行時間是將近gin(https://github.com/gin-gonic/gin)的50倍,
簡單來說就是個垃圾,沒有比較快、沒有比較方便、還有一堆不可用等級的bug相伴(主要是路由衝突很嚴重,順序不同會引發不同的比對結果XD)
當時並沒有真正的深入去研究路由比對究竟是怎麼一回事,所以也就在開發不久後陷入停滯
後來會再度開發起這個東西是因為在前公司的經驗,發現很多簡單的接收JSON發送request到真正的處理者的程式碼非常的重複,感覺大概就是
func(c *gin.Context) {
var user User
c.BindJSON(&User)
// verifying, ...
result := Request(...)
c.JSON(result)
}
所以我當時就把四處重複的驗證放進被填充的那些結構裡,這也就讓我開始思考能不能讓unmarshal本身也是自動觸發的呢?
我可以展示一下自動unmarshal的範例:
type User {
Name string `json:"name"`
Password string `json:"password"`
}
func(u *User) {}
沒錯,就是直接用結構取代框架spec的context type
有了想達成的目標就有了重啟專案的念頭(原本應該會基於gin寫一些lib來達成功能,不過沒多久之後我就離職了,也就沒有立即的需求,所以就變成了改造這個專案了)
第一步是修正那個問題百出的比對程式,所以我跑去查了怎麼做超快的路由比對,發現了radix tree這個東西,
接下來我參考echo(https://github.com/labstack/echo)的實作,修改成了符合我的需求的結構,我只有用`/`分隔節點
,特別處理帶有`:`, `*` prefix的部分(他們分別代表了variant跟wildcard),基於這個版本的測試已經進化到了約略是gin的1.5倍
Example:
/a/:a
/*b
/a/b -> /a/:a
/a/b/c -> /*b
後來我的朋友也加入開發行列,我們在這段時間沒有特別關注效能,而是增加測試覆蓋率以增加功能,而且基本上加上越多需要執行的功能速度就會越慢,
到目前應該已經快變成gin的2倍了XD,另外開發期間 https://astaxie.gitbooks.io/build-web-application-with-golang/content/en/ 這個線上資源對我們有很大的幫助
,因為他踩了很多Golang net/http的雷點XD,讓我們知道實作時需要注意的一些細節,目前下一步計劃是讓各個sub package能夠被當成獨立的lib去使用
我會說專案的成形需要一點時間,你可以看到最開始的時候我就是越級所以才會做不出來,但也是因為有去做才會有進步,與其花費心思思考什麼會被打槍,
不如先動手做出東西試試。一年多前休學工作以來,我被打槍的次數可多了,英文不好、作品太廢、演算法不夠強
模仿一個東西,出發點應該是弄清楚自己想要什麼東西,就算看了程式碼,那也不會是有用的參考,因為別人的程式庫必然會有他們做出的抉擇,
而這些抉擇會捨棄一些功能以達到另一些特性,而這些如果不能過濾這些取捨,找出你想要的部分,那麼有原始碼也沒有用,難不成要直接複製貼上?
而找出自己需要的部分是需要理解自己的需求的,而這是只有你才能做到的事情。
累積經驗是很重要的,雖然我有個專案一直被吐嘈這到底能幹嘛啦,但事實上rocket的unmarshal的部分能被抽取出來就是因為我該專案大量使用reflection,
讓我知道reflection完全可以做到我想要做的功能,否則我根本想不到可以那麼做
而分割問題也是非常關鍵的技巧,一般來說我會寫一些pseudo code來驗證一個程式介面可行,再根據一個可行的介面編寫簡單的、不完整的實作,
這個實作會伴隨著一個特例作為測試,接下來我會把程式修改成通用的版本,移除原先的假設,為絕大多數的情境運作,
這時候第一個特例就能檢查這個正確的版本是不是真的那麼正確
最後我想相較於作品到底有多厲害,我想真正重要的是當人們問你:「為什麼你要做出這個東西?」的時候,能夠言之有物的說出你到底學到了什麼