破解前端面试系列(3):如何搞定纸上代码环节?
很多重視技術(shù)的互聯(lián)網(wǎng)公司在工程師招聘的技術(shù)面環(huán)節(jié)都會(huì)要求候選人在紙上寫代碼(后文用“紙上代碼”代稱),面試官想通過這種方式考察哪些點(diǎn)?候選人該注意哪些點(diǎn)?本文基于美團(tuán)早幾年常用的一道區(qū)分度比較高的面試題來做詳細(xì)講解,這道題我現(xiàn)在還在用,面過的人很多,但是紙上代碼環(huán)節(jié)能答到滿分的少之又少。
本文為《破解前端面試》系列文章的第 3 篇,前 2 篇鏈接在這里:閉包篇、DOM 篇。
為什么要紙上代碼?
紙上代碼(也有可能在白板上寫)的做法乍看起來不夠人性,但如果你是團(tuán)隊(duì)的 Leader,什么樣的人能更好的融入團(tuán)隊(duì)?如果你是老板,你愿意掏錢養(yǎng)什么樣的員工?紙上代碼的基本目的就是考察候選人是否具備出活的能力,附帶考察候選人是否思路靈活、知識(shí)面廣。
紙上代碼環(huán)節(jié)怎么考察出活的能力?首先是出活的速度,沒有編碼基本功的人快速出活的概率是極低的,100% 依賴百度或者 IDE 自動(dòng)完成才能完成基本任務(wù)的工程師算不上合格的工程師;其次是出活的質(zhì)量,通過編碼過程可以了解候選人通過學(xué)習(xí)和訓(xùn)練積累下來的編碼風(fēng)格、思考方法等;此外,通過紙上代碼也可以了解候選人接受和完成任務(wù)的主動(dòng)性,是不是愿意接受任何團(tuán)隊(duì)需要完成的任務(wù)。
某種程度上說,紙上代碼過程就是今后工作的縮影,既然如此,面試時(shí)排練下不是挺好的么?
紙上代碼該怎么做?
通常來說,紙上代碼都不會(huì)問特別復(fù)雜的問題,很可能只是完成非常通用的需求,解決實(shí)際遇到的業(yè)務(wù)問題,或者用某種語言實(shí)現(xiàn)某種算法。在提出實(shí)際業(yè)務(wù)問題的代碼題之前,面試官會(huì)通過部分前置問題了解候選人對(duì)解決業(yè)務(wù)問題所需知識(shí)的掌握程度,并在必要的情況下給出知識(shí)補(bǔ)充。
比如,前文提到的那道美團(tuán)的代碼題是:不借助第三方庫的條件下,用 JS 編寫函數(shù)從下面的 URL 串中解析出所有的參數(shù):
http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&d&enabled期望的返回結(jié)果格式如下:
{user: 'anonymous',id: [123, 456], // 重復(fù)出現(xiàn)的 key 要組裝成數(shù)組,能被轉(zhuǎn)成數(shù)字的就轉(zhuǎn)成數(shù)字類型city: '北京', // 中文enabled: true, // 未指定值的 key 約定值為 true }對(duì)于使用過 Node.js 中的 querystring 或者社區(qū)中的 qs、uri.js 模塊的同學(xué)對(duì)這個(gè)可能再熟悉不過了,而那些不熟 HTTP GET 請(qǐng)求參數(shù)攜帶方式的候選人也不用著急,因?yàn)檫@種情況下面試官會(huì)解釋 URL 參數(shù)的構(gòu)造規(guī)則,至于對(duì)網(wǎng)絡(luò)知識(shí)的掌握程度,是另外的關(guān)注點(diǎn)了。實(shí)際操作中,在我拿出這個(gè)問題之前,已經(jīng)跟候選人聊了比較多的 HTTP 話題了。
1. 開始動(dòng)手前
相當(dāng)比例的候選人拿到問題,會(huì)立即提筆開始寫代碼,這是面試官最不愿看到的,和學(xué)校考試的填空題不同,紙上代碼作為綜合素質(zhì)環(huán)節(jié),面試官希望看到全面的你,如果工作中也是這樣拿到需求不分青紅皂白就開搞,最終的結(jié)果可能常常是事倍功半。
謀定而后動(dòng),動(dòng)手前一定要搞清楚問題。怎樣才算是把問題搞清楚了?要清楚輸入的特征,是否會(huì)出現(xiàn)各種奇怪的輸入(腦子里面有這根弦的人通常不會(huì)差,但是面試官會(huì)小心求證,看看你能想到哪些);要清楚對(duì)解決辦法的其他約束條件,比如時(shí)間復(fù)雜度,空間復(fù)雜度。而搞清楚問題的方法就是追問面試官,比如,針對(duì)上面的代碼,可以追問的問題:
未指定值的 key 是否會(huì)重復(fù)出現(xiàn)?如果重復(fù)出現(xiàn)該怎么處理?
數(shù)字中只包含整數(shù)?是否包含浮點(diǎn)數(shù)?科學(xué)計(jì)數(shù)法?
對(duì)代碼的性能要求是怎樣的?提出這個(gè)問題的時(shí)候,候選人心中可能已經(jīng)有多重方法了。
就如同在實(shí)際工作中接需求的時(shí)候,需要知道需求的邊界,各種可能的特殊情況,合作方對(duì)于排期的期望,需求中各個(gè)要點(diǎn)優(yōu)先級(jí)界定,從決策論的角度來看,掌握更充分的信息,才能讓你對(duì)技術(shù)復(fù)雜度、需求排期有更合理的預(yù)估,避免在做到一半或做完的時(shí)候發(fā)現(xiàn)與實(shí)際需求不符。
搞清楚問題之后,相信你心中已經(jīng)有了基本思路,不過動(dòng)手的時(shí)機(jī)還沒到,你應(yīng)該把思路介紹給面試官,確認(rèn)自己是否自己是否忽略了某些要點(diǎn),這也是展示溝通能力的好機(jī)會(huì),知道什么是有效溝通的同學(xué)應(yīng)該能明白接收信息后向信源確認(rèn)的重要性。
需要注意的是,質(zhì)疑精神強(qiáng)烈的同學(xué)在動(dòng)手前會(huì)提很多問題,看起來是好事情,但如果只是停留在質(zhì)疑層面,不愿意動(dòng)手,留給面試官的印象就會(huì)是你是個(gè)挑活的人。在我的招聘經(jīng)歷中就曾遇到過因?yàn)橛X得代碼題要解決的問題沒有任何意義而拒絕寫代碼的人,我沒辦法只能客氣的把他送走。因?yàn)?#xff0c;對(duì)不認(rèn)同事物的寬容程度很低的人很容易給團(tuán)隊(duì)帶來壞味道。
確定了問題邊界和解決問題的思路,接下來你可以開始動(dòng)手編碼。
2. 編碼過程中
解決 QueryString 參數(shù)解析問題的思路有好多種,比如字符串線性遍歷法、字符串分割法、正則表達(dá)式方法,在我面過的人中,用字符串分割法的人最多,下面的討論我們就圍繞這種方法展開。線性遍歷法的實(shí)現(xiàn)可以參考 Node.js 內(nèi)置的 querystring 模塊。
編碼過程中需要考慮哪些要素呢?下面用具體的例子來分析,比如我經(jīng)常拿到這樣的結(jié)果代碼:
function parse(str) {var obj = {};var ary = str.split('&');for (var i = 0; i < ary.length; i++) {var tmp = ary[i].split('=');if (!obj[tmp[0]]) {obj[tmp[0]] = tmp[1] || true;} else {var tmp2 = [obj[tmp[0]], tmp[1] || true];obj[tmp[0]] = tmp2;}}return obj; }看到這樣的代碼,相信你也已經(jīng)皺起了眉頭,這段代碼在表層、邏輯嚴(yán)謹(jǐn)性、健壯性都存在問題,更嚴(yán)重的是沒有滿足數(shù)值型參數(shù)的需求,透過這段代碼也可以推斷候選人大概率是個(gè)不善于學(xué)習(xí)的人。
表層問題
表層問題主要指代碼可讀性,評(píng)價(jià)標(biāo)準(zhǔn)是:是否看起來簡(jiǎn)潔?是否看一眼就能理解它在做什么?上面的結(jié)果有哪些具體的表層問題呢?
可讀性方面,如果你想在循環(huán)體里面要追蹤解析到的鍵值對(duì),需要在大腦中保持映射 key = tmp[0], value = tmp[1];
變量命名方面,比如 tmp 的多次使用,ary 代稱數(shù)組雖然也可以,社區(qū)中用 arr 比較多,變量命名多用約定俗成的會(huì)更好;
做了表層改進(jìn)的參考代碼如下:
function parse(str) {var paramObj = {};var paramArr = str.split('&');for (var i = 0; i < paramArr.length; i++) {var tmp = paramArr[i].split('=');// 把 key 和 value 單獨(dú)拆開來,會(huì)清晰很多var key = tmp[0];var value = tmp[1] || true;if (!paramObj[key]) {paramObj[key] = value;} else {var newValue = [paramObj[key], value];paramObj[key] = newValue;}}return paramObj; }邏輯問題
邏輯不嚴(yán)謹(jǐn)?shù)拇a在不同輸入情況下的結(jié)果是不穩(wěn)定的,具體表現(xiàn)為:
obj[tmp[0]] 不能正確判斷結(jié)果中是否已經(jīng)存在某個(gè) key,因?yàn)榭赡艹霈F(xiàn)值為 0 的情況;
上面的代碼不能正確處理重復(fù)出現(xiàn) 2 次以上的 key,部分候選人到面試結(jié)束還沒想明白為啥;
按照規(guī)范,URL 中的的各種參數(shù)需要在 encode 之后拼接到 URL 中,對(duì)應(yīng)的解析時(shí)需要 decode;
解決掉邏輯問題的參考代碼如下:
function parse(str) {var paramObj = {};var paramArr = decoeURI(str).split('&'); // 先解碼for (var i = 0; i < paramArr.length; i++) {var tmp = paramArr[i].split('=');var key = tmp[0];var value = tmp[1] || true;if (typeof paramObj[key] === 'undefined') { // 判斷 key 是否存在paramObj[key] = value;} else {var newValue = Array.isArray(paramObj[key]) ? paramObj[key] : [paramObj[key]]; // 正確處理數(shù)組newValue.push(value);paramObj[key] = newValue;}}return paramObj; }健壯問題
整段代碼沒有做任何的防御性編程,會(huì)讓它很容報(bào)錯(cuò),哪些地方該做防御性編程是值得拿捏的問題。QueryString 解析函數(shù)至少要要求自己的參數(shù)是字符串吧?在函數(shù)開頭增加如下代碼會(huì)更好:
//... if (typeof str !== 'string') {return {}; } //...需求問題
代碼中沒有對(duì)數(shù)字做任何處理,拿到問題就埋頭寫代碼的候選人幾乎都有這個(gè)問題,這個(gè)問題的考點(diǎn)是怎么把能轉(zhuǎn)換成數(shù)字的值轉(zhuǎn)成數(shù)字。你想好怎么做了么?用 parseInt?還是用 parseFloat?
下面是能正確處理數(shù)字的參考代碼:
function parse(str) {if (typeof str !== 'string') {return {};}var paramObj = {};var paramArr = decodeURI(str).split('&');for (var i = 0; i < paramArr.length; i++) {var tmp = paramArr[i].split('=');var key = tmp[0];var value = tmp[1] || true;// 處理數(shù)字:很多人忽略這里的類型判斷,布爾值傳給 Number 也會(huì)解析出數(shù)字if (typeof value === 'string' && isNaN(Number(value)) === false) {value = Number(value);}if (typeof paramObj[key] === 'undefined') {paramObj[key] = value;} else {var newValue = Array.isArray(paramObj[key]) ? paramObj[key] : [paramObj[key]];newValue.push(value);paramObj[key] = newValue;}}return paramObj; }不算問題的問題
下面兩點(diǎn)不算是問題,但是如果候選人能做到,無疑是加分項(xiàng)。
在 ES6 成為新語言標(biāo)準(zhǔn)的情形下,候選人還在大量的使用 var,雖然并沒有錯(cuò),但是你要有沒有更好的方法;
可以用更語義化的 JS 數(shù)組方法來組織代碼,比如 map、reduce,如果你知道的化,在面試中可以大膽使用;
使用 ES6 編寫的參考代碼如下:
function parse(str) {if (typeof str !== 'string') {return {};}return decodeURI(str).split('&').map(param => {const tmp = param.split('=');const key = tmp[0];let value = tmp[1] || true;if (typeof value === 'string' && isNaN(Number(value)) === false) {value = Number(value);}return { key, value };}).reduce((params, item) => {const { key, value } = item;if (typeof params[key] === 'undefined') {params[key] = value;} else {params[key] = Array.isArray(params[key]) ? params[key] : [params[key]];params[key].push(value);}return params;}, {}); }此外,關(guān)注前端技術(shù)進(jìn)展的同學(xué)可能會(huì)注意到部分現(xiàn)代瀏覽器提供了 URLSearchParams 的支持,可以用這個(gè)特性 1 行代碼就搞定需求。
3. 編碼結(jié)束后
代碼初版寫完之后,不要著急馬上展示給面試官,就像是需求開發(fā)完,你至少得自己先按需求文檔走一遍,把代碼原題中的輸入代進(jìn)自己的代碼做推演和簡(jiǎn)單的邊界測(cè)試,然后再對(duì)著代碼和面試官講解。不出意外的話,推演過程你自己會(huì)發(fā)現(xiàn)部分問題,或者明顯的改進(jìn)點(diǎn),這些內(nèi)容你都可以跟面試官提出來,因?yàn)檫@也是展示你的能力的機(jī)會(huì)。
總結(jié)
感謝你花時(shí)間讀到這里,相信你已經(jīng)理解了通過紙上代碼的過程和結(jié)果可以深入考察候選人的基本素質(zhì)、工作方式、出活能力,也知道了在解答代碼題的不同環(huán)節(jié)該注意哪些要點(diǎn):動(dòng)手前搞清楚問題;編碼時(shí)注意編碼風(fēng)格、邏輯嚴(yán)謹(jǐn)性、程序健壯性;編碼后要先自己測(cè)試和推演。當(dāng)然,如果你之前沒注意到這些,需要接下來工作中多加練習(xí)。最后祝你能找到你想要的工作。
One More Thing
本文作者王仕軍,商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。如果你覺得本文對(duì)你有幫助,請(qǐng)點(diǎn)贊!如果對(duì)文中的內(nèi)容有任何疑問,歡迎留言討論。想知道我接下來會(huì)寫些什么?歡迎訂閱我的掘金專欄或知乎專欄:《前端周刊:讓你在前端領(lǐng)域跟上時(shí)代的腳步》。
Happy Hacking
總結(jié)
以上是生活随笔為你收集整理的破解前端面试系列(3):如何搞定纸上代码环节?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Reat学习01——初步接触与安装
- 下一篇: 如何保证access_token长期有效