---
# System prepended metadata

title: 應徵《軟體工程師-Web後端網頁》 - Terence

---

# 應徵《軟體工程師-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 ，所以只是用來做邏輯表達。
:::