2019/03/09~2019/04/03【Software Engineer, Frontend Development】錄取
由於大一的室友在Appier工作,剛好趁著就業博覽會期間讓他幫我內推,順便賺個推薦獎金,也幸運地得到了面試機會。公司地點位於101大樓附近的華南銀行大樓,是台北的市中心,生活機能極佳;大樓的門禁非常先進而且嚴謹,要先到發卡機填寫資料,接著拿起話筒與要拜訪的公司通話,確認無誤後才會拿到拿到一片塑膠磁扣,大樓閘門及電梯上下樓都需要掃描磁扣,到了6樓的Appier公司大門後還有最後一層門禁,需要用對講機與員工聯絡後才能踏入公司大門。
請見官方職缺連結—Software Engineer, Frontend Development,Appier 沛星互動科技open_in_new。
本次面試是由2位Tech Leader CT及CJ負責,分別是對外產品及內部產品的前端技術主管,並且直接表明此次面試只會有2題Technical Tests,一題pure css,一題javascript。
首先還是讓我進行自我介紹來開場,雖然我自認為用心地介紹著自己的經歷,但兩位面試官似乎沒有什麼回饋,只有提問「你覺得過去的經歷中遇過最困難的是?」,等我回答地差不多時,面試官有點突兀地說,由於時間關係,要直接進行Technical Test了。
CJ給出的題目是他自己工作中實際遇到的Card實作問題,題目已經準備在jsbin了:
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title></head><body> <div class="card"> <div class="content">I am content</div> <div class="overlay">I am overlay</div> <div class="more">Show more</div> </div></body></html>
.card { height: 200px; width: 200px; background: #efefef; border-radius: 3px;}.content {}.overlay {}.more {}
CJ僅提供了html結構、class命名以及Card容器的基本樣式,並且demo最終要我實作的效果:
(實作參考解答附於文末)
由CT出題,題目同樣準備在jsbin:
function run(promiseFactories, concurrency) { return new Promise(resolve => { // implement it });}
function delay(data, delay) { return () => new Promise(resolve => setTimeout(() => { console.log('resolves', data); // Debug resolve(data); }, delay * 1000))}
run([ delay('a', 1), delay('b', 2), delay('c', 4), delay('d', 2), delay('e', 3), delay('f', 2), delay('g', 1)], 3).then(console.log);
// prints a (pause 1s) b (pause 1s) d (pause 1s) c (pause 1s) e f g (in the same time)// then prints ['a', 'b', 'c', 'd', 'e', 'f', 'g']// Visualization: https://docs.google.com/drawings/d/1R1z2-3jDTK2muutdSsze1W1_nF8SGHZBAZhBRzeRjtw/edit?usp=sharing
是一個function陣列,其中每個function都會回傳一個promise,並且回傳的promise會在經過指定的秒數後resolve,此題解答的目標是實作這個function,使其回傳的promise resolve時得到promiseFactories的所有執行結果,此執行結果必須是一個陣列,並且保留promiseFactories元素之間的順序。
題目乍看之下很冗長且難懂,但其實只要把他想像成實作Promise.all()這個method即可,唯一不同之處在於,接受的參數是promise陣列,而本題的接受的參數則是function陣列。
原先預定還會有變化題,是要考量到的第二個參數,例如本題設定為3,即同時間只能有3個Promise在等待resolve,概念與thread pool類似,面試官還有特別準備視覺化圖片供參考(如下圖),但是由於時間不夠,這題就直接跳過了。
(實作參考解答附於文末)
實作過程中可以隨時說明自己的想法,與面試官交流解答思路,也可以查詢任何網路文件,面試官在整個過程中也都會十分和善地提供各種提示。例如我對CSS動畫其實非常不熟,當場各種查詢MDN和Stackoverflow,然後才在面試官的引導下逐步實作出來;第二題解答的過程中因為太緊張而犯了一些Typo,面試官也會直接指出。
這關是HR跟我安排二面,並說明後續如果能有三面及四面的話面試官將會是誰。二面會有CTO及Engineering VP;三面是PM;最後階段的四面則由CEO進行面試。
這大概是我自介過最多次的一天,先是CTO,接著Engineer VP,最後HR考量到我要南北奔波,當天想直接幫我塞一個Team Lead面試,不過Team Lead不在,便由Team Member代替。
講話節奏很跳躍的交大學長,非常認真而且用心地閱讀我的履歷,基本上看到或聽到你介紹某個關鍵字時,就會抓著當下的討論拋出很艱澀的大哉問,例如我介紹Scribo時,就被問到「你的MongoDB怎麼做text search?」「MongoDB每個document有32MB上限,如果你的文章超過32MB要怎麼辦?」
表面上彼此是在亂聊,但其實跟如此Geek的人聊天時內心是很暢快的!此外,CTO是穿著藍白拖鞋和襪子進來會議室的,我一直投入在技術的討論上,直到他把腳盤上來沙發時,我才注意到原來他在公司裡是穿拖鞋XD
Engineer VP是一位講中文有點口音的新加坡人,並且為了Appier全家移民台灣。這關主要是針對人格特質和團隊合作進行面試,講到一半時還要求與我用英文交談。本以為他是屬於管理相關的主管,應該不會問技術題,不過還是被問到「你對PWA了解多少?」
HR與CTO及Engineer VP討論過後,當下就挑出了適合我的Team,不過Team Lead不在,因此這關由一位負責Data Science及另一位負責Backend的Team Member進行面試。原則上針對此團隊負責的專案討論,不過也有被問到一些不易回答的問題,像是「前端開發上最近遇過什麼難題,能否用白話解釋到我們backend或PM都能聽得懂?」
前一輪已經和Team Member聊過了,此次基本上只是和Team Lead見個面閒聊一下,一樣我先自介,接著就補問一些上一輪面試沒問到的問題,於是就這樣順順地度過這關了。
最後一輪面試是由CEO及COO擔任面試官,一如以往自我介紹後,以下是仍有印象的對話:
CEO:為什麼會想來Appier?
我:(自由發揮)
COO:你認為你最大的優點及缺點是什麼?
我:(按照自己的套路回應)
我:這個職位可能面對的最大困難是?
COO:會參與客戶溝通,還有把AI為人詬病的black box運算過程視覺化,讓客戶能夠理解
我:為什麼會面試這麼多次?
CEO&COO:精兵策略。Larry Page在公司500人的時候仍然是親自面試呢!
解答重點:
1<!DOCTYPE html>2<html>3<head>4 <meta charset="utf-8">5 <meta name="viewport" content="width=device-width">6 <title>JS Bin</title>7</head>8<body>9 <div class="card">10 <div class="more">Show more</div>11 <div class="content">I am content</div>12 <div class="overlay">I am overlay</div>13 </div>14</body>15</html>
1.card {2 height: 200px;3 width: 200px;4 background: #efefef;5 border-radius: 3px;6 position: relative;7 overflow: hidden;8}9.content {10 width: 100%;11 height: 180px;12 transition: height 1s ease-in-out;13 overflow: hidden;14}15.overlay {16 position: absolute;17 top: 100%;18 height: calc(100% - 20px);19 width: 100%;20}21.more {22 position: absolute;23 bottom: 0px;24 width: 100%;25 height: 20px;26}27.more:hover ~ .overlay {28 transition: transform 1s;29 transform: translateY(-200px);30}31.more:hover ~ .content {32 height: 0px;33}
解答重點:
1function run(promiseFactories, concurrency) {2 return new Promise(resolve => {3 let results = new Array(promiseFactories.length);4 let counter = 0;5
6 promiseFactories.forEach((pf, idx) => {7 pf().then(result => {8 results[idx] = result;9 counter++;10
11 if (counter === promiseFactories.length) {12 resolve(results);13 }14 });15 });16 });17}18
19function delay(data, delay) {20 return () => new Promise(resolve => setTimeout(() => {21 console.log('resolves', data); // Debug22 resolve(data);23 }, delay * 1000))24}25
26run([27 delay('a', 1),28 delay('b', 2),29 delay('c', 4),30 delay('d', 2),31 delay('e', 3),32 delay('f', 2),33 delay('g', 1)34], 3).then(console.log);35
36// prints a (pause 1s) b (pause 1s) d (pause 1s) c (pause 1s) e f g (in the same time)37// then prints ['a', 'b', 'c', 'd', 'e', 'f', 'g']38// Visualization: https://docs.google.com/drawings/d/1R1z2-3jDTK2muutdSsze1W1_nF8SGHZBAZhBRzeRjtw/edit?usp=sharing
面試過後在家把變化題給實作出來,主要是利用遞迴函式來達成:
1function run(promiseFactories, concurrency) {2 return new Promise(resolve => {3 let results = new Array(promiseFactories.length);4 let resolvedCounter = 0;5 let idxNext = 0;6 let runNextPf = (idxToStore) => {7 if (idxNext >= promiseFactories.length) {8 return9 }10
11 let pf = promiseFactories[idxNext];12
13 idxNext++;14 pf().then(result => {15 resolvedCounter++;16 results[idxToStore] = result;17 runNextPf(idxNext);18 if (resolvedCounter === promiseFactories.length) {19 resolve(results);20 }21 });22 }23
24 promiseFactories.slice(0, concurrency).forEach((_, idx) => {25 runNextPf(idx);26 });27 });28}29
30function delay(data, delay) {31 return () => new Promise(resolve => setTimeout(() => {32 console.log('resolves', data); // Debug33 resolve(data);34 }, delay * 1000))35}36
37run([38 delay('a', 1),39 delay('b', 2),40 delay('c', 4),41 delay('d', 2),42 delay('e', 3),43 delay('f', 2),44 delay('g', 1)45], 3).then(console.log);46
47// prints a (pause 1s) b (pause 1s) d (pause 1s) c (pause 1s) e f g (in the same time)48// then prints ['a', 'b', 'c', 'd', 'e', 'f', 'g']49// Visualization: https://docs.google.com/drawings/d/1R1z2-3jDTK2muutdSsze1W1_nF8SGHZBAZhBRzeRjtw/edit?usp=sharing