如果你應徵的是軟體開發職位,你需要準備一套特殊的技能。是的,你會被要求寫程式。不,你通常不會用電腦——只會有一塊白板,或有時只是一張紙。白板與面試中的程式撰寫需要一套特別的技巧,即使是最優秀的工程師也可能在 coding 面試中表現失常。

典型的軟體開發面試,開頭會簡短聊聊你過去的經驗,結尾會聊聊公司的情況,而面試的主體則花在 coding 和 algorithm 問題上。

Coding 問題可能很快就能解決,但通常會佔滿整場面試時間。你不需要是一個完美無缺的 coder——大多數問題都刁鑽到連最好的候選人也會犯一些錯誤。

What’s the Point?#

Coding 面試是許多求職者的痛點。在白板上寫程式有什麼意義?難道看過去的經歷不就能知道你會寫程式嗎?為什麼要強迫你用白板而不是電腦?畢竟你不可能在白板上寫出完美的程式碼。

當雇主被問到這個問題時,通常會提出以下幾點理由:

  • 鼓勵對話:在白板上寫程式時,你會被鼓勵與面試官交談,這有助於面試官了解你的思考過程。
  • 聚焦核心:白板上寫程式讓你專注在演算法和程式碼的核心邏輯,而不是被細節困住。例如在電腦上,候選人可能花大量時間查正確的排序方法語法,但這不是面試官在意的。
  • 處理更複雜的程式碼:因為不用擔心所有小細節都正確,你其實可以寫出更複雜的程式碼。需要一個找出陣列最小值的 helper function?沒問題,假裝你已經有了就好。
  • 測試推理能力:白板上寫程式測試的是你對程式碼運作方式的推理能力。你只是透過執行來驗證程式碼,還是真正從概念上理解它如何運作?
  • 培養乾淨的程式碼:白板上寫程式鼓勵你從一開始就寫出好的、乾淨的程式碼,因為修改成本更高。在電腦上你可以隨意一點,事後再清理。

這不是要你完全認同這些面試方式,而是幫助你了解公司的思考邏輯。

What’s Expected — And What’s Not#

不需要在白板上寫出完美無缺的程式碼。面試官自己也經歷過這個過程,他們知道寫出完美的程式碼並不是合理的期望。

然而,Pseudocode 通常不被接受。你的程式碼應該使用特定的語言(Python、Java、C++ 等),並且盡量接近正確的程式碼。如果你混淆了一些小細節——例如用 list.add 代替 list.append——面試官通常不會在意。

你能做的是展示你在乎乾淨、漂亮的程式碼

  • 將程式碼包在 method declaration 中;散亂的程式碼看起來不專業且容易混淆
  • 通常不需要寫 class definition,除非它與當前問題特別相關
  • 模組化你的程式碼,這展示了你對可維護性的重視
  • 如果你注意到可以 refactor 或清理的部分,就去做——展示你是一個在乎程式碼品質的人

好的經驗法則是看職位和公司:如果是 web-based 的公司、團隊或角色,預期會有 scalability 問題;如果是前端職位,預期有前端問題。但幾乎所有公司都會考 algorithm 和 problem-solving 問題。

How They Differ: Microsoft, Facebook, Google, Amazon, Yahoo, and Apple#

整體而言,頂尖科技公司的面試風格相似多於不同。並不存在所謂的「Google 面試題」或「Microsoft 面試題」。每位面試官自行決定自己的題目,在某家公司被問到的題目幾乎也能出現在其他公司。

不過,某些公司確實有輕微的偏好:

  • Google:傾向強調 scalability 問題(例如「設計一個 web crawler」)。Bit manipulation 的問題也很常見。
  • Amazon:喜歡 object-oriented design 問題。如果你要面試 Amazon,務必準備這類題目。由於 Amazon 是 web-based 公司,也要準備 scalability 問題。
  • Microsoft:題目範圍很廣,這與其多元的產品線有關。面試官傾向問更多 C 和 C++ 的問題。如果你的履歷沒有列出這些語言,不用擔心;但如果有列出,確保你能熟練使用。此外,Microsoft 比其他公司更強調 testing 和 design 技能。
  • Apple:希望找到對公司真正有熱情的人。確保你了解 Apple 的產品,特別是你面試的團隊的產品。你會如何改進該產品?記住 Apple 有很多聰明人還沒做你建議的事情——想想為什麼。
  • Facebook:面試官有特定的角色分工以確保問題的多樣性。他們優先看重的是你是否是一個好的 hacker——也就是說,你能不能把事情做出來?準備好談論你過去的經驗。
  • Yahoo:歷史上強調更多術語和知識型問題,但已經開始轉向典型的 algorithm 和 problem-solving 問題。

How to Prepare#

在練習面試題時,品質比數量重要。網路上有成千上萬的範例面試題,不要試著背答案——那不可能做到,也不會幫助你。

花時間解題,嘗試以下七步練習法

  1. 從零實作常見的 data structures 和 algorithms,先手寫再用電腦。很多面試題需要這些知識,你必須非常熟練。同時推導這些演算法的 time complexity。
  2. 用真正的面試題練習。演算法或資料結構的書可能有用,但往往超出面試所需的範圍。讓面試題引導你學習方向。
  3. 自己真正嘗試解題。如果卡住了,這很正常——題目本來就設計得很有難度。不要放棄,繼續思考。
  4. 在紙上寫出演算法的程式碼。你一直在電腦上寫程式,已經習慣了編譯器、code completion 等工具。面試中沒有這些,所以要用最傳統的方式寫程式碼,精確到每個分號。
  5. 手動測試你的程式碼!不能用電腦作弊!
  6. 把程式碼原封不動地輸入電腦。重新執行你試過的測試案例和一些新的測試案例。
  7. 列出你犯的所有錯誤,分析你最常犯哪些類型的錯誤。有沒有特定的錯誤模式?

你可以在 CareerCup.com 上找到數千道來自 Google、Microsoft、Amazon 及其他主要科技公司的面試題。

What If I Hear a Question I Know?#

如果你準備了很多,很可能會遇到你已經知道的面試題。如果發生這種情況,告訴面試官:「我覺得我聽過這題」或「我聽過一個類似的問題——你希望我繼續嗎?」

對於複雜的 algorithm 問題,面試官想看的是你如何解決問題。如果他們看不到你的 problem-solving 過程,就無法判斷你是否是一個好的問題解決者。

更重要的是:許多面試官覺得不承認你聽過這題是一種作弊行為,他們可能會因此直接拒絕你。但如果你誠實說出來,反而會贏得額外加分——面試官重視誠實。

Must-Know Data Structures, Algorithms, and Topics#

大多數面試官不會問你 binary tree balancing 或其他複雜演算法的具體細節。坦白說,他們自己大概也不記得了。(是的,這意味著你可以放下那本厚厚的演算法書了。)

你通常只需要知道基礎知識。以下是必知的核心清單(這不是一份完整的清單,只是絕對必知的項目):

Data StructuresAlgorithmsConcepts
Linked listsBreadth-first searchBit manipulation
Binary treesDepth-first searchRecursion
TriesBinary searchMemoization / Dynamic programming
StacksMerge sortMemory (stack vs. heap)
QueuesQuick sortBig-O time
Dynamically resizing arraysTree insert/find/etc.
Hash tables
Heaps
Graphs

對每個主題,確保你理解如何實作和使用它們,以及(如果適用)它們的 space 和 time complexity

練習實作這些 data structures 和 algorithms。你可能會被要求直接實作,或實作它們的變體。無論如何,你越熟練越好。

Memory Usage#

在複習 data structures 時,記得練習計算資料結構或演算法的記憶體使用量。面試官可能會直接問你某個東西佔多少記憶體,或者當你的問題涉及大量資料時,你可能需要自己計算。

  • Data structures:別忘了包含指向其他資料的 pointers。例如,一個存放 1,000 個整數的 doubly linked list 大約使用 12KB 的記憶體(4 bytes 資料、4 bytes 前指標、4 bytes 後指標)。把 singly linked list 改成 doubly linked list 可以大幅增加記憶體使用量。
  • Algorithms:Recursive algorithm 通常比 iterative algorithm 佔用更多空間。例如,計算 singly linked list 第 j 個到最後一個元素的演算法,用陣列排序每個元素的方法可能和 recursive algorithm 一樣都使用 O(n) 記憶體。最佳解法是使用兩個 pointers,其中一個先走 j 步。

許多候選人只從時間維度思考演算法,但 space complexity 同樣重要。我們經常需要在時間和空間之間做 trade-off,有時會犧牲時間效率來減少記憶體使用。

Coding Questions#

面試本來就設計得很有難度。常態是你一聽到問題就不知道怎麼解,然後你會掙扎、從面試官那裡得到一些提示,最終得到比一開始更好的解法。

遇到難題時不要慌張,開始大聲說出你的思路。以下七步法適用於大多數問題:

  1. 理解問題:如果有任何不懂的地方,在這裡釐清。特別注意題目中給出的特定細節(例如輸入已排序)。你需要這些資訊。
  2. 畫一個範例:在腦中解題與在白板上畫出來非常不同。起身到白板前畫一個好的範例——夠大(例如陣列有八個元素)、不是特殊案例。
  3. 設計一個 brute force 演算法:一聽到問題就先想出一個解法,即使不完美。你可以之後再優化。這是一個起點,也能確保面試官知道你至少走到了這一步。
  4. 優化 brute force:不一定每次都能做到,但通常可以從 brute force 找到通往最佳解的路徑。
  5. 理解程式碼:在你有了最佳演算法後,花時間真正理解你的演算法。先不要急著寫程式碼。
  6. 實作程式碼:如果你對流程感到舒適,就開始實作。
  7. 測試:完美的白板程式碼很罕見。如果發現錯誤,沒關係,分析為什麼犯錯並修復它。

記住:面試官說結束之前,你都還沒結束!當你想出演算法時,開始思考可能的問題。當你寫完程式碼時,開始尋找 bugs。大多數候選人都會犯錯。

Step 1: Understand the Question#

技術問題比表面看起來更模糊,確保問清楚任何不清楚的地方。你可能最終會面對一個完全不同——或簡單得多——的問題。事實上,有些面試官(特別是在 Microsoft)會刻意測試你是否會問好問題。

例如,「設計一個排序 list 的演算法」可能變成「排序一個 linked list 中 1 到 10 之間的數字序列」——這是一個完全不同的問題。

此外,確保你記住面試官提到的所有細節。如果面試官說資料已排序,那你的最佳演算法很可能依賴這一點。如果你覺得忘了某些細節,隨時可以請面試官重複問題。

Step 2: Draw an Example#

不知道為什麼,大多數候選人本能地想坐在椅子上解題。不要這樣!起身到白板前畫一個範例。沒有範例很難解決問題。

確保你的範例足夠有趣——不要太小、不要太難、也不要是特殊案例。

使用特殊案例作為範例是出奇常見的錯誤。特殊案例可能讓你看到不存在的模式,或讓你錯過實際存在的模式。很難區分「對這個問題有效」和「普遍有效」的差別。

例如,計算兩個排序且不重複的陣列有多少共同元素的問題:

  • 壞範例:A: [1, 3, 8, 9]、B: [3, 4, 5, 10](太小,且兩個陣列長度相同)
  • 稍好但仍是特殊案例:A: [1, 3, 8, 9]、B: [2, 3, 4, 5, 10](只有一個共同元素,且在兩個陣列中的 index 相同)
  • 好範例:A: [1, 5, 9, 13, 14, 20, 21]、B: [1, 9, 10, 11, 13, 14, 15, 16, 21](夠大、多個共同元素分散在陣列各處、甚至有相鄰的重疊元素如 13 和 14)

Step 3: Design a Brute Force Algorithm#

一聽到面試問題,就先想辦法得到一個解法,即使不完美。你可以在 brute force 的基礎上優化。

設計演算法時,別忘了考慮:

  • Space 和 time complexities 是什麼?
  • 如果資料量很大會怎樣?
  • 你的設計是否造成其他問題?(例如你在建一個修改過的 binary search tree,你的設計是否影響 insert/find/delete 的時間?)
  • 如果有其他 issues,你是否做了正確的 trade-offs?
  • 如果面試官給了特定的資料特徵(例如資料是年齡、或已排序),你有沒有利用這些資訊?

一個差的解法總比沒有解法好。說出你的差解法,然後說明它的問題所在。

Step 4: Optimize the Brute Force#

一旦有了解法,就專注於讓它更好。

如果你有 brute force 演算法,用你的範例手動執行它(不是寫程式碼),然後尋找可以優化的地方。具體來說,尋找 BUD——Bottlenecks(瓶頸)、Unnecessary work(不必要的工作)、Duplicated work(重複的工作):

  • Bottlenecks:有沒有某段程式碼特別耗時?例如你的演算法第一步是 O(N log N)、第二步是 O(N),優化第二步沒什麼意義——第一步永遠是瓶頸。瓶頸也可能是被重複呼叫的特別慢的程式碼段。
  • Unnecessary work:有沒有你在做的其實不必要的事情?例如你在 tree 的兩邊搜尋某個元素,但你其實有線索知道它在哪一邊。
  • Duplicated work:有沒有你在重複做的事情?例如你持續搜尋相同的元素,這就構成了重複工作,你可以用 hash table 來優化。

當然,如果一個全新的、不相關的方法出現在你腦中,不要害怕從頭開始。

Step 5: Understand the Code#

面試者在這一步上花的時間太少,結果往往寫出草率且不正確的程式碼。

在白板上寫程式碼非常慢,測試也更慢。所以確保你真正理解你在做什麼。

在開始 coding 之前,詳細地跑一遍你的演算法。例如,合併兩個排序陣列到一個新的排序陣列,你應該在 coding 前有這樣的邏輯:

  1. 初始化兩個 pointers p 和 q,分別指向 A 和 B 的開頭
  2. 初始化 index k 指向結果陣列 R 的開頭
  3. 比較 p 和 q 的值
  4. 如果 A[p] 較小,將 A[p] 放入 R[k],p 和 k 遞增
  5. 如果 B[q] 較小,將 B[q] 放入 R[k],q 和 k 遞增
  6. 回到步驟 3

你不需要寫出這些步驟,但你需要理解到這個層次。

Step 6: Implement the Code#

不需要趕著寫程式碼;事實上,趕進度很可能害到你。慢慢來,用有條理的步調,記住以下建議:

  • 善用 data structures:在適當的地方使用好的資料結構。例如,如果問題涉及找一群人中的最小年齡,考慮定義一個 data structure 來表示一個人。這向面試官展示你在乎良好的 object-oriented design。
  • 先模組化你的程式碼:如果你的演算法有離散的步驟,把它們拆成獨立的 functions。這不僅展示良好設計,還能幫你避免繁瑣的工作。例如,作為更大演算法的一部分,你需要將字母 A-Z 轉換成數字 0-26,把它模組化成另一個 function 就不用真的寫出來了。
  • 不要擠程式碼:許多候選人從白板中間開始寫,前幾行還好,但白板沒那麼大。

如果在 coding 過程中感到困惑,停下來回到你的範例。不需要一口氣寫完。中斷比寫出無意義的程式碼好得多。

Step 7: Test#

候選人很少能寫出完美的程式碼。不測試會有兩個問題:第一,程式碼中會留下 bugs;第二,這暗示你不是一個會好好測試程式碼的人。

用以下五步法來找 bugs:

  1. 概念性地審查程式碼:每一行的意義是什麼?它是否做了你認為它該做的事?
  2. 審查容易出錯的地方:程式碼中有沒有看起來奇怪的地方(例如 int n = length - 2)?邊界條件正確嗎?遞迴的 base cases 正確嗎?
  3. 用小範例測試:你之前想要範例夠大,但現在你需要一個小的。太大的範例會花很長時間跑,還可能讓你急於測試而錯過嚴重的 bugs。
  4. 針對潛在問題設計測試:什麼樣的測試案例會測出特定的潛在問題?例如,你可能覺得當一個陣列比另一個短得多時會有 bug,就專門測試這種情況。
  5. 測試 error cases:最後,測試真正的錯誤條件。如果輸入是 null string 或負值會怎樣?

當你發現錯誤(你一定會的),放輕鬆。幾乎沒有人能寫出零 bug 的程式碼。重要的是你如何反應——指出錯誤,仔細分析 bug 為什麼出現,然後正確修復它。不要只為一個測試案例打補丁,要確保修復是正確的。

Algorithm Questions: Four Ways to Create an Algorithm#

沒有解決 algorithm 問題的萬能方法,但以下四種方法很有用。練習越多,越容易判斷該用哪種方法。

這四種方法可以混合搭配。例如,你用了 Simplify and Generalize 之後,可能接著想用 Pattern Matching。

Approach 1: Pattern Matching#

Pattern Matching 是將問題與類似的問題聯繫起來,看看能否修改已知的解法來解決新問題。這也是為什麼多做題很重要——做的題目越多,你就越厲害。

範例:一個排序陣列被旋轉過,使得元素可能以 3 4 5 6 7 1 2 的順序出現。你如何找到最小元素?

這題與以下兩個已知問題最相似:

  1. 在未排序陣列中找最小元素
  2. 在陣列中找特定元素(例如 binary search)

在未排序陣列中找最小值不是一個特別有趣的演算法(直接遍歷所有元素),也沒有利用陣列已排序的資訊,所以不太適用。

binary search 非常適用。你知道陣列是排序但被旋轉過的,所以它先遞增、然後重置、再遞增。最小元素就是重置點。比較首尾和中間元素(3 和 6),如果範圍仍在遞增,表示重置點在之後。繼續用 binary search 的思路,尋找 LEFT > RIGHT 的範圍來定位重置點。

Approach 2: Simplify and Generalize#

Simplify and Generalize 中,我們改變問題的限制條件(資料類型、大小等)來簡化問題,然後嘗試解決簡化後的問題。一旦有了簡化版的演算法,就將問題還原到原始形式,看看能否應用新的解法。

範例:贖金信可以從雜誌中剪下單字組成新句子。如何判斷一封贖金信(字串)能否由給定的雜誌(字串)組成?

我們可以先簡化問題:不是剪下單字,而是剪下字母來組成贖金信。簡化版的問題可以用一個陣列解決——每個位置對應一個字母,計算贖金信中每個字母出現的次數,然後遍歷雜誌看是否有足夠的字母。

推廣到原始問題時,做法非常類似。這次不是建立字母計數的陣列,而是建立一個 hash table,每個單字對應它出現的次數。

Approach 3: Base Case and Build#

Base Case and Build 建議你先解決 base case(例如只有一個元素),然後嘗試解決 1 和 2 個元素的情況(假設你已有 1 個元素的答案),再解決 1、2、3 個元素的情況(假設你已有 1 和 2 個元素的答案),以此類推。

這種方法經常自然地引導出 recursive algorithms

範例:設計一個演算法印出字串的所有排列組合。為簡化起見,假設所有字元都是唯一的。

考慮字串 abcdefg:

  • Case “a” -> {a}
  • Case “ab” -> {ab, ba}
  • Case “abc” -> ?

如果我們已經知道 “ab” 的排列答案,如何產生 “abc” 的排列?額外的字母是 c,所以我們可以把 c 插入每個可能的位置:

  • merge(c, ab) -> cab, acb, abc
  • merge(c, ba) -> cba, bca, bac

我們可以用 recursive algorithm 解決這個問題:先砍掉最後一個字元,產生 s[1…n-1] 的所有排列,然後將 s[n] 插入排列字串的每個位置。

Approach 4: Data Structure Brainstorm#

Data Structure Brainstorm 方法雖然感覺有點取巧,但經常有效。在這個方法中,我們簡單地跑一遍常見的 data structures 清單,逐一嘗試應用。這個方法之所以有效,是因為很多演算法一旦找到正確的 data structure 就變得很直觀。

一個好的提示是:如果面試官沒有指定資料的 data structure,那你很可能需要自己選一個,而這可能就是解題的關鍵。

範例:你正在建一個有兩個方法的 class:addNumber(n) 和 getMedian()。addNumber(n) 會被某個外部函式以整數值週期性呼叫。當 getMedian() 被呼叫時,你需要有效率地回傳所有先前數字的中位數。

讓我們逐一檢視常見的 data structures:

  • Linked list? 可能不行——linked list 不擅長存取和排序數字。
  • Array? 也許可以保持元素排序,但那可能太慢了。先保留這個選項。
  • Binary tree? 這是可能的,因為 binary tree 天生就有排序功能。如果 binary search tree 完美平衡,頂端就可能是中位數。但要注意——偶數個元素時,中位數是中間兩個數的平均值。
  • Heap? Heap 擅長基本排序和追蹤最大/最小值。如果用兩個 heaps——一個 max heap 存較小的一半,一個 min heap 存較大的一半——你就可以從兩個 heap 的根取得潛在的中位數元素。如果 heaps 大小不同,只需從一個 heap pop 一個元素 push 到另一個來重新平衡。

你做的題目越多,你對該使用哪種 data structure 的直覺就越好。Hash tables、trees、tries 和 heaps 是一些最常用來解題的 data structures。

Object-Oriented Design#

Object-oriented design (OOD) 問題有兩種類型:為一個軟體設計 OOD,或為一個現實世界的物件設計 OOD。儘管看似差異很大,但處理方式基本相同:

  1. 你的目標是什麼? 例如,你被要求設計一副撲克牌的 classes——什麼類型的牌?標準撲克牌、UNO 牌、還是其他?它該有多通用?
  2. 核心物件是什麼? 例如,為一家餐廳做 OOD,核心物件可能是 Restaurant、Patron、Party、Host、Server、Busser、Table 等。每個都會成為一個 class。
  3. 物件之間如何關聯? 可能只有一個 Restaurant(singleton class)。Restaurant 有多個 Servers、一個 Host、多個 Bussers、多個 Tables、多個 Parties 和多個 Patrons。每個 Table 有一個 Server 和一個 Party。尋找並移除冗餘——例如 Restaurant 可能不需要 Patrons 的 list,因為可以從 Parties 的 list 取得。
  4. 物件如何互動? 想想餐廳中主要的動作是什麼。例如,一個 Party 向 Host 預約 Reservation,Host 安排 Party 到一個 Table 並分配一個 Server。每個動作通常對應一個或多個 methods。
  5. 有沒有需要特別演算法的地方? 在某些情況下,可能有影響設計的演算法。例如,實作 findNextReservation(int partySize) 可能需要對 reservations 的引用方式做一些改變。與面試官討論這些細節。

記住,OOD 問題需要大量與面試官的溝通,討論設計的彈性和各種 trade-offs。OOD 問題沒有所謂的「正確答案」。

Scalability Questions#

Scalability 問題對候選人來說可能很嚇人,特別是應屆畢業生或沒有做過大型系統設計的人。你可能會被問到很廣泛的問題(例如設計 Google Maps),或更具體的問題(例如設計一個 web crawler)。

從根本上說,這些是 problem-solving 問題,應該以此方式來處理。例如,Google 不期望你知道 MapReduce 和 BigTable 等 Google 特有的技術。

Preparation#

準備 scalability 問題的最好方法之一是閱讀真實系統的設計。它有哪些組件?設計者為什麼做出這些選擇?有什麼 trade-offs?

這些系統可以是大型產品(如 Twitter、Google Maps)或現有產品的組件(如 MapReduce、BigTable)。你分析的系統越多,你就越能設計新系統。重點不是記住這些系統的設計,而是分析它們為什麼被這樣設計。

How to Approach#

以下四步法通常很有效:

  1. 界定問題範圍(Scope the problem):你不能在不知道系統要做什麼的情況下建造它。在問問題的同時,列出所需的演算法或組件。例如,設計 Google Maps,你可能需要路線規劃、顯示地圖、蒐集交通資料等。
  2. 架構設計(Structure the architecture):概述問題的關鍵組件。例如,你可能需要 data store、crawler、前端等。這是站起來用白板的好時機,讓你和面試官的思路都清晰有條理。
  3. 找出關鍵問題(Identify key issues):描述系統會遇到的主要問題。應用程式是 read-heavy 還是 write-heavy?有什麼 bottlenecks 或關鍵資源?是否需要分散式資料庫?
  4. 解決問題(Resolve the issues):現在你已經找出了挑戰,是時候解決它們了。如果不確定該討論哪一個,就問面試官。

雖然這個架構是線性的,但你不需要嚴格遵循。找出關鍵問題可能導致核心架構的調整,或你可能有後續問題需要與面試官討論。在整個過程中優先保持溝通

Testing Interviews#

測試相關的工作有許多名稱,容易混淆,但大致可分為兩類:

  1. Quality assurance / Software test engineer / Tester:這些職位的人設計和執行測試計畫。不是每個軟體功能都能被測試——有太多不同的配置。Testers 會優先選擇最重要的功能進行測試。
  2. Software engineer in test / Software design engineer in test:這些職位的人是 coders,但不是建造功能,而是寫自動化測試程式碼。例如,他們可能透過自動建立大型檔案集來測試 Microsoft Word,然後自動化 Word 的開啟、處理和關閉流程。

如果測試工作需要 coding(無論職稱為何),你可能會被問到 coding 問題。這意味著你需要在一般的測試問題之上,也要練習 coding、algorithms 和 data structures。

此外,你可以預期以下三類測試問題:

  1. 你如何測試這個真實世界的物件?
  2. 說明你如何測試這個軟體。
  3. 測試一個 method(可能是你剛寫的)。

開發者在某些情況下也可能被問到測試問題。

Testing a Real-World Object#

測試迴紋針和筆跟測試 Office 或 Gmail 有什麼關係?看似不多,但你的面試官肯定認為有關。

面試官用這類問題測試你處理模糊性的能力、理解預期和非預期行為的能力、以及結構化和溝通你想法的能力。

以下是測試一支筆的六步法範例:

  1. 問清楚物件是什麼:筆看似不含糊,但其實可以是鋼筆、兒童彩色筆、太空人用的筆。問面試官釐清。
  2. 誰在使用它,用來做什麼? 小孩在畫畫,所以筆需要粗一點。他們可能在紙上、地板上畫。
  3. 有什麼非預期的用途? 吃它、在其他小孩或牆壁上畫、踩它。
  4. 有額外的壓力情境嗎? 高溫天氣、寒冷天氣等。不是所有都適用於每個問題。
  5. 能否優雅地失敗? 理想情況下我們希望筆不會壞,但如果壞了,能否防止它爆開?
  6. 測試案例是什麼? 到這裡,我們已經發現需要測試:
    • Nontoxic:討論成分與毒物控制
    • Washable:在地板、牆壁、衣服和皮膚上測試
    • Thickness:進行一系列測試了解什麼粗細對兒童不舒適,並進行實際測試
    • Softness/lightness:材料應為輕量塑膠,被打到不會太痛
    • Durability:筆不應輕易斷裂,與面試官討論需要承受多大壓力
    • Leakage:如果筆壞了,確保墨水不會造成損害

你可能注意到測試與設計息息相關——這是正常的。畢竟,測試者需要分析物件是否符合設計需求。

Testing a Piece of Software#

測試軟體的方法與測試真實世界物件非常類似。

範例:說明你如何測試一個 email client。

  1. 問清楚模糊的地方:是企業 email client 還是個人的?Web-based 還是 desktop?
  2. 使用者是誰? 企業使用者和個人使用者在安全性、儲存、維護等方面有非常不同的需求。
  3. 功能集是什麼? 收信、寄信等是基本功能,但其他功能可能更需要深入對話。Email 是否在伺服器上?是否加密?
  4. 有非預期的使用或壓力情境嗎? 例如大量 email 湧入、巨大附件等。
  5. 失敗時如何優雅處理? 如果檔案太大無法處理,client 至多應該拒絕附件,但不應該永久凍結。
  6. 什麼可以自動化,什麼必須手動測試? 可以測試的東西幾乎無窮無盡。重要的是你聚焦在最大或最有趣的項目上,討論如何測試它們。

Testing a Method#

寫完程式碼後,你可能會被要求測試程式碼或產生測試案例。

範例:測試一個排序陣列的 method。

  1. 問清楚模糊的地方:陣列要升序還是降序排序?對時間、記憶體等有什麼期望?陣列的資料型別是什麼?
  2. 需要測試什麼? 在很多情況下可能只是結果(陣列是否排序?),但在其他情況下你可能還需要檢查附帶效果(例如記憶體使用量、其他資料是否被改變)。
  3. 寫出預期案例:這是最簡單的——其中一個測試案例應該是未排序的陣列。
  4. 寫出極端案例:檢查 null、空陣列、超大陣列、已排序的陣列等等。

Questions and Answers#

Too Much Prep, Too Little Time#

我在一家諮詢公司當了幾年的軟體工程師,但工作很無聊,大部分是程式碼維護。我寫的少量程式碼是 C 語言——沒有 object-oriented programming。我覺得自己沒有在學習,也沒有在進步。

我夢想在像 Microsoft 這樣的大公司工作。但我覺得我需要好幾個月來準備面試。我應該辭職專心準備嗎?

老實說,辭職專門準備面試並不是最好的主意:

  • 首先,Microsoft 這類公司的錄取率不到 5%,即使大量準備,機會仍然渺茫。
  • 其次,你可能需要向面試官解釋為什麼辭職,而「為了準備面試」不是一個好理由。
  • 第三,長期密集準備的價值取決於你的弱點。如果只是缺乏 object-oriented programming 知識,你可能不需要幾個月來學。

建議只在以下三個條件都成立時才考慮辭職:

  1. 你知道不準備也能找到和現在一樣好的工作
  2. 你無法同時工作和準備
  3. 準備確實需要很長時間

如果你決定辭職,建議做一些更有形的事情。與其只專注於面試準備,不如花時間建造一個軟體產品或網站,用它來學習你需要的知識(如 OOP 等)。這樣當雇主問你辭職後在做什麼,你可以說你嘗試創業,而且你的履歷上也會有具體的東西來展示經驗。

Know It All#

為了準備 Google 面試,我複習了所有大學電腦科學課程的內容。我在 algorithms 上花了最多時間,特別是 dynamic programming 和 tree balancing。但我仍然不確定能否在面試中完成這樣的題目。

成功的候選人是如何處理這些問題的?

讓我們退一步,站在面試官的角度思考。他們想知道你是否聰明、能否寫程式。具體知識並不重要,除非它是:(1) 工作所需,或 (2) 基本到你應該知道的電腦科學教育內容。

在 tree 中插入元素屬於第二類——trees 在業界不常直接使用,但它們太基礎了,你怎麼能不知道呢?

然而,tree balancing 不屬於這個類別。你應該知道它的存在,大致了解它如何運作(當兩邊太不平衡時進行旋轉),但細節不是必須的。跳過它。

複雜的 dynamic programming 問題——演算法書中那種——通常對面試來說太複雜了。它們可以出現,但通常不是好的面試題。

不過,簡單的 dynamic programming 問題——memoization(在 recursive calls 之間快取結果)——是會出現的。花一些時間練習這類問題,但它們沒有你想像的那麼複雜。

另外記住,面試中的程式碼通常很短——你通常不會寫超過 20 行。在設計演算法、測試程式碼和修復錯誤之間,根本沒有時間寫更多。所以放輕鬆,專注於準備一般範圍的問題——那些你可以在 45 分鐘內解決的問題。

Misleading Information#

我在 Microsoft 面試時被問了一個難題。我想到了一個 brute force 解法,面試官說 brute force 可以。我開始寫程式碼,但還沒寫完,面試官就開始用問題轟炸我。他的問題引導我找到了更好的解法。我也注意到我的程式碼有一些 bugs 和其他錯誤,但這些看起來都是小問題。

我覺得他告訴我 brute force 可以是在誤導我,結果我被拒絕了。我有機會抗議嗎?

這裡有很多需要分析的問題:

  1. 面試官是否在誤導你說 brute force 可以(其實不行)? 有可能你遇到了一個不好的面試官。即使在最好的公司,壞面試官也是存在的。但更可能的是,面試官是在觀察你是否會自己注意到需要尋找更優解,或者你是否會滿足於一個勉強可以的解法。根據面試進度,面試官也可能只是想先看你的程式碼,然後再引導你優化。
  2. 這是否導致你被拒絕? 很難說這是唯一原因。首先,通常 50% 到 75% 的候選人在每個階段被拒絕,所以你幾乎必須每件事都做對才能不被拒。其次,不太可能是單一因素導致拒絕。你的面試官的想法可能更像是:「嗯,我挺喜歡這個人,但他的解法不太好,程式碼也有一些 bugs 和其他錯誤。」
  3. 你能抗議嗎? 不建議。無論你對 recruiter 說什麼,他或她幾乎一定會站在面試官那邊。你更可能因此損害你在公司的聲譽,不值得。

面試很難,而且很不幸地,很隨機。大多數 Google 的同事都承認他們覺得自己第二次可能不會通過面試。幸運的是,大多數公司都理解這一點,通常讓你六個月後再次申請。

Additional Resources#