Communitygithub.com

mathias-mrsn/skills

審查並簡化 Python 程式碼設計。

skills 是什麼?

此技能專注於提升 Python 程式碼的品質與設計,協助開發者審查現有程式碼,使其更易於理解、擴展和維護。其核心理念是「尋求解決問題的最小結構」,強調以 Python 慣用的方式(如使用函式、字典或語言協定)取代不必要的類別或設計模式,並在抽象化時仔細權衡隱藏的細節與相依性。此技能支援所有 Python 開發環境,提供從變數命名、可變性管理、數值與字串處理,到抽象化、重構策略與模式詞彙應用等一系列準則。透過遵循其結構化程序(從識別目標環境到逐步重構),並注意常見陷阱(如過早泛化或忽略資料保留),開發者能有效產出簡潔且穩固的程式碼。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/mathias-mrsn/skills/tree/HEAD/skills/technical/python/python-code-quality

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Python 程式碼品質與設計

使用時機

  • 撰寫或審查能運行但難以理解、擴展或維護的 Python 程式碼。
  • 將直接從 Java 或 C++ 翻譯而來的結構,替換為更適合該語言的建構。
  • 選擇抽象化、評估耦合度,或在新需求出現後進行重構。
  • 辨識模式的意圖,而不將其名稱視為架構上的義務。
  • 此範圍涉及對程式碼的推理;它既不指定部署環境,也不指定框架。

哲學

尋求能解決觀察到問題的最小建構,而非想像中最通用的設計。 當 Python 已提供某模式的結構時,保留該結構的意圖;移除該結構是一種改進,而非缺乏精緻度。 好的抽象化能在不抹除客戶端所需資訊的前提下,降低變更成本。 源自 Bruce Eckel 所著《Thinking in Python》的實務綜合;語料庫 a8feb860。 尊重專案實際支援的 Python 版本:語料庫的近期目標並不授權任何隱含的版本升級。

規則

可讀的語法與可預測的名稱

  • 讓縮排與實際的區塊結構相符;使用四個空格,不要混用 Tab 和空格。
  • 區塊標頭結尾使用冒號;避免使用分號和多個陳述式來人為壓縮程式碼。
  • 對於較長的陳述式,使用括號、方括號或大括號內的續行,而非難以跟隨的分行方式。
  • 不要混淆區塊與作用域:在 if 或 for 中的指派,在區塊結束後仍可能保持可見。
  • 檢查在名稱被使用之前,每個執行路徑上是否都有指派;僅靠縮排無法保證其綁定。
  • 變數、函式、方法和檔案使用 snake_case;類別使用 PascalCase;常數使用大寫。
  • 保留以函式樣式命名的類別這個例外,僅用於實際以該方式使用的物件;不要將其視為通用風格。
  • 不要從大寫名稱推斷執行時期的保護:它對讀者傳達的是意圖。
  • 保留專案一致的慣例,並使用其現有的 linter,而非建立任意的本地規則。

參考、值與可變性

  • 將指派理解為將名稱綁定到物件;它不會複製該物件。
  • 明確區分重新指派(reassign)與變異(mutate):重新指派名稱不會改變其先前的別名,而變異物件則可能影響所有別名。
  • 使用 == 進行值相等比較;將 is 保留用於預期的身分比較,特別是用於 is None 和其他單例(singleton)。
  • 不要使用字串或小整數的身分作為值測試:共用最佳化並不提供此合約。
  • 清單的切片或淺層複製會重建外部結構,而非巢狀元素;檢查哪些物件仍然被共用。
  • 僅當也需要複製包含的物件時,才使用深層複製;指派無法提供這種隔離。
  • 不要機械性地將 x += y 替換為 x = x + y:對於清單,第一種形式會原地修改它,而第二種則會產生一個新的。
  • 對於不可變的數字,理解擴增指派是將名稱重新綁定到結果,而非修改共用的數字。
  • 當資料不需要演化時,偏好使用不可變資料,但不要將容器的不可變性與其內容的不可變性混淆。

數字與真值

  • 區分 /(對於整數運算元會產生浮點數的除法)和 //(向負無窮大進位的除法)。
  • 在移植時測試負數情況:整數商並非向零截斷,且餘數 % 的符號會跟隨除數。
  • 不要假設 Python 整數有固定寬度;它們不像其他語言的有限整數那樣會溢位。
  • 將精確的整數算術與浮點數算術分開;不要將一個的保證引入另一個。
  • 考慮 round() 和格式化時的「四捨六入五成雙」規則;不要假設四捨五入總是遠離零。
  • 在字面量中使用 _ 分隔符,讓大數字易於閱讀,且不改變其值。
  • 使用 += 1 和 -= 1 來遞增或遞減;Python 沒有 ++ 或 -- 運算子。
  • 布林值也是整數:對條件求和可以計數真值情況,但 True 並不構成一個與 int 不同數值型別。
  • 當問題僅僅是容器是否為空時,偏好使用 if items: 而非長度測試。
  • 記住物件的真值可以由 __bool__() 定義,或者若無則由 __len__() 定義。
  • and 和 or 會短路並返回一個運算元,不一定是布林值;不要僅憑其名稱來註釋它們的結果。
  • 僅在應該替換所有假值時才使用 x or default;明確使用 None 測試來保留零或空文字。
  • 對於遮罩,區分位元運算子與邏輯運算子;~x 不會反轉一個固定寬度的字組。
  • 不要期望 @ 對內建數字執行乘積:矩陣乘法取決於型別,且與裝飾器語法不同。

字串與插值

  • 字串是不可變的:如果需要重複使用轉換結果,請保留它。
  • 選擇能限制跳脫的引號;當換行是文字的一部分時,使用三重引號。
  • 對於文字反斜線,使用原始字串(raw string),並注意無法以單一反斜線結束該字面量的限制。
  • 使用內建的搜尋、分割、替換和聯結操作,而非在沒有特定需求的情況下使用字元操作迴圈。
  • 不要使用 strip、lstrip 或 rstrip 來移除精確的前綴:它們的引數表示字元,而非不可分割的子字串。
  • 當目標版本支援時,使用 removeprefix() 和 removesuffix() 來移除精確的開頭或結尾子字串。
  • + 不會隱式地將數字轉換為字串;使用明確的轉換或 f-string。
  • 偏好使用 f-string 來產生一般文字;其表達式會被求值,且結果會立即呈現。
  • 使用格式規範來指定精度、寬度和對齊方式;!r 顯示適合診斷的表示形式,= 則為表達式加上標籤。
  • 不要僅僅因為舊的 % 或 str.format() 用法存在就改寫它們:它們仍然有效,且遷移必須能改善相關程式碼。
  • 僅當目標版本支援且消費者需要在組裝之前區分字面量與值時,才考慮使用 t-string。
  • t-string 產生的是結構化物件,而非完成的字串;消費者必須根據其領域實際地引用、跳脫或拒絕插值。
  • 不要僅基於插值語法就承諾安全性,也不要將說明性的字元拒絕機制重新用作通用保護。
  • 模板消費者不得假設文字和值之間嚴格交替:迭代可能會省略空字面量。

抽象化、相依性與內聚力

  • 在新增類別之前,檢查函式、字典、模組或語言協定是否足夠。
  • 僅僅因為一個類別託管了一個方法,並不能僅憑此組織方式就證明其正當性。
  • 基於實際需求來命名變化的軸向;反覆在同一位置發生的變更,比預期的靈活性更有用的證據。
  • 將會變化的部分與保持穩定的部分分開;如果沒有東西在變化,就不要建立變化機制。
  • 將慣用語、特定解決方案和通用模式視為可能的階段,而非必須攀登的階梯。
  • 對於每個抽象化,識別它所抹去的細節,並確認沒有任何正當的客戶端需要恢復它們。
  • 同時檢查介面背後隱藏的工作:複製、排序或間接存取在實際資料規模下可能變得主導。
  • 透過一個部分知道什麼來評估耦合度:父類別細節、具體類別、介面、呼叫簽章或簡單的值。
  • 不要將型別化的相依性與沒有相依性混淆:Protocol 避免了宣告式的繼承,但其成員仍然是合約。
  • 當客戶端只需要一個操作時,偏好使用函式簽章;不要在沒有用途的情況下建立多方法介面。
  • 模式通常會移動相依性而非移除它;找出仍然必須命名具體類別的部分。
  • 當具體相依性被刻意放置在更換成本低的組合點時,接受它。
  • 不要深入間接取得的物件的內部細節;透過明確的介面請求所需的服務。
  • 將服務於同一主題的元素分組到一個元件中;將可以獨立演化的想法分開。
  • 偏好使用組合來變化協作者,而無需繼承其實作細節;如果可替代性是真實的,則保留繼承。
  • 檢查子型別是否滿足基底型別客戶端的期望,而不僅僅是方法的名稱。
  • 提取表達相同意圖且出於相同原因的重複程式碼;偶然的相似性是不夠的。
  • 讓常見情況簡單、罕見情況可行、介面一致;行為上的例外會造成記憶成本。
  • 如果移除一個層級既不會移除必要的行為,也不會移除有用的隔離,就移除它;在兩個足夠的解決方案中,選擇較簡單的那個。
  • 在可能的情況下,讓計算保持純粹:相同輸入產生相同結果,且沒有任何可觀察到的外部變化。

沿著擴展的兩個軸向重構

  • 區分新增型別與新增操作;設計通常會偏重其中一個而犧牲另一個。
  • 列舉開放家族所有子類別的 match 需要維護清單,且可能會默默地忽略新型別。
  • 如果操作僅按確切類別分組,則使用以 type(obj) 為索引的字典可以避免此列舉。
  • 不要將此替換視為純語法性的:子類別隨後會獲得自己的鍵,這與父類別模式不同。
  • 對於首次出現時建立的群組,defaultdict(list) 提供了新的清單;字典註解不會建立此行為。
  • 將統一的操作保留為讀取相關屬性的普通函式;如果演算法不因型別而異,則分發機制不會增加任何價值。
  • 當每個型別自然地攜帶其回應且操作很少時,將方法保留在小型、受控的層級結構上。
  • 考慮使用 functools.singledispatch 來新增因型別而異的操作,而無需修改外部層級結構或將新操作散佈到每個類別中。
  • 使用 singledispatch 時,實作是根據第一個引數選擇的,並且可能根據 MRO 被繼承;它不是一個確切類別的表格。
  • 預設回應必須對未註冊的型別真正有效;否則引發 NotImplementedError 以使遺漏變得明顯。
  • 不要從任意模組新增方法:這會迫使讀者在類別定義之外搜尋其行為。
  • 根據變更的位置和保留的資料來衡量重構,而不僅僅是移除的行數。

使用模式詞彙而不強加目錄

  • 先描述問題:建立、介面適配、演算法選擇、通知、樹狀結構或狀態恢復;名稱之後再決定。
  • 當傳遞或儲存函式已足夠時,簡化 Strategy 和 Command;鏈可以是一系列按順序嘗試的函式清單。
  • 認識到共用模組、類別表格或迭代協定可能已經滿足 Singleton、Factory 或 Iterator 的意圖。
  • 區分 Adapter(轉換介面)、Façade(簡化子系統)和 Proxy(控制對另一個物件的存取)。
  • 區分 State(根據狀態改變行為)與狀態機(根據輸入選擇轉換)。
  • 區分從外部注入協作者與在註冊表中查找該協作者;兩者都以不同方式轉移控制權,而不會暴露相依性。
  • 將架構、持久化、訊息和雲端名稱保持在所描述意圖的層級:它們在目錄中的存在並不建立實作配方。
  • 不要從僅僅提及 Circuit Breaker、Saga、Repository 或 Event Sourcing 就推斷出部署、恢復或一致性的建議。

步驟

  1. 識別目標 Python 版本、慣例和已使用的檢查;記錄要保留的行為。
  2. 陳述具體需求與觀察到的變化軸向;列出目前受影響的元件。
  3. 檢查涉及的共用參考、真值、數值運算和字串轉換。
  4. 撰寫最小解決方案,然後比較提議的抽象化:抹去的細節、隱藏的工作和移動的相依性。
  5. 分別測試新增型別和新增操作;注意每個變體中所需的變更。
  6. 分階段重構,同時保留行為測試以及傳入和傳出資料的計數。
  7. 移除已變得不必要的層級,然後在專案支援的版本上執行其測試和檢查。

陷阱

  • 程式可能在遺失新型別後產生看似合理的總數:請檢查資料保留,而不僅僅是輸出呈現。
  • 備援分支或預設實作可能會隱藏被遺忘的擴展;如果沒有中性的回應是正確的,則偏好明確的失敗。
  • 在擁有幾個實際案例之前進行一般化,會創造沒有證據證明其有用性的選項;拒絕推測性的框架。
  • 簡短的抽象化可能隱藏高昂的成本或抹去不可或缺的細節;僅計數行數不足以判斷其品質。
  • 語料庫中的最新語法和 API 不一定適用於較舊的專案;不要為了採用它們而提高專案的最低 Python 版本。

驗證

  • 每個新的類別或介面都保護一個已命名的需求;沒有模式只是為了其名稱而存在。
  • 條件路徑在使用變數前已綁定它們;身分、變異和相等性沒有被混淆。
  • 當零、空值、None、負數和巢狀資料的區別至關重要時,它們已被測試過。
  • 新的子類別不會默默消失,並且確切類別或繼承行爲已得到充分考慮。
  • 移除一個層級不會暴露一個先前隱藏的耦合,也不會破壞必要的隔離。
  • 重構保留了傳入和傳出的資料,以及它們具有任何意義的順序。
  • 文件已更新以反映任何新的抽象化或簡化。

相關技能