破解前端面試系列(3):如何搞定紙上代碼環節?

不少重視技術的互聯網公司在工程師招聘的技術面環節都會要求候選人在紙上寫代碼(後文用「紙上代碼」代稱),面試官想經過這種方式考察哪些點?候選人該注意哪些點?本文基於美團早幾年經常使用的一道區分度比較高的面試題來作詳細講解,這道題我如今還在用,面過的人不少,可是紙上代碼環節能答到滿分的少之又少。javascript

本文爲《破解前端面試》系列文章的第 3 篇,前 2 篇連接在這裏:閉包篇DOM 篇html

爲何要紙上代碼?

紙上代碼(也有可能在白板上寫)的作法乍看起來不夠人性,但若是你是團隊的 Leader,什麼樣的人能更好的融入團隊?若是你是老闆,你願意掏錢養什麼樣的員工?紙上代碼的基本目的就是考察候選人是否具有出活的能力,附帶考察候選人是否思路靈活、知識面廣。前端

紙上代碼環節怎麼考察出活的能力?首先是出活的速度,沒有編碼基本功的人快速出活的機率是極低的,100% 依賴百度或者 IDE 自動完成才能完成基本任務的工程師算不上合格的工程師;其次是出活的質量,經過編碼過程能夠了解候選人經過學習和訓練積累下來的編碼風格、思考方法等;此外,經過紙上代碼也能夠了解候選人接受和完成任務的主動性,是否是願意接受任何團隊須要完成的任務。java

某種程度上說,紙上代碼過程就是從此工做的縮影,既然如此,面試時排練下不是挺好的麼?node

紙上代碼該怎麼作?

一般來講,紙上代碼都不會問特別複雜的問題,極可能只是完成很是通用的需求,解決實際遇到的業務問題,或者用某種語言實現某種算法。在提出實際業務問題的代碼題以前,面試官會經過部分前置問題了解候選人對解決業務問題所需知識的掌握程度,並在必要的狀況下給出知識補充。git

好比,前文提到的那道美團的代碼題是:不借助第三方庫的條件下,用 JS 編寫函數從下面的 URL 串中解析出全部的參數:github

http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&d&enabled

指望的返回結果格式以下:面試

{
  user: 'anonymous',
  id: [123, 456],     // 重複出現的 key 要組裝成數組,能被轉成數字的就轉成數字類型
  city: '北京',        // 中文
  enabled: true,      // 未指定值的 key 約定值爲 true
}

對於使用過 Node.js 中的 querystring 或者社區中的 qsuri.js 模塊的同窗對這個可能再熟悉不過了,而那些不熟 HTTP GET 請求參數攜帶方式的候選人也不用着急,由於這種狀況下面試官會解釋 URL 參數的構造規則,至於對網絡知識的掌握程度,是另外的關注點了。實際操做中,在我拿出這個問題以前,已經跟候選人聊了比較多的 HTTP 話題了。正則表達式

1. 開始動手前

至關比例的候選人拿到問題,會當即提筆開始寫代碼,這是面試官最不肯看到的,和學校考試的填空題不一樣,紙上代碼做爲綜合素質環節,面試官但願看到全面的你,若是工做中也是這樣拿到需求不分青紅皁白就開搞,最終的結果可能經常是事倍功半。算法

謀定然後動,動手前必定要搞清楚問題。怎樣纔算是把問題搞清楚了?要清楚輸入的特徵,是否會出現各類奇怪的輸入(腦子裏面有這根弦的人一般不會差,可是面試官會當心求證,看看你能想到哪些);要清楚對解決辦法的其餘約束條件,好比時間複雜度,空間複雜度。而搞清楚問題的方法就是追問面試官,好比,針對上面的代碼,能夠追問的問題:

  • 未指定值的 key 是否會重複出現?若是重複出現該怎麼處理?

  • 數字中只包含整數?是否包含浮點數?科學計數法?

  • 對代碼的性能要求是怎樣的?提出這個問題的時候,候選人心中可能已經有多重方法了。

就如同在實際工做中接需求的時候,須要知道需求的邊界,各類可能的特殊狀況,合做方對於排期的指望,需求中各個要點優先級界定,從決策論的角度來看,掌握更充分的信息,才能讓你對技術複雜度、需求排期有更合理的預估,避免在作到一半或作完的時候發現與實際需求不符。

搞清楚問題以後,相信你心中已經有了基本思路,不過動手的時機還沒到,你應該把思路介紹給面試官,確認本身是否本身是否忽略了某些要點,這也是展現溝通能力的好機會,知道什麼是有效溝通的同窗應該能明白接收信息後向信源確認的重要性。

須要注意的是,質疑精神強烈的同窗在動手前會提不少問題,看起來是好事情,但若是隻是停留在質疑層面,不肯意動手,留給面試官的印象就會是你是個挑活的人。在個人招聘經歷中就曾遇到過由於以爲代碼題要解決的問題沒有任何意義而拒絕寫代碼的人,我沒辦法只能客氣的把他送走。由於,對不認同事物的寬容程度很低的人很容易給團隊帶來壞味道。

肯定了問題邊界和解決問題的思路,接下來你能夠開始動手編碼。

2. 編碼過程當中

解決 QueryString 參數解析問題的思路有好多種,好比字符串線性遍歷法、字符串分割法、正則表達式方法,在我面過的人中,用字符串分割法的人最多,下面的討論咱們就圍繞這種方法展開。線性遍歷法的實現能夠參考 Node.js 內置的 querystring 模塊。

編碼過程當中須要考慮哪些要素呢?下面用具體的例子來分析,好比我常常拿到這樣的結果代碼:

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;
}

看到這樣的代碼,相信你也已經皺起了眉頭,這段代碼在表層、邏輯嚴謹性、健壯性都存在問題,更嚴重的是沒有知足數值型參數的需求,透過這段代碼也能夠推斷候選人大機率是個不善於學習的人。

表層問題

表層問題主要指代碼可讀性,評價標準是:是否看起來簡潔?是否看一眼就能理解它在作什麼?上面的結果有哪些具體的表層問題呢?

  • 可讀性方面,若是你想在循環體裏面要追蹤解析到的鍵值對,須要在大腦中保持映射 key = tmp[0], value = tmp[1]

  • 變量命名方面,好比 tmp 的屢次使用,ary 代稱數組雖然也能夠,社區中用 arr 比較多,變量命名多用約定俗成的會更好;

作了表層改進的參考代碼以下:

function parse(str) {
  var paramObj = {};
  var paramArr = str.split('&');
  for (var i = 0; i < paramArr.length; i++) {
    var tmp = paramArr[i].split('=');
    // 把 key 和 value 單獨拆開來,會清晰不少
    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;
}

邏輯問題

邏輯不嚴謹的代碼在不一樣輸入狀況下的結果是不穩定的,具體表現爲:

  • obj[tmp[0]] 不能正確判斷結果中是否已經存在某個 key,由於可能出現值爲 0 的狀況;

  • 上面的代碼不能正確處理重複出現 2 次以上的 key,部分候選人到面試結束還沒想明白爲啥;

  • 按照規範,URL 中的的各類參數須要在 encode 以後拼接到 URL 中,對應的解析時須要 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]];  // 正確處理數組
      newValue.push(value);
      paramObj[key] = newValue;
    }
  }

  return paramObj;
}

健壯問題

整段代碼沒有作任何的防護性編程,會讓它很容報錯,哪些地方該作防護性編程是值得拿捏的問題。QueryString 解析函數至少要要求本身的參數是字符串吧?在函數開頭增長以下代碼會更好:

//...
if (typeof str !== 'string') {
  return {};
}
//...

需求問題

代碼中沒有對數字作任何處理,拿到問題就埋頭寫代碼的候選人幾乎都有這個問題,這個問題的考點是怎麼把能轉換成數字的值轉成數字。你想好怎麼作了麼?用 parseInt?仍是用 parseFloat

下面是能正確處理數字的參考代碼:

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;

    // 處理數字:不少人忽略這裏的類型判斷,布爾值傳給 Number 也會解析出數字
    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;
}

不算問題的問題

下面兩點不算是問題,可是若是候選人能作到,無疑是加分項。

  • 在 ES6 成爲新語言標準的情形下,候選人還在大量的使用 var,雖然並無錯,可是你要有沒有更好的方法;

  • 能夠用更語義化的 JS 數組方法來組織代碼,好比 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;
  }, {});
}

此外,關注前端技術進展的同窗可能會注意到部分現代瀏覽器提供了 URLSearchParams 的支持,能夠用這個特性 1 行代碼就搞定需求。

3. 編碼結束後

代碼第一版寫完以後,不要着急立刻展現給面試官,就像是需求開發完,你至少得本身先按需求文檔走一遍,把代碼原題中的輸入代進本身的代碼作推演和簡單的邊界測試,而後再對着代碼和麪試官講解。不出意外的話,推演過程你本身會發現部分問題,或者明顯的改進點,這些內容你均可以跟面試官提出來,由於這也是展現你的能力的機會。

總結

感謝你花時間讀到這裏,相信你已經理解了經過紙上代碼的過程和結果能夠深刻考察候選人的基本素質、工做方式、出活能力,也知道了在解答代碼題的不一樣環節該注意哪些要點:動手前搞清楚問題;編碼時注意編碼風格、邏輯嚴謹性、程序健壯性;編碼後要先本身測試和推演。固然,若是你以前沒注意到這些,須要接下來工做中多加練習。最後祝你能找到你想要的工做。

One More Thing

本文做者王仕軍,商業轉載請聯繫做者得到受權,非商業轉載請註明出處。若是你以爲本文對你有幫助,請點贊!若是對文中的內容有任何疑問,歡迎留言討論。想知道我接下來會寫些什麼?歡迎訂閱個人掘金專欄知乎專欄:《前端週刊:讓你在前端領域跟上時代的腳步》。

Happy Hacking

相關文章
相關標籤/搜索