2014 年度小結(Node.js 與 單元測試)

2014 年的第一個項目是一個有關比特幣交易的系統,規模並不大。前端

這是我除了 Hello World 以外的第一個 Node.js 項目,也是我第一次寫 CoffeeScript. 簡單地看了一遍「CoffeeScript 小書」,又隨便搜了搜對 CoffeeScript 的評價,你們說得最多的是「CoffeeScript 嘛,哪有什麼語法,想怎麼寫就怎麼寫就好了」,提及來倒還真是如此,差很少只花了兩個小時就學會了 CoffeeScript, 並且以後幾乎沒在這上面遇到什麼坑。git

提及 CoffeeScript, 彷佛在 2012 年中旬,whtsky 牛就開始學習了,而且在博客上發了兩篇文章,雖然 CoffeeScript 很簡單,但這也可見 whtsky 老是站在潮流浪尖。github

雖然以前一直對比特幣非常關注,但對比特幣的交易規則其實仍是隻知其一;不知其二,其實比特幣交易平臺的工做模型和股票是類似的——提及來在此以前我也不知道股票是如何工做的。由於對這個項目業務邏輯的知識瞭解不夠充分,因此這個項目我差很少是隻寫了個開頭,就由別人徹底接手了。算法


第二個項目是也是有關比特幣的,一個 Web 系統,也是 Node.js.數據庫

由於這時搬到了蘇州,有不少的時間和小明交流,因此總算是對股票的工做方式,以及類似的比特幣交易平臺的工做方式,有了一個比較深刻的瞭解。編程

當一個 Web 系統的規模稍大一些,好比有幾十個 API, 以及幾個比較大塊的業務邏輯;再對這個系統進行重構就很是困難了,但碰巧我又老是在重構——這有主觀緣由也有客觀緣由,客觀緣由就是畢竟我初學 Node.js, 還在探索所謂的「最佳實踐」,免不了要走一些彎路。因而在這個系統主體已經完工的時候,我開始着手引入自動測試。後端

提及自動測試,在此以前,我無數次地聽到這個詞。我知道這是一種先進的開發技術,但不知道究竟如何應用,也不知道用好了以後會有怎樣的效果。在 LightPHP 上我作過一些探索,但感受大部分模塊由於有複雜的依賴關係,不容易編寫單元測試;而爲簡單的模塊編寫單元測試又沒有帶來實際的正面影響。理想的狀況下,應當將程序劃分爲模塊,而後對每一個模塊或者說單元進行測試。但實際上大多數狀況下,我仍是沒能完全地剝離掉模塊之間的依賴關係,因此更多的時候我在使用「自動測試」這個詞,而不是「單元測試」。設計模式

因此,仍是老問題,剝離模塊之間的依賴關係令我很是頭痛。雖然在這個項目上,後來仍是作到了爲絕大部分功能編寫測試,可是用了不少不夠優雅、不夠健壯的實現方案,致使測試很是容易出問題,並且出了問題以後每每要進行大幅的修改才能解決,且測試代碼之間也有錯綜複雜的依賴關係。數組


而後依然是一個有關比特幣的交易系統,依然 Node.js.瀏覽器

這是一個自動交易系統,整個系統每時每刻都在進行大量的計算,基本上佔用了服務器的幾乎全部資源。這個項目的代碼量並不大,可是邏輯密集,計算量大,對性能有必定要求 —— 事實上這差很少是我作過的惟一對性能敏感的項目,由於大多數項目的用戶量實在太少了,根本談不上優化性能。

爲了能讓算法邏輯更清晰,也爲了找出改善性能的關鍵點,這個項目前先後後重構了不少次。在最後一次重構後,彷佛效果還不如以前,不過由於比特幣的價格一路在跌,這個項目被暫時擱置了,最後的一個版本開始在無人看管的狀態下繼續吃 CPU. 可是這不影響比特幣繼續一路下跌,終於在最近,這個項目被完全關掉了——提及來比特幣差很少是 2014 年度最差的投資品了。

這個項目使用了 MongoDB 做爲數據庫,程序差很少是在虐待數據庫 —— 我沒有花太多時間來對數據庫進行優化,連索引也是隨便想固然建的,而後程序每秒鐘都在寫入和讀取數據,以及一些沒有被很好地優化的聚合查詢。不過 MongoDB 在這半年間徹底沒有出過問題,讓我對 MongoDB 好感倍增;提及來 RP 主機上的 MySQL 曾經出過無故丟數據的狀況,再加上畢竟 MySQL 的做者已經不建議咱們使用 MySQL 了,頓時以爲 MySQL 的前途一片灰暗。


以後開始咱們(指 番茄土豆團隊, 下同)開始着手重寫以前 PHP 版本的 番茄土豆. 由於以前在設計上考慮不充分,在用戶量增長了以後,出現了一些性能問題,爲了系統性地解決性能和其餘的一些問題,索性不如直接用 Node.js 和 MongoDB 進行重寫,而不是在原來的 PHP 版本上再修修補補。作出這個決定也是由於這時候咱們大概用了半年 Node.js, 相比於 PHP, 咱們以爲基本上只有優點,而沒有發現有什麼不如 PHP 的地方,因而但願將原有的 PHP 項目都改成 Node.js.

此次重寫主要是其餘人完成的,我只是稍微參與了一下,寫了其中幾個小的功能點。這是我第一次使用 Mongoose, 以前我認爲既然 MongoDB 的特色是無模式,那麼何須非要用一個 ORM 從新定義模式呢;後來我又稍稍改變了一點想法,確實 MongoDB 的 API 提供的是一種較爲底層的數據庫操做,仍是須要一些輔助的功能來更好地完成業務邏輯,例如字段的檢查器、在文檔上定義實例方法等。

Mongoose 差很少是 Node.js 社區最主流的 MongoDB ORM, 選擇它是一個沒有懸念的事情。但用了一陣 Mongoose 以後我發現,雖然 Mongoose 設計了一個美麗的圖景,但在細節上坑實在太多了。好比它雖然提供了在文檔間定義引用關係和嵌入關係的方法,但這個功能很是弱;另外一方面由於它用了一些比較 hack 的方式來實現對字段的驗證,這致使又沒有辦法自由地修改從數據庫中取出的文檔,例如本身來實現引用關係,這給向視圖傳遞數據形成了一些困難。

總之,在我使用 Mongoose 的過程當中,老是有一種本身從新造一個輪子的衝動,但我又沒有把握設計得更好,須要很努力地剋制這種衝動 —— 好吧,其實我連給這個輪子的名字都想好了。

在 Node.js 版基本完成後,如何將新版本部署上線成了一個很大的問題,這個問題困擾了咱們幾個月的時間。番茄土豆在 2014 年初也上線了一個重寫的版本,但那次的狀況要簡單得多,一個晚上就基本搞定了。而此次由於除了 Web 版以外還有幾個平臺的客戶端,須要保證這些客戶端所使用的 API 依然可用,這就要求新版本的上線過程必須是持續的、平滑的,新舊版本須要共存一段時間,目前咱們還在逐步完成有關新版本上線的工做。


咱們給番茄土豆設計了一個「週報」的功能,會在每一個週末向用戶的郵箱發送一週的工做報告,這個工做由我負責,但這差很少是我 2014 年度完成得最很差的一個項目,前先後後花費了不少時間,但仍是錯誤百出。

提及來也簡單,無非是每週運行一次:從數據庫查到數據、生成統計數據、渲染郵件、發送郵件,但每一個步驟都出了不少問題。首先要保證這個任務每週運行一次就花了一些功夫,由於番茄土豆的用戶來自不一樣的時區,因此須要在當地時間每週日早上來發送這封郵件,這就將「每週一次」變成了「每週 24 次,每次完成一部分」。而郵件一旦發出又不能撤回,試錯有很大的代價 —— 在這個項目上我出現了太屢次嚴重的失誤。

由於這是咱們第一個與郵件相關的工做,所以以後大部分與郵件相關的工做也歸我了。我圍繞着郵件寫了一些通常化的庫,好比 pomo-mailerpomo-sender. 前者用於渲染涉及多語言的郵件,後者是一個考慮了時區和定時任務的郵件隊列。

在實現郵件隊列上,我遇到了一些有關 Node.js 異步流程控制的坑。在 PHP 和 Node.js 中,都不須要咱們人工地建立和管理線程,所以以前從 C++ 上學習到的有關多線程編程的知識也快忘光了 — —或者說那些知識其實根本沒實踐過。直到年底看了 JavaScript 異步編程 這本書以後才基本掌握瞭如何在 Node.js 中優雅地控制異步流程。


RootPanel 是貫穿我 2014 全年的一個項目,也貫穿我學習 Node.js 的整個過程。

在以前寫 PHP 的時候,當須要寫前端 JavaScript 時老是很是苦惱,由於 JavaScript 語言的設計並不全然合理,瀏覽器間又有不兼容的拓展,再加上市面上全是些不靠譜的 21 天學通 JavaScript 教程。因此在一開始使用 Node.js 的時候我也對 JavaScript 比較抗拒,加上 CoffeeScript 屏蔽了 JavaScript 語言的一些細節,因此在最開始的一段時間,我對 JavaScript 的瞭解其實不多。例如原型系統、真假值表什麼的都是後來才系統地瞭解。

RootPanel 3 被我定義爲「一個插件化的 PaaS 開發框架」,實現完全地插件化是最重要的一點。2013 年底的時候我已經開始嘗試用 PHP 實現 RP3 了,但很是困難,主要是由於 PHP 畢竟仍是傳統的面向對象架構,正統的方式是經過類的繼承、定義接口來實現插件化,這就致使大量的代碼是在維護這種「模式」而不是專一於業務邏輯。JavaScript 就好像無模式的 MongoDB 同樣,PHP 中的類、對象、數組、函數,在 JavaScript 中都是 object, 能夠自由地添加和讀取屬性,以實如今運行時拓展功能。固然 JavaScript 的靈活性也致使了我做爲一個 Node.js 新手,一開始花了不少時間去探索什麼纔是好的設計模式,浪費了不少時間,尤爲是浪費了不少時間在重構 RootPanel 上。

在 2014 年 8 月,由於 us1 被反覆 DDoS 直到下線。我不得不加快了 RP3 的開發,在以後的三個月裏,快速地發佈了幾個版本,還節外生枝地發佈了一個 GreenShadow. 在以後,尤爲最近兩個月,RP3 的進度明顯慢了下來,主要緣由是我對插件系統的設計依然不理想,還在繼續探索更好的實現方式;目前 RootPanel 的版本號是 0.8, 但願在新的一年裏我能將插件化的實現方式肯定下來,發佈 1.0 版。


在 2014 下半年,我斷斷續續地看完了 SQL 反模式, 這本書中列舉了一些好的和很差的數據庫設計模式。提及來個人 SQL 基礎很是不紮實,基本上也就會個增刪查改,從未系統性地學習過 SQL. 我忽然開始反思,是不是由於我只用到了 MySQL 衆多功能中很小的一部分,因此才以爲關係性較弱、無模式的 MongoDB 更好用呢。因而我開始嘗試系統地學習 SQL, 但彷佛 SQL 原本就不是一個系統的語言,有大量「取決於實現」的細節。因此我其實也只是先補習了一下以前瞭解不多的子查詢、GROUP BY, 以及 JOIN.

這時,咱們但願將番茄土豆中的訂單和支付系統獨立出來,用 Node.js 重寫,我決定在這個項目中使用 MySQL.

這個訂單系統是我第一次在一開始就引入單元測試的項目。由於咱們一直都是先後端獨立開發,因此在此以前,我都是經過 Postman 在測試個人程序。所以常常發生這樣的狀況:在開發完成一個功能後,測試沒有問題,但以後由於改動其餘部分而產生了問題,這種狀況每每只有到前端開發用到這個接口的時候纔會發現,由於咱們團隊都是遠程工做,常常發生這樣的事情會對工做效率有一些負面的影響。而在重構以後這個問題更加嚴重,每每要從新測試全部接口來確認重構沒有對其形成影響。

而若是從一開始就引入單元測試,開發就變得容易得多,開發的大部分時間就是在看單元測試的結果而已,一旦單元測試顯示經過,那麼你就知道程序至少在按照你單元測試中寫清的規則在運行。有人認爲編寫單元測試須要花費額外的時間,進而以爲很不值得,其實否則,開發一個程序終究是須要測試的,單元測試會將本來須要人工測試的步驟自動化,以即可以隨時完整地從新運行全部測試。只要探索出了正確的方法,編寫單元測試並不會比人工測試花費更多的時間,並且單元測試會有一項額外的好處:測試的步驟被存檔了下來,並且會進入源代碼的版本控制中。

固然,爲了可以編寫單元測試,是須要在設計項目結構上花一些功夫的。前輩們在這一點上的經驗總結起來就是三個字母 —— MVC. 以前我一直錯誤地在 Controller 中包含了太多的邏輯,好比在 Controller 中實現大部分的錯誤處理。其實這一般也不會有太大問題,但一旦引入了單元測試,這種架構就暴露出了問題。在 Web API 中每每一個接口包含了一組邏輯,若是在 Controller 中包含這些邏輯就會出現一些重複,這些重複的邏輯沒辦法被抽象成一個函數。更嚴重的是由於在單元測試中須要構造一些特定的環境,若是經過調用 Web API 的方式來實現會很是繁瑣,由於 Web API 每每是被保護在用戶認證、權限認證以後的。因此更正確的方式是儘量在 Model 中實現大部分的邏輯,以便被劃分紅更細粒度的單元,被單元測試直接使用。

Node.js 上主流的 MySQL ORM 應該是 Sequelize, 不過我在這個項目中本着「步子不要邁太大」的原則,並無使用 ORM, 也沒有使用 MySQL 的外鍵和事務,這些功能估計要在新的一年裏去探索了。


咱們團隊的前端項目一直在使用 AngularJS, 若是我還在寫 PHP 的話,那這應該和我關係不大。不過既然已經掌握了 JavaScript, 就不如嘗試一下。因而買了一本 JavaScript Web Applications 學習如何在前端實現 MVC. 這本書簡單地介紹了 Backbone 這個框架,我發現相比於 AngularJS 我更喜歡 Backbone 這種侵入式弱,定製型強的輕量級框架。因而我讀了一遍 Backbone 僅有 2000 餘行的實現,並決定在新的一年裏用 Backbone 來重構 RP3 的前端。


2014 年的最後一個項目也是一個 Node.js 的 Web 系統。

這個項目也是從一開始就引入了單元測試。我發現我以前爲 Web 系統編寫的單元測試存在一個問題,即究竟應該以什麼爲「單元」進行測試。以前一般是以每一個 API 接口爲一個單元,測試這個 API 接口在各類狀況下的工做狀況。但當程序的邏輯複雜起來之後,爲了測試一個 API 接口在某種環境下的工做狀況,須要花費大量的代碼來準備這個環境。因而一個更好的方式就是以「行爲」爲單位,一種行爲包含了一組 API 請求,它們每每須要通用的環境,所以更適合被放在同一個單元中。

https://jysperm.me/technology/1976

相關文章
相關標籤/搜索