CoffeeScript的全局變量污染與Node.js的模塊加載

最近發現Continuation.js的一個Bug:命令行使用-c開啓緩存模式的時候,有時候更新了代碼緩存不會更新,這個Bug時隱時現,難以捕捉。今天發現這個Bug升級了,不僅僅是在緩存模式的時候會有問題,即時沒有開啓-c一樣會發生這個問題。再到後來發現這個Bug只在CoffeeScript代碼中出現,於是就鎖定了目標開始調試。

Continuation.js和CoffeeScript一樣支持動態加載編譯,就是可以在Node.js中使用require的方法加載原始代碼,運行時編譯。這樣的好處不言而喻,給用戶提供了一致而透明的接口,無需事先編譯好再加載。具體的實現方法是,修改require.extensions下面的回調函數,把加載定向到自定義的函數來處理,最後再調用require.main.compile運行編譯後的代碼。

requiremodule是Node.js運行時的兩個重要變量,所有模塊的運行其實都是在一個這樣的函數中的:

function(module, require) {
  // Your code
}

所以requiremodule是模塊內的全局變量。require.extensions是一個對象,用於根據擴展名註冊require的回調函數,默認情況下,require.extensions是這樣的(Node.js 0.10.12):

{
  '.js': function (module, filename) {
    var content = NativeModule.require('fs').readFileSync(filename, 'utf8');
    module._compile(stripBOM(content), filename);
  },
  '.json': function (module, filename) {
    var content = NativeModule.require('fs').readFileSync(filename, 'utf8');
    try {
      module.exports = JSON.parse(stripBOM(content));
    } catch (err) {
      err.message = filename + ': ' + err.message;
      throw err;
    }
  },
  '.node': function () { [native code] }
}

在Node.js代碼中使用require(filename)時,實際上會根據filename的後綴擴展名依次來選擇加載的回調函數,例如我想增加一種自定義的自動加載類型.byv,只需設置require.extensions['.byv']即可。同理,也可以修改已有的後綴的加載函數,Continuation.js就是這麼做的(修改了.js文件的加載函數)。

爲了透明支持CoffeeScript,Continuation.js修改了.coffee的加載函數,在自定義的回調函數中調用CoffeeScript模塊,調用CoffeeScript編譯,然後再使用Continuation.js編譯。問題就在這裏,是加載CoffeeScript的時候require.extensions['.coffee']被修改了。閱讀CoffeeScript的代碼(版本1.6.3),發現在'coffee-script'模塊中,有這麼幾行代碼:

if require.extensions
  for ext in ['.coffee', '.litcoffee', '.coffee.md']
    require.extensions[ext] = loadFile

這段代碼不應該在加載CoffeeScript模塊中運行,而應該在通過命令行運行coffee命令的時候運行,可惜CoffeeScript沒有注意到這一點,應該算是一個Bug吧。給CoffeeScript提交了一個推送請求https://github.com/jashkenas/coffee-script/pull/3054 ,等待審覈中。

更新:這個issue被標註爲重複,已經在 https://github.com/jashkenas/coffee-script/issues/2323 合併了,CoffeeScript 2.0.0(未發佈)以後默認已經取消這個特性了。

海外實習面試記

去年八月份寫的一篇文章一不小心讓我稍微火了一把,短短時間內在微博達到了上千的轉發量,甚至連像搜狗首席執行官王小川這樣的業界大佬都轉發了那條微博。微博上面的聲音有兩派,一派是支持者,認爲「這個年輕人很有想法」,另一個聲音則對我的想法、行爲提出了質疑,認爲我貶低學術、太過浮躁。我原來在微軟亞洲研究院系統組的老闆對我的想法尤其不滿,甚至對我進行了半公開的點名批評,並告誡其他在微軟亞洲研究院的實習生不得浪費機會。

後來我瞭解到,我寫這篇文章的時候是八月份,正好是個大互聯網公司校園招聘開始的前夕。由於這篇文章裏面公開讚揚了Hulu,而且鼓勵和我一樣的年輕人投身業界,同時表達了對大公司的不滿,被許多人認爲是符合他們的利益的。由於轉發量過大,甚至已經引起了微軟公關部門的注意,以至於向我之前在微軟老闆施壓,要求他立刻「解決問題」。我恍然大悟,只好大呼被人利用了!

(補充:此文發佈後微軟又聯繫到我,認爲以上說法不正確,以下爲解釋)

这个不符合事实哟,我不想再有不必要的误解。实际发生的是负责我们的实习生项目的高校关系部一直致力于保证实习生计划的质量,觉得我们工作没有做好,才找到我们。我们也发现了问题并马上采取措施,加强了对实习生的关注。我不希望大家了解的和事实不符。

轉眼間又過去了一年,已經快到我大三的暑假了,再開學就是大四,眼瞅是要畢業的人了。我快要與在清華的日子告別了,同時我十六年的求學生涯也將接近尾聲。有人說現在感嘆還太早,大四還沒開始,距離畢業還有一年呢!但也有人告訴我,其實大學只有三年,大四和前三年的「過法」不一樣。在我看來,大學每年都不一樣,這一年來我接觸了許多新東西,我和去年的我想法已經有了很大的區別,正所謂是「年年歲歲花相似,歲歲年年人不同」。想想看明年這時候就要畢業了,實習的機會也不多了,所以就寫篇文章記錄一下當時坎坷的經歷吧。

面試準備

有了去年面試美國公司失敗的經歷,我早早就開始了準備。從去年十月份,我就開始瀏覽硅谷各個公司招聘暑期實習生的信息了。由於沒有先驗的知識,根本不知道哪些公司在招實習生,只好像爬蟲一樣查找。在查找的過程中學會了使用LinkedIn和Glassdoor,這兩個網站是非常好的工具,尤其是Glassdoor,還可以看到許多公司的薪酬信息、面試題等。這些信息都是員工匿名披露的,不違反保密規則,甚至還被許多公司的HR鼓勵。

英語

去年面試失敗很大的一個原因可能就是英語。當時我見到外國人根本不知道如何張口,更別說在電話裏面了。我當時的每個面試都是戰戰兢兢,如履薄冰地準備好一切可能問到的的問題的回答,然後唸出來,一旦遇到了沒準備的內容可想而知。

認識到問題以後,該怎麼做就很清楚了,就是練習英語,尤其是口語。但是具體怎麼練呢?這要感謝Gmail廣告。Google在詳細掃描我的郵件和即時通信記錄以後,分析出了我有學習英語的打算,於是就在郵箱界面上不斷給我推送學習英語的廣告。我發現有一種叫做「電話英語」的廣告看起來似乎很有趣,於是就抱着試一試的心態找了幾家電話英語看(請不要諮詢我電話英語相關問題,我不會推薦任何具體的一家)。在購買電話英語服務之前,這些公司都有免費試聽的服務,或者說叫分級測試,於是我就找了好幾家測試了一下自己的分級。電話英語的模式就是一對一和一個英語國家的外教在電話中交談,以此提高英語水平。由於歐美國家勞動力成本較高,加上有時差因素,這些電話英語公司僱傭的外教許多是菲律賓的。菲律賓長期以來是殖民地,平均英語水平在亞太地區是最高的,而且不像印度英語有嚴重口音。許多美國的公司都把電話服務熱線外包給菲律賓。比較各種學習方式後,我不得不承認電話英語是一種價格不貴,而且可以保證學習質量的途徑。經過幾個月的練習,我的口語水平提高了很大一個層次,於是我都敢一個人去美國了。

簡歷

我買了一本《The Google Resume》,仔細研讀了裏面關於寫簡歷和面試的部分。《The Google Resume》可謂是幫了我的大忙,還有這本書作者創辦的網站CareerCup上面以一個工程師出身的HR的視角全面介紹了技術職位面試的細節。依據這本書的建議,我精心寫了一頁簡歷,但是前前後後修改了幾十個版本。

The Google Resume

準備好簡歷以後,我分別在Google、Facebook、Apple、Twitter、Amazon網站上面投了簡歷,其中Google、Facebook、Twitter有學長幫我內推。在經過難熬的等待之後,最終除了Amazon以外,均得到了電話面試的機會。

Google

Google是我接到面試通知的第一家公司,在11月份就開始面試。我收到郵件通知以後,HR問我哪天有時間,然後給我安排電話面試。Google的HR並沒有給我打電話,而是直接安排面試的。由於有時差,所以面試都是在午夜到凌晨之間,所以必須熬夜或者早起了。如果作息不正常,會對自己能力發揮造成很大影響,需要慎重對待。Google給我安排了兩輪連續的面試,每輪一個小時,從北京時間凌晨兩點面試到四點。

由於簽署了保密協議,我不能透露面試的具體題目。題目的類型都是算法題,還有個算概率的數學題。答題的方式是打開一個Google docs頁面,你和面試官共同編輯,同時用電話保持交談。面試官會直接說題目的內容,或者把題目寫在Google docs上,看完以後回答問題。一般來說是先描述算法,然後面試官會繼續追問或者要求證明,最後寫程序。程序直接寫在Google docs上,由於沒有語法高亮和縮進,還是挺不舒服的。不管問題會不會,一定要主動和面試官交流,澄清問題,也許是少了什麼條件或者理解錯誤,最忌諱的就是一句話不說自己一直在想,或者上來就寫代碼。

面試完了以後,大概過了兩個星期,HR發郵件給我說面試結果不錯,給我約了個時間打電話。電話中HR告訴我說我通過了面試,下一步就是進入Google的Host Match流程了。在Google,美國實習生都要有一個Host,大概是經理(Manager)或者導師(Mentor)的職責。每個面試通過的實習生需要等待有Host看中你的簡歷,然後和你聯繫,交流以後決定是否接受你。我一直等到一月份,HR纔告訴我說有了Host願意和我聯繫。和Host電話聊過以後,我對他們做的項目還挺有興趣的,但是我七月份纔放假,和他們的計劃有些衝突,所以就沒有然後了。過了一段時間又有一個Host聯繫我,不過我對他們做的東西實在不感興趣,所以就拒絕了。後來由於HR好像知道我已經在Google北京入職實習了,就沒再用心給我找其他Host,所以最終Host Match失敗。

Apple

得到消息的第二個公司是Apple,我的簡歷經過HR的篩選,被安排了電話面試。同樣是硅谷的公司,面試要在半夜進行,對精力是很大的考驗。Apple公司是出了名的重視設計,就連HR給我發的郵件都和別的公司HR不一樣,一般公司的郵件都挺樸素的,Apple的郵件是有信紙的,默認還被Gmail關閉着,點開以後纔能看到。

面試也是通過電話進行的,寫代碼是通過一個叫collabedit的在線編輯工具,許多公司都喜歡用這個東西面試,包括Twitter和Facebook。面試官先問了我一個算法題,挺簡單的,我回答出來以後繼續追問新的問題,然後讓我證明自己的想法,並寫代碼。寫完以後面試官問我做過的最有意思的一個項目是什麼,我告訴他是Continuation.js,然後向他介紹了一下。最後他問我用過Apple的哪些產品,爲什麼喜歡Apple,我就說我用Mac,爲什麼喜歡我實在沒說出來,因爲我也不是特別喜歡Apple。看來Apple果真是一個要求員工都必須是忠誠的教徒式的公司。

Apple是我自己在網上投遞的一家公司,沒有任何內推,這種情況下面試通過概率挺小的,畢竟你還是一個外國人。面試完一輪以後,就沒有了音信,反覆給HR發郵件問,說正在進行中,可是進行了很久還是沒有結果,所以我知道我是被默拒了。

Twitter

Twitter是我面試過的惟一一家私有公司(未上市),因此決策權全部都在CEO手中。而且未上市的公司資金有限,面試門檻要比面試過的其他大公司高不少。據說Twitter發的每個Offer都是要經過CEO親手簽字的,包括實習生在內。作爲一個實習生,Twitter竟然給我安排了六輪面試,比我之前面試Hulu的輪數還多,可見還是比較嚴格的。

Twitter的每輪面試都是通過Skype進行的,寫代碼也是collabedit。相比與其他幾個公司,Twitter面試的算法挺有難度的,同時也很考察細緻程度。Twitter面試的一個特點是需要寫的代碼量很大,其中一場面試上我一個算法寫了一百行的程序,不僅複雜,還要保證正確性。面試的算法涉及到了回溯搜索、二叉樹操作、動態規劃、緩存替換以及貪心策略和證明。

第一輪面試是我在去美國之前面試,印象中是我期末考試的前一天晚上,當時讓我真的是焦頭爛額不知道該先準備哪個。第二輪面試時間更是巧合,正好是在美國的旅途中,那天我只好躲在Berkeley的旅館中電話面試。我倒是挺想直接去Twitter面試的,但是HR就是沒給我安排,我只好後來自己過去找學長參觀。第三輪是在我回國後,正好是大年初三,這次更悲劇,我的電腦由於被人潑水損壞了,過年又沒人修,我只好一個人跑到五道口的一個網吧裏面,待了一夜。第四五六輪按說應該Onsite面試,但是考慮到我面試的是實習生而且在國外,就改成電話了,這三輪連着面時,時間總共長達四個小時,從凌晨三點到早上七點,十分消耗體力,而且需要保持高度的清醒。

最終我拿到了Twitter的offer。

Facebook

Facebook上市不久,許多老員工拿了一筆錢都紛紛走人了,空出大量的職位,在加上上市融資到不少錢,Facebook一直在急速擴張中,就連實習生職位也很多。和面試Twitter類似,我面試Facebook的時候正好在紐約,於是我只好把寶貴的觀光時間拿出來躲在旅館裏面面試。

Facebook一共面試了兩輪,兩輪之間相隔一個多星期,面試的難度和之前面試Google(美國)差不多。面試涉及的問題主要是動態規劃和二叉樹,感覺這兩個東西是面試最喜歡考到的了。其中有一輪面試中面試官還是一個中國人,聽他說完以後我頓時對我的英語有了信心,最後我和他說了兩句中文,他立刻說「I am not supposed to speak Chinese with you.」,真是無語。Facebook的Offer我也順利拿到了。

Twitter Facebook

值得一提的是Facebook的HR是一個非常靠譜的人,她自始至終一直在幫助我,幫我爭取到了關鍵機會,如果不是她,我很可能此次實習申請就失敗了。我後來在她的LinkedIn簡歷上面看到了傳奇的經歷:她2000年進入華盛頓大學,主修英文文學。在2003年的時候爲了響應國家的召喚,她毅然離開了校園,加入了國家衛士,遠征伊拉克。她在伊拉克和科威特等地擔任戰地記者和編輯,一直到2006年,纔榮歸故里,然後完成學業,開啓了她的事業。

棘手的簽證

最終我拿到的有效的Offer有兩個:Twitter和Facebook。其中Twitter是先拿到的,拿到同時HR給我打電話告訴我薪水、團隊和簽證的信息,並給了我一個期限要求在此前決定。Facebook的Offer是在Twitter後兩個星期內拿到的,HR告訴了我差不多同樣的信息。

聽起來一切都很好,但簽證出現了問題。按照美國法律規定,實習生應該申請J-1簽證,又稱交流訪問學者簽證。這個簽證有一個叫做兩年規定(2-year-rule)的東西,意思是我作爲「交流學者」訪問以後,有回到祖國服務兩年的義務。在這兩年內,我不得申請任何美國的允許移民傾向的簽證,如H-1B或L-1。很顯然這是各國政府爲了防止人才流失和美國博弈的結果。對於那些拿過J-1簽證去美國,然後留學美國,最後留在美國工作的那些人他們是怎麼搞的呢?其實雖然有兩年規定,但是可以找中國大使館申請豁免,然後拿着「不持異議信」就可以免受兩年規則的限制。一般來說申請豁免只要不是國家公派的就差不多都會給,但問題在於是時間很長,而且只給拿超過六個月以上簽證的中國公民辦,像我這種實習完了就回來的是不行的。

這個問題非常棘手,而且只對中國人產生約束,所以許多大公司的HR和法律諮詢部門都不知道。甚至有些公司十分傲慢,你給他們解釋都沒用,因爲他們覺得他們自己是正確的,在他們眼中J-1沒什麼問題,反正可以豁免嘛,殊不知中國大使館有中國特色的規定。只有極少數曾經被這個問題困擾過,有着一手經驗的HR纔會明白其中玄機。好在我相當幸運,我Facebook的HR之前面試過一個中國人,他因爲有J-1在身,而且人在國內,最後窮盡一切辦法還是沒有辦成,雙方損失慘重。於是到我這裏就成了經驗,她小心翼翼地幫我弄清了一切問題。

除了J-1以外,還有一個可以考慮的途徑是H-3簽證,又稱「培訓簽證」。事實上培訓簽證是一個更好的選擇,它沒有任何的後遺症,只是對公司來說稍微貴一些,需要準備的材料陌生一些。我分別向Facebook和Twitter的HR問看能不能幫我辦H-3簽證,得到的答案均是沒辦法,理由是H-3簽證要求培訓者接受的培訓必須是在國內無法接受的,並且目的是爲了回國以後培訓他人。對於Microsoft、Google這樣的跨國公司的來說,辦理H-3簽證是合理的,但Facebook和Twitter在中國根本沒有辦公室,你說你回國培訓誰呢?

於是我陷入了一個窘境:要麼接受J-1簽證和附帶的兩年規則,要麼放棄這次實習機會。其實兩年規則對我來說主要是心理成本,因爲拿了它以後就像一個枷鎖套在我的身上。這個枷鎖只有人在中國大陸待夠兩年以後纔能解除,儘管我畢業就去美國工作並不是我的第一選擇,但少了這個選擇卻是令人沮喪的。人性就是這樣,失去一個東西的痛苦要遠遠大於獲得同一樣東西的喜悅。

正當我在猶豫時,Facebook的HR突然幫我申請到了一個新的機會:可以去英國倫敦的辦公室。英國不存在美國這樣的簽證問題,我可以申請Tier-5臨時工作簽證,只需要Facebook擔保即可。這樣一來一切問題就解決了,因此我接受了這個Offer,並禮貌地拒絕了Twitter。

Google北京

去年十二月我還申請了Google北京的暑期實習生,由於距離較近,直接去Google辦公室進行了面試。我一共面試了三輪,其中前兩輪是連續進行的。第一輪面試我遇到了很難的組合數學題目,由於思路不對,浪費了不少時間,最終沒有答出來。第二輪面試難度也比較大,涉及了C語言字符串處理、動態規劃和貪心算法證明,但是我答得還不錯。過了幾天,HR告訴我說兩位面試官反饋差別較大,於是對我增加了第三輪面試。第三輪面試是一位很資深的工程師,令我驚奇的是他在面試我之前仔細看了我的簡歷以及我網站上列出的我做過的所有項目,並針對我的簡歷進行了提問,包括函數式編程的一些內容,以及JavaScript的CPS變換

過了幾天,HR告訴我通過了面試,但是開始時間是暑假。我聯繫到Google北京輸入法組的楊帆學長,希望可以提前開始實習。Google考慮到我的特殊情況,答應了我的要求,於是我就從今年2月開始在Google北京實習了。

Google i18n

總結

我將在7月1日奔赴英國倫敦,然後開始爲期兩個多月的暑假實習。這次機會對我來說是十分來之不易的,而且最終能夠如願以償是因爲我很幸運。如果沒有Facebook的HR爲我努力爭取到倫敦的機會,我很可能在猶豫中放棄。如果她沒有遇到上一個相同情況的中國人,她也不會爲我爭取這個方法。如果沒有衆多學長指點我、給我幫助,我很可能還在矇昧中。我相信機會是隨機的,但是面對機會的選擇是可以由個人意志決定的。