前幾天參加了 HWDC,裡面有不少厲害的講者,其中最讓我印象深刻的就是「走一遭『寫程式』以外的軟體開發 - 大型軟體公司的工作日誌」,講者用詞簡單明瞭、內容清晰好理解、語調不會讓人覺得想睡,讓我覺得這是一場很棒的分享,故我決定寫成一篇文記錄一下其中內容。
這是講者的 Medium: https://medium.com/@johnliutw
這場主要是講者去訪談一些大型公司的工程師,詢問他們的技術文化,得出一些結果。
摘要
- 程式開發
- 品質與自動化
- 系統迭代
- 團隊文化
這四個要點主要是基於一本書叫「Google 的軟體工程之道」的面向來延伸的。
講者的 Medium 也有分享這本書,有興趣的可以自己去看一下。
開發
靜態工具 - Style 原則
- 要發揮作用
這意思是像是有些公司在你剛入職時會叫你讀一些文件,有超多的規則,然後要你遵守這些之類的,可能你讀完就想離職了,沒辦法運作。
故我們要簡化這些流程,切中要點。
- 以讀者角度來撰寫
在實際開發中,我們其實有 9 成以上的時間都是在閱讀別人的程式碼,所以說你寫出來的程式有 9 成的時間是被閱讀的,故讓程式碼易讀是很重要的。
- 保持一致性
- 要避免易錯、非預期的情況發生
這邊在後面 CR 階段會再特別提到。
- 要針對實際情況做調整
例如像是 PHP 8.1 出了一個叫 Enum 的功能,像這種新的東西若使用指引的方式來運作,而不是直接 block 掉,針對實際情況做調整。
靜態工具 - Style Guide
這是在 Google 裡面實際的一個靜態工具的範例,這是在非常大的公司會有的原則,例如 Amazon、TikTok。
而在中小型公司則是依靠自動化或 lint。
靜態工具 - Sonarqube
這是一個開源的工具,但也提供需付費的 SASS 服務,能自動檢查程式碼的工具,包含但不限於:
- 程式碼是否重複。
- 是否有安全性問題。
- 測試覆蓋率。
- style check。
- 可維護性。
CR (Code Review) - 看哪裡?
一般在發了一個 PR (Pull Request) 後,你會希望它快點被通過以及被 merge,所以在設計上會注重:
- 正確性
確認程式碼是否能正常運作
- 安全性
確認沒有 security 的問題
- 效能
效能是大部分講者去訪談的公司的工程師都非常看重的一塊,例如:Shopback、Nvidia、Appier、Tesla,看看是否有一定的效能,使用的演算法是否有問題。
- 強固性
看看這個程式碼是否很容易出錯、是否很敏感,例如一個程式碼接受一個參數,型態是數字 (int),然後你帶了一個字串 (string),這個程式碼就會出錯,你可能會想設計上就是要數字,帶字串進來本來就會錯,不過如果它夠強固的話,能替它想到各種情境、做各種轉換,就能提升程式碼的強固性。
因為在大型公司,你永遠不知道你的程式碼會給什麼人使用,例如你可能在維護一個 N 年前的程式碼,你根本不知道當初那個人在哪了。
- 可讀性
如果一段程式碼會讓你停頓一下、讓你感到困惑,可能原因有兩種:
- 可能才剛加入團隊,不了解這個 domain、knowhow。
- 可能性可以加強,因為你看不懂,如果你看不懂,其他人也很可能看不懂。
所以可讀性是 CR 階段非常看重的一點。
- 風格一致
假設有一段程式,可讀性、效能、安全性什麼的都非常好,但一樣的問題,其他團隊都用了 A 設計方式,就這裡使用了 B 設計方式,這樣可能在未來會造成維護上的困惑:「為什麼這種情境要使用 B 的方式?」。
CR - 怎麼運作?
- 保持禮貌和專業
像是在 CR 時發現對方少加了什麼,可以使用「Could you please…」、「不好意思幫我加一下…謝謝」、「幫我確認一下…我也不太確定」。
如果是偏向主觀的議題,以作者 (開發者) 的決策比較好,除非是重要的 issue,就要提出來,設定客觀條件,讓大家都認同。
- 盡可能縮小 PR 範疇
可以以 200 行為參考基準 (自己決定就好),小的 PR 能加速被通過的機率。
若真的太複雜,可以開一個 PR review 會議,讓開發者介紹他的架構、怎麼運作之類的,但嫌開會太麻煩,那就把 PR 拆小,就不會有這些事出現了。
- commit message
使用 conventional commit 來陳述變更類別。
- reviewers
假設一個 PR 有 20 個 reviewer,但第一個 reviewer 可能有 80% 的貢獻,第二個 5%,第三個剩下 2% 3%,因他們大概率 context 都是差不多的、都是同個 team 的,思考點都差不多。故 reviewer 不是越多越好。
講者說他公司是兩個 reviewer,除非是大型變更。
- 盡可能自動化
搭配剛剛提到的 Sonarqube,搭配一些 CI。
CR - code owner
可以使用 Github 本身擁有的 code owner,可以設定哪些檔案是哪些團隊擁有的,當有這些檔案變更時,會明確指示誰該去 review。
文件的核心價值
- 針對 feature design 寫文件
除了 API 要寫文件外,也要針對 feature 去寫文件,強迫設計者去清楚了解規格,如果設計者無法文字化來表達他要怎麼設計,通常代表他想的不夠清楚,所以可以強迫開法者釐清自己思維。
- 交給新人
將文件交給新人,就可以不用手把手帶新人,如果要規模化的話可以做一個文件庫。
- 手動流程更容易遵守
例如像是 SOP,當發生問題時,support engineer 該 follow 什麼流程,可能哪天自己的文件會幫助到自己。
- 能回答出為什麼要做這個決策?
例如現在在選資料庫,要 MySQL 還是 PostgreSQL,若你的文件只有介紹用了 MySQL 怎樣怎樣的,但是沒有寫當初為什麼選擇 MySQL,在未來可能會一直被問,例如:「為什麼明明這個語言的生態系最常搭配 PostgreSQL,為什麼這裡要 MySQL?」
文件的難處
第一個難處是文字與事實不符
假設要開始一個功能,然後大家叫你去看文件,結果發現文件與事實完全不匹配。
所以文件也有版本問題,所以不是所有東西都要記成文件,不然很容易發生這個問題,盡量針對重要、長期有價值的來記錄。
也要持續培養自己寫作的能力,持續優化寫文件的技巧。
第二個難處是到處都是文件
什麼地方都有文件,google drive 有文件,他的電腦桌面有文件,他的抽屜也有文件,到底文件都放在哪?
所以要盡可能的把文件都集中在幾個地方。
演講者的公司有自己的 AI gpt,可以在裡面搜尋問題,因為統一化了,所以可以 training。
哪時候要寫文件?
- on call SOP
前面提到的操作手冊。
- 系統架構
例如系統有 20 30 幾個服務,他們怎麼交互運作的。
或是這個 E2E 流程超長,該怎麼設計之類的。
- onboarding guild
新人入職手冊。
- 功能的整體設計
在滿多公司中都會規定在啟動一個 feature 前都要寫 feature design,這裡指的不是 API,而是例如:需要設定文件 reviewer、UI 你會用到哪些 shared component、你會跟哪些 API 互動、你的 case 是什麼?
如果有資料的 migration,你的 schema 設計、migration plan 是什麼?
關於測試,你的 QA 有沒有提供測試計畫、他如果要測你的功能,你有沒有什麼特別的 context 要先設計?
所以有非常多的環節要設計,所以寫程式只是非常短的一個環節,大部分時間都在文件討論。
在越複雜的功能上越需要這麼做。
- 對外的 API
對外開出來的 API 也需要文件,像是 Amazon 有文件委員會,要送到美國總部給 principle engineer 等級以上的人查看,看看狀態如何、有沒有符合規範等等。
講者分享之前的一個慘痛經驗:曾經發生了一個 incident,在修的過程發現一個字拼錯,例如 apple 拼成 appla,而這個 API 也早已開出去給客戶,且已開出去好幾年了,根本不能改,只好繼續這樣不舒服的看著,也造成閱讀上的困擾,所以這種對外的 API 要非常非常嚴格的對待。
品質和自動化
測試的好處
測試的好處有幾點,例如增加基本品質、增加信心,並且可規模化的,你的單元測試可以給很多人跑。
另外單元測試可以督促更嚴格的設計,迫使開發者去思考可測試性。
講者分享公司曾發生一個 P0 的 incident,它是之前發生了一個改變,但這個改變並沒有程式碼 level 的測試,如果有的話就能在很早的階段被 catch 住。後來詢問為何沒有測試,得到的答案是這個不太好加測試。
測試的維度
測試有不同的維度,最小的像是單元測試,中間的像是手動測試,大的像是 E2E 測試。
不理想的測試環境:
不少公司的測試組成都如上圖那樣,手動測試佔最大量,其次是自動化,再來是整合測試,最後才是單元測試。
理想上應該要反過來。
單元測試的生態系
- 新人入職訓練的一環
因為我們已經做了自動化了,所以要 merge code 就一定要過測試涵蓋率,迫使人去做這件事。
- 透過儀表板查看測試狀況
例如一個 channel 每週傳送一個報告,顯示各個團隊測試涵蓋率是多少,促使彼此競爭。
- 廁所測試
Google 有一個廁所測試,會在上廁所這種這麼短的時間內告訴你怎麼寫好測試。
- 部分仍需要手動測試
雖然自動化測試很好,但仍然有些東西無法自動化,例如影片品質、搜尋結果、或複雜的安全漏洞,依然得仰賴人工測試,所以在自動化測試中可以使用 82 法則,80% 的案例使用自動化測試,把精力放在剩餘的 20% 上,這種特別負責、特別需要人類去判斷的測試上。
Agoda 曾分享他們會對單元測試的一些條件做抽換,看看是否一定的安全性。
大型測試
大型測試,例如 E2E testing,因單元測試有許多的 mock,所以可能有模擬度不足的問題。
以及 configuration,在不同環境,你的 config 是否正確。
再來是特殊情境,例如效能和壓力。
看看有沒有什麼非預期的行為或 side effects,因單元測試是取決於開發者對這個功能的理解透不透徹。
最後是真空效應,指的是單元測試或自動化測試都是在嚴格的環境內,但 user 都是很有想像力的,所以還是需要大型測試去 cover 這種亂七八糟的世界。
大型測試類別
- 探索性測試
例如聘請團隊來測試你的產品,他們不會針對你的測試案例做測試,他會像是探險一樣,對你的產品點點看、打打看之類的。
- Binary 檔案
假設你的產品是非 web 服務的產品,例如 desktop 的 application,會有 binary 檔案。
- 故障與混沌
例如 monkey testing,找一隻猴子來亂點你的產品,看看有沒有什麼意外發生,類似探索性,但更非理性一點。
- User Evaluation - 內部員工測試
在 release 前或第一天,讓大家一起來測最新的版本,各自看看有沒有什麼問題,或許可以提早發現一些 bug。
測試 - CI
- 測試左移
讓開發者透過自動化測試,盡可能少的 effect 來完成這件事,搭配 lint 或安全性的確認。
有些公司會透過 build,實際跑起來看看。
測試 - CI 的失敗案例
Google 有一個案例叫「Google Takeout」,他們為了做到這個測試,會把所有的 server 串在一起,因所有服務都串在一起,所以 log 會非常多、非常難以追蹤,等於一個小小工程師發了一個 PR 就要測試整個 Google。
測試 - CD
自動部署強調是敏捷、小、並且快,以及要自動化、隔離性、模組化、且可靠\n 假設一個部屬的 script,執行五次有三次會失敗,就不會有人想用了。
再來是 feature toggle,很多大型公司都會使用,因為即使功能經過測試,但可能還是有 edge case 沒 cover 到,造成客戶困擾,故會仰賴 feature toggle 的保護。
測試 - Release Train
- 沒有完美的 release
不應該為了一些小小的問題而停下,不可能修復完每個 bug,即使有些 feature 仍有 bug,我們還是可以移到下個階段。
- 嚴格執行 deadline
deadline 是固定的,不應該在 deadline 後仍有東西被合併,若有東西要修改,需要高階主管的 approval。
系統迭代
依賴的自評清單
- 該依賴是否有自動化測試?
- 誰提供的?是否是大的組織?或夠有信賴的組織?
- 相容性如何,是否能向前相容?
- 會使用多久?
- 多久會有大的異動?若每個禮拜都有大的異動會直接哭死
- 我們實現這個依賴的成本多少?
- 確保這些依賴有持續被更新
為什麼棄用很難?
有一個 Hyrum’s 的定律:使⽤者時常以意外或不可預⾒的⽅式使⽤系統,尤其是使⽤者越多且越古⽼的系統
想像一個系統已經 10 年了,誰知道誰已經用了什麼方式在使用它,只要是寫在程式碼中的邏輯,都非常非常有可能被某個重要的客戶給依賴。
大規模的變更,需要特別講
- 因為會有技術限制,以及合併的衝突。
- 一些大規模變更會因為 CI 的整合會有很多的複雜性。
- 難以測試。
- 大型變更會有數萬行,所以 CR 困難。
團隊文化
工程效率
來到最後一個環節,工程效率,分為三點:
- 個人層面
主要是個人技術能力、寫程式的能力。
紀錄自己過去一年參與的專案,當做到很重要的 ticket、解了很有價值的 bug 時把它記錄下來,在考核時可以拿出來參考,不要考核時想不起來自己過去一年中做了什麼。
- 團隊層面
怎麼溝通、怎麼跟其他人合作。
- 組織層面
你做的東西做出了什麼貢獻
以上就是這次「走一遭『寫程式』以外的軟體開發 - 大型軟體公司的工作日誌」分享的東西,我覺得很多層面都是過去工作中都遇過的事,但一個不注意就會被你忘記的。所以時常去複習這些事,讓自己成為更好的工程師!