干了10年軟件工程師,我學(xué)到10個(gè)教訓(xùn)
“三人行必有我?guī)熝?,擇其善者而從之,其不善者而改之?rdquo;這句話出自論語,卻同樣可應(yīng)用到我們的職業(yè)生涯中。在職業(yè)道路中,你應(yīng)該學(xué)會(huì)向那些成功人士請(qǐng)教,詢問他們做了什么、為何這樣做和具體實(shí)踐。在本文中,我將分享我在職業(yè)生涯中所學(xué)到的全棧工程師經(jīng)驗(yàn)。
作為一名年輕工程師,我在科技行業(yè)和硅谷打拼了十年。這十年,我一直在高速增長(zhǎng)的創(chuàng)業(yè)公司工作,經(jīng)歷了與此相關(guān)的所有起起落落。從構(gòu)建 nextgen 電子郵件客戶端,到在全球范圍內(nèi)推廣電動(dòng)汽車,再到網(wǎng)上購(gòu)物結(jié)賬,我學(xué)到很多。
當(dāng)回想過往,我覺得有些錯(cuò)誤完全可以避免。在本文中,我將分享我在職業(yè)生涯中所學(xué)到的全棧工程師經(jīng)驗(yàn)。從中,我總結(jié)了十大經(jīng)驗(yàn)教訓(xùn)。我相信,這些經(jīng)驗(yàn)教訓(xùn)值得起時(shí)間考驗(yàn),并在未來幾年里依然適用。
這份列表從前端開始,然后是后端 API 和數(shù)據(jù)庫(kù),最后是工程最佳實(shí)踐 / 流程。
經(jīng)驗(yàn)總結(jié)
- CSS Specificity
- 組件層次結(jié)構(gòu)中的設(shè)計(jì)狀態(tài)
- 后端編程中的面條式代碼、千層餅式代碼和餛飩式代碼
- 生產(chǎn)中的 Postgres
- 欲速則不達(dá)
- 投資自動(dòng)化
- 掌握你的工具
- 最小可行性產(chǎn)品(Minimum Viable Product,MVP)
- 研究支持開發(fā)
- 科學(xué)調(diào)試
1. CSS Specificity
錯(cuò)誤:我的 CSS 不適用。我要用 !important。
教訓(xùn):應(yīng)該為特殊情況保留使用 !important,因?yàn)樗鼈兤茐牧苏麄€(gè) CSS 層次結(jié)構(gòu),并強(qiáng)制使用特定樣式。所以,你有必要了解 CSS Specificity。
CSS specificity 是瀏覽器應(yīng)用的一組規(guī)則,用來確定哪個(gè) CSS 樣式更 specific。你可以將其視為基于點(diǎn)的系統(tǒng),它決定哪種 CSS 樣式獲得優(yōu)先權(quán),并最終應(yīng)用于 DOM 元素。
如果你想知道,為什么你的 CSS 沒有被應(yīng)用,這與 CSS specificity 有關(guān)。在大型項(xiàng)目中,這是一個(gè)很常見的問題,在這類項(xiàng)目中,像 SCSS 這樣的預(yù)處理程序與復(fù)雜的 CSS 層次結(jié)構(gòu)一起使用。了解 CSS specificity 將有助于你保留使用 !important,僅在極罕見的覆蓋情況下使用,例如,當(dāng)你想要覆蓋 CSS 庫(kù)或讓 iframe 覆蓋主機(jī)站點(diǎn)樣式時(shí)。
本質(zhì)上,優(yōu)先順序是這樣:ID selectors > Class selectors > Type selectors。!important 和內(nèi)聯(lián) style 屬性會(huì)覆蓋所有的 CSS。
對(duì)于應(yīng)用于元素的每個(gè) CSS,你都可以輕松確定哪種樣式將生效。例如,如果你加載上面的 HTML 代碼:
在本例中,ID selector 優(yōu)先級(jí)高于 type selector。如果沖突的 CSS 選擇器具有相同的優(yōu)先級(jí),那么將選擇 CSS 文件中的最后一個(gè)。
最后,Chrome DevTools 將會(huì)顯示如上圖所示的 specificity 順序。如果你無法使用 CSS,請(qǐng)查看 Chrome 使用的 specificity 順序,然后添加一個(gè)更具體的選擇器(如 ID 選擇器、類選擇器、類型選擇器),讓你的 CSS 更特定,并指示瀏覽器選擇它。
如果你不想這么做,請(qǐng)看看這個(gè) specificity 計(jì)算器
鏈接:https://specificity.keegan.st/
2. 組件層次結(jié)構(gòu)的設(shè)計(jì)狀態(tài)
錯(cuò)誤:我需要添加這個(gè)新狀態(tài)。我只需要將它放到這個(gè) reducer 中,但不確定為什么這個(gè) reducer 還有這么多其他狀態(tài)?
教訓(xùn):管理不當(dāng)?shù)?redux 狀態(tài)會(huì)在開發(fā)人員中造成混亂,并導(dǎo)致 bug。如果用 react 和 redux 來構(gòu)建前端應(yīng)用程序,那么你可以考慮使用這種可視化技術(shù),從用戶界面組件層次結(jié)構(gòu)中構(gòu)建狀態(tài)和 reducer 層次結(jié)構(gòu)。從零到統(tǒng)一的組件狀態(tài)層次結(jié)構(gòu)有三個(gè)步驟:
- 在線框圖(wireframe)將用戶界面進(jìn)行可視化
- 將狀態(tài)層次結(jié)構(gòu)進(jìn)行可視化以反映用戶界面
- 構(gòu)建 reducer 層次結(jié)構(gòu)以反映狀態(tài)層次結(jié)構(gòu)
讓我們來看一個(gè)例子。在這個(gè)例子中,我們構(gòu)建一個(gè)博客網(wǎng)站,它有兩個(gè)頁(yè)面,一個(gè)頁(yè)面用于博客列表,另一個(gè)頁(yè)面用于個(gè)人博客:
第一步:在線框圖中將用戶界面進(jìn)行可視化
主頁(yè)和個(gè)人博客頁(yè)面
第二步:將狀態(tài)層次結(jié)構(gòu)進(jìn)行可視化以反映用戶界面
相應(yīng)的狀態(tài)層次結(jié)構(gòu)圖如下所示:
狀態(tài)層次結(jié)構(gòu)
請(qǐng)注意,公共 Header 狀態(tài)是如何被拉到根狀態(tài)的。類似的,任何共享狀態(tài)都可以在層次結(jié)構(gòu)中“冒泡”,因此很明顯,子組件共享該狀態(tài)。
第三步:構(gòu)建 reducer 層次結(jié)構(gòu)以反映狀態(tài)層次結(jié)構(gòu)
reducer 層次結(jié)構(gòu)
這是一個(gè)簡(jiǎn)單而強(qiáng)大的例子,展示了如何構(gòu)造狀態(tài)和 reducer 層次結(jié)構(gòu)來匹配用戶界面。這一過程可以輕松擴(kuò)展到復(fù)雜的應(yīng)用程序和大型團(tuán)隊(duì)。最后,你可以在這個(gè)結(jié)構(gòu)上構(gòu)建操作和表示層。
3. 后端編程中的面條式代碼、千層餅式代碼和餛飩式代碼
錯(cuò)誤:這個(gè)代碼庫(kù)是如何組織的?也許我可以在這里添加這個(gè)文件,就像所有其他倉(cāng)庫(kù)代碼一樣。
教訓(xùn):我寫過三種“意大利面條式代碼”。說實(shí)話,我覺得在“餛飩式代碼”里放上“迷你千層餅式代碼”也許是個(gè)好辦法。組織和培訓(xùn)所有開發(fā)人員以這種方式構(gòu)建代碼庫(kù),可以保持代碼的可維護(hù)性、可測(cè)試性,最重要的是能保持敏捷性。你能在不影響其他功能的情況下,輕松修改特定的“餛飩式代碼”(也就是功能)的實(shí)現(xiàn)細(xì)節(jié)。
“千層餅式代碼”是:
- 分層架構(gòu)
- 架構(gòu)整潔
- 外層是 I/O,內(nèi)層是純數(shù)據(jù)結(jié)構(gòu)
- 依賴關(guān)系向內(nèi)注入
- 內(nèi)層不依賴于外層
- 優(yōu)先考慮組合,而非繼承
“餛飩式代碼”是:
- Screaming architecture
- 切片與分層
- 文件夾和文件的空間位置
- 可以是微服務(wù)形式
將它們放在一起,就能得到一個(gè)可擴(kuò)展、可維護(hù)的代碼庫(kù)。如果你可以想出一種方法,通過特征名來組織文件夾,并且在每個(gè)特征中實(shí)現(xiàn)整潔結(jié)構(gòu),這將讓你使用很長(zhǎng)時(shí)間。
4. 生產(chǎn)中的 Postgres
錯(cuò)誤:為什么這個(gè)查詢速度這么慢?我認(rèn)為是因?yàn)?Postgres 很慢的緣故。我需要切片,或者我認(rèn)為這是 ORM,或者,我需要一個(gè)不同的數(shù)據(jù)庫(kù),Postgres 并不適合我。
教訓(xùn):如果你在生產(chǎn)環(huán)境中運(yùn)行 Postgres,那么你將會(huì)遇到查詢緩慢、表鎖、無限等待遷移和錯(cuò)誤的問題。如果不是這樣,那么對(duì)你有好處,你又是怎么做到的?這并不意味著 Postgres 不再是正確工具,而是意味著你需要揭開帷幕,看看下面發(fā)生了什么。
截至目前,我發(fā)現(xiàn)的最好工具是 pgbadger。你可以用它解決幾乎所有的 Postgres 問題。
鏈接:https://github.com/darold/pgbadger
這是一個(gè) Perl 命令行工具,它將 Postgres(如果你使用 AWS 的話,就是 RDS)日志作為輸入,并輸出報(bào)告。該報(bào)告的好壞取決于你在 Postgres 上啟用的日志。因此,在第一步時(shí)你可能需要啟用這些日志:
- log_checkpoints = on
- log_connections = on
- log_disconnections = on
- log_lock_waits = on
- log_temp_files = 0
- log_autovacuum_min_duration = 0
- log_error_verbosity = default
- log_min_duration_statement = 1s
此外,你可能還需要啟用 pg_stat_statements 語句來實(shí)時(shí)分析查詢,并啟用 auto_explain 來自動(dòng)解釋日志中運(yùn)行緩慢的查詢。
運(yùn)行報(bào)告:
- pgbadger --prefix '%m %u@%d %p %r %a : ' /pglog/postgresql.log
該報(bào)告將匯總數(shù)據(jù),并提供有關(guān) Postgres 所做工作的大量信息。你可以找到關(guān)于錯(cuò)誤、最慢查詢、等待最長(zhǎng)查詢、獲取的鎖類型、臨時(shí)文件是否用于排序、檢查點(diǎn)運(yùn)行的頻率、真空運(yùn)行的頻率以及其他類似信息。有了這些數(shù)據(jù),你就可以識(shí)別和修復(fù)運(yùn)行緩慢的查詢,并通過調(diào)優(yōu)提高 Postgres 的性能。
你可以持續(xù)運(yùn)行此報(bào)告(CLI 支持增量模式),從而隨時(shí)掌握新問題。
另外,如果你想理解解釋輸出,可以用這款工具。
鏈接:https://tatiyants.com/pev/#/plans/new
該工具將解釋 JSON 和原始查詢作為輸入,并將在如下所示的可視化樹形圖中對(duì)解釋輸出進(jìn)行解釋:
正如你所見,節(jié)點(diǎn)將有最大、最慢、最貴等標(biāo)簽。這將幫助你根據(jù) Postgres 的執(zhí)行方式來優(yōu)化查詢。
最后,如果在 Postgres 中建立能力不可行,我建議你問問像 Percona 這樣的數(shù)據(jù)庫(kù)咨詢公司。
鏈接:https://www.percona.com/
5. 欲速則不達(dá)
錯(cuò)誤:看起來不錯(cuò)嘛,讓我們交付吧!
教訓(xùn):
在這個(gè)快速行動(dòng)、打破常規(guī)的時(shí)代,這似乎不是最受歡迎的建議,但是“慢慢來”會(huì)有回報(bào)的。與其快速發(fā)布糟糕的代碼,還不如有條不紊地來,發(fā)布幾乎沒有生產(chǎn) bug 的代碼。
優(yōu)秀工程師會(huì)考慮到軟件系統(tǒng)的所有問題。
鏈接:https://codesqueeze.com/the-7-software-ilities-you-need-to-know/
他們不僅關(guān)心代碼覆蓋率,還關(guān)心可能破壞相同代碼路徑的奇怪輸入。通過分層架構(gòu),它們可以實(shí)現(xiàn)模擬層,并只測(cè)試所考慮的層。他們不僅實(shí)現(xiàn)單元測(cè)試,而且還實(shí)現(xiàn)了集成和功能測(cè)試。如果你的團(tuán)隊(duì)還有 QA 工程師,就與他們一起測(cè)試這些用例。
子曰:“無欲速,無見小利。欲速則不達(dá),見小利則大事不成。”
6. 在自動(dòng)化上投資
錯(cuò)誤:我們手動(dòng)部署到 staging 和沙箱,是臨時(shí)的。生產(chǎn)也是手工部署,但每天執(zhí)行一次。
教訓(xùn):擁有一個(gè) CI/CD 系統(tǒng)管理部署意味著更可預(yù)測(cè)的結(jié)果。軟件以促銷策略在 pipeline 中移動(dòng),而臨時(shí)部署則被降級(jí)到特殊情況下。這可以確保你正交付軟件的穩(wěn)定性和可靠性,這是工程團(tuán)隊(duì)的主要責(zé)任。
投資:
- 培訓(xùn)團(tuán)隊(duì)成員學(xué)習(xí)如何進(jìn)行代碼審查。你的團(tuán)隊(duì)成員可能有各種各樣的技能,但不是每個(gè)人都知道如何進(jìn)行更好的 code review。因此,你應(yīng)在學(xué)習(xí)和教授 code review 的最佳實(shí)踐上進(jìn)行投資。
- 使用像 peril 和 hound 這樣的自動(dòng)化 code review 系統(tǒng)。Peril 可以檢查代碼更改,并根據(jù)預(yù)先配置的設(shè)置,標(biāo)記警告和構(gòu)建失敗。例如,如果數(shù)據(jù)庫(kù)遷移文件缺少 statement_timeout 或包含不必要的 DEFAULT NULL,則 pull request 可能失敗。你可以編寫許多這樣的檢查和特定于團(tuán)隊(duì)的規(guī)則,并讓 Peril 成為更改的“看門人”。HoundCI 可以做類似的事情,而且規(guī)則是完全可配置的。
- 使用 CircleCI 之類的工具,通過自動(dòng)推廣策略設(shè)置 CI/CD pipeline。隨著時(shí)間推移,優(yōu)化構(gòu)建和部署管道。
鏈接:https://circleci.com/
7. 掌握你的工具
錯(cuò)誤:我需要找到這個(gè)實(shí)現(xiàn)接口,先搜索一下。我記得它以前就在這個(gè)文件夾里,現(xiàn)在卻不見了?在那個(gè)文件夾里找找,我還是問問別人吧!
教訓(xùn):不知道如何使用你的工具,這會(huì)讓你效率變低。你能想象一個(gè)邋遢的裁縫使用縫紉機(jī)的樣子嗎?這不僅關(guān)系到代碼結(jié)果,還關(guān)系到構(gòu)建軟件的效率。
了解你的工具,掌握捷徑。代碼編輯器可能是你要掌握的第一個(gè)工具。你應(yīng)該知道如何設(shè)置選項(xiàng)卡的排序、打開最后的編輯文件、顯示調(diào)用圖等。如果你使用基于文本的編輯器,而不是圖形用戶界面,這也可行。類似 Vim 這樣的編輯器,有很多實(shí)用技巧。
請(qǐng)注意手動(dòng)執(zhí)行的常見操作,并學(xué)習(xí)如何通過快捷鍵來執(zhí)行這些操作。要做到這點(diǎn),一個(gè)簡(jiǎn)單的方法是先記住 5 條捷徑,并熟練掌握它們,然后再記住 5 個(gè)捷徑。
全棧工程師每天接觸的其他常用工具有終端、docker、tableplus/pgadmin/ 一些其他數(shù)據(jù)庫(kù)客戶端用戶界面、Chrome 開發(fā)工具等。
8. 最小可行性產(chǎn)品(MVP)
錯(cuò)誤:我覺得這個(gè)功能會(huì)很有用。我要使用分布式容錯(cuò)復(fù)制高可用數(shù)據(jù)存儲(chǔ)。我還要構(gòu)建一個(gè)基于插件的架構(gòu),使這個(gè)軟件具有超強(qiáng)的可擴(kuò)展性。
教訓(xùn):在構(gòu)建某個(gè)東西前,請(qǐng)確保它是你要構(gòu)建的正確的東西。這就是 MVP 的用武之地。
理想的最小可行性產(chǎn)品(Minimum Viable Product,MVP)應(yīng)該盡可能少地觸及所有層面,而不僅僅是一個(gè)層。這是降低風(fēng)險(xiǎn)的一種做法。最好是最低限度地構(gòu)建所有層,而不是完善單個(gè)層。最小可行性產(chǎn)品并不意味著技術(shù)債、糟糕的代碼或缺乏測(cè)試。它不是拋棄型代碼(throw-away code)。
如果最小可行性產(chǎn)品花的時(shí)間太長(zhǎng)(在某種程度上),那么它就有可能是錯(cuò)誤的方案,并且可能有更簡(jiǎn)單的解決方案。
奧卡姆剃刀:在其他一切同等的情況下,較簡(jiǎn)單的解釋普遍比較復(fù)雜的好。
9. 研究支持開發(fā)
錯(cuò)誤:我(工程師)認(rèn)為這是我們應(yīng)該構(gòu)建的。
教訓(xùn):在開發(fā)前,應(yīng)該先進(jìn)行大量的研究以佐證。與其跟隨你的直覺,不如進(jìn)行一項(xiàng)用戶研究。要了解用戶需求,可以親自或者通過視頻采訪,進(jìn)行調(diào)查、查看日志等。這將幫助你更好地了解用戶。然后,你可以提出一個(gè)假設(shè)并進(jìn)行實(shí)驗(yàn)。當(dāng)形成一個(gè)假設(shè)時(shí),請(qǐng)使用反演來駁斥自己的主張。
鏈接:https://fs.blog/2013/10/inversion/
在一個(gè) A/B 測(cè)試框架上投資,可以讓你進(jìn)行實(shí)驗(yàn)。
時(shí)間寶貴,要明智使用。最聰明的工程師會(huì)嘗試優(yōu)化一些不應(yīng)該存在的東西。盡早提出正確問題非常重要。
10. 科學(xué)的調(diào)試
錯(cuò)誤:有一個(gè) bug。我想是因?yàn)榇a改變所致。讓我看看這個(gè)文件。沒準(zhǔn)也許是內(nèi)存問題所致。或者這兩個(gè)原因都有可能。
教訓(xùn):作為一名工程師,無論是作為事件一部分,還是在本地環(huán)境中,你都將調(diào)試軟件中的問題。如果不是通過結(jié)構(gòu)化推理來完成的話,調(diào)試可能會(huì)非常痛苦、緩慢。
我們?nèi)绾蜗到y(tǒng)找出程序失敗的原因?如果沒有“直覺”、“敏銳思維”等模糊的概念,我們又該怎樣才能做到這一點(diǎn)?我們想要找到一種查找失敗原因的方法——這種方法:
- 不需要先驗(yàn)知識(shí)
- 是有系統(tǒng)的
- 我們可以確定找到根本原因,并隨意復(fù)制
將科學(xué)方法應(yīng)用于調(diào)試問題,是發(fā)展失敗理論的公正方法??茖W(xué)調(diào)試的步驟如下:
- 重現(xiàn)錯(cuò)誤(通常是一些時(shí)間、數(shù)據(jù)、用戶、操作系統(tǒng)、調(diào)試器的組合)
- 觀察事實(shí)(徹底讀取日志、錯(cuò)誤跟蹤等)
- 在日志中明確地陳述假設(shè),而不是在心里做假設(shè)
- 如果你發(fā)現(xiàn)程序中的某部分存在錯(cuò)誤,使用結(jié)構(gòu)化方法來縮小錯(cuò)誤范圍,如二分搜索
- 測(cè)試假設(shè):使用日志、斷點(diǎn)、斷言
- 如果通過驗(yàn)證,應(yīng)用修復(fù)并確保沒有新錯(cuò)誤
- 如果無效,請(qǐng)重復(fù)步驟 3 到步驟 6
對(duì)簡(jiǎn)單的調(diào)試情況來說,這可能看起來有點(diǎn)過頭。但是,對(duì)于涉及多個(gè)團(tuán)隊(duì)的復(fù)雜分布式系統(tǒng)而言,一個(gè)系統(tǒng)科學(xué)的調(diào)試過程為消除模糊性提供必要的結(jié)構(gòu)。
11. 額外福利
(1)分享知識(shí),服務(wù)他人
優(yōu)秀的行為是幫助他人成長(zhǎng)。當(dāng)你需要用別人能理解的方式來解釋某事時(shí),你對(duì)事情的理解會(huì)更清晰。
每天在 Slack 上分享有思想的鏈接,進(jìn)行演示、稱贊他人的積極行為,挑戰(zhàn)不明確的決定,并在你希望與某人或某項(xiàng)決定有著不同的方向時(shí),給予建設(shè)性的反饋。你可以使用“感謝 ABC……希望 XYZ”的句式表達(dá)你的反饋。
通過這樣做,你可以為自己打造個(gè)人品牌,從而獲得職業(yè)資本。研究表明,那些擁有強(qiáng)大個(gè)人品牌、網(wǎng)絡(luò)形象和幫助他人的記錄的人,會(huì)取得成功,更重要的是,還會(huì)擁有令人滿意的職業(yè)生涯。
(2)塑造自己的世界
你無需接受這一現(xiàn)實(shí)世界。你可以通過坐在駕駛座上,塑造你所感知的世界。
這可能意味著在設(shè)計(jì)討論和 code reviews 期間發(fā)表意見,或者修復(fù)關(guān)鍵的不穩(wěn)定的測(cè)試(flaky test)。很多人會(huì)告訴你要多發(fā)言,提高知名度,以便在公司內(nèi)部發(fā)展,但他們卻從來不告訴你怎樣才能做到這一點(diǎn)。要做到這一點(diǎn),最好的方法是擁有堅(jiān)定的觀點(diǎn)和信心,將人們拉向你的方向。不要畏懼組建小型團(tuán)隊(duì)來構(gòu)建 / 改進(jìn)事物。不要屈服于你的恐懼。要大聲說出來,只要不是無禮的,你都可以說出來。
負(fù)面情緒是改變的巨大動(dòng)力。如果有問題讓你感到困擾,你要捫心自問,并想出該如何進(jìn)行改變。如果你將每一天都當(dāng)作成長(zhǎng)的途徑,那么生活就會(huì)成為一種鍛煉。
(3)結(jié)識(shí)朋友
如果你像我一樣,想弄清楚什么對(duì)你真正重要,那就去結(jié)識(shí)許多朋友吧,尤其是那些讓你感興趣的人。這可能意味著去參加會(huì)議、參加在線社區(qū)或者在黑客松活動(dòng)和項(xiàng)目上進(jìn)行合作。這種接觸會(huì)幫你弄清楚你想要做什么。這樣,你就可以對(duì)那些無關(guān)緊要的事說“No”,并抓住對(duì)你來說重要的那些機(jī)會(huì)。
許多成功人士感到幸運(yùn),宣稱只是因?yàn)樗麄冊(cè)谝粋€(gè)正確的時(shí)間和正確的地點(diǎn)做了正確的事情而已,從而知道自己想從什么開始。這讓他們能隨機(jī)應(yīng)變,抓住機(jī)會(huì),減少遺憾。遇到聰明人時(shí),不要輕易拒絕。
通過這樣做,我發(fā)現(xiàn)我更喜歡的是廣度而不是深度,更看重創(chuàng)造力和自由,喜歡多樣化和非正式的關(guān)系。我不適合從事結(jié)構(gòu)化的重復(fù)性工作、例行公事、穩(wěn)定和安全的事情。
如果你知道你想要什么,世界會(huì)給你所需的信息。
全棧編程充滿樂趣。這是一個(gè)不斷發(fā)展的領(lǐng)域,有一片學(xué)習(xí)沖浪的海洋。不要把自己或錯(cuò)誤看得太重。分享它們,不斷成長(zhǎng)。