Go standard library net/rpc/jsonrpc 不支援 HTTP

之前寫 JSON-RPC over HTTP 的時候是直接用 github.com/gorilla/rpc,蠻簡 單就寫出來。這次打算用 standard library 去做,不額外引用 gorilla,結果 踢到鐵板。

目前 net/rpc 支援兩種 codec,一種是 Go 內建的 encoding/gob,另一種 是 net/rpc/jsonrpc。gob 可以透過 HTTP 包起來使用,也可以獨立作為 TCP service。而 jsonrpc 則是「只能獨立使用」。這在 API 裡面並沒有說明得很清 楚。相關討論在: https://github.com/golang/go/issues/2738 ,裡面提到原因是

jsonrpc can be used with HTTP by writing a suitable codec. Given that there is no standard definition of jsonrpc over http, I think that’s the best we can do.

原來 jsonrpc over http 沒有標準定義啊…

reflect.DeepEqual 與 time.Time

Go 很像加強版的 C,例如只要把 type 想像成 function call 的第一個參數, 那 method 就只是自動放參數而已。比如 POSIX C 裡頭的 time:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

NAME
time - get time in seconds

SYNOPSIS
#include <time.h>

time_t time(time_t *t);

DESCRIPTION
time() returns the time as the number of seconds since the
Epoch, 1970-01-01 00:00:00 +0000 (UTC).

If t is non-NULL, the return value is also stored in the memory
pointed to by t.

對照 Go 的 time.Unix():

1
2
3
4
func (t Time) Unix() int64

Unix returns t as a Unix time, the number of seconds elapsed since
January 1, 1970 UTC.

這樣就很明顯了。

會講到這個是因為這陣子碰到一個奇怪的 bug,相同的情況在 native 環境下 reflect.DeepEqual 是相等,而 Docker 環境下 DeepEqual 不相等。印 log 出來發現不相等原因是 struct 裡面 time.Time 這個 field 比出來不相等, 但用 %v 印出來卻是一樣的。參考 DeepEqual 的說明,推測應該是針對 Time 裡面的每個 field 用 == 去比對,所以就改用 %#v 印,抓到是 loc *Location 這欄位不一致,原因是 Docker 預設 timezone UTC,而我本機環境 是 CST。參考原始碼,time.Time 定義如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Time struct {
// sec gives the number of seconds elapsed since
// January 1, year 1 00:00:00 UTC.
sec int64

// nsec specifies a non-negative nanosecond
// offset within the second named by Seconds.
// It must be in the range [0, 999999999].
nsec int32

// loc specifies the Location that should be used to
// determine the minute, hour, month, day, and year
// that correspond to this Time.
// Only the zero Time has a nil Location.
// In that case it is interpreted to mean UTC.
loc *Location
}

sec, nsec 這兩個欄位放的是 Epoch 絕對時間,而用 loc 放時區。所以 同樣的「時間點」,不同的「時區」時,會造成 struct 內容不一致, DeepEqual 就認為不相等了。追到這裡覺得似乎很合理,就把兩邊的時間都轉換 成 func (Time) Local 再來比對,結果卻仍然不相等,而且 %#v 印出來 「每個欄位」的值,包括 *Location 的 pointer 指向位址都一樣,這樣還是不 相等,就很傻眼了。

要再知道原因當然有辦法,讀 DeepEqual 的原始碼就可以,但已經花了不少時間, 而且這方向感覺解法不漂亮。因為 reflect 相關函數的速度都會慢,已經看這個 DeepEqual 不爽很久了,就直接另外用 type switches 寫了針對不同型別的比對 方式,碰到 time.Time 就直接呼叫 func (Time) Equal,把 DeepEqual 當 成最後手段來用。

然而這個疑惑還是沒完全解開,最近時間不夠用,只好先丟著以後再追。

Netflix says Geography, Age, and Gender are “Garbage” for Predicting Taste

FORTUNE 原文連結

一如往常的這背後有很多可以談,但所知不足,也恐怕沒有足夠空閒好好整理出 來,只能簡短打一些。首先這篇點出個人資訊的差異性遠遠不如「好惡」的差異, Netflix 以使用者喜好去 grouping 時,地理位置、年齡、性別等等幾乎完全不 列入考慮。在全世界都是同一個演算法。比如文中例子,90% 動漫流量來自日本 之外,國籍影響遠不如宅度來得大。

以這個類比到廣告產業,有趣的因素就多了。首先影片與文學、繪畫等等有類似 性質,往往可以跨越語言、地理限制等,但廣告常常是與地理高度相關的。比如 新開一家餐廳,若不是高度針對某特殊族群,例如高價位米其林美食,或是主打 韓劇歐巴主角等,往往較為重視餐廳周遭的客人,而非對全市或全縣打廣告,更 不用說全台灣。但若是大品牌廣告,例如某車廠新車上市,就會是全台。再例如 新的化妝品,針對女性就遠大於針對男性。之前忘記從哪裡看到的資料,也說若 發傳單這行為能達到某種針對性,轉化率可以有很大的差異。

把這兩個觀點結合起來會是什麼呢?個人認為這在暗示目前網路廣告準確度還有 非常大空間,而有趣的是,儘管這是個事實,但從廣告主開始,卻似乎還無法做 出很好的應對。這是指台灣情況,再遠我就無知了。廣告主目前的嘗試,多半是 採取風險轉嫁,將 CPV (cost per view), CPC (cost per click) 一步步推到 CPI (cost per installation),或是更廣義的 CPA (cost per action),簡言之 就是「我賺到錢才付你錢」。然後中間又穿插了 agent 下單到各種平台,但以實 際接觸經驗,也多半是各種經驗上、實務上理由來決定怎麼配,中間並沒牽涉到 什麼神奇的 DM/ML 技術。如此無效率的情況下必然中間有市場,往這方向談下去, 立刻就會碰到 Appier 這類公司。

數據要多大才有用?這中間牽涉到媒體、廣告平台、agent、廣告主、以及一大堆 沒寫出的各種角色競合,有興趣可以看看 LUMA Partners 廣受引用的 這些圖。似 乎掌握相當多有效資料的是 Facebook 跟 Google,但在我看到的、弱弱的場景中, 這兩間往往只被當成下單的標的之一,是 agent 在決定這邊下多少、那邊下 多少、怎麼下。在我沒有實際看到的地方,聽說有很多大咖在互相接近,消滅中 間人,直接以各自掌握的資源對接,並藉以壓迫沒跟上時代的人。

這一片亂局中,目前所處的公司正站在哪裡,該站在哪裡,要怎樣才能走過去, 這些我都還不是看得很清楚。

Go 1.5 Vendor Experiment with Git Submodule

一開始當然要先看這篇 Go 1.5 Vendor Experiment了解一下究竟 怎麼做的,簡言之就是把 /vendor 這目錄當成類似 /node_modules 這樣的 概念去用,從目錄最尾端開始往上,有就優先用,都沒有才會用到 GOPATH 裡 面的。

由於最終打算用 docker 包起來,所以想用 git submodule 來管 vendor 底下的東西,這樣 docker build 的過程中可以不需要裝 godep 這樣的 package manager,也不用把關聯的程式碼放入 vcs 裡。但在把專案換過去的時 候,覺得最麻煩的是沒有適合的自動化工具,godep save 是在 GOPATH 的想 法下運作的,若把 GO15VENDOREXPERIMENT=1 打開,會把目前用到的複製到 vendor 底下,然後不包括 vcs 目錄例如 .git,這個在轉換到 submodule 的時候會有問題。研究出來比較簡單的方式是:

  1. glide,或只要類似功能的都 可以。

  2. glide init,如果之前有用 godep 之類其他的,它會自動嘗試匯入,不 然會自己找關聯。跑完會產生 glide.yaml。注意,這邊只會找第一層,也 就是你的專案裡面直接 import 的關聯。glide.yaml是可以自己編的,要 固定版本等等的都是在這邊指定。

  3. glide install,會遍查各層關聯,把所有用到的全部抓下來放到 vendor,並把版本也一併列出放入 glide.lock。這檔案是自動產生,自 己編了下一次跑也會被蓋掉。

  4. 接下來用這支 python script 自動產生命令,餵給 shell 執行。python glide-gitsubmodule.py | sh

Method as Argument in Javascript

前些時候談過 在 Go 中使用 method 作為 argument ,它的觀念、適用場合有點像 partial application ,但當然形式完全不同。若把使用語言換為 JavaScript,要傳 function 通常都 是拿來當 callback,常常是 anonymous。如果用到超過一次,通常就會拉出來取 個名字。如果會重複用但參數略有不同,那可以寫個角色像是 factory 的 function 用來產生「要被當成 callback 的 function」。不過並非所有人都像 我一樣那麼不愛用 JavaScript 的(半殘)物件導向,自然免不了會有「想拿 method 當成 callback」的情況。以下程式碼雖然直覺,但就會掉入 this 陷 阱裡:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'use strict';

function Bot(name) {
this.name = name;
}

Bot.prototype.selfintro = function () {
return "I'm " + this.name;
};

function roll(intro) {
console.log(intro());
}

var no5 = new Bot('Number 5');
roll(no5.selfintro);

原因是執行到 roll 裡面時,this 並未被定義,所以呼叫傳入的參數 no5.selfintro 時,裡面 this 也一樣沒有定義。修正方法是用 bind 指 定 this,把最後一行換成:

1
roll(no5.selfintro.bind(no5));

語法很簡單,但在 JavaScript 程式中盡量把一切都化為資料與 function 個人 覺得是蠻好的設計方式。

高科技人才難尋?

這話題也出來一陣子了,但自身看法似乎比較怪異,沒看到類似的,遂姑且寫下 留個紀錄。由於經驗限制,本文所指人才,範圍侷限在「軟體工程師」。

企業說高科技人才難尋,而觀察周遭同行,抱怨多半集中在老闆沒用、薪水太低、 長官太蠢、工時過長等等,似乎少有人抱怨失業,所以大概可判斷此事為真。從 供給與需求來看,人才難找、就業充分,表示需求高而供給低。

先從需求高談起。需求高,企業搶人,自然有動力盡量提高薪水及福利。常看到 工程師抱怨老闆小氣,但若是提高薪資福利能提高獲利,老闆就會去做。薪水停 滯,背後原因並不一定是簡單的「小氣」,而可能是企業獲利能力沒那麼好,提 高薪資無法反映在獲利上,賺不到錢,自然也付不出錢。

而從供給面談,個人覺得最受忽視的一點是:人才數量,本來就是有限的。 台灣新生的人力有限,條件好的,除資訊外,也有其他學門可選擇。如果條件沒 變好,人才選擇學資訊的比例就不會增加。增設相關科系,只會產出更多不適任 者,不會憑空變出人才來。而若讓排名好的大學增加招生人數,教學品質必定降 低,還會壓擠到原本分配已經極度不均的教學資源。

只看資訊人才的話,與為台灣企業工作相比,外商(包括中國)以及新創公司也 吃掉了不少供給。這都代表價值選擇上,後兩者能夠提供文化、環境、薪資、福 利等綜合起來相對有競爭力的選項。

談完現象,來談解法;一樣從供給與需求入手。

搶不到人,但又無法增加獲利,一個合理的解決方案就是「減少需求」,也就是 減少所需的工程師數量。台灣工程師普遍程度不錯,但賣肝的多,抱怨公司無效 率的也多,與其找更多人,不如設法改善效率。這可由幾個不同面向著手:

  1. 中高階主管的世代交替很重要,因台灣是硬體起家,研發主管懂軟體開發的相 對少,需盡快汰換。

  2. 制度上需加強中階主管的實作責任,不能只是發號施令,要把實作設定為工作 一部分。只有真正參與,才能正確辨認出提昇效率的方法,以及避免錯誤決策。

  3. 與前項搭配,制度上增進開會效率、盡可能資訊透明、組織扁平,以上種種都 是為了減少主管的「非研發庶務」。

  4. 與 2, 3 項搭配,優先升遷具有研發實力以及溝通能力的工程師。簡言之,設 法讓「喜歡研發並擅長溝通」的人出線,而非「不寫程式也沒關係,只要升遷 就好」的人。

工程師的美德是懶惰,若讓能力最佳的工程師最大程度發揮懶惰的美德,並且將 此美德在組織中擴散,整體效率就會提高。不看程式的主管容易讓「寫爛程式快 速達成上級要求」的工程師出頭,而這樣的人上位後也只會搞政治、尸位素餐, 惡性循環。科技業幾間巨頭內部不乏人才,但往往排列沒有最佳化,搖動一下, 會有很大改善空間。

而在業務擴張方面,台灣企業主常一開口就大氣地說要雇幾百個工程師,這很可 能正是問題所在。究竟這幾百人進來要做什麼事?這事能賺多少錢?而這些錢, 分給這幾百人,還剩多少?如果換一下觀念,同樣預算,人找少一點,錢給多一 點,花更多功夫在組織效率上,很可能做得到一樣的事情。差別是錢多了,找來 的是人才,錢少了,只能找人家挑剩的。

另外一個可能解法是「提高獲利能力」。但如何提高?現有的高科技業巨頭,商 業模式已經固定,不太可能突然獲利大增。新創是個機會,但由於世界最佳新創 環境仍在矽谷,以及中國的資金與市場優勢,要成氣候,無可避免要與矽谷或中 國接軌。如此結果,雖然賺到錢的不見得是台灣人,但會是台灣人才的創業經驗, 然後以這些人為種子,才容易產生成功的本土新創事業。如此案例,已經有好幾 個正在發生。

另一機會是傳產。台灣傳產一直很強,而現在的高科技,已經不能只視為單一產 業,而應該是「所有行業的高科技化」。高科技業待遇相對不差,所以傳產要高 科技化的第一個功課,是要狠下心出得起錢。當然,一開始來的不見得是實力派, 很有可能是「口才很好派」,但只要待遇好、講實績、砍人不手軟,遲早會吸引 到高科技業內幹得不爽、敢衝敢拼的實力者。

與此同時,要理解的是,並不是架了網站、發行了 app 就是高科技化,創新才是。 比如 data visualization 鼎鼎大名的 D3.js 創始者之一 Mike Bostock 當時是 任職於紐約時報,傳產要做高科技,應以此為師。從這角度思考,前面所提新創 事業人才與傳產,就有了強烈結合理由。

整理一下以上提出的幾點看法:

  1. 已建立的大型科技企業,應以改善組織效率、精簡人力、提高待遇為優先。對 新增業務,應以精兵為目標。

  2. 以國外經驗與資金為引,培植創業人才,進而培育自有、高獲利之新創企業。

  3. 傳產應跳入競爭,追求創新或新創公司,吸引人才回流。

而其後邏輯是:

人才數量有其自然上限,提高較為困難,不如設法集中資源、提高獲利能力以爭 搶人才。若以「生產更多軟體從業人員」為解方,恐怕適得其反。

Ruby or JavaScript?

最近接觸到一些 RoR 的東西,覺得很有趣,以開發速度而言 RoR 真的很快,且 生態完整,但效能就是問題。反觀 node.js 族群,效能好些,但似乎開發速度還 沒那麼快。很可能接下來會變成看是 Ruby 先出現 HHVM 那樣的 VM,還是 JavaScript 開發時間也越來越短,最終真的一統天下。

以語言來說並不喜歡 JavaScript,就算到 ES6 也還是沒有很愛,但若把目 標放在 full stack,那一定得懂前端,要懂前端,就一定得把 JavaScript 學好。 既然學好了,後端時若能繼續用同樣語言開發,就能少學一種語言。

然而,若以開發時間而論,這不一定是最快的方式。因為很多 best practice (convention) 仍然存在 RoR 之中,所以花時間去學 Ruby,然後直接用 RoR,現 階段來說省下的功夫很可能可以把學習 Ruby 的時間賺回來。

語言跟應用領域,往往有很強的相關性。例如 Linux 之於 C,userspace 的基礎 架構如 database, browser engine, server 之於 C/C++,資料科學之於 R。但 也有相當通用的語言,例如 Python 以及 Java 用途都相當廣,Python 開發快, Java 效能好。所以說,若打定主意專精於 web 領域,那為了 RoR 學習 Ruby 是 很合理的。

但這優勢其實可取代。因 JavaScript 在 web 應用太廣泛,而基於 isomorphic 的需求,JS 會很自然的往後端與 mobile app 那邊長。且 async non-blocking 的天性已經在語言中,這並不只是效能問題,還牽涉到 scale up 的難易程度。 若把 node.js 的 opinionated web framework 例如 sails.js 開發到像 RoR 一 樣的方便,那麼全部轉換到 JS 為基礎的全端架構,有明顯的優勢。

這件事適合熟悉 RoR,知道其中哪些特性相對於其他解決方案是比較有優勢的工 程師去做。相對於寫一個 VM 可能要公司的能力才做得到,把 sails.js 這類 framework 往 RoR 的方向改善則只需要幾個工程師就行了,後者或許比較容易發 生。

另一個有趣的可能性是,Ruby 到 WebAssembly 的 interpreter 被發展出來且內 建到瀏覽器。由於 client 端運算能力日漸強大,interpreter 慢些也還好,說 不定 RoR 也可以用目前的方式往前端長,直接用一樣的方式寫前端行為,打包成 一整個 full stack web app,這樣感覺也是很厲害。

Method as Argument in Go

說來慚愧,這麼好用的作法竟然今天才發現。

一直以來都覺得 net/http 有點難用,http.Handlehttp.Handler,定義是

1
2
3
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

所以非得有個 method ServeHTTP,但如果要在一個 Handler 底下有好幾個 method 用以處理各類 request,進入點都是 ServeHTTP 就有點麻煩。這時用 http.HandleFunc 就比較合理,只要傳入一個 function:

1
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

然而,這類函數經常需要 context 才能執行,例如把資料庫連結存在 type 裡。 以往碰到這情況會利用 closure 的特性,在 method 中宣告符合介面的函數,然 後再傳給 HandleFunc,現在才知這完全是不必要的。舉例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package test

import (
"log"
"net/http"
)

type Model struct {
// Your database connection, etc.
}

func (t Model) Foo(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Foo"))
}

func (t Model) Bar(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Bar"))
}

func Example() {
model := Model{}
http.HandleFunc("/foo", model.Foo) // method as argument
http.HandleFunc("/bar", model.Bar)
log.Fatal(http.ListenAndServe(":8080", nil))
}

實在是蠻漂亮的作法,搭配 Gorilla web toolkit 很容易就可以寫出 簡單明瞭的 server side 程式。

寫 Mobile App 簡單嗎?

之前面試 mobile app 工程師時,一位人選告訴我,他不想在原先公司(頗知名, 薪水也高)繼續做下去的原因,是因為 Android SDK 他已經很熟,再寫覺得也沒 有進步空間,所以想出來學一些其他的,例如底層一些的技術。

這似乎是個常見的說法,從剛入職場就常聽到,但可能年歲漸長,有些不同體悟。 例如寫 driver 就是那樣,系統整合就是那樣,所以想學別的。這其中必定有什 麼不對的地方,但是是什麼呢?

問題出在需求

Android SDK 本來創作出來的用意就是希望盡可能簡單,因為 Google 希望能讓 開發 Android app 是簡單的,吸引更多 developer 投入,才能讓生態圈蓬勃發 展。但開發界面上的簡單、API 的簡單,不表示功能上的簡單。如果覺得 SDK 學 完了,工作就變得太簡單,那應該是因為需求太簡單了。

為何需求是簡單的

Google 新出的 Inbox 功能簡單嗎?Facebook app 簡單嗎?要看由什麼角度來看。 他們致力追求的都是使用界面上的單純,但往往越單純的界面,後面的機制越複 雜。一個極度好用的功能,往往實作上相對困難。所以這裡延伸出的問題是,為 何在知名大廠,所面對的,仍然是實作上的簡單。

Mobile App 之外也經常如此

例如在 ODM 廠,處理來自各廠商的 BSP,做久了架構熟了,釐清的方式也熟了, 有問題找原廠支援,工作就開始容易。這表示 Android 系統面簡單嗎?問題恐怕 出在,為何沒辦法做到複雜的東西。BSP 已經是相對應用端,使用一個本來就是 為了方便使用者的 package,本來就不該困難。

廣 vs 深

於是可以發現,很少有真正簡單的領域,往往問題出在身處那領域的什麼位置。 台灣求深的廠商不多,相對的職位也不多,往往造成求深不如求廣,我自身就是 個例子。但這是個惡性循環,在職涯選擇上,或許可針對某特定領域,透過自我 要求,追求卓越,盡力往研發的上游端前進。

大勢不佳,但人才全球流動,以海盜心態武裝自己,拋棄故鄉,該去哪就去哪, 應該是概率最佳的選擇。若在本土發展,另闢蹊徑,總是比較困難的。

Solarized color 8 (bright black) 與背景雷同問題

使用 Solarized 這套配色對眼睛蠻 舒服的,而且有 light 或 dark 型態可以切換,跟 Base 16Zenburn 比起來,我是最喜歡 Solarized。但他有個長久以來的問題,就是在 terminal 下,background 跟 color 8 (bright black) 是一樣的顏色,都是 base 03。Node.js 很多套件在 彩色顯示時都會用到 bright black,一旦用到就會跟背景同色,看不見。

之前我的解法是把 background 設為 color 0,也就是真的 black,但在 Solarized 設計上,這兩色是鄰近的而不是對比色,所以雖不是同色,但仍然很 難看到。後來參考了內建 Solarized theme 的 Terminator 解 法,是直接把 color 8 設定成 #6a848a,這不論在 light 或 dark 模式下, 反差都很清晰,是目前為止我覺得最好的解。

相關討論可以看 issue 220,這問 題存在很久了但作者就是不爽解決。