Photo by Jonathan Kemper on Unsplash
在大學時期,我在寫程式時,總是習慣根據腦中的想法,直接開始寫程式。在一半的時間其實蠻順利能寫出結果;但另一半的時間會卡住,而當我卡住或專注力不佳時,解問題的效率就變得非常差。
而且常常寫到一半,就忘記自己在哪一個階段,資料處理到什麼程度,甚至在最後結果發生 bug 時,也不知該如何處理。直到最近,在解決問題和撰寫專案時,嘗試使用解決問題的流程,讓我意外發現這個流程節省了我超過 30% 以上的時間。
也和超過上百位的工程師聊過,我才發現在選擇程式語言,如 Python、Java、JavaScript、C 等之前,真正重要的關鍵–––
建立解決問題的心態與方法。這也是我聊過這麼多人選,他們在找工作時,決定他們面試順利的關鍵。(延伸閱讀:
如何以恰當的心態學習程式)
今天,就在這一篇文章,和大家分享解決問題的四步驟,就讓我們從解決問題四步驟開始,架構的流程如下:
- 釐清問題
- 拆分問題
- 進行研究
- 寫偽代碼
解決技術問題的四步驟
解決技術問題的四步驟 我自己過往曾經學過設計思考(Design Thinking)、麥肯錫問題解決流程,甚至台大的 CTPS 等。每種解決問題的流程,都是針對不同的問題設計與應用。
因此,當我面對到技術問題時,也一樣會有相應的技術解決流程。今天,就和大家分享,結合過往學過的問題解決流程,可以如何應用在解決技術問題。
如果我有1小時拯救世界,我會花55分鐘去確認問題為何,只以5分鐘尋找解決方案 –––愛因斯坦 Albert Einstein。
無論哪一個問題解決的流程,釐清問題總是佔了流程的重要階段。甚至可以說,沒有釐清問題,就沒有解決問題。過往,我習慣在一收到需求後,就開始搜尋可能的問題解決方案;但有時會落入不停的試錯卻無果的狀態。
因此在後來,我習慣遇到問題時,就先停下來思考。
為了讓讀者能夠一起深入瞭解,解決問題的流程該如何實際應用,就讓我們以大華的故事,來深入理解吧!
一、釐清問題(Clarify)
Photo by Emily Morter on Unsplash
大華是一位在電商公司的工程師。有一天,團隊的菜鳥 PM 跑來找大華,跟你說:「欸!我們最近新的商品推薦頁面要上線,你幫我寫一個邏輯,可以處理用戶丟進來的資訊,並到資料庫撈推薦的清單推薦回去」。
1. 確認效益與商業價值
大華收到這個問題後,就先停下來思考–––我現在面對的問題是什麼?
接著,大華又問了自己下面兩個問題:
- 該功能帶來什麼商業價值?
- 該功能預期實現的效果是什麼?
於是他跑回去,詢問 PM 上面兩個問題。PM 跟他說:「這個功能,是因為我們有新的聯名產品頁面要上線,所以需要讓用戶在搜尋後,能夠收到聯名內容推薦的商品。」
「這一次找了很大牌的品牌商聯名,因此公司蠻看中這一次的上線。至於預期的呈現效果,是我們會提供給用戶,幾個商品內容常見的 hashtag 來點選。」
「因為目前的產品都是新合作的廠商,都沒有相關的購買記錄。我們請行銷先給了一份名單,並請 Data Scientist 根據過往的商品類別,跑了一份預設的推薦清單。」
大華接收到上述資訊後,釐清到了幾個重點:
- 該專案為重點專案:後續的需求可能會增多,或要求較高。
- 預計呈現效果固定:因為沒有過往銷售記錄,因此會根據 Data Scientist 提供的清單匯出。呈現的狀態如其他的商品頁相似,但需要加上品牌商的設計與官方網站連結(因應要求)。商品呈現的內容含括:商品名、圖片、合作品牌、價格、簡介。
2. 確認技術產出
大華確認了整個專案的目的,以及預計呈現的效果後,才開始著手規劃功能。接著,面對這個問題,大華列出了幾個問題:
- 資料是怎麼產生的?
- 預期的輸入是什麼?格式是什麼?
- 如果輸入出現例外,如何處理?
- 可以如何解決問題?
- 會有歷史資料的影響嗎?
- 輸入與輸出的單位是什麼?
- 預期的輸出是什麼?格式是什麼?
- 後續有什麼規劃?需要產生什麼資料?
隨著向 Data Scientist、後端確認資料,確定輸入的資料是以 JSON 輸出,串接的 API 是 XXX。傳入的資料都是 JSON 格式,用戶輸入的也僅有固定的標籤,不會有例外。若網路錯誤或資料錯誤,則說明搜尋有誤,直接跳回全部商品頁面。
屆時輸出的格式,產品名與價格需要有 i18n 處理多國語言與貨幣,呈現的格式會請 Designer 設計。每一次推薦的順序都一致,不需要根據使用者點擊後有差異。因為這次是和品牌商合作,需要有 GA 資料確認成效,後續才有數據可以分析。
接著在後面,會有其他相關的產品頁面,因此有可能會有其他類型的聯名品牌產品頁,要注意未來輸出的擴張性,不要直接將資料處理寫死。
二、拆分問題(Problem Destruction)
有了具體的輸入與輸出,接著就要將問題逐步拆分。大華重新審視 PM 提供的產品需求文件,大致區分為幾個步驟來處理:
- 輸入資料格式為 JSON,使用非同步 fetch 資料
- 確認 i18n 的設定,確認產品名、價格順利設定為當地資料
- 處理資料的判斷,透過 Tag 標籤屬性,確認該群產品類別
- 輸出為產品頁面,以 React 來進行開發
藉由拆分整個需求問題,將需求切分為四個部分,又各自根據資料處理,切分為數十個函式個別處理。到目前為止,大華才具體知道整個功能,可以如何被開發與各自處理。
三、進行研究(Technical Research)
到目前為止,大華都有比較具體的想法;但他是這幾個月才加入公司,因此過往沒有碰過 i18n 多國語系的設定,因此如何處理資料需要研究。另外因為頁面的版本較舊,但技術主管希望能以新版本 React hook 架構進行開發,並評估把原先的 Redux 拆掉變成 useReducer 和 useContent。
這些技術,剛好都是大華之前不熟悉的應用,因此他便把這些技術列下來:
- 研究 i18n 架構
- 研究 i18n 語言判別與更新
- 研究 React hook 與 Class Component 共存的開發模式
- 研究 useReducer 與 useContent 取代 Redux 的應用情境。
接著,大華就花了兩個小時研究,把基本需要知道的知識,以及相依的套件都先整理下來。接著,才準備來寫 Code。
適得其反的操作
這時大華的朋友–––小明,看到大華到目前為止,都還沒有開始寫 Code,就好奇問他說:「為什麼你不一邊寫一邊找資料呢?」這樣不是比較快嗎?
大華這時才跟小明說,之前也是看到問題後,就直接開始寫程式。例如看到 i18n,就先跳進去看怎麼寫;看到 useReducer 和 useContent,就跳進去看要怎麼用。
但麻煩的是,i18n 開發到一半,發現現有的邏輯,在部分元件的狀態管理很複雜,但因為已經導入到一半 useReducer,有一些資料流已經拆開。到後面一直來回修修改改,花的時間,反而比預期還要長了一倍,也因此被主管念了一頓。
因此,在實際開始寫程式碼之前,先確認怎麼開始寫、可能遇到的問題有哪些、預計如何處理,才能避免寫到一半出問題;但程式碼與時間成本已經花下去的窘境。
四、寫偽代碼(pseudocode)
Photo by Joshua Aragon on Unsplash
大華將前面的四個步驟,各自寫逐步寫了偽代碼(Pseudocode),來確認邏輯設計沒有問題。隨著時間漸漸接近,也順利把新的頁面成功上架,讓新的品牌聯名大獲成功。但相信到這個階段,還沒有實際展示,偽代碼如何撰寫?要寫到什麼程度?
Given an array of integers nums, return the number of good pairs.
A pair (i, j) is called good if nums[i] == nums[j] and i < j.
Example 1:
Input: nums = [1,2,3,1,1,3]
Output: 4
Explanation: There are 4 good pairs (0,3), (0,4), (3,4), (2,5) 0-indexed.
Example 2:
Input: nums = [1,1,1,1]
Output: 6
Explanation: Each pair in the array are good.
Example 3:
Input: nums = [1,2,3]
Output: 0
如何開始寫 pseudocode?
在開始寫之前,有幾個 pseudocode 的重點需要掌握:
- pseudocode 的目的在於理解邏輯,程式碼則是讓電腦去跑
- 盡量不要使用特定語言的寫法,將邏輯寫出來即可
- 若有邏輯處理,則可使用語句敘述
開始撰寫
以下的寫法並不一定是最佳寫法,而是展現 pseudocode 的撰寫邏輯。
建立函式 goodPair (nums)
// 初始化 goodPair 的數量
// 實例化物件(Map)
// for 0 ~ n (n 是輸入陣列長度)
// if Map 沒有第 n 個元素 then Map key 設定為第 n 個元素,value 為 1
// else (Map 有第 n 個元素)
// goodPair 加上 Map 第 n 個元素的累積值
// 給 Map 第 n 個元素的累積值 + 1
實際成果如下:
var numIdenticalPairs = function (nums) {
let goodPairs = 0;
const hashMap = new Map();
for (let i = 0; i < nums.length; i++) {
if (!hashMap.has(nums[i])) {
hashMap.set(nums[i], 1);
} else {
goodPairs += hashMap.get(nums[i]);
hashMap.set(nums[i], hashMap.get(nums[i]) + 1);
}
}
return goodPairs;
};
另外一個寫偽代碼的優勢,是當我們的資料與預期不符時,更容易 Debug 與對照邏輯。也因此也避免了寫到後面邏輯混亂的狀況。
解決技術問題不在於技術,而在於解決問題
當我們在思考技術問題,總是先停下來,確認我們要解決的是什麼問題?甚至有可能,我們面對到的問題,其實不一定是技術問題。例如行銷後台蒐集的資料非常混亂,常常找不到需要的數據。
可以簡單把他們要的資料抓出來;但會不會其實是,行銷和工程團隊的溝通出現落差,所以每一次開發時,行銷的需求都沒能正確傳達,導致後續的需求都是以 Hot fix 上架,自然而然數據與規劃就會難以維護。
因此,釐清該問題的商業價值,確認真正要解決的問題,接著再以偽代碼確認撰寫邏輯。才能最大程度地避免,我們花了大量時間解決的,其實不是真正重要的問題。
延伸閱讀