從武俠小說到程序員面試
概要
受到浣花洗劍錄和射雕英雄傳的啟發(fā),本文使用武俠小說中的人物和事件來類比并分析當(dāng)前程序員面試存在的弊端,并嘗試給出一個更好的程序員面試方案。
估計你會認(rèn)為我在胡扯,程序員面試和浣花洗劍錄怎么能扯上關(guān)系,但它們確實有關(guān)系,而且是很有意思的關(guān)系:
兩個故事
古龍的中期作品浣花洗劍錄講了一個這樣的故事:來自東瀛的白衣人為了追求武道,遠(yuǎn)渡重洋前往中原向各路高手挑戰(zhàn),見人殺人,見佛殺佛,直到中原***高手紫衣侯出手才以一招險勝白衣人,但紫衣侯也深受重傷不久而逝,紫衣侯的后人(也就是主角)在紫衣侯師兄的教導(dǎo)和各種奇遇下學(xué)得絕世武功,在白衣人第二次到訪中原時將白衣人擊敗。
相對于楚留香系列和陸小鳳系列,浣花洗劍錄的故事情節(jié)并不出彩,但我覺得很有意思的是白衣人初到中原向各路高手挑戰(zhàn)下的戰(zhàn)書——一截枯枝:
白袍人目中卻又露出不屑之色,突然后退幾步,只見劍光一閃,立刻回鞘,拔劍、揮劍、插劍,三個動作一眨眼已完成。等到清平門八弟子定睛去瞧時,他手中已多了段枯枝。原來他方才一拔劍,便已削下這段枯枝。
只聽他緩緩道:“拿去給你師父瞧瞧!”轉(zhuǎn)身遠(yuǎn)遠(yuǎn)走開,坐到樹下一方青石上,不言不動,似已入定。
在場的清平門弟子并沒有意識到這段枯枝的特別,但他們的師傅卻被這段枯枝震懾住了:
白三空雙眉緊皺,接過枯枝,起先隨意瞧了幾眼,然后目光突然瞬也不瞬地凝注在那枯枝切口上,竟看得呆住了。
之后清平門弟子胡不愁設(shè)法把這段枯枝送到中原***高手紫衣侯手里,以激他出手挽救中原武林,紫衣侯一生自視甚高,但也被這段枯枝所折服:
眾人也不知那枯枝究竟有何好看處,紫衣侯為何竟瞧得如此入神,直過了三四盞茶功夫,紫衣侯方自緩緩長嘆一聲,道:“好高明的劍法!好快速的劍法!好精深的劍法……”
旁人并不理解一款枯枝有什么好看,紫衣侯解釋道是你們的功力不夠:
鈴兒卻忍不住問道:“難道侯爺只是瞧了瞧這段枯枝便可看出那人劍法的高低不成?”
紫衣侯道:“正是!”
鈴兒道:“從哪里看出來的?”
紫衣侯長嘆一聲,道:“你劍法到了我這樣的造詣,便可自這枯枝切口上看出來了。否則我縱然向你解釋三天三夜,你也不會懂的。”
鈴兒怔了怔,苦笑道:“看起來我一輩子也不會懂了。”
隨后紫衣侯以一劍刺中一人七處大穴,并將此人作為戰(zhàn)書送至白衣人:
白衣人道:“這算什么戰(zhàn)書?”雖然他能無論見著什么驚奇之事面上都不動聲色,但此刻語聲中也不免露出詫異之情。
王半俠雙手一分,撕開了岑陬之衣襟,只見他雙肩前胸七道劍痕,傷口早已結(jié)疤,驟眼望去,也和尋常傷痕沒什么兩樣,只是這劍痕都在肩井、乳泉等大穴之上,縱橫上下,去路分明,劍痕與劍痕之間還有條淡淡的紅線,仔細(xì)一瞧,亦是劍鋒劃出來的。白衣人不等王半俠說話,目光立即被這劍痕吸引,腳步也開始移動,一步步走向岑陬面前。
同樣,旁人看不明白傷痕有什么特別之處,但白衣人卻異常激動:
白衣人再也不瞧他一眼,揮起長劍,劍尖向天,微微顫抖。白衣人語聲也微微顫抖,仰天道:“天地?zé)o極,終于還是有一人能作我的對手……”突然垂首跪下,滿頭長發(fā)四散披落,似是感激蒼天終能賜給他一個對手,又似在贊佩蒼天之能,竟能造出個能與他作對手的英雄!
另一個故事來自金庸的射雕英雄傳里郭靖和黃蓉到陸家莊的那一段,裘千丈(但眾人以為他是裘千仞)通過嘴冒青煙和肉掌碾磚等“絕技”讓眾人以為他身懷絕世武功:
嘴冒青煙
陸莊主生怕要是不去,這位發(fā)起嬌嗔來,非驚動裘千仞不可,當(dāng)下命莊丁放輕腳步,將自己扶過去,俯眼窗紙,在黃蓉弄破的小孔中向里一張,不禁大奇,只見裘千仞盤膝而坐,雙目微閉,嘴里正噴出一縷縷的煙霧,連續(xù)不斷。
肉掌碾磚
裘千仞站起身來,走到天井之中,歸座時手中已各握了一塊磚頭。只見他雙手也不怎么用勁,卻聽得格格之聲不絕,兩塊磚頭已碎成小塊,再捏一陣,碎塊都成了粉末,簌簌簌的都掉在桌上。席上四人一齊大驚失色。
但“絕技”并非真正的功力,即便是當(dāng)時初出茅廬的郭靖,一掌就把裘千丈打飛:
裘千仞見他左臂掃來,口中卻說“吃我一掌”,心道:“你臂中套拳,誰不知道?”雙手摟懷,來撞他左臂。哪知郭靖這招“龍戰(zhàn)于野”是降龍十八掌中十分奧妙的功夫,左臂右掌,均是可實可虛,非拘一格,眼見敵人擋他左臂,右掌忽起,也是蓬的一聲,正擊在他右臂連胸之處,裘千仞的身子如紙鷂斷線般直向門外飛去。
之后眾人才發(fā)現(xiàn)這位“裘千仞”是個假冒:嘴冒青煙是把茅點燃藏在袖里吸一口噴一口;肉掌碾磚是用面粉做的磚;而輕功水上飄則是提前在水底打了暗樁。
程序員面試
也許你會疑惑這兩個故事和程序員面試有什么關(guān)系,先拿白衣人來說:
- 東瀛修煉絕世武功(在??炭鄬W(xué)習(xí)技術(shù));
- 遠(yuǎn)赴中原挑戰(zhàn)群雄(即將畢業(yè)開始求職);
- 拔劍削枯枝作戰(zhàn)書(撰寫簡歷進(jìn)行面試);
- 驚動中原***高手(簡歷/面試得到賞識);
- 海上決戰(zhàn)名揚天下(得到Offer搞定工作)。
寫到這里,不用說你也知道為什么會提到裘千丈這個金庸小說里略搞笑的人物。對應(yīng)到程序員,這大概會是一個簡歷很華麗,面試時非常能侃,可以非常流利的回答一些常見面試問題(因為刷過題庫),但實際工作起來卻錯誤百出的人物。
毫無疑問,沒有一家公司想招聘裘千丈這樣的“高手”,而白衣人這樣的真正高手則是任何一家公司都夢寐以求。所以問題來了——如何鑒別一個人是白衣人這樣的真正高手,而不是裘千丈這樣的“高手”呢?
你一輩子也不會懂
浣花洗劍錄中有這樣一個細(xì)節(jié):
鈴兒卻忍不住問道:“難道侯爺只是瞧了瞧這段枯枝便可看出那人劍法的高低不成?”
紫衣侯道:“正是!”
鈴兒道:“從哪里看出來的?”
紫衣侯長嘆一聲,道:“你劍法到了我這樣的造詣,便可自這枯枝切口上看出來了。否則我縱然向你解釋三天三夜,你也不會懂的。”
鈴兒怔了怔,苦笑道:“看起來我一輩子也不會懂了。”
據(jù)我了解,一些公司把程序員招聘的決定權(quán)交給HR,這無疑是最蠢的決定——HR和獵頭可以確定程序員的背景,并通過求職者的以往經(jīng)歷來推測程序員的能力,但就像鈴兒看不出枯枝的奧妙,HR和獵頭無法鑒別程序員的能力(除非他們以前也是優(yōu)秀的程序員)。鑒別程序員能力這項工作,還是留給程序員最為適合。而且優(yōu)秀的程序員往往需要至少同樣優(yōu)秀的程序員去發(fā)掘。
但即便是程序員自己去面試程序員也依然存在問題,就像裘千丈在射雕英雄傳里面糊弄群雄一樣。
輕功水上漂
裘千丈在射雕英雄傳里先后“表演”了水上漂、嘴冒青煙、指劃酒杯和肉掌碾磚這些“絕技”,在場的眾人(其中不乏陸冠英這樣的高手)卻沒有一個人識破,如果不是郭靖這個二貨傻乎乎的沖上去比劃,恐怕眾人還會被繼續(xù)糊弄下去。
回到程序員面試,大多數(shù)筆試/面試題目都可以在網(wǎng)上找到,而一些公司在招聘時為了省事甚至直接到網(wǎng)上搜題,這就導(dǎo)致看似很高的程序員面試門檻實際變的很低——得到一份還不錯的工作并不需要花一兩年系統(tǒng)的學(xué)習(xí)計算機技術(shù),而只需一兩個月到leetcode、CareerCup以及未名求職版刷題目。原本很有區(qū)分度的算法題目也變的毫無價值——誰知道你是自己想出來的還是背出來的。就像輕功水上漂,誰知道你是真的功力深厚,還是提前在水底打了暗樁。
所以算法題目是一個很尷尬的存在——為了考察程序員的水平,不可能不考算法題目,但一旦考算法題目,求職者就可以通過背題的方式答題從而使得考察變的毫無意義。面試者接下來會找更難的題目,但相對于面試題的數(shù)量無法與求職者的數(shù)量相比,所以***還是會陷入這種出題——背題的惡性循環(huán),這個惡性循環(huán)的直接后果就是公司招進(jìn)來一票“裘千丈”,而一些水平不錯但沒有背題目的程序員卻被拒之門外。
那是不是就沒有辦法了呢?我不這么認(rèn)為,讓我們回到浣花洗劍錄的那段枯枝:
#p#
枯枝
在浣花洗劍錄里,白衣人遠(yuǎn)赴中原挑戰(zhàn)群雄,他并沒有表演水上漂或是嘴冒青煙這種外表華麗的“絕技”,而只是削下一段枯枝作為戰(zhàn)書。而這段在眾人眼中平淡無奇的枯枝卻震懾了中原***高手紫衣侯:
眾人也不知那枯枝究竟有何好看處,紫衣侯為何竟瞧得如此入神,直過了三四盞茶功夫,紫衣侯方自緩緩長嘆一聲,道:“好高明的劍法!好快速的劍法!好精深的劍法……”
重劍無鋒,大巧不工。程序設(shè)計也是如此。程序設(shè)計能力并不一定需要通過復(fù)雜算法才能體現(xiàn)。程序員面試需要考察深度,這里的深度是程序員對程序設(shè)計以及編程語言的理解,也是其在多年編程經(jīng)驗中得到的感悟。
這么說還是很玄,所以我在這里舉一個實例:
恐怕這道題會是你見過的最簡單的面試題——使用C語言把字母轉(zhuǎn)換成大寫,不能使用庫函數(shù)。
以至于很多面試者聽到這道題時的***反應(yīng)都是:
但我并沒有打算開玩笑,你可以試著用C寫一個大寫轉(zhuǎn)換,然后繼續(xù)閱讀本文。
比較有意思的是,一部分面試者給出了類似這樣的答案:
- #include int main() {
- char c = 'a';
- printf("a的大寫是%c\n", c - 32);
- return 0;
- }
其實要是寫成這樣也就沒有往下問的必要了 –_–#
當(dāng)然不少面試者還是比較靠譜:
- char daxie(char c) {
- return c - 32;
- }
這時我會建議面試者不要使用拼音命名,并會提示如果輸入的字母不是小寫程序會怎么樣,一般來說面試者都會在這時引入范圍檢查,但有些人會寫成這樣:
- char to_upper(char c) {
- if (c >= 'a' && c <= 'z') {
- return c - 32;
- } else {
- printf('Input error!');
- return 0;
- }
- }
如果要寫成這樣也沒有往下問的必要了(個人懷疑是看譚浩強學(xué)的C) –_–#
相對靠譜的那部分面試者會給出這樣的答案:
- char to_upper(char c) {
- if (c >= 'a' && c <= 'z') {
- return c - 32;
- }
- return c;
- }
這已經(jīng)很接近我的及格要求,接下來我會問面試者能不能改善它的可讀性(Readability),一些面試者會在命名上下文章(比如把參數(shù)c重命名為input):
- char to_upper(char input) {
- int offset = 32;
- if (input >= 'a' && input <= 'z') {
- return input - offset;
- }
- return input;
- }
這時我會提示能不能去掉這個詭異的32,一般來說能到這一步的面試者都可以反應(yīng)過來:
- char to_upper(char input) {
- if (input >= 'a' && input <= 'z') {
- return input - 'a' + 'A';
- }
- return input;
- }
這就是我的及格要求。一般我會提示面試者能不能繼續(xù)改進(jìn)可讀性,但遺憾的是,到現(xiàn)在也沒有一個面試者能在這一步給出我滿意的答案:
- char to_upper(char input) {
- if ('a' <= input && input <= 'z') {
- return input - 'a' + 'A';
- }
- return input;
- }
其實就是用'a' <= input && input <= input="">= 'a' && input <= 'z'——這個技巧源自于代碼大全,代碼大全里面專門有一節(jié)講解如何編寫可讀的布爾表達(dá)式。從這里我可以看出這些面試者都沒有讀過代碼大全,考慮到代碼大全幾乎是程序設(shè)計的必讀書籍,我可以推斷出這些面試者很可能沒有閱讀習(xí)慣,而不閱讀的程序員一般都不會太出色。
剛剛提到,到了這一步其實也只是過了及格線而已(如果你能寫出可讀的布爾表達(dá)式,我會在內(nèi)心提前給你打個優(yōu)秀),接下來我會詢問能不能進(jìn)一步提升性能,少數(shù)面試者在提示下會想到使用數(shù)組:
- char to_upper(char input) {
- static char convert_table[] = { ... };
- return convert_table[input];
- }
如果面試者能提到他是從C語言標(biāo)準(zhǔn)庫里面學(xué)到這個技巧,加10分 :–)
有的面試者會想到使用宏:
- static char convert_table[] = {...};
- #define TO_UPPER(input) convert_table[input]
這時我會詢問宏的優(yōu)點和缺點,以及在這里使用宏會不會有錯誤??傊褪谴_定面試者確實理解宏,而不是從哪里(比如編程之美之類的面試書籍)背了一個答案出來。
有的面試者會在一開始直接給出使用數(shù)組+宏的***方案(我?guī)缀蹩梢灾苯哟_定他背過題目),這時我會要求他給出一個函數(shù)+非數(shù)組的實現(xiàn)。如果他寫不好這個函數(shù),那么依然無法通過。
可能你們以為到這里就完結(jié)了,其實還不是,考慮下C語言的EOF(即-1),以及to_upper的應(yīng)用場景,下面這段代碼會出現(xiàn)什么問題?
- char c = to_upper(getchar());
如果getchar()返回EOF,由于to_upper接收的類型是char,如果該系統(tǒng)的char是無符號的話,就會出現(xiàn)轉(zhuǎn)換問題,這也是為什么C標(biāo)準(zhǔn)庫(ctype.h)中的toupper函數(shù)簽名是int toupper(int c)而非char toupper(char c)。
接下來,讓我們回顧這道簡單的題目都考察了哪些點:
- 函數(shù)的概念(而不是寫在main里);
- 縮進(jìn)和命名(而不是拼音);
- 使用可讀的字面量('a' - 'A'而非32);
- API設(shè)計(當(dāng)to_upper接收到非小寫字母字符應(yīng)該返回什么?0?報錯?還是返回原值?考慮到to_upper的應(yīng)用場景是把一個字符串中的小寫字母轉(zhuǎn)化為大寫,返回原值顯然更合理);
- 是否有閱讀習(xí)慣(至少可以看出你有沒有認(rèn)真的讀過代碼大全);
- 是否讀過C標(biāo)準(zhǔn)庫源碼(指出toupper數(shù)組實現(xiàn)的出處);
- 數(shù)組的運用(使用轉(zhuǎn)換表);
- 了解宏,以及宏的危害(使用宏);
- 是否背過這道題(在***時間給出使用數(shù)組+宏的***方案);
- EOF以及C標(biāo)準(zhǔn)庫風(fēng)格。
接下來我還會要求面試者測試這個函數(shù)并給出測試代碼,這里恕不贅述。
這道題目就很像浣花洗劍錄里的那段枯枝——它看起來非常簡單,但實際并不簡單——每個人都能削一段樹枝,但削成什么樣子就是另一回事;每個程序員都能寫出大小寫轉(zhuǎn)換,但寫到什么程度就是另一回事。
我認(rèn)為這樣的題目才是程序員面試的***:
- 它看似十分簡單,但做好又非常困難;
- 它能反映出很多問題——比如轉(zhuǎn)化大小寫這道題就至少反映出了10個。
- 和復(fù)雜的算法題目不同,它不會讓面試者卡殼(或無從下手),從而避免一些水平經(jīng)驗還不錯的程序員被誤拒;
- 它沒有標(biāo)準(zhǔn)答案——所以即便面試者把題目放在網(wǎng)絡(luò)上也不會有絲毫影響,因為面試官的評價標(biāo)準(zhǔn)對面試者不透明;
- 背題目是沒有效果的——從而保證不會招進(jìn)“裘千丈”這樣的應(yīng)試程序員;
可能有人會問,既然有諸多好處,為什么這些公司依然使用復(fù)雜的算法題目作為面試題?
我的答案是,排除對算法的盲目崇拜,因為這樣的題目非常難出,而且對面試官的要求又很高,所以絕大多數(shù)面試官都選擇去網(wǎng)上搜題目而不是自己出題這條捷徑。殊不知這條捷徑正是人才招聘失敗的源泉——優(yōu)秀的程序員因為沒有背題而被拒絕,而水平平平的“裘千丈”們卻因為背過題目而被錄用,這些錄用的“裘千丈”們又會用同樣的方式招聘下一批更加糟糕的“裘千丈”,諷刺至級。
結(jié)論
- 程序員招聘的決定權(quán)應(yīng)在程序員手里,而不是HR;
- 優(yōu)秀的程序員往往需要至少同樣優(yōu)秀的程序員去發(fā)現(xiàn);
- 復(fù)雜的算法題目是一種很糟糕的考察程序員的方式;
- 面試官應(yīng)當(dāng)去自己出題,而不是去網(wǎng)上搜現(xiàn)成的題目;
- 面試官(以及公司)應(yīng)該投入大量時間在程序員面試的題目,從而拒絕魚目混珠,保證招聘質(zhì)量。
以上。