如果你應徵的是軟體開發職位,你需要準備一套特殊的技能。是的,你會被要求寫程式。不,你通常不會用電腦——只會有一塊白板,或有時只是一張紙。白板與面試中的程式撰寫需要一套特別的技巧,即使是最優秀的工程師也可能在 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#
在練習面試題時,品質比數量重要。網路上有成千上萬的範例面試題,不要試著背答案——那不可能做到,也不會幫助你。
花時間解題,嘗試以下七步練習法:
- 從零實作常見的 data structures 和 algorithms,先手寫再用電腦。很多面試題需要這些知識,你必須非常熟練。同時推導這些演算法的 time complexity。
- 用真正的面試題練習。演算法或資料結構的書可能有用,但往往超出面試所需的範圍。讓面試題引導你學習方向。
- 自己真正嘗試解題。如果卡住了,這很正常——題目本來就設計得很有難度。不要放棄,繼續思考。
- 在紙上寫出演算法的程式碼。你一直在電腦上寫程式,已經習慣了編譯器、code completion 等工具。面試中沒有這些,所以要用最傳統的方式寫程式碼,精確到每個分號。
- 手動測試你的程式碼!不能用電腦作弊!
- 把程式碼原封不動地輸入電腦。重新執行你試過的測試案例和一些新的測試案例。
- 列出你犯的所有錯誤,分析你最常犯哪些類型的錯誤。有沒有特定的錯誤模式?
你可以在 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 Structures | Algorithms | Concepts |
|---|---|---|
| Linked lists | Breadth-first search | Bit manipulation |
| Binary trees | Depth-first search | Recursion |
| Tries | Binary search | Memoization / Dynamic programming |
| Stacks | Merge sort | Memory (stack vs. heap) |
| Queues | Quick sort | Big-O time |
| Dynamically resizing arrays | Tree 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#
面試本來就設計得很有難度。常態是你一聽到問題就不知道怎麼解,然後你會掙扎、從面試官那裡得到一些提示,最終得到比一開始更好的解法。
遇到難題時不要慌張,開始大聲說出你的思路。以下七步法適用於大多數問題:
- 理解問題:如果有任何不懂的地方,在這裡釐清。特別注意題目中給出的特定細節(例如輸入已排序)。你需要這些資訊。
- 畫一個範例:在腦中解題與在白板上畫出來非常不同。起身到白板前畫一個好的範例——夠大(例如陣列有八個元素)、不是特殊案例。
- 設計一個 brute force 演算法:一聽到問題就先想出一個解法,即使不完美。你可以之後再優化。這是一個起點,也能確保面試官知道你至少走到了這一步。
- 優化 brute force:不一定每次都能做到,但通常可以從 brute force 找到通往最佳解的路徑。
- 理解程式碼:在你有了最佳演算法後,花時間真正理解你的演算法。先不要急著寫程式碼。
- 實作程式碼:如果你對流程感到舒適,就開始實作。
- 測試:完美的白板程式碼很罕見。如果發現錯誤,沒關係,分析為什麼犯錯並修復它。
記住:面試官說結束之前,你都還沒結束!當你想出演算法時,開始思考可能的問題。當你寫完程式碼時,開始尋找 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 前有這樣的邏輯:
- 初始化兩個 pointers p 和 q,分別指向 A 和 B 的開頭
- 初始化 index k 指向結果陣列 R 的開頭
- 比較 p 和 q 的值
- 如果 A[p] 較小,將 A[p] 放入 R[k],p 和 k 遞增
- 如果 B[q] 較小,將 B[q] 放入 R[k],q 和 k 遞增
- 回到步驟 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:
- 概念性地審查程式碼:每一行的意義是什麼?它是否做了你認為它該做的事?
- 審查容易出錯的地方:程式碼中有沒有看起來奇怪的地方(例如
int n = length - 2)?邊界條件正確嗎?遞迴的 base cases 正確嗎? - 用小範例測試:你之前想要範例夠大,但現在你需要一個小的。太大的範例會花很長時間跑,還可能讓你急於測試而錯過嚴重的 bugs。
- 針對潛在問題設計測試:什麼樣的測試案例會測出特定的潛在問題?例如,你可能覺得當一個陣列比另一個短得多時會有 bug,就專門測試這種情況。
- 測試 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 的順序出現。你如何找到最小元素?
這題與以下兩個已知問題最相似:
- 在未排序陣列中找最小元素
- 在陣列中找特定元素(例如 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。儘管看似差異很大,但處理方式基本相同:
- 你的目標是什麼? 例如,你被要求設計一副撲克牌的 classes——什麼類型的牌?標準撲克牌、UNO 牌、還是其他?它該有多通用?
- 核心物件是什麼? 例如,為一家餐廳做 OOD,核心物件可能是 Restaurant、Patron、Party、Host、Server、Busser、Table 等。每個都會成為一個 class。
- 物件之間如何關聯? 可能只有一個 Restaurant(singleton class)。Restaurant 有多個 Servers、一個 Host、多個 Bussers、多個 Tables、多個 Parties 和多個 Patrons。每個 Table 有一個 Server 和一個 Party。尋找並移除冗餘——例如 Restaurant 可能不需要 Patrons 的 list,因為可以從 Parties 的 list 取得。
- 物件如何互動? 想想餐廳中主要的動作是什麼。例如,一個 Party 向 Host 預約 Reservation,Host 安排 Party 到一個 Table 並分配一個 Server。每個動作通常對應一個或多個 methods。
- 有沒有需要特別演算法的地方? 在某些情況下,可能有影響設計的演算法。例如,實作
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#
以下四步法通常很有效:
- 界定問題範圍(Scope the problem):你不能在不知道系統要做什麼的情況下建造它。在問問題的同時,列出所需的演算法或組件。例如,設計 Google Maps,你可能需要路線規劃、顯示地圖、蒐集交通資料等。
- 架構設計(Structure the architecture):概述問題的關鍵組件。例如,你可能需要 data store、crawler、前端等。這是站起來用白板的好時機,讓你和面試官的思路都清晰有條理。
- 找出關鍵問題(Identify key issues):描述系統會遇到的主要問題。應用程式是 read-heavy 還是 write-heavy?有什麼 bottlenecks 或關鍵資源?是否需要分散式資料庫?
- 解決問題(Resolve the issues):現在你已經找出了挑戰,是時候解決它們了。如果不確定該討論哪一個,就問面試官。
雖然這個架構是線性的,但你不需要嚴格遵循。找出關鍵問題可能導致核心架構的調整,或你可能有後續問題需要與面試官討論。在整個過程中優先保持溝通。
Testing Interviews#
測試相關的工作有許多名稱,容易混淆,但大致可分為兩類:
- Quality assurance / Software test engineer / Tester:這些職位的人設計和執行測試計畫。不是每個軟體功能都能被測試——有太多不同的配置。Testers 會優先選擇最重要的功能進行測試。
- Software engineer in test / Software design engineer in test:這些職位的人是 coders,但不是建造功能,而是寫自動化測試程式碼。例如,他們可能透過自動建立大型檔案集來測試 Microsoft Word,然後自動化 Word 的開啟、處理和關閉流程。
如果測試工作需要 coding(無論職稱為何),你可能會被問到 coding 問題。這意味著你需要在一般的測試問題之上,也要練習 coding、algorithms 和 data structures。
此外,你可以預期以下三類測試問題:
- 你如何測試這個真實世界的物件?
- 說明你如何測試這個軟體。
- 測試一個 method(可能是你剛寫的)。
開發者在某些情況下也可能被問到測試問題。
Testing a Real-World Object#
測試迴紋針和筆跟測試 Office 或 Gmail 有什麼關係?看似不多,但你的面試官肯定認為有關。
面試官用這類問題測試你處理模糊性的能力、理解預期和非預期行為的能力、以及結構化和溝通你想法的能力。
以下是測試一支筆的六步法範例:
- 問清楚物件是什麼:筆看似不含糊,但其實可以是鋼筆、兒童彩色筆、太空人用的筆。問面試官釐清。
- 誰在使用它,用來做什麼? 小孩在畫畫,所以筆需要粗一點。他們可能在紙上、地板上畫。
- 有什麼非預期的用途? 吃它、在其他小孩或牆壁上畫、踩它。
- 有額外的壓力情境嗎? 高溫天氣、寒冷天氣等。不是所有都適用於每個問題。
- 能否優雅地失敗? 理想情況下我們希望筆不會壞,但如果壞了,能否防止它爆開?
- 測試案例是什麼? 到這裡,我們已經發現需要測試:
- Nontoxic:討論成分與毒物控制
- Washable:在地板、牆壁、衣服和皮膚上測試
- Thickness:進行一系列測試了解什麼粗細對兒童不舒適,並進行實際測試
- Softness/lightness:材料應為輕量塑膠,被打到不會太痛
- Durability:筆不應輕易斷裂,與面試官討論需要承受多大壓力
- Leakage:如果筆壞了,確保墨水不會造成損害
你可能注意到測試與設計息息相關——這是正常的。畢竟,測試者需要分析物件是否符合設計需求。
Testing a Piece of Software#
測試軟體的方法與測試真實世界物件非常類似。
範例:說明你如何測試一個 email client。
- 問清楚模糊的地方:是企業 email client 還是個人的?Web-based 還是 desktop?
- 使用者是誰? 企業使用者和個人使用者在安全性、儲存、維護等方面有非常不同的需求。
- 功能集是什麼? 收信、寄信等是基本功能,但其他功能可能更需要深入對話。Email 是否在伺服器上?是否加密?
- 有非預期的使用或壓力情境嗎? 例如大量 email 湧入、巨大附件等。
- 失敗時如何優雅處理? 如果檔案太大無法處理,client 至多應該拒絕附件,但不應該永久凍結。
- 什麼可以自動化,什麼必須手動測試? 可以測試的東西幾乎無窮無盡。重要的是你聚焦在最大或最有趣的項目上,討論如何測試它們。
Testing a Method#
寫完程式碼後,你可能會被要求測試程式碼或產生測試案例。
範例:測試一個排序陣列的 method。
- 問清楚模糊的地方:陣列要升序還是降序排序?對時間、記憶體等有什麼期望?陣列的資料型別是什麼?
- 需要測試什麼? 在很多情況下可能只是結果(陣列是否排序?),但在其他情況下你可能還需要檢查附帶效果(例如記憶體使用量、其他資料是否被改變)。
- 寫出預期案例:這是最簡單的——其中一個測試案例應該是未排序的陣列。
- 寫出極端案例:檢查 null、空陣列、超大陣列、已排序的陣列等等。
Questions and Answers#
Too Much Prep, Too Little Time#
我在一家諮詢公司當了幾年的軟體工程師,但工作很無聊,大部分是程式碼維護。我寫的少量程式碼是 C 語言——沒有 object-oriented programming。我覺得自己沒有在學習,也沒有在進步。
我夢想在像 Microsoft 這樣的大公司工作。但我覺得我需要好幾個月來準備面試。我應該辭職專心準備嗎?
老實說,辭職專門準備面試並不是最好的主意:
- 首先,Microsoft 這類公司的錄取率不到 5%,即使大量準備,機會仍然渺茫。
- 其次,你可能需要向面試官解釋為什麼辭職,而「為了準備面試」不是一個好理由。
- 第三,長期密集準備的價值取決於你的弱點。如果只是缺乏 object-oriented programming 知識,你可能不需要幾個月來學。
建議只在以下三個條件都成立時才考慮辭職:
- 你知道不準備也能找到和現在一樣好的工作
- 你無法同時工作和準備
- 準備確實需要很長時間
如果你決定辭職,建議做一些更有形的事情。與其只專注於面試準備,不如花時間建造一個軟體產品或網站,用它來學習你需要的知識(如 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 可以是在誤導我,結果我被拒絕了。我有機會抗議嗎?
這裡有很多需要分析的問題:
- 面試官是否在誤導你說 brute force 可以(其實不行)? 有可能你遇到了一個不好的面試官。即使在最好的公司,壞面試官也是存在的。但更可能的是,面試官是在觀察你是否會自己注意到需要尋找更優解,或者你是否會滿足於一個勉強可以的解法。根據面試進度,面試官也可能只是想先看你的程式碼,然後再引導你優化。
- 這是否導致你被拒絕? 很難說這是唯一原因。首先,通常 50% 到 75% 的候選人在每個階段被拒絕,所以你幾乎必須每件事都做對才能不被拒。其次,不太可能是單一因素導致拒絕。你的面試官的想法可能更像是:「嗯,我挺喜歡這個人,但他的解法不太好,程式碼也有一些 bugs 和其他錯誤。」
- 你能抗議嗎? 不建議。無論你對 recruiter 說什麼,他或她幾乎一定會站在面試官那邊。你更可能因此損害你在公司的聲譽,不值得。
面試很難,而且很不幸地,很隨機。大多數 Google 的同事都承認他們覺得自己第二次可能不會通過面試。幸運的是,大多數公司都理解這一點,通常讓你六個月後再次申請。
Additional Resources#
- 造訪 www.crackingthetechcareer.com ↗ 獲取額外資源
- 造訪 www.careercup.com ↗ 獲取數千道面試題和答案