每個開發者的職涯中都會有那麼一個時刻,他們會意識到光是寫出能執行的程式碼是不夠的。每個人經歷這一刻的方式都不盡相同。也許你正盯著六個月前提交的拉取請求,對當時自己做出的決定感到懊惱不已。也許你凌晨兩點還在除錯生產環境的問題,周圍堆滿了能量飲料罐,百思不得其解,為什麼這麼簡單的問題會演變成如此災難性的後果。又或許你正在和一位程式設計高手結對編程,他處理問題毫不費力——幾分鐘就能解決你可能需要花費數小時才能解決的問題——而你卻不禁疑惑,自己和他之間究竟存在什麼差距。
我從事專業程式設計工作十餘年,可以肯定地告訴你:優秀程式設計師和卓越程式設計師之間的區別,與掌握更多演算法或記住更多語法無關。這與是否畢業於名牌大學或在FAANG公司工作也無關。真正的差別在於那些不易察覺的地方──日常習慣、無數次重複的微小決策,以及默默無聞地完成那些不為人知的瑣碎工作的自律。
這無關天賦。我見過才華洋溢的開發者因為只依賴人的智力而最終失敗。我也看過平庸的程式設計師透過刻意練習和習慣養成,蛻變為傑出的人才。偉大的人並非天生,而是後天培養的,一個習慣,一個習慣。
接下來要介紹的並非效率提陞技巧或快捷鍵,而是隨著時間推移不斷累積的深層、根本性的習慣。無論你今天寫的是 Python 微服務,還是十五年後的量子計算演算法,這些習慣都至關重要。其中一些習慣可能會讓你感到挑戰,一些習慣可能會讓你覺得有違常理,但所有這些習慣都需要你付出努力才能養成。
但如果你堅持下去,你不僅會成為一名更優秀的程式設計師,還會成為其他團隊夢寐以求的那種開發者——那種會被拉去解決最棘手問題、塑造工程開發方式的人。
我們開始吧。
當我指導初級開發人員時,我經常問他們:「你花多少時間閱讀別人的程式碼,又花多少時間寫自己的程式碼?」答案幾乎總是相同的:不多。也許他們在遇到問題時會快速瀏覽文件或庫的源程式碼,但真正有意識地深入閱讀程式碼?很少。
這是區分優秀開發者和卓越開發者的第一個習慣:卓越的開發者都是如飢似渴的程式碼閱讀者。
不妨這樣想。如果你想成為一位偉大的小說家,你不會整天埋頭寫作。你會廣泛閱讀,進行批判性、分析性的閱讀。你會研究海明威如何建構句子,厄休拉‧勒奎恩如何建構世界,托妮‧莫里森如何運用語言喚起情感。程式設計也是如此。軟體工程的技藝既需要實踐,也需要觀察。
但正是這一點讓這個習慣如此強大:閱讀程式碼能教你一些單靠編寫程式碼永遠無法學到的東西。當你在寫程式碼時,你會被困在自己的思維模式、固有思維模式和固有偏見中。你會自然而然地選擇你已經知道的解決方案。而閱讀他人的程式碼則能讓你接觸到不同的思考方式、不同的問題解決方法以及不同的抽象層次。
我記得第一次通讀 Redux(這個流行的狀態管理庫)原始碼時的情景。當時我的 JavaScript 等級屬於中級,雖然熟悉一些 JavaScript,但不算是高級。讓我印象深刻的不僅是程式碼的運作方式,更是它的簡潔性。 Redux 的核心實作只有幾百行程式碼。開發者們將一個複雜的問題(管理應用程式狀態)提煉到了最本質的層面。閱讀這段程式碼徹底改變了我對軟體設計的看法。我意識到,複雜性並非值得稱讚之處,簡潔才是。
優秀的開發者將閱讀程式碼作為日常習慣。他們不會等待特定的理由才深入研究程式碼庫。他們這樣做是因為他們好奇,因為他們渴望學習,因為他們知道,那些文件中蘊藏著別人花費數年時間才學到的經驗教訓。
以下是培養這種習慣的實際方法:
安排專門的閱讀時間。就像你會安排時間做程式設計專案一樣,也要安排時間閱讀程式碼。可以先從每週兩次,每次 30 分鐘開始。選擇一個你經常使用的函式庫或框架,通讀它的原始碼。不要略讀-要逐行閱讀。遇到不理解的地方,不要急著跳過。停下來,查閱資料,弄清楚。
帶著目的去閱讀,不要被動地閱讀。閱讀時要不斷提問:他們為什麼要這樣組織程式碼?他們用這種抽象方式解決了什麼問題?如果是我,我會怎麼做?有哪些模式可以參考?是什麼讓這段程式碼易於理解或難以理解?
閱讀不同領域和不同語言的程式碼。如果你是 Web 開發人員,就去讀嵌入式系統程式碼;如果你用 Python,就去讀 Rust。其中的模式和原則往往超越了具體的技術。我曾經將從 Erlang 的 OTP 框架中學到的知識應用到 Node.js 微服務的架構設計中,儘管這兩種語言截然不同。容錯和監督樹的基本原理是普遍適用的。
加入程式碼閱讀俱樂部運動吧!一些開發團隊已經成立了“程式碼閱讀俱樂部”,開發者們定期聚會,共同閱讀和討論一些有趣的程式碼庫。如果你的團隊還沒有這樣的俱樂部,那就趕快建立一個吧!選擇一個備受好評的開源專案,大家一起深入研究。這些討論會的成果彌足珍貴──你會聽到不同的人如何解讀同一段程式碼,他們注意到了什麼,以及他們重視什麼。
學習大師們的作品。有些程式設計師的程式碼值得專門研究。例如,John Carmack 的遊戲引擎程式碼、Rich Hickey 的 Clojure、Linus Torvalds 的 Git 以及 DHH 的 Rails。這些程式碼並非完美無缺(世上沒有完美),但它們凝聚了數千小時的精雕細琢和深入思考。閱讀他們的作品就像跟隨一位技藝精湛的工匠學習。
這種習慣帶來的改變雖然微妙,卻意義深遠。你會開始培養對程式碼品質的直覺,更快地辨識出各種模式,並建立一個可以隨時呼叫的解決方案庫。當你遇到新問題時,不會立刻上網搜尋,而是會想起:「哦,這和 React 處理 reconciliation 的方式很相似」或「這是我在那個 Python 庫裡看到的策略模式」。
我採訪過數百位開發者,通常只需幾個技術問題就能判斷對方是否認真閱讀程式碼。他們會引用自己研究過的實作方案,比較不同函式庫中的實現方式,他們的觀點是基於對各種方案的實際考察,而不僅僅是 Stack Overflow 上的答案。
單靠閱讀程式碼並不能讓你成為一個優秀的開發者,但它是基礎。其他一切都建立在這個基礎上。因為如果你從未見過優秀的程式碼是什麼樣子,你就無法寫出優秀的程式碼。
優秀的程式設計師實現功能,而卓越的程式設計師則理解他們所建立產品的業務背景、使用者需求以及系統性影響。
這聽起來似乎顯而易見,但卻是最常被忽略的習慣之一,尤其是在那些自詡技術精湛的開發者中。我曾與一些才華橫溢的工程師共事,他們能夠實現任何演算法、優化任何查詢、建立任何系統——但他們卻把需求奉為圭臬,從不質疑自己被要求建立的東西是否真的是正確的解決方案。
這裡有一個故事完美地說明了這一點。幾年前,我參與開發一個金融科技平台,我們收到一個功能請求,要求增加「待處理交易」功能。產品經理希望使用者能夠看到已授權但尚未結算的交易。這聽起來很簡單。
一個優秀的開發人員會接受這個需求並加以實現。在資料庫中建立一個新的狀態字段,加入一些使用者介面元件,編寫業務邏輯。搞定。發佈吧。
但我們的一位資深工程師採取了不同的做法。她安排了一次與產品經理的會議,並問道:“用戶為什麼需要查看待處理的交易?他們試圖解決什麼問題?”
原來用戶抱怨的是帳戶餘額顯示不正確——他們購物後,餘額卻不會立即更新。他們並非真的想查看待處理交易,而是對可用餘額感到困惑。真正的解決方案並非顯示待處理交易,而是顯示兩個餘額:當前餘額和可用餘額,後者會考慮待處理的授權。
這看似微小的差別,卻徹底改變了實現方式。我們沒有為待處理交易建立一個全新的使用者介面(這會增加使用者的認知負擔),而是優化了現有的餘額顯示。這個方案更簡潔,更符合用戶需求,實現時間也縮短了一半。
這就是在實踐中投資於「為什麼」的意義。
優秀的開發者會將每一個功能請求、每一份錯誤報告、每一個技術決策視為理解更深層背景的契機。他們不會只問“需要建造什麼?”,而是會問:
這項技術旨在解決什麼問題?不是技術問題,而是人的問題。哪些人會受到影響?他們正在經歷怎樣的痛苦?
有哪些限制因素?是因為監理規定的最後期限而緊急?是因為競爭壓力?還是因為重要客戶威脅流失?了解緊急程度有助於您做出更明智的權衡決策。
二階效應是什麼?這將如何改變使用者行為?這將如何影響系統的複雜性?我們將承擔哪些維護負擔?
這是正確的解決方案嗎?有時候,最好的程式碼就是沒有程式碼。我們能否透過更好的使用者體驗來解決這個問題?透過配置而不是程式設計?透過找出問題的根源而不是僅僅治標不治本?
我曾經花了三個小時參加一個快取層技術設計評審,這個方案本來可以解決我們的效能問題。提出這個方案的工程師做得非常好——詳細的基準測試、穩健的架構、清晰的遷移計畫。但隨後有人問:“我們為什麼會遇到這些性能問題?”
我們深入調查後發現,根本原因在於一個優化不佳的查詢語句,它導致了數百萬次不必要的資料庫呼叫。我們原本打算建立一個快取系統來繞過這個問題,而實際上只需兩行 SQL 最佳化語句就能解決。在弄清楚「為什麼」之後,我們避免了數週不必要的工作。
養成這種習慣需要勇氣,尤其是在職業生涯初期。質疑需求、反駁產品經理或資深工程師、提出既定方案可能並非最優,這些都讓人感覺到冒險。但我學到的是:人們尊重那些能夠批判性思考自己所開發產品的開發者。他們需要的是能夠及早發現問題、參與產品思考、並將軟體開發視為解決問題而非僅僅完成任務的合作夥伴。
如何養成這個習慣:
把「為什麼?」當作你的首要問題。在開始任何重要的工作之前,確保你能清楚地闡述這項工作的重要性。如果你做不到,表示你對問題的理解還不夠透徹。安排時間與提出這項工作的人——產品經理、其他工程師、客戶支援人員——溝通,並不斷提問,直到徹底弄清楚問題的來龍去脈。
研究你所處的領域。如果你在開發醫療保健軟體,那就去了解醫療保健產業。閱讀 HIPAA 相關內容。了解醫院的運作方式。如果可以,盡量與醫生溝通。你對這個領域了解得越透徹,就越能更好地評估技術方案是否真正解決了實際問題。我看過一些開發人員把領域知識當作背景噪音,他們的程式碼也印證了這一點——技術上很精湛,但卻與實際業務運作方式脫節。
參與用戶調查。觀看用戶測試。閱讀支持工單。加入客戶電話會議。沒有什麼比親眼目睹真實使用者在使用你的軟體時所遇到的困難更有效的了。這會從根本上改變你對產品開發的思考。只要觀看一次使用者測試,以後就不會再寫晦澀難懂的錯誤訊息了。
培養系統思維。你做的每一個改動都會對系統產生連鎖反應。看似無害的功能加入可能會增加資料庫負載、使部署流程複雜化,或產生新的極端情況,從而破壞現有功能。優秀的開發者在編寫程式碼之前,會在腦海中建構這些連鎖反應的模型。他們以系統化而非孤立的功能進行思考。
記錄“為什麼”,而不僅僅是“做了什麼”。編寫程式碼註解時,不要解釋程式碼的功能(閱讀程式碼後應該很清楚),而是要解釋它存在的意義。為什麼選擇這種方法而不是其他方法?是什麼限製或要求促使你做出這個決定?未來的你和維護者都會感激你的。
說實話,這種習慣確實很累人。照著指示去做,在心理上確實更輕鬆。但關鍵在於──優秀的開發者並非因為選擇了捷徑才變得優秀,而是因為他們勇於承擔結果的責任,而不僅僅是產出。他們明白,自己的工作不是寫程式碼,而是解決問題。而你無法解決你不理解的問題。
養成這種習慣的開發者會成為值得信賴的顧問。他們會被邀請參加規劃會議,影響產品方向,並成為團隊的倍增器,因為他們能及早發現問題,避免造成迭代周期浪費和用戶失望。
理解「為什麼」能讓你從程式碼編寫者蛻變為工程師。而這種轉變至關重要。
晚上11點,生產系統宕機了。客戶怨聲載道。經理每隔十分鐘就要求報告最新情況。壓力巨大,你的第一個反應是趕緊想辦法解決問題──重啟伺服器、回溯上次部署、調整配置──總之,想盡辦法解決問題。
這是優秀開發者和卓越開發者之間最顯著的差異。
優秀的開發者靠猜測。他們依賴直覺、過往經驗和希望。他們會在未完全理解問題的情況下進行修改,把除錯當作打地鼠遊戲。有時他們運氣好,偶然發現解決方案。但更多時候,他們找不到,幾個小時的功夫就此白白浪費在挫折感中。
優秀的開發者將除錯視為嚴謹的科學過程。他們提出假設,收集資料,進行實驗,並有系統地排除各種可能性,直到找到問題的根源。即使耐心看似不可能,他們也能保持耐心。即使一片混亂,他們也能條理清晰地解決問題。
讓我來跟你說說我遇到過的最嚴重的生產環境漏洞。我們的電商平台開始隨機丟單——不是所有訂單都丟,只是一部分。大概有2-3%的交易在支付端完成了,但資料庫裡卻沒有產生任何訂單記錄。公司收入因此大幅下降。漏洞每拖延一小時,公司就要損失數千美元。
「趕緊修復」的壓力巨大。最簡單的做法是憑直覺就開始部署修補程式。但我們的首席工程師卻反其道而行:她讓所有人退後一步,遵循一套結構化的除錯流程。
首先,要重現問題。這看似顯而易見,但許多開發人員都會忽略這一步,尤其是在壓力之下。她搭建了一個測試環境,並用大量的測試事務進行測試,直到我們能夠穩定地重現訂單下降的問題。這一步至關重要——這意味著我們可以在不影響生產環境的情況下驗證各種假設。
其次,收集資料。這些被丟棄的訂單有什麼共通點?我們提取了日誌,追蹤了請求在每個系統元件中的流轉,分析了時間節點,檢查了用戶代理,仔細審查了支付網關的回應。我們當時並非急於尋找答案,而是要建構問題的完整圖像。
第三,形成假設。基於資料,我們產生了一個可能原因列表,並按可能性排序:資料庫連接逾時、訂單建立邏輯中的競爭條件、支付網關 webhook 故障、API 速率限制、網路分區、Redis 快取中的狀態損壞。
第四,有系統地進行測試。我們逐一測試每個假設,從最可能的假設開始。對於每個測試,我們都明確定義了什麼樣的結果才能證實或證偽理論。不靠猜測,也不搞「試試看會發生什麼事」這種隨意的做法。每一個實驗都是經過深思熟慮的。
經過四個小時的細緻調查,我們終於找到了問題所在:一個競態條件,即並發的支付 Webhook 可能導致支付狀態被標記為成功,但訂單建立交易卻被回滾。該漏洞僅在高負載且滿足特定時間條件時才會出現—因此具有間歇性。
關鍵在於:我們原本可能要花二十個小時胡亂摸索,隨意修改,在修復舊 bug 的同時又製造出新的 bug。但係統性的除錯方法卻讓我們在四分之一的時間內找到了根本原因。更重要的是,我們正確地修復了問題,並且確信問題已經徹底解決。
將除錯視為一種有條不紊的實踐,而不是混亂的故障排除,這種習慣或許是軟體工程中最被低估的技能。
優秀開發者是如何除錯的:
他們克制住急於尋找解決方案的衝動。當你發現錯誤時,你的大腦會立刻想要修復它。要克服這種本能。首先要花時間去理解問題。我有一個個人原則:花在理解一個bug上的時間至少是你預計修復它所需時間的兩倍。這個原則讓我節省了無數個小時,避免了僅僅追蹤症狀而忽略根本原因。
他們明確地運用了科學方法。寫下你的假設。寫下哪些證據可以證實或反駁它。進行實驗。記錄結果。如果需要,就轉向下一個假設。我甚至專門寫了一個除錯日誌,記錄複雜 bug 的整個過程。這能讓我保持嚴謹,避免因為忘記之前已經嘗試過而重複驗證同一個理論。
他們能把問題縮小。優秀的除錯員精通二分查找法。如果一個 bug 存在於 1000 行程式碼的某個地方,他們會註解掉 500 行,看看 bug 是否仍然存在。然後是 250 行。再然後是 125 行。他們有系統地縮小問題範圍,直到 bug 無處可藏。
他們對工具的理解非常透徹。除錯器、效能分析器、日誌分析器、網頁檢查器、資料庫查詢分析器-優秀的開發人員會投入時間精通這些工具。他們可以設定條件斷點、分析記憶體轉儲、追蹤系統呼叫、解讀火焰圖。這些工具大大提升了他們的效率。我曾親眼目睹一些資深開發人員在幾分鐘內就除錯出了其他人束手無策數天的問題,原因僅僅是他們懂得如何高效地使用性能分析器。
他們將除錯功能融入程式碼中。優秀的開發者編寫的程式碼易於除錯。他們在關鍵決策點加入有意義的日誌語句。他們從一開始就將可觀測性融入系統中——指標、追蹤、結構化日誌。他們深知,一個 bug 生命週期中 80% 的時間都花在了理解 bug 的來龍去脈上;讓這個過程更輕鬆,絕對是值得投入的時間。
他們先重現問題,然後修復,最後驗證。永遠不要修復無法重現的 bug——那隻是在猜測。一旦能夠重現問題,就修復它。然後驗證修復後的 bug 在最初出現問題的條件下是否真的有效。太多開發者忽略了驗證這一步驟,最終發布的修復程式實際上並沒有解決任何問題。
他們會深入挖掘問題的根本原因。當你發現一個 bug 時,連續問自己五次「為什麼會發生這種情況?」。每個答案都會讓你更深入地了解問題所在。 「伺服器崩潰了。」為什麼? 「記憶體不足。」為什麼? 「記憶體洩漏。」為什麼? 「物件沒有被垃圾回收。」為什麼? 「事件監聽器沒有被移除。」為什麼? 「元件卸載時沒有進行清理。」現在你找到了問題的根本原因,而不僅僅是症狀。
我曾與一些開發人員共事,他們似乎擁有某種超乎常人的能力,總是能找到 bug。剛入行時,我以為他們只是更聰明或更有經驗。現在我明白了真相:他們只是將一套系統化的方法內化於心。他們相信流程,而非直覺。
這種習慣還有深遠的心理益處。除錯不再令人倍感壓力,變成了智力上的享受。當出現 bug 時,你不再感到無助,而是充滿信心——你擁有流程、方法和解決之道。 bug 或許很複雜,但你知道如何應對。
優秀的開發人員在遇到問題時不會驚慌失措是有原因的。他們訓練自己將每個 bug 都視為一個有解的謎題,而不是一場危機。他們知道,系統性的調查最終總是能解決問題。這種自信正是源自於這種習慣。
還有一點很妙:當你用科學的方法除錯時,你不僅能更快地修復 bug,還能從每個 bug 中學到更多。每個 bug 都成為你了解系統、了解邊界情況以及了解自身思維模式的一課。只會猜測和檢查的除錯人員什麼也學不到。而科學的除錯人員則在解決每個問題的過程中累積對系統的深刻理解。
下次遇到 bug 時,不要急著修改程式碼。深呼吸。打開筆記本。寫下你已知的資訊。提出假設。驗證假設。讓科學方法來指導你。
你會驚訝於自己效率的提升幅度。
一個令人不快的真相是:你作為開發人員的大部分職業生涯並非花費在編寫新程式碼上,而是花費在閱讀、理解和修改現有程式碼上——這些程式碼可能是其他人寫的,也可能是過去的自己寫的,而過去的自己實際上可能就是其他人。
然而,當我審查優秀開發者的程式碼時,我總是發現同樣的錯誤:他們追求的是巧妙或簡潔,而不是清晰易懂。他們寫的程式碼雖然精妙複雜,卻需要高度集中註意力才能理解。他們把編譯器或解譯器當作了主要讀者。
優秀的開發者會顛倒這種優先順序。他們首先編寫供人使用的程式碼,其次才是機器使用的程式碼。
這聽起來或許是一句老生常談,但它代表了一種根本性的思維轉變,會影響你寫的每一行程式碼。讓我來解釋一下我的意思。
以下是我在生產程式碼庫中找到的一段程式碼片段:
def p(x): return sum(1 for i in range(2, int(x**0.5)+1) if x%i==0)==0 and x>1
你看得出這個函數是做什麼的嗎?如果你熟悉演算法,你可能會認出它是一個質數檢測器。它運作完美,機器執行起來也毫無問題。但對於閱讀這段程式碼的人呢?這卻是一個需要解開的謎題。
以下是優秀開發者編寫相同函數的方式:
def is_prime(number):
"""
Returns True if the number is prime, False otherwise.
A prime number is only divisible by 1 and itself.
We only need to check divisibility up to the square root of the number
because if n = a*b, one of those factors must be <= sqrt(n).
"""
if number <= 1:
return False
if number == 2:
return True
# Check if number is divisible by any integer from 2 to sqrt(number)
for potential_divisor in range(2, int(number ** 0.5) + 1):
if number % potential_divisor == 0:
return False
return True
第二個版本更長,也更冗長。機器並不在意-兩個版本的時間複雜度都是 O(√n)。但對人類來說,差別卻天壤之別。第二個版本具有自文件性,即使是初級開發人員也能理解。即使六個月後你已經忘記自己寫過它,你仍然能夠理解。它的意圖非常清晰。
這種習慣──即寫作時力求讓讀者理解──體現在許多方面:
命名要體現意圖。像temp 、 data 、 obj 、 result的變數名稱毫無意義。優秀的開發者會選擇能夠編碼意義的名稱: unprocessed_orders 、 customer_email_address 、 successfully_authenticated_user 。沒錯,這些名稱更長。沒關係。多出的幾個字元是值得的。程式碼你只寫一次,但要讀幾十遍。
我記得看過一段程式碼,有人把一個變數命名為x2 。我必須追蹤 50 行邏輯才弄清楚它代表的是「XML 轉 JSON 轉換器」。他們省下了 18 個字符,卻讓未來的讀者浪費了幾分鐘的認知時間。這簡直太不划算了。
功能單一的函數和方法。當一個函數試圖完成多項任務時,命名、測試和理解都會變得困難。優秀的開發者會將功能提取到命名清晰的函數中,即使這看起來有點「殺雞用牛刀」。他們明白,一系列命名良好的函數呼叫通常比原始實作更能清晰地傳達意圖。
策略性註釋。很多開發者都忽略了一個關鍵點:優秀的開發者不會註解程式碼的功能,而是會註解程式碼為什麼要這樣做。如果你的程式碼需要註解來解釋其功能,那麼程式碼本身就不夠清晰。但是,解釋某些決策背後的原因的註釋呢?這些註解才是真正的寶藏。
「為什麼」之類的評論可能會解釋:
「我們之所以使用演算法 X 而不是顯而易見的演算法 Y,是因為對於我們的資料模式,演算法 Y 的時間複雜度為 O(n²)」。
“這個奇怪的超時值是通過使用外部 API 進行大量測試得出的——較小的值會導致間歇性故障。”
“我們故意不處理極端情況 X,因為考慮到遷移 Y 所強制執行的資料庫限制,這是不可能的。”
這些註釋保留了原本會遺失的上下文資訊。它們可以防止未來的開發者「優化」你精心選擇的方法,或刪除他們認為不必要的程式碼。
程式碼結構應與思維模型相呼應。優秀的開發者會按照人類自然思考領域的方式來組織程式碼。如果你正在建立一個電子商務系統,你的程式碼結構應該反映訂單、客戶、付款和庫存等概念,而不是像管理器、處理程序和處理器這樣的通用抽象概念。
我曾經參與過一個程式碼庫的開發,其中包含DataManager 、 DataHandler 、 DataProcessor和DataController幾個元件。這些名稱根本無法反映它們的實際功能。當我們將其重構為OrderValidator 、 PaymentProcessor和InventoryTracker之後,程式碼庫突然變得易於理解。新加入的團隊成員可以輕鬆找到所需的元件。程式碼結構也與他們對業務的理解相符。
一致的模式。人類天生擅長模式匹配。當你的程式碼庫遵循一致的模式時,開發者就能輕鬆地將知識從一個部分遷移到另一個部分。如果每個模組的實作方式都不同,那麼每次上下文切換都需要重新學習。優秀的開發者重視一致性,即使他們個人可能更傾向於不同的方法。
合適的抽象層次。這一點看似細微,但其實至關重要。優秀的開發者會謹慎地避免在同一個函數中混用不同的抽象層次。如果你正在編寫高層業務邏輯,就不應該突然深入到底層字串操作的細節。應該將這些細節提取到一個命名清晰的輔助函數中。保持程式碼的每一層在概念層次上保持一致。
以下是一個混合抽象層次的例子:
function processOrder(order) {
// High-level business logic
validateOrder(order);
// Suddenly low-level string manipulation
const cleanEmail = order.email.trim().toLowerCase().replace(/\s+/g, '');
// Back to high-level
chargeCustomer(order);
sendConfirmation(order);
}
更好的:
function processOrder(order) {
validateOrder(order);
const normalizedOrder = normalizeOrderData(order);
chargeCustomer(normalizedOrder);
sendConfirmation(normalizedOrder);
}
現在,該函數讀起來就像一系列業務步驟,而不是業務邏輯和實作細節的混合。
養成這種習慣需要自律,因為為機器編寫程式碼通常比為人類編寫程式碼容易。機器比較寬容-它不在乎你的變數名稱是x還是customer_lifetime_value_in_cents 。但人類卻非常在意。
我見過一些才華洋溢的開發者因為這個習慣而自毀前程。他們編寫的程式碼簡潔精煉,展現了對語言特性的精通。但之後,他們卻要在程式碼審查中花費數小時解釋程式碼的功能,因為其他人根本看不懂。他們優化的方向錯了。
有一句名言常被歸功於多位程式設計界泰斗:「任何傻瓜都能寫出電腦能理解的程式碼,而優秀的程式設計師則能寫出人類能理解的程式碼。」這句話的智慧會隨著經驗的累積而愈發凸顯。
當你養成以人為本的寫作習慣時,奇妙的事情就會發生:你的程式碼將變得易於維護。團隊協作速度更快,因為程式碼更容易理解。新開發人員的入職培訓只需幾天而不是幾週。 bug 減少,因為程式碼的意圖清晰明了。技術債累積速度也更慢,因為未來的修改無需費力地去研究晦澀難懂的邏輯。
在程式碼審查中,我總是能透過一個特點辨識出優秀的開發者:我很少需要問「這段程式碼是做什麼的?」程式碼本身就能告訴我答案。我可能會問一些關於權衡取捨、效能影響、替代方案的問題——但我從不擔心他們理解不了程式碼的基本邏輯。
寫程式碼時,要像對待一個有暴力傾向、知道你住址的變態殺手。六個月後,維護你程式碼的人就是你自己,到時候你會感謝自己當初的清晰思路。
當我還是初級開發人員時,我把各種限制都視為需要克服或繞過的問題。時間有限?令人沮喪。與舊系統相容性?令人惱火。記憶體限制?限制了開發。我當時認為我的工作就是克服這些限制,實現「正確」的解決方案。
優秀的開發者看待限制的方式截然不同。他們欣然接受限制,充分利用限制。他們明白,限制並不會限制創造力──反而能集中和引導創造力,常常能創造出比擁有無限資源時更優秀的解決方案。
這是區分優秀和卓越最違反直覺的習慣之一,需要數年時間才能內化。
讓我分享一個讓我對此有更深刻理解的故事。當時我在一家新創公司工作,開發一款面向新興市場的行動應用程式。我們的目標用戶使用的是低階安卓設備,網路連線不穩定,只有2G網絡,而且流量套餐有限。我們最初的想法是把這些限制當作障礙——開發一個精簡版的「輕量級」產品,功能有所簡化和妥協。
然後,我們的技術主管說了一句話,改變了我的看法:“這些不是限制,而是我們的設計參數。它們告訴我們,在這種情況下,卓越是什麼樣子。”
我們徹底改變了方法。我們不再問“如何在有限的環境下實現所有功能?”,而是問“在這些參數條件下,我們能創造出的最佳體驗是什麼?”
我們從零開始設計,始終堅持離線優先原則。我們大幅壓縮影像,並儘可能使用 SVG 格式。我們實現了增量更新,使應用程式能夠在網路連線不穩定的情況下自動更新。我們採用了智慧快取和預測性預取。我們讓每一位元組都發揮最大效用。
結果如何?即使在網路連線極差的情況下,這款應用程式也能流暢執行,反應迅速。其體驗甚至優於許多面向高端市場的應用,因為我們必須深入思考性能和效率。那些專為高頻寬、高性能設備設計的西方競爭對手,在這個市場上根本無法與之匹敵。他們的應用臃腫、運作緩慢且消耗大量資料。
這些限制並沒有阻礙我們,反而使我們變得更強。
這條原則遠不止於技術限制。想想時間限制吧。優秀的開發者會把緊迫的截止日期視為壓力,而卓越的開發者則將其視為清晰目標的契機。當你擁有無限的時間時,你可以探索每一種可能的解決方案,無止盡地重構,無限地打磨程式碼。聽起來很棒,對吧?但無限的時間往往會帶來更糟糕的結果,因為沒有任何東西能迫使你分清輕重緩急,去辨識真正重要的事項,去做出艱難的取捨。
我曾目睹一些專案因為截止日期寬鬆而漫無目的地拖延數月,不斷加入新功能,重構程式碼,卻始終無法真正發布。我也看過一些團隊,他們被要求在兩週內交付最小可行產品(MVP),最後卻打造出了目標明確、範圍清晰、真正解決使用者問題的產品。時間限制迫使他們釐清哪些才是真正重要的。
或者考慮一下團隊的限制。也許你是一個小型團隊中唯一的後端開發人員。優秀的開發人員會覺得壓力巨大──責任太重,維護工作太多。而卓越的開發人員則會將其視為塑造整個後端架構、做出一致的決策、累積深厚專業知識的機會。獨自工作的限制迫使你編寫極易維護的程式碼,因為維護工作將由你一人承擔。
或者,也可能是遺留系統的限制。你正在整合一個已有 15 年歷史、文件很糟糕的 SOAP API。優秀的開發人員會抱怨,而卓越的開發人員則會將其視為建立一個清晰抽象層的機會,從而將程式碼庫的其他部分與這種複雜性隔離開來。遺留系統的限制迫使你認真思考邊界和介面。
以下是培養這種習慣的方法:
重新組織語言。不要再說“由於限制條件 Y,我們不能做 X”,而要說“在限制條件 Y 下,我們能設計出的最佳解決方案是什麼?”這種語言上的轉變會帶來思維上的轉變。你的思維方式會從以問題為中心轉變為以解決方案為中心。
研究歷史案例。 Twitter最初的 140 個字元限制並非漏洞,而是塑造平台特性的一種限制。為超級任天堂開發遊戲的開發者僅憑 32 KB 的記憶體就創作出了傑作。他們沒有無限的資源,但正是這些限制激發了驚人的創造力和效率。阿波羅計畫的導引計算機的運算能力甚至不如現代計算器,但它卻將人類送上了月球。研究這些案例中限制是如何推動創新的。
人為地設定限制。這聽起來很瘋狂,但確實有效。如果你正在開發一個 Web 應用,不妨挑戰自己:如果它不能使用 JavaScript 呢?如果它的打包大小必須小於 50KB 呢?如果它必須在 30 美元的安卓手機上執行呢?這些人為的限制會迫使你質疑既有假設,並探索不同的方法。你最終可能不會按照這些限制發布應用,但這種練習會讓你成為更優秀的開發者。
接受「寧可差也好」的理念。有時候,一個雖然無法處理所有極端情況的簡單方案,反而比一個能夠處理所有情況的複雜方案更好。限制條件迫使你明確地做出這種權衡。 UNIX 的設計理念——小型程序,每個程序做好一件事——正是源於記憶體和儲存空間的極端限制。這些限制條件反而催生了比無限資源下更好的設計原則。
尋找限制的饋贈。每一種限制都在試圖告訴你一些事情。記憶體限制提醒你要考慮效率。時間限制提醒你要專注於影響力。遺留系統限制提醒你要設計簡潔的介面。預算限制提醒你要使用成熟的技術,而不是一味追求新奇。那麼,限制究竟在教你什麼呢?
我看過很多開發者浪費大量精力去對抗限制,而不是順應限制。他們會花幾週時間設計繞過資料庫查詢限制的方案,而不是重構資料模型以適應限制。他們會為了繞過框架的設計而增加複雜性,而不是接受框架的概念。
優秀的開發者會選擇值得挑戰的目標。有時,限制條件確實有缺陷,應該要質疑。但更多時候,限制條件代表複雜系統中的現實權衡,在限制條件下工作比對抗限制更能取得理想的結果。
這種習慣也能塑造性格。接受限制需要謙遜-承認你不可能擁有一切,權衡取捨是真實存在的,完美是無法企及的。它需要創造力——在限制範圍內找到巧妙的解決方案。它需要專注——區分什麼是必要的,什麼是錦上添花的。
現代開發領域似乎總是追求更多:更多的工具、更多的框架、更多的函式庫、更多的功能、更強的可擴充性。但一些最具影響力的軟體是在嚴格的限制下建構的。 Redis 最初是為了解決一個對效能要求極高的特定問題而開發的。 Unix 系統是為記憶體佔用極小的機器設計的。 Web 本身的設計初衷就是為了在不可靠的網路上執行,並且對客戶端的能力要求極低。
當你接受限制條件時,你便不再與現實對抗,而是開始與之合作。你會成為一個務實的解決問題者,而不是一個理想主義的完美主義者。你會交付解決方案,而不是無止盡地追求最優解。
這就是美妙的弔詭:接受限制,往往反而能超越限制。限制迫使你培養的自律和創造力,反而能帶來更優的解決方案,而不是更差的解決方案。例如,針對 2G 網路最佳化的應用,在 5G 網路下也能流暢運作。由單人開發者編寫的、旨在易於維護的程式碼,在團隊壯大後依然易於維護。受時間限製而精簡的功能集,最終卻恰好滿足了使用者的需求。
限制並非你的敵人。它們是你的老師,你的焦點,也是你激發創意解決方案的催化劑。學會擁抱它們。
現代開發者的環境就像一台精心設計的干擾機器。 Slack 的提示音、電子郵件通知、沒完沒了的會議、“快速提問”,以及社交媒體和新聞推送的誘惑——所有這些都在合謀將你的注意力分割成無數碎片。
優秀的開發者在這樣的環境下工作。他們不斷切換任務,同時處理多個線程,並將快速回應視為美德。他們以忙碌為榮。
優秀的開發者會建構專注的堡壘。他們深知,自己最寶貴的資產並非對框架或演算法的掌握,而是能夠長時間深度專注於複雜問題的能力。他們將不受干擾的時間視為不可或缺的資源,比任何雲端運算額度都更珍貴。
這不僅是個人偏好,更是基於我們工作性質的必然要求。程式設計並非機械地敲擊程式碼行,而是一種建構和解決問題的過程,很大程度上是在腦海中進行的。你需要建構複雜的系統、資料流和邏輯的心理模型。這些模型非常脆弱,一次小小的干擾就可能摧毀數小時的心血,迫使你從頭開始重建。
我在職業生涯早期就為此付出了慘痛的代價。我一直以「永遠在線」為傲。我同時打開八個不同的通訊軟體,秒回訊息,整天在編碼、程式碼審查和技術支援工單之間來回切換。到了下午三點,我已經筋疲力盡,但工作效率卻很普通。我投入了大量時間,卻沒有拿出最佳水準。
一切都在我與一位名叫大衛的高級工程師合作一週後發生了變化。大衛的工作方式很神秘,他會以兩小時為一個工作時段。在這兩個時段裡,他會關閉所有通知,除了IDE和終端機之外關閉所有應用程式,然後戴上耳機。起初,我以為他有點孤僻。但後來我看到了他的成果。在兩個小時的專注時間內,他往往能完成我一整天分心工作才能完成的工作。而且品質更高——更少的bug,更簡潔的設計,更周全的邊界情況處理。他不僅速度更快,而且工作品質也達到了另一個層次。
大衛讓我明白,專注力是一種需要培養的技能,而不是與生俱來的特質。而且,它或許是你所能培養的最有價值的技能。
以下是優秀開發者如何保持和培養高度專注力的方法:
他們會嚴格安排專注時間,絕不掉以輕心。他們會在日曆上預留出幾個小時的時間段,並像對待與CEO的會議一樣認真對待這些與自己的約定。在這段時間裡,他們實際上處於離線狀態。有些公司甚至會制定諸如「無會議星期三」或「專注早晨」之類的政策,但優秀的開發者無論公司政策如何,都會自覺地遵循這些原則。
他們精通工具,但不會過度依賴。優秀的開發者使用「勿擾模式」、網站攔截器和全螢幕IDE等工具,並非為了提高效率,而是為了刻意避免干擾。他們的目標不是找到完美的應用程式,而是創造一個能夠進行深度工作的環境。他們明白,工具本身並不重要,重要的是工作的目的。
他們奉行單工處理。多工處理其實是個誤區,尤其是在程式設計領域。我們所謂的多工處理,實際上是快速切換上下文,而每次切換都會消耗認知資源。優秀的開發者會訓練自己專注於一件事,直到它自然而然地結束。他們可能會在手邊放一個「幹擾清單」——一個用來記錄突發想法或待辦事項的記事本——這樣他們就能在不中斷當前任務的情況下注意到這些想法。
他們勇敢地捍衛自己的專注。這是最難的部分。這需要對好心的同事說“不”,與管理者劃清界限,並抵制隨時待命的文化壓力。優秀的開發人員懂得清晰禮貌地溝通這些界限:「我現在正在深度工作,但我可以在下午3點幫您。」 如果溝通方式始終如一,大多數通情達理的人都會尊重這一點。
他們深知切換工作內容的成本。每一次中斷不僅耗時,還會耗費重新投入最初問題的時間。一個 30 秒的 Slack 問題就可能輕易中斷 15 分鐘的高效工作。優秀的開發者會讓團隊成員清楚地看到這種成本,從而幫助創造一種尊重深度工作的文化。
他們會根據自身的精力水平來安排一天的工作。專注力是一種有限的資源。優秀的開發者知道自己何時思維最敏捷——對許多人來說,是早晨——他們會盡全力把這段時間留給最需要投入精力的工作。會議、行政事務和程式碼審查都被安排在精力較低的時段。他們不會把精力最充沛的時間浪費在低價值、淺薄的工作上。
他們樂於接受無聊。這聽起來很奇怪,但卻至關重要。在感到沮喪或思維停滯時,人們的第一個反應往往是拿起手機——從推特或電子郵件中尋求多巴胺的刺激。優秀的開發者會抵制這種衝動。他們會專注於問題本身,必要時甚至凝視窗外,讓潛意識發揮作用。一些最精妙的解決方案並非源自於瘋狂的敲擊,而是在靜默的思考中誕生的。
這種習慣的好處遠不止於提高效率。深度專注是精通的來源。正是在這些不受干擾的專注時刻,你會遇到真正棘手的問題,這些問題會迫使你成長。你會培養出系統性除錯的耐心,洞察精妙架構的清晰思路,以及克服複雜難題的毅力——這些難題足以讓心不在焉的人束手無策。
此外,專注力會增強。就像肌肉一樣,你的專注力會隨著練習而增強。一開始可能難以集中註意力30分鐘,但隨著時間的推移,就能穩定地進行兩小時的深度工作。
在一個崇尚淺薄響應的世界裡,選擇深度專注似乎與主流文化背道而馳。但正是這種選擇,將合格的開發者與卓越的開發者區分開來。那些能夠經常進入心流狀態的開發者,才能交付複雜的功能,解決最棘手的bug,並創造出近乎神奇的高品質作品。
你最寶貴的程式碼將在專注狀態下編寫。務必誓死捍衛這種狀態。
習慣七:他們奉行策略性懶惰
如果你覺得「懶惰」是一種惡習而不是美德,那就大錯特錯了。優秀的開發者往往都很勤奮——他們會投入大量時間進行手動測試、重複配置和使用蠻力解決問題。他們認為付出就等於價值。
優秀的開發者懂得策略性地偷懶。他們樂於花一個小時自動化一個手動只需五分鐘就能完成的任務,這並非因為這樣能立即節省時間,而是因為他們極其厭惡重複勞動,願意前期投入以徹底消除這種弊端。他們不斷尋找能夠以最小的持續投入獲得最大產出的槓桿、捷徑和抽象方法。
這項原則通常被認為是 Perl 語言的創始人 Larry Wall 提出的,也是程式設計師的三大美德之一(另外兩個是急躁和傲慢)。策略性懶惰並非逃避工作,而是透過自動化枯燥乏味的工作來大幅提高效率,從而將精力集中在真正有挑戰性、有趣的問題上。
我曾親眼目睹一個典型的例子,我的一位DevOps工程師就完美地詮釋了這一點。我們的部署流程包含15步驟的檢查清單,大約需要30分鐘,而且需要高度集中。任何一個步驟出錯都可能導致生產環境宕機。我們大多數人都把它當作工作中必不可少但又繁瑣的部分。
然而,她卻無法忍受。兩天之內,她編寫了一套腳本,實現了整個流程的自動化。最初的投入相當可觀——大概花了16個小時。但之後,部署只需2分鐘,而且零失誤。一個月後,她就為整個團隊收回了時間成本。一年下來,她節省了數百小時,並避免了無數潛在的故障。這就是所謂的「策略性偷懶」。
這種習慣主要體現在以下幾個方面:
他們孜孜不倦地自動化。如果某件事需要重複執行兩次以上,他們就會寫腳本。環境建置、資料庫遷移、建置流程、測試例程—所有這些都是自動化的理想物件。他們不僅考慮節省時間,還考慮減少認知負擔和避免錯誤。
他們建構工具和抽象層。優秀的開發者不只解決眼前的問題,他們解決的是一類問題。當他們發現程式碼中存在重複模式時,他們不會簡單地複製貼上並稍作修改——而是會提取函數、建立庫或建置框架。他們寧願花時間設計簡潔的 API,而不是第十次寫相同的樣板程式碼。
他們是委派任務的高手──把任務委派給電腦。他們不斷問自己:「這其中哪些部分可以由電腦處理?」程式碼檢查、格式設定、依賴更新、效能監控-優秀的開發人員會手動完成這些任務,而卓越的開發人員則會將它們委派給自動化系統。這解放了他們的精力,讓他們可以專注於真正需要人類智慧的任務。
他們追求的是長期的簡潔性,而非短期的速度。這種「策略性偷懶」的開發者深知,最容易寫的程式碼往往最難維護。因此,他們會在前期投入更多時間,打造一個簡潔清晰、方便日後修改的設計。他們對未來的工作比較“懶惰”,所以現在就把最難的部分都考慮進去。
他們會充分利用現有解決方案。策略性地偷懶的開發者不會在 Auth0 存在的情況下自行建構身分驗證系統,也不會在已有結構化日誌庫的情況下編寫自訂日誌框架。他們傾向於使用經過實戰檢驗的解決方案,而不是重複造輪子。他們的目標是解決業務問題,而不是自己編寫每一行程式碼。
如何培養策略性懶惰:
培養對重複性工作的厭惡感。留意那些你反覆做的任務。它們是否讓你感到厭倦?如果是,那就表示你需要自動化了。從小處著手-例如用 shell 腳本搭建專案,或是用巨集生成樣板程式碼。消除重複性煩惱帶來的滿足感會讓人上癮,並激勵你進一步自動化。
問自己一個「懶人問題」。在開始任何任務之前,先問問自己:「有沒有更簡單的方法?」「我以後還要再做一次嗎?」「我能讓電腦幫我完成那些枯燥的部分嗎?」這種簡單的後設認知能力能夠區分習慣性懶惰和策略性懶惰。
投資你的工具鏈。花時間學習 IDE 的快捷鍵、精通 shell 或設定程式碼檢查工具並非浪費時間——而是會產生複利效應。花幾個小時學習 Vim 的手勢操作或 VS Code 的多遊標編輯,一年下來可以節省你數天的打字時間。
先建置,再利用。建立自動化流程或抽象層時,請考慮如何使其可重複使用。一個僅適用於單一專案的腳本固然不錯;但一個可以跨多個專案使用的工具則更勝一籌。為你的工具寫文件-未來的你會感謝自己當初的明智之舉。
策略性偷懶的妙處在於它惠及所有人,而不僅僅是你。你寫的部署腳本對整個團隊都有幫助。精心設計的抽象層讓程式碼庫更易於所有人協作。自動化測試套件則能為所有未來的開發人員預防 bug。
這個習慣能讓你從一個只會寫程式的程式設計師變成一個效率倍增器。你不再只是程式碼的生產者,而是能夠建立更有效率、更有價值的系統的開發者。你不僅能埋頭苦幹,還能讓整個團隊的工作更輕鬆、更有效率。
歸根究底,這才是值得培養的那種懶惰。
習慣八:他們與生產環境保持回饋循環
優秀的開發人員編寫程式碼、執行測試,然後部署到生產環境。他們相信,只要測試通過且建置成功,他們的工作就完成了。他們把生產環境看成一個遙遠而略帶神秘的地方,是維運團隊需要操心的。
優秀的開發者與生產環境保持著密切且持續的聯繫。他們不會發布程式碼後就置之不理;他們會密切注意程式碼的執行狀態,並跟隨其進入實際應用程式場景。他們把生產環境視為檢驗程式碼行為、效能和價值的最終標準,而不是最終目的地。
這種習慣決定了理論正確性和實際應用之間的差異。你的程式碼可以通過所有測試,滿足所有要求,在程式碼審查中看起來也很漂亮,但如果它在生產環境中崩潰,這一切都毫無意義。優秀的開發者明白,生產環境是他們的假設與現實碰撞的地方,而現實總是勝出。
我職業生涯早期的一次災難性表現下降就讓我吸取了教訓。當時我們開發了一個新功能,其中包含一個複雜的資料庫查詢。這個查詢語句很優雅,使用了所有正確的連接(JOIN),並且通過了所有的單元測試和整合測試。我們的測試資料庫只有幾百行合成資料,查詢速度非常快。
我們在周五下午發布了程序。到了周六早上,資料庫就崩潰了。在生產環境中,面對數百萬行真實資料,那條看似「優雅」的查詢竟然執行了全表掃描。它超時了,導致表被鎖定,整個應用程式癱瘓。我們整個週末都在驚慌失措地回滾並編寫修復程式。
我們團隊裡一位優秀的開發人員瑪麗亞對此事非常在意。倒不是因為是她寫的那條錯誤的查詢語句(她並沒有),而是因為她認為這是系統性的問題。 “我們不能只測試程式碼是否有效,”她說,“我們必須測試它在實際環境下是否有效。”
從那天起,她就成了我們生產回饋循環的守護者。
以下是保持緊密生產回饋循環在實務上的樣子:
他們會監控一切。優秀的開發者不僅記錄錯誤,還會衡量所有重要的指標,例如回應時間、吞吐量、錯誤率、業務指標、快取命中率和資料庫查詢效能。他們從一開始就將可觀測性——指標、日誌和追蹤——融入程式碼中。他們深知,看不到的問題就無法修復。
他們像老鷹一樣緊盯部署情況。程式碼發布後,他們不會立刻著手處理下一個問題。他們會密切注意部署指標,監控錯誤率,查看效能儀錶板。他們甚至會花幾分鐘觀察真實使用者的會話,以了解功能的實際使用情況。這種即時回饋讓他們能夠及時發現那些繞過測試的迴歸問題。
他們奉行「誰開發誰維運」的理念。這種源自亞馬遜的理念意味著開發者要對生產環境中的程式碼負責。他們需要隨時待命,確保功能正常運作。一旦出現問題,他們會被通知。這聽起來或許有些苛刻,但卻是你能想像的最有效的回饋機制。沒有什麼比知道如果你不寫健壯、容錯的程式碼,凌晨三點你的電話就會響起更能激勵你編寫出健壯且容錯性強的程式碼了。
他們非常重視功能開關的使用。優秀的開發者不會一次發布所有功能。他們會將新功能封裝在功能開關中,然後逐步推出——先面向內部用戶,然後是 1% 的客戶,再是 10%,以此類推。這樣一來,他們就能在最小範圍內獲得真實用戶的回饋。如果出現問題,他們只需單擊即可關閉該功能,而無需進行完全回滾。
他們分析生產資料來做決策。我們應該優化這個查詢嗎?一個優秀的開發人員可能會憑感覺判斷。而一個卓越的開發人員會查看生產指標,以了解查詢的呼叫頻率、平均延遲和 p95 延遲是多少,以及它對使用者體驗的影響。他們讓生產資料指導他們的優先排序。
他們會坦然面對並從事故中學習。當生產環境出現問題時,優秀的開發人員不會互相指責。他們會主導並參與客觀的事故分析。他們會深入挖掘問題的根源,而不僅僅是表面症狀。更重要的是,他們專注於系統性的解決方案,以防止此類問題再次發生,而不僅僅是修補眼前的漏洞。
如何養成這個習慣:
從一開始就讓你的應用可觀測。在編寫業務邏輯之前,先設定好結構化日誌、指標收集和分散式追蹤。之後再增加這些功能會困難得多。從簡單的入手——即使只是記錄關鍵業務事件和績效邊界,也是向前邁出的一大步。
建立個人儀錶板。建立一個儀表板,用於顯示你負責的功能的運作狀況。讓它成為你每天早上醒來第一件事,也是每次部署前最後一件事。養成這個習慣,可以增強你對程式碼實際運作狀況的掌控感和認同感。
主動要求輪值值班。如果你的團隊有輪值值班制度,就加入;如果沒有,就提議設立。被尋呼機吵醒,發現自己寫的程式碼出了問題,這段經驗會讓你終生難忘。它將徹底改變你對錯誤處理、日誌和系統設計的看法。
練習「生產環境除錯」。下次生產環境出現問題時,即使問題不在你的程式碼中,也要詢問是否可以跟著除錯人員學習。觀察他們如何使用日誌、指標和追蹤資訊來精確定位問題。這項技能只能透過實踐來學習。
小規模、高頻率地發布。部署頻率越高,每次變更的幅度就越小,系統變更與系統行為變更之間的關聯性就越容易建立。頻繁部署可以降低對生產環境的恐懼,使其變得熟悉且易於管理。
維護這種回饋循環的作用遠不止於預防 bug——它還能形成一個完整的學習閉環。你基於假設編寫程式碼,而生產環境會告訴你哪些假設是錯的。也許用戶使用你的功能的方式是你從未預料到的。也許你認為的「極端情況」其實很常見。也許你假設的效能特徵在實際負載下完全不同。
這種持續學習才能將優秀的程式設計師培養成卓越的工程師。你不再閉門造車地設計系統,而是開始基於對系統在實際運作環境中表現的深刻而直觀的理解來建構它們。
生產環境是你所能遇到的最嚴格、最誠實的程式碼審查員。聽從它的。
技術領域瞬息萬變,猶如一條奔騰的河流。新的框架、語言、工具和典範層出不窮,迅速風靡一時,卻又往往在短短幾年內銷聲匿跡。優秀的開發者如同在這條河流中奮力游弋,努力保持清醒。他們的學習方式是被動的——工作需要才去學習新的 JavaScript 框架,Hacker News 上突然冒出的博客文章讓他們匆匆瀏覽,遇到具體問題才去看教程。他們的學習是臨時性的,受制於眼前的需求和生態系統中最具影響力的聲音。
優秀的開發者並非隨波逐流,而是建造一艘船,規劃航線。他們深知,在特定技術生命週期僅有數年的領域,唯一可持續的優勢在於深入且有效率地學習。他們並非被動地學習,而是主動學習。他們的學習是一項系統性的、持續性的投資,以對基本原理的清晰理解和長遠目標為指導,而非受制於科技潮流的瞬息萬變。
這可以說是所有習慣中最重要的一種,因為它是其他所有習慣的基礎。它是成長的引擎。
我親眼見證了這個習慣的力量,它體現在我團隊中兩位背景和才能都相似的開發人員身上。我們姑且稱他們為 Alex 和 Ben。
Alex是個典型的被動學習者。他聰明能幹。當團隊決定採用新的狀態管理函式庫時,他立刻投入其中。他只學習了完成任務所需的基本知識。他搜尋特定的錯誤訊息,從現有程式碼複製模式,最終熟練了相關功能。他的知識面很廣,但深度卻很淺——他掌握了一系列針對特定問題的解決方案,卻缺乏統一的思維模式。
本採取了不同的方法。面對同樣的新庫,他並沒有隻閱讀“入門指南”,而是花了一個週末的時間,用它搭建了一個臨時專案。之後,他又從頭到尾閱讀了官方文件。他還觀看了庫建立者的演講,以了解該庫背後的設計理念——它真正旨在解決哪些問題,以及它做出了哪些權衡。他不僅學會如何使用它,還了解了它為何如此設計,以及它在什麼情況下是合適的工具,在什麼情況下是錯誤的工具。
六個月後,情況發生了驚人的變化。 Alex 可以使用函式庫完成任務,但他寫的程式碼經常違反函式庫的核心原則,導致一些不易察覺的錯誤和效能問題。每當遇到新問題時,他往往束手無策。
另一方面,本來已經成了團隊的專家。他能預見問題,並能設計出巧妙的解決方案,充分利用庫的優勢。他還能用淺顯易懂的方式向初級員工解釋庫的概念,讓他們牢記於心。他不只是工具的用戶,更是它的專家。
亞歷克斯是偶然學會的,本來是刻意學習的。
優秀的開發者是這樣安排他們的刻意學習的:
他們學習的是基礎知識,而非花樣。優秀的開發者深知,儘管 JavaScript 框架層出不窮(還記得 jQuery、AngularJS 和 Backbone.js 嗎?),但 Web 的底層基礎——DOM、事件循環、HTTP 和瀏覽器渲染——卻始終如一。他們投入時間理解電腦科學的基礎:資料結構、演算法、網路、作業系統和設計模式。這些永恆的原則使他們能夠快速而深入地評估和學習任何新的技術「花樣」。如果你已經了解宣告式 UI、虛擬 DOM 概念和單向資料流的原理,學習 React 就變得輕而易舉。你不是在死記硬背 API,而是在理解更深層概念的體現。
他們會維護一個「學習待辦清單」。就像他們有待開發的功能清單一樣,他們也會維護一個個人學習清單,列出需要學習的概念、需要探索的技術和需要閱讀的書籍。這並非空泛的“我總有一天會學Go語言”,而是一份具體的清單:“閱讀《設計資料密集型應用程式》”、“建立一個簡單的Rust命令列工具來理解記憶體安全”、“完成Coursera上的‘開發者網路程式設計’課程”。這使得學習從抽象的意圖轉化為可管理、可執行的專案。
他們會專門安排“學習時間”,並且竭力保護它。他們不會把學習浪費在一天疲憊的會議和編碼工作之後剩下的零碎時間裡,而是會把它安排進日程。我認識的許多優秀開發者每週都會抽出一個下午,或是每天早上上班前抽出幾個小時,專門學習。這段時間對他們來說彌足珍貴,不是用來查看郵件或處理緊急事務的,而是用來進行深入、不受干擾的學習和練習的。
他們透過實踐學習,而非僅僅被動接受。被動接受——閱讀、觀看影片——只是第一步。優秀的開發者透過實踐來內化知識。他們不會只閱讀關於新資料庫的資料;他們會安裝資料庫,匯入資料集,並執行查詢。他們不會只是觀看關於新架構的教程;他們會建立一個實現該架構的演示專案。這種實踐能夠建立起理論無法企及的強大而持久的神經路徑。他們明白,真正的精通不僅體現在大腦中,更體現在指尖的實踐中。
他們直奔源頭。當一款新工具出現時,被動學習者會閱讀一篇「十分鐘入門」的部落格文章。而主動學習者則會直接查閱原始資料:官方文件、原始研究論文(如果有的話)或開發者的演講。他們明白,二手資料往往過度簡化、帶有主觀色彩或過時。真相,以及其中所有微妙的複雜性,通常都隱藏在源頭之中。閱讀 React 官方文件或 Dan Abramov 的部落格文章,與閱讀某個不知名部落格上的「React 使用技巧」列表,是截然不同的學習方式。
他們教自己學到的東西。有意識的學習者深知,檢驗理解程度的最終標準是能否將概念解釋給他人。他們會撰寫部落格文章,為團隊準備自備午餐,參與文件編寫,或簡單地向同事解釋自己學到的知識。整理思路以便進行教學的過程,迫使他們正視自身理解上的不足,並鞏固已有的知識。這是學習循環的最後一步。
他們會明智地篩選資訊來源。數位世界充斥著大量低品質、重複且常常錯誤的訊息。優秀的開發者會嚴格篩選自己的資訊。他們不會試圖閱讀所有內容。他們會甄選出少數值得信賴、高品質的資訊來源——特定的部落格、期刊、播客或個人——然後忽略其他資訊。他們更注重深度而非廣度,更注重質量而非數量。
如何培養這種習慣:
每季進行一次「技能盤點」。每三個月,認真盤點自己的技能。哪些技能在增強?哪些技能正在過時?你需要了解哪些新興趨勢?根據這次盤點,更新你的學習計畫。這將使你的職涯發展從被動的過程轉變為由你掌控的主動過程。
遵循「20%法則」。拿出固定比例的時間──即使一開始只有5%──去學習那些與你目前工作沒有直接關係的知識。這樣可以避免科技過時,也能讓你意外發現更有效率的工作方式和新的機會。
制定一份「個人學習計畫」。如果你想成為分散式系統專家,你需要學習哪些內容?學習順序又該如何安排?一個有意識的學習者會像製定大學課程一樣,為自己制定學習計畫。他們可能會先從教科書入手,然後閱讀經典論文,最後完成一個專案。這種結構化的方法比漫無目的地探索要有效得多。
找到一個學習小組。獨自學習很難。找一兩個和你擁有共同成長型思維的同事。組織讀書會、學習小組或「技術深度研討」活動。這種社交互動能讓你保持自律,而討論則能加深你的理解。
這種習慣帶來的回報是無法估量的。它決定了開發人員的價值是會在職業生涯的第五年達到頂峰,還是會隨著時間的推移而不斷提升。它決定了你是任由就業市場擺佈,還是成為各公司爭相搶奪的物件。
刻意學習是職業生涯的終極資本。在這個瞬息萬變的世界裡,學會如何學習,並有目的地、有策略地學習,絕非錦上添花。它是預測長期成功的最佳指標。它就像一台靜默而持續的引擎,將優秀的程式設計師培養成卓越的程式設計師,再將卓越的