# 應徵《軟體工程師-Web後端網頁》 - Terence ## Q1-1 問題:某位使用者在Google Photos儲存的所有照片。 使用的 API 為 ```mediaItems.search``` 。 因為 library 內的媒體類型不只有照片,還包括其他類型(比如影片),所以要用 filter 過濾出照片: ```json= { "filters": { "mediaTypeFilter": { "mediaTypes": ["PHOTO"] } } } ``` ## Q1-2 問題:某位使用者在Google Photos儲存的相簿名稱,以及各相簿裏面的照片 分成幾個步驟: 1. 取得 album 清單,使用的 API 為 ```albums.list``` 2. 以 ```albumId``` 為 filter 條件,呼叫 ```mediaItems.search``` 3. 將第 2 步驟取得的 ```mediaItems``` 過濾,只留下 PHOTO 格式的 items (取出在 ```MediaMetadata``` 底下的 ```photo": {}``` 與 ```video": {}``` 裡的內容做判斷)。 :::info 為何不直接將 ```albumId``` 與 ```mediaTypes``` 一起過濾? API 官方網站提到 ```If you set both the album and the filters, the request results in an error.``` 禁止將兩者一起使用。 ::: :::info 以上 API 都會回傳 ```nextPageToken``` 的字串,若不為空字串表示以下還有內容,可以用這個 token 繼續呼叫 API 取回更多值。 實作時應使用 while/for 來重複呼叫 API ;用遞迴的話要注意 stack 容量的可能問題,實務上會多一項問題要考慮,所以不推薦使用遞迴。 ::: :::info 第 3 步驟雖然執行時間為 *O(n)* ,但項目較多的時候仍然可能造成效能衝擊,可能需要在前端的 GUI 安排對應元件,比如進度條、進度圓圈、取消鈕……等。 ::: --- ## Q2 問題:影像處理佇列程式虛擬碼 ```vb= CLASS Queue constructor() // 初始化 array _items ← [imagePath] isEmpty // 若 array 為 0 表示這是空的 Queue let isZero ← isEqual(_items.length, 0) return isZero addToQueue(imgPath) // 將路徑加到 array 最後 _items.insertAt(imgPath, _items.length) dequeue() // 取出第 1 個影像的路徑、移除 let item ← _items[0] _items.removeFrom(0) return item removeFromQueue(imgPath) // 線性遍歷找出相符的影像路徑並自 queue 內移除 FOR i ← 0 TO _items.length - 1 IF _items[i] == imgPath THEN let item ← _items[i] _items.removeFrom(i) return item END IF END FOR // 在正常情況下,不太可能找不到;以下放置處理錯誤的程式碼 // ... END CLASS ``` :::info ```removeFromQueue()``` 在正常情況下不會發生找不到 node 的現象,可預期不會出現這種異常。所以應該丟出 error 讓上一層的呼叫者知道這個錯誤。 ::: :::info 這個簡單的 queue 無法提供 concurrency 或是 multi-threading 的功能。 不過題目中提到「程式碼皆在主執行緒上,因此不必考慮 class 中的不同方法對於共同的變數會發生競爭情況 (race condition) 」,情境單純,不必過度設計 concurrency 。 ::: Queue 的操作範例(也是虛擬碼): ```verilog= processQueue(Q) WHILE !Q.isEmpty() let imgPath ← Q.dequeue() imageProcess(imgPath, function(){}) main const tasks = [ {"priority": 5, "image_path="user/img_folder/image_776b}, {"priority": 9, "image_path="user/img_folder/image_cefb}, {"priority": 1, "image_path="user/img_folder/image_6f55} ] // 依照重要程度排序 tasks tasks.sort( (a, b) => return a.priority - b.priority ) let q ← new Queue(); FOR i ← 0 TO tasks.length - 1 q.addToQueue(tasks[i]) END FOR // 將 queue 內的工作交給函數 processQueue() 處理 processQueue(q) ``` :::info 題目中提到「先被選擇的照片必須先被處理」,所以依照優先程度 sort() ,把需要提前處理的照片放在 queue 的前面。 ::: :::info 題目中提到「處理照片佇列的過程中,必須要確保同一時間,只能夠對一張照片進行處理」,所以可以將 processQueue 修改成「不可重複進入」,如下: ::: ```diff= + let reentryFlag ← false processQueue(Q) + IF reentryFlag THEN return + reentryFlag = true WHILE !Q.isEmpty() let imgPath ← Q.dequeue() imageProcess(imgPath, function(){}) + reentryFlag = false ``` --- ## Q3 問題:伺服器溝通 ```verilog= const STATUS_NONSET ← 1 const STATUS_SUCCESS ← 2 const STATUS_FAILURE ← 3 async FUNCTION performUpload() let params ← {"id": 9999, "image": img_file, "type": 3} let result ← await request(URL_UPLOAD, params, function(){...}, function(){...} return (result == 1) ? STATUS_SUCCESS : STATUS_FAILURE async FUNCTION performPolling() let params ← {"id": 9999} let result ← await request(URL_POLLING, params, function(){...}, function(){...} IF result == 0 THEN return STATUS_NONSET IF result == 1 THEN return STATUS_SUCCESS IF result == 2 THEN return STATUS_FAILURE async FUNCTION main() // 伺服器上傳照片api若無法順利執行,或者api回覆失敗,則進行重試 let resultUpload ← await performUpload() IF result != STATUS_SUCCESS THEN resultUpload ← await performUpload() IF result != STATUS_SUCCESS THEN return // 重試仍然失敗則返回 END IF // 最多重試的次數 const MAX_RETRY_COUNT ← 20 let retry_count ← 0 WHILE count < MAX_RETRY_COUNT // 等待2秒 await (() => new Promise(resolve => setTimeout(resolve, 2000)))(); // 照片狀態的請求 let resultPolling ← performPolling() IF resultPolling != STATUS_NONSET THEN break END IF retry_count ← retry_count + 1 // 若20次後,照片仍處於處理中的狀態時,則顯示訊息詢問客戶端是否繼續等待 IF count == MAX_RETRY_COUNT THEN let answer ← await showMessage("Do you want to wait for image processing?", function(){...}) IF answer == "no" THEN return count ← 0 END IF END WHILE PRINT(resultPolling == STATUS_SUCCESS ? "Done!" : "Failed") CALL main() ``` :::info 上述虛擬碼皆非可直接編譯、執行的程式碼,只是示意的作用;也缺乏 test cases ,所以只是用來做邏輯表達。 :::