Andrew Hunt & David Thomas:pragmatic Programmers——from Journeyman To Master@1999 (第1版)
《The Pragmatic Programmer》主要論點闡述
《The Pragmatic Programmer: From Journeyman to Master》這本書旨在深入探討軟體開發的核心過程,從接收需求到產出可工作且易於維護、令使用者滿意的程式碼。書中融合了作者安德魯·亨特(Andrew Hunt)與大衛·托馬斯(David Thomas)在程式開發前線的豐富經驗,提煉出一系列實用原則和技巧,旨在幫助程式設計師提升個人生產力、準確性及工作滿意度。書中的核心精神圍繞著「務實」(Pragmatic)一詞展開,強調「實踐」,鼓勵程式設計師培養習慣與態度,以實現長期的職業成功。
以下根據提供的文字內容,詳盡闡述書中的主要論點:
務實的程式設計師特質與心態
書中首先定義了務實的程式設計師應具備的特質與基本態度。他們不僅技術熟練,更重要的是擁有一套特定的心態:
- 關注技藝 (Care About Your Craft) (Tip 1): 程式設計被視為一門技藝,而非僅僅是科學或工程。務實的程式設計師在意把工作做好,對自己的作品感到自豪。他們不僅追求程式碼能運作,更追求程式碼的品質、可讀性與優雅性。
- 思考工作 (Think! About Your Work) (Tip 2): 這是一個持續的過程,要求程式設計師在工作的每一個環節都保持批判性思考,而不是盲目地運行或依賴自動駕駛模式。面對每一個決策、每一行程式碼,都應該深入思考其意義、潛在影響和替代方案。
- 負責任 (Take Responsibility): 對自己的職業、專案和日常工作負責。承認無知或錯誤,不找藉口或責怪他人(供應商、語言、管理層或同事)。當問題發生時,應提出解決方案而非藉口 (Tip 3: Provide Options, Don’t Make Lame Excuses)。
- 應對軟體熵 (Software Entropy): 軟體系統傾向於無序和腐敗,這被稱為「軟體熵」或「軟體腐敗」。書中借用「破窗理論」的類比 (Tip 4: Don’t Live with Broken Windows),指出一個小的缺陷(破窗)若不修復,會傳達一種被遺棄的感覺,導致更多缺陷出現並加速系統衰敗。因此,必須立即修復發現的任何缺陷,即使不能立即修復,也要採取措施阻止進一步惡化(例如註解掉有問題的程式碼)。
- 成為變革的催化劑 (Be a Catalyst for Change) (Tip 5) 與 記住大圖景 (Remember the Big Picture) (Tip 6): 務實的程式設計師能夠像「石頭湯」故事中的士兵一樣,從小的成功開始,逐步引導他人參與並共同實現更大的目標。同時,他們也警惕「煮青蛙」的陷阱,持續關注周圍環境的變化,不因漸進的負面變化而麻木。
- 「夠好的」軟體 (Good-Enough Software): 認識到完美軟體不存在,應追求「夠好」的軟體。這並不意味著草率,而是指系統必須滿足使用者需求,並且要讓使用者參與決定何時達到「夠好」的標準 (Tip 7: Make Quality a Requirements Issue)。偉大的軟體今天發布通常比完美的軟體明天發布更好。同時,也要知道何時停止過度潤飾。
- 知識投資組合 (Your Knowledge Portfolio): 將知識和經驗視為過期資產,需要像管理金融投資組合一樣定期投資 (Tip 8: Invest Regularly)。建議每年學習一種新語言,定期閱讀技術和非技術書籍,參加社群活動,嘗試不同的環境,並保持批判性思考 (Tip 9: Critically Analyze What You Read and Hear)。
- 溝通 (Communicate!) (Tip 10: It’s Both What You Say and the Way You Say It): 有效溝通至關重要。了解自己要說什麼、聽眾是誰、選擇合適的時機和風格。讓表達看起來更好,讓聽眾參與,並學會傾聽和回覆。
務實的開發方法
書中提出了一系列務實的開發原則,旨在提高程式碼和系統的品質與靈活性:
- 不要重複自己 (Don’t Repeat Yourself – DRY) (Tip 11): 系統中的每一項知識都必須有一個單一、明確、權威的表示。重複知識會導致維護惡夢。杜絕各種形式的重複(強加的、無意的、不耐煩的、開發者之間的)。鼓勵透過程式碼生成、元資料或良好的溝通和重用機制來避免重複 (Tip 12: Make It Easy to Reuse)。
- 正交性 (Orthogonality) (Tip 13: Eliminate Effects Between Unrelated Things): 系統的組件應該相互獨立,改變一個組件不應影響其他組件。這類似於幾何學中的垂直概念。正交系統易於設計、構建、測試和擴展,能提高生產力並降低風險。正交性應用於團隊組織、系統設計(分層、模組化)、工具包選擇和程式碼編寫(低耦合、避免全域資料)。
- 可逆性 (Reversibility) (Tip 14: There Are No Final Decisions): 避免做出難以逆轉的關鍵決策。保持架構、部署和供應商選擇的靈活性。在不得不依賴特定因素時,盡可能將其抽象化或透過元資料管理,以便未來更容易更改。認識到沒有什麼是永恆的。
- 追蹤子彈 (Tracer Bullets) (Tip 15: Use Tracer Bullets to Find the Target): 採用一種快速、可見且可重複的開發風格,從需求快速建構到最終系統的某個方面(通常是端到端的路徑)。追蹤子彈程式碼不是一次性的,而是最終系統骨架的一部分。它能讓使用者早期看到進度並提供回饋,幫助開發者建立工作結構和整合平台,並更好地衡量進度。
- 原型 (Prototypes and Post-it Notes) (Tip 16: Prototype to Learn): 使用一次性(或低保真)模型來探索特定的想法、降低風險。原型可以是程式碼、圖紙或便利貼。原型旨在回答特定問題,因此可以忽略正確性、完整性、健壯性或風格等細節。原型不應被部署到生產環境。
- 領域特定語言 (Domain Languages) (Tip 17: Program Close to the Problem Domain): 在可能的情況下,使用問題領域的詞彙、語法和語義來編寫程式碼。這可以是簡單的腳本語言、配置語言,或更複雜的解釋器/編譯器。領域特定語言讓程式設計師更接近使用者的思維方式,並能夠提供更貼近領域的錯誤訊息。實現領域特定語言可能需要額外的努力,但能顯著提高維護效率。
- 估計 (Estimating) (Tip 18: Estimate to Avoid Surprises): 培養估計時間、資源和演算法複雜度的能力。所有估計都基於對問題的模型。了解需求的準確性、建立模型、分解組件、估計參數、計算結果,並追蹤過去的估計準確度。專案進度應與程式碼一起迭代 (Tip 19: Iterate the Schedule with the Code)。對於要求立即估計的情況,最務實的回答是「我稍後回覆你」。
基本工具的掌握
務實的程式設計師將其工具視為技藝的延伸,並精通如何有效地使用它們:
- 純文本的力量 (The Power of Plain Text) (Tip 20: Keep Knowledge in Plain Text): 將知識(程式碼、文檔、配置、數據)儲存為純文本格式。純文本具有抗過時性、可利用各種工具進行處理,且易於測試。
- 命令列殼層 (Shell Games) (Tip 21: Use the Power of Command Shells): 掌握命令列殼層及其工具(如 grep, find, sed, awk)的能力,用於自動化、處理文本、快速組合命令。GUI 環境雖然方便,但在自動化和靈活性方面有所限制。
- 強大的編輯器 (Power Editing) (Tip 22: Use a Single Editor Well): 精通一個功能強大的編輯器,並用於所有編輯任務。好的編輯器應可配置、可擴展和可編程。
- 原始碼控制 (Source Code Control) (Tip 23: Always Use Source Code Control): 無論是個人專案還是大型團隊,無論是程式碼還是文檔,所有內容都應置於原始碼控制之下。這能追蹤變化、回溯版本、管理分支、實現自動化和可重複的建構。
- 除錯 (Debugging): 除錯是解決問題,而非尋找責備對象 (Tip 24: Fix the Problem, Not the Blame)。除錯時不要慌亂 (Tip 25: Don’t Panic)。從乾淨編譯的程式碼開始,準確觀察並使問題可重現。利用除錯器視覺化數據、使用追蹤訊息、橡皮鴨除錯法、排除法。不要輕易認為底層系統有問題 (Tip 26: “select” Isn’t Broken)。遇到意外的錯誤時,不要假設不可能,要證明它 (Tip 27: Don’t Assume It—Prove It)。維護除錯清單。
- 文本處理語言 (Text Manipulation) (Tip 28: Learn a Text Manipulation Language): 學習並使用像 Perl, Python, Tcl 這樣的腳本語言來快速處理文本、建立工具、自動化任務。
- 程式碼生成器 (Code Generators) (Tip 29: Write Code That Writes Code): 寫程式碼來生成程式碼,就像木工製作模板一樣。這能避免重複、減少錯誤,並提高效率。區分一次性運行的被動生成器和每次需要時運行的主動生成器(主動生成器是 DRY 的關鍵)。
務實的防禦性程式設計
承認軟體不完美 (Tip 30: You Can’t Write Perfect Software),並採取措施防禦自身和他人程式碼中的錯誤:
- 契約式設計 (Design by Contract – DBC) (Tip 31: Design with Contracts): 透過定義模組的先決條件 (Preconditions)、後置條件 (Postconditions) 和類別不變量 (Class Invariants),明確模組的權利和責任。這能幫助及早發現錯誤,並驗證程式碼的正確性。DBC 與繼承和多態性配合良好。
- 讓失效的程式不會說謊 (Dead Programs Tell No Lies) (Tip 32: Crash Early): 當程式碼發現「不可能」的事情發生時,最好立即崩潰,而不是繼續運行並可能損壞數據。一個崩潰的程式通常比一個有缺陷但繼續運行的程式造成的損害小。
- 斷言式程式設計 (Assertive Programming) (Tip 33: If It Can’t Happen, Use Assertions to Ensure That It Won’t): 使用斷言 (assertions) 來檢查那些理論上「永遠不會發生」的情況。斷言是防禦性編程的手段,不應用於正常的錯誤處理。不要關閉斷言,因為即使經過測試,程式碼運行在真實世界中仍可能遇到意外情況。斷言條件不應有副作用。
- 何時使用異常 (When to Use Exceptions) (Tip 34: Use Exceptions for Exceptional Problems): 異常應用於處理真正異常和非預期的事件,而非作為程式正常流程控制的一部分。濫用異常會導致程式碼難以閱讀和維護。錯誤處理器可以是異常的替代方案。
-
平衡資源 (How to Balance Resources) (Tip 35: Finish What You Start): 分配資源(記憶體、文件、網路連接、執行緒等)的程式碼也應負責釋放這些資源。採用嵌套分配、以相反順序釋放資源的模式。在支援異常的語言中,利用語言特性(如 C++ 的 RAII 或 Java 的
finally塊)確保資源在異常發生時也能被釋放。考慮使用工具檢查資源洩漏。
靈活與可適應的設計
設計能夠彎曲而非折斷的系統,以適應不斷變化的需求和環境:
- 解耦與得墨忒耳法則 (Decoupling and the Law of Demeter) (Tip 36: Minimize Coupling Between Modules): 降低模組之間的耦合度。遵循得墨忒耳法則(盡量只與自己的「朋友」互動,不透過朋友去呼叫朋友的朋友),避免深度遍歷對象結構來呼叫方法。低耦合的系統更易於維護和演進。
- 元程式設計 (Metaprogramming) (Tip 37: Configure, Don’t Integrate, Tip 38: Put Abstractions in Code Details in Metadata): 透過元資料(描述應用程式運作方式的數據)來配置和驅動應用程式,而非將所有細節硬編碼在程式碼中。將抽象放在程式碼中,細節放在元資料中。這使得應用程式更靈活、可適應,無需重新編譯即可更改行為,並能將業務邏輯等易變部分移出程式碼。
- 時間耦合 (Temporal Coupling): 考慮系統中的並發性 (concurrency) 和排序 (ordering) 問題。分析工作流程 (Tip 39: Analyze Workflow to Improve Concurrency) 以發現並行機會。將系統設計為獨立服務 (Tip 40: Design Using Services),而不是相互依賴的組件。始終為並發性設計 (Tip 41: Always Design for Concurrency),即使在看似單線程的環境中也是如此,這能帶來更清晰的介面和更大的部署靈活性。
- 這只是一個視圖 (It’s Just a View) (Tip 42: Separate Views from Models): 將數據模型與其表示(視圖)分離。使用發布/訂閱 (Publish/Subscribe) 或模型-視圖-控制器 (MVC) 模式來實現這種分離。這使得可以為同一數據模型提供多個視圖,或在不同模型上使用相同的視圖,同時降低模型與視圖之間的耦合。
- 黑板 (Blackboards) (Tip 43: Use Blackboards to Coordinate Workflow): 使用黑板系統作為協調工作流程的工具。黑板是一種共享空間,知識的生產者和消費者可以在其中匿名地交換信息。這能進一步解耦參與者,使其獨立運作並對系統變化更具韌性。
程式碼編寫的實踐
在實際編寫程式碼時,應保持警覺和思考:
- 偶然程式設計 (Programming by Coincidence) (Tip 44: Don’t Program by Coincidence): 避免依賴偶然的成功或未經證明的假設。要故意的 (deliberately) 編寫程式碼,了解其為何能運作,並測試自己的假設。
- 演算法速度 (Algorithm Speed) (Tip 45: Estimate the Order of Your Algorithms): 估計演算法的資源使用(時間、記憶體),特別是複雜度(O() 標記法)。了解常見演算法的複雜度,並測試估計的準確性 (Tip 46: Test Your Estimates)。最快的演算法不一定是最好的,要務實選擇。
- 重構 (Refactoring) (Tip 47: Refactor Early, Refactor Often): 將軟體開發視為園藝而非建築。持續地改進程式碼結構和設計,即使在專案進行中也是如此。不要因為程式碼「能工作」就停止重構,因為今日的小問題可能成為明日的大難題。重構應小心進行,確保有良好的測試覆蓋並頻繁運行測試。
- 易於測試的程式碼 (Code That’s Easy to Test) (Tip 48: Design to Test): 從一開始就將可測試性納入設計考量。單元測試 (Unit Testing) 是基礎,應針對模組的契約進行測試。撰寫單元測試程式碼,並使用測試框架 (Test Harnesses)。測試是程式碼「完成」的標誌 (Tip 63: Coding Ain’t Done ‘Til All the Tests Run)。建立測試窗口以觀察運行時狀態。推廣測試文化 (Tip 49: Test Your Software, or Your Users Will)。
- 邪惡的精靈 (Evil Wizards) (Tip 50: Don’t Use Wizard Code You Don’t Understand): 不要使用精靈 (Wizards) 或程式碼生成器生成的程式碼,除非你完全理解它。不理解的程式碼會成為維護和除錯的黑箱。
專案啟動前的考量
在開始編寫程式碼之前,確立一些關鍵的原則:
- 需求陷阱 (The Requirements Pit) (Tip 51: Don’t Gather Requirements—Dig for Them): 需求並非顯而易見,需要深入挖掘。區分真正的需求、業務政策和實現細節。與使用者緊密合作 (Tip 52: Work with a User to Think Like a User),了解他們的實際工作方式和根本問題。需求應保持抽象,抽象比細節壽命長 (Tip 53: Abstractions Live Longer than Details)。管理需求蔓延,並維護專案詞彙表 (Tip 54: Use a Project Glossary)。將文檔發布到 Web 上以便溝通。
- 解決不可能的謎題 (Solving Impossible Puzzles) (Tip 55: Don’t Think Outside the Box—Find the Box): 面對看似無解的問題時,識別真正的約束和擁有的自由度。挑戰先入為主的觀念,並質疑「必須這樣做」的假設。
- 準備好了再開始 (Not Until You’re Ready) (Tip 56: Listen to Nagging Doubts—Start When You’re Ready): 傾聽內心的疑慮,如果感覺不對,不要勉強開始。可以透過原型來探索並解決疑慮。
- 規範陷阱 (The Specification Trap) (Tip 57: Some Things Are Better Done than Described): 避免過度詳細的規範。有些事情最好是透過做來理解,而不是透過描述。將規範和實現視為同一過程的不同方面,避免規範成為逃避實際編碼的藉口。
- 圓圈和箭頭 (Circles and Arrows) (Tip 58: Don’t Be a Slave to Formal Methods): 形式化方法只是工具箱中的一個工具。不要盲目遵循某種方法論而成為其奴隸。批判性地評估方法論的價值和局限性。昂貴的工具不一定能產生更好的設計 (Tip 59: Expensive Too Do Not Produce Better Designs)。
務實的專案實踐
將務實原則應用於團隊和專案層面:
- 務實的團隊 (Pragmatic Teams): 將務實原則(不容忍破窗、警惕被煮、有效溝通、避免重複、注重正交性)應用於團隊。圍繞功能組織團隊 (Tip 60: Organize Around Functionality, Not Job Functions),而非職能。團隊需要技術和行政領導者。
- 無處不在的自動化 (Ubiquitous Automation) (Tip 61: Don’t Use Manual Procedures): 自動化所有重複性任務,包括建構、測試、部署和行政流程。自動化能確保一致性和可重複性,並提高效率。利用 Makefile、腳本語言、自動化工具和排程工具(如 cron)。
- 無情的測試 (Ruthless Testing) (Tip 62: Test Early. Test Often. Test Automatically. Tip 63: Coding Ain’t Done ‘Til All the Tests Run): 進行徹底和無情的測試。進行單元測試、整合測試、驗證和確認、資源耗盡測試、性能測試和可用性測試。自動化大部分測試過程。使用真實數據和合成數據。測試 GUI 系統。測試測試本身 (Tip 64: Use Saboteurs to Test Your Testing)。關注狀態覆蓋而非程式碼覆蓋 (Tip 65: Test State Coverage, Not Code Coverage)。最重要的是,找到一個錯誤後,必須添加一個測試來防止它再次發生 (Tip 66: Find Bugs Once)。
- 一切皆寫作 (It’s All Writing) (Tip 67: Treat English as Just Another Programming Language): 將文檔視為開發過程的內在部分 (Tip 68: Build Documentation In, Don’t Bolt It On)。程式碼注釋應解釋原因而非如何做。使用有意義的變量名。透過工具從程式碼或其他單一來源自動生成文檔(可執行文檔),實現內容與表示分離。將文檔發布到 Web 上。
- 偉大的期望 (Great Expectations) (Tip 69: Gently Exceed Your Users’ Expectations): 專案的成功在於滿足並溫和地超越使用者的期望。與使用者溝通,了解他們的期望,並透過原型和追蹤子彈來管理這些期望。在交付時,提供一些額外的、能讓使用者感到驚喜和愉悅的功能或細節。
- 驕傲與偏見 (Pride and Prejudice) (Tip 70: Sign Your Work): 對自己的工作負責並感到自豪,在作品上簽名。這能培養責任感和對品質的追求。在團隊中,這需要建立相互尊重的文化,避免領土意識和偏見。
總結來說,《The Pragmatic Programmer》鼓勵程式設計師採取主動、負責任、不斷學習、並關注程式設計各個方面的實用方法。從個人心態的培養、程式碼的編寫技巧、系統的設計原則,到團隊協作和專案管理的實踐,這本書提供了一個全面的框架,旨在幫助開發者成為更高效、更可靠、更受人尊敬的務實專業人士。
comments
comments for this post are closed