如何寫好 Python 註解——《Python 工匠》筆記
文章目錄
Python 工匠
這是《Python 工匠|案例、技巧與開發實戰》筆記的第 1 篇,你可以把它當作是一則重點整理,加上我個人的開發經驗與心得。
從書名推敲,我們並不容易知道本書的主題為何。事實上,和《Python 功力提升的樂趣》類似,這是一本關於「Clean Code in Python」的書。
而且我認為它的難度適中(好吧,後半部難度比較高,而且本書「不適合」初學者),非常推薦看完《Python 功力提升的樂趣》後,想要更進一步寫出 Pythonic 程式碼的讀者與開發人員。
我覺得,兩本恰恰都是屬於「從書名看很容易被忽略」的好書。因此,作為喜歡本書的讀者,我覺得自己有義務,向你們轉述書中一些值得傳誦的內容。
這也是我寫「閱讀筆記」類文章的核心精神——分享書中那些我覺得特別精彩、贊同的部分,並加上自己的看法。
系列:Python 工匠
- 如何寫好 Python 註解——《Python 工匠》筆記
- 為什麼你「應該」寫單元測試——《Python 工匠》筆記
- 精通「變數」的宣告與使用——《Python 工匠》筆記
本文目錄
- 如何寫好 Python 註解
- Python 註解基礎知識
- 新手常犯的三種註解錯誤
- 一、直接註解程式碼
- 二、僅用註解「重述」程式碼行為
- 指引型註解
- 提煉為獨立函式
- 三、弄錯註解的「受眾」
- 自言自語的註解
- Docstring 與讀者意識
- 好的程式碼到底需不需要註解?
- 作者的觀點與我的看法
如何寫好 Python 註解
本文整理第一章的其中一部分——關於「如何寫好註解」的討論。
之所以要特別寫成筆記,是因為這是我目前看過的書中,討論註解時講最得好的一本。尤其是一些使用上的建議,和我的開發經驗與價值觀可謂非常吻合!
本書作者朱雷(piglei),擁有超過 10 年的 Python 開發經驗,精通 Python 語言特性,對如何開發高品質的大型 Python 專案有獨到見解。
其實我一直也想整理一篇關於寫好註解的基本守則,但遲遲沒有行動。但現在有這本書,我只要整理書中的內容,並加上自己的看法即可。
有關 Python 註解或 docstring 的討論,我在過去多篇文章中都有提到,可參考如下:
開始正文。
Python 註解基礎知識
Python 中,一般我們講到註解,指的是程式碼中的註解,用#
來實現。
而 docstring 則是另一種更具有 Python 特色的註解。主要寫在模組、類別、與函式的開頭,並透過物件的__doc__
屬性,自然地化為程式碼的一部分。
書中提到了 docstring 的幾種常見風格(畢竟它本質只是一堆字串,所以怎麼寫都行),最常見的為 Sphinx 文件風格。而我個人在工作上最常用的,則是 Google 風格。
簡言之,下面提到「註解」二字時,對上述兩種 Python 註解都適用。
新手常犯的三種註解錯誤
你可能聽過「很多註解都是爛註解」這種說法。不得不承認,這相當程度是對的!我確實看過很多爛註解——但這不是我們因此不寫註解的理由。
實務上會有很多爛註解,正是因為我們沒有正視註解的價值,認真學習如何寫好註解。
因此,就讓我們用書中所舉三種常見的註解錯誤,作為學習的切入點。
書中提到的這三點——尤其是第 3 點,應盡可能避免。作者也提出了相應的解決之道,而我會適時補充我的看法。
附帶一提,本書是從作者過去的網路文章整理、出版——但內容增加了很多。而且作者也很大方,在網路上公開了部分的內容。而本文整理的部分恰恰是公開的部分。有興趣的話可以直接參考本頁。
當然,我還是強烈建議,為自己入手一本,你絕對不會後悔。
一、直接註解程式碼
把已經寫完但暫不需要(以後是否需要還不確定)的程式碼,先註解起來,方便日後需要時可以快速「還原」,絕對是我們非常熟悉的手段。
我回想一下,不得不說,這類被註解的程式碼,十之八九都是不會再用到了!如果這類「程式碼註解」愈積愈多,真的會讓人看了很「阿雜」!
所以基本上,現在比較正規的做法,都是建議你直接刪除,以後真的需要時,再透過版控回復即可。
對此我基本認同,所以工作上 code review 時,我「不會」放行這種直接註解的程式碼。
僅有的例外
但,你我都知道,有時事情也沒那麼簡單。
用 Git 版控回復的前提是:你的團隊 commit 記錄要寫好!不然真的要「回復」的時候,你可能連它在哪一個 commit 都要找好一陣子。
所以,基於方便與實際考量,事實上我還是有一點點折衷:原則上不可以註解程式碼,但如果確定只是「暫時」用不到,等別的元件完成後,就會繼續開發、使用,例外可以暫時註解就好——但必須加上 codetag 標記。
一般我們用 TODO
這個 codetag。比如:
1 | # TODO 日後改回一對一時,請用下面方式重寫: |
不過大原則還是:
別註解了,刪除吧!
二、僅用註解「重述」程式碼行為
這應該是我們最常看到的爛註解的形式,也是你在所有討論程式碼註解的書中,一定會提及的。
而且它真的爛,沒有藉口。比如這樣:
1 | # 初始化 x 為 0 |
這當然是一個誇張的例子🤣。但在日常開發中,這種「脫褲子放屁」的註解還真的不算少見。
確實,如果我常常看到這樣的註解,恐怕真的會忍不住說出「你還別寫註解了吧!」
但,請不要放棄治療!
想避免這個問題,請遵守一個簡單且常見的大原則,如書中所言:
應該儘量提供那些讀者無法從程式碼裡讀出來的資訊。描述程式為什麼要這麼做,而不是簡單複述程式碼本身。
不過,光寫「為什麼」註解,有時候還是遠遠不夠的。
指引型註解
因此,本書更進一步提出所謂的「指引性註解」:
這種註解並不「直接」複述程式,而是簡明扼地概括程式碼功能,起到「程式碼導讀」的效果。
書中的例子:
1 | # 初始化存取服務的 client 物件 |
不難看出,這種描述「程式碼流程大綱」的指引型註解,很像 docstring 在做的事。從這個角度,我們可以說 docstring 也是一種指引型註解。
但是,docstring 畢竟只出現在元件的「開頭」,對複雜的程式而言,在元件的內部,往往也需要這樣的註解,作為閱讀複雜程式碼的指引。
要寫好指引型註解,甚至知曉「什麼時候」應該要寫下指引型註解,需要你內心清楚——「這段程式碼的哪些部分,別人可能會看不懂!」
這是為什麼我總說,要寫好註解,就一定要培養好「讀者意識」的理由。
指引型註解的價值
前面提到,寫註解時「應該儘量提供那些讀者無法從程式碼裡讀出來的資訊」,這是我們寫註解的大原則。
但指引性註解和上述註解所有區別,它的獨特價值,如書中所言:
指引性註解並不提供程式碼裡讀不到的東西——如果沒有註解,耐心讀完所有程式碼,你也能知道程式做了什麼事。指引性註解的主要作用是降低程式碼的認知成本,讓我們能更容易理解程式碼的意圖。
說得非常好。
提煉為獨立函式
對於複雜而冗長的程式流程,書寫「指引性註解」是一個協助閱讀理解的好方法。
而另一個有效的方法,就是把這些程式碼片段獨立成一個又一個的函式,透過有意義的函式名稱來描述流程、展現意圖,此時就可以刪除指引性註解:
1 | service_client = make_client() |
其中的重點在於,你要能判斷,什麼時候該寫「指引性註解」,而什麼時候則適合獨立成函式、方法。
這當然需要經驗累積,但我們心中要先有這樣的意識,不是嗎?
三、弄錯註解的「受眾」
本書這段主要適用的是 docstring,不過我覺得「指引型註解」也有類似議題,所以本段提到「註解」一詞時,皆包括這兩者。
註解的正確受眾,或說讀者,應該是誰?我想基本上是這兩種人:
- 未來的自己(注意「未來」二字)
- 專案協作者(同事、主管)
我們先看看書中所舉的「反例」:(我直接引用網頁上的內容,所以技術名詞並非台灣用語,還請見諒)
1 | def resize_image(image, size): |
這個例子的最大問題,是寫了太多「實作細節」,講白了就是提供了太多程式碼的讀者並不關心的內容!
為什麼這樣是不妥的?細節不是很好嗎?
對,但大部分時候,docstring 或「指引型註解」的主要寫作目的,是為了讓讀者不用一行一行閱讀程式碼,就能夠快速知道目前程式碼的流程與意圖。
這種過多細節的寫法,恰恰與這個目標背道而馳——增加了太多理解上的「雜訊」。
書中給出的改善版本如下:
1 | def resize_image(image, size): |
以下主要是我自己的看法。
自言自語的註解
你有沒有一個疑問,為什麼說註解寫了太多細節,是「弄錯受眾」?
我是這樣看的:留下這麼多細節的註解,與其說是註解,更像寫給自己看的筆記——而且是給「現在」的自己,真的就像在作筆記一樣!
我想強調,「現在的自己」並不是註解的受眾!因為現在的自己對程式細節非常清楚,即使沒有註解也能讀懂程式碼。
相反的,註解是為沒時間慢慢讀程式碼的人服務的。
未來的你,再回來看這段程式,絕對不會想要在 docstring 看到這麼多「廢話」。這種寫法無疑是搞錯了對象。
當然,你的同事也不會想看這麼多雜訊——同事往往更關心「這函式到底要怎麼用」!
抽象層次混亂
我覺得還有另一種「弄錯受眾」的註解也很經典,甚至更加常見!那就是混合底層實作與業務邏輯:
註解中參雜了業務邏輯,又不時會出現底層實作的細節描述。
事實上,我倒覺得實務中會願意寫一大串 docstring 的人,可以說是少之又少。大部分的問題其實是:有寫,但寫得「零零落落」。
Docstring 與讀者意識
Docstring 絕對能看出一個人的基本寫作能力,以及是否具備「讀者意識」。
說真的,這不止是作為一個軟體工程師的核心技能——更是任何表達者的核心技能。而程式,只是表達的其中一種形式。
我很想要寫一篇「如何寫好 docstring」,這需要再構思一番。但常見的錯誤不外乎:
- 預知能力:知曉「函式以外」的事、不僅知道函式會怎麼、何時被調用,還會在 docstring 中寫下呼叫時的情境細節——你知道的太多了!
- 太多底層細節:跟前面的書中內容相似,只是我覺得現實中,往往是東寫一點、西寫一點,不會像寫筆記一般完整——所以讀起來更痛苦,真的是自言自語!除了開發者自己,誰能輕易讀懂那些細節?
- 業務邏輯與底層實作的用詞混雜:一下子是「無法取得租戶資訊」一下子卻又是「防止 SQL insert 錯誤、RabbitMQ 如何如何」——讓人大腦很混亂。
總的來說,無論是「自言自語」還是「抽象層次混亂」,它們的本質都差不多——這些註解都像是寫給「現在的自己」看的筆記。
但就像前面說的,現在的自己是最不需要看註解的人!所以才說是「弄錯受眾」了。
最後不免俗地,我們要討論,提到「程式碼註解」就一定避不開的問題:
到底要不要寫註解?
好的程式碼到底需不需要註解?
寫程式到底要不要寫註解,一直有兩派說法,是個老掉牙又爭論不休的問題。我們先來看看這兩派的觀點。( ChatGPT 整理)
寫註解的一派
這派人認為註解是程式碼的一部分,應該寫註解,主要論點為:
- 可讀性提升:註解能夠幫助人們更快理解程式碼的意圖和複雜的邏輯,尤其是對於那些不那麼直觀的部分。
- 溝通工具:註解被視為開發者之間的溝通方式,尤其在團隊協作時,能夠快速傳遞開發者的想法和注意事項。
- 提醒與說明:註解可以用來提醒未來可能的問題,或是對程式碼中的決策提供背景說明。
不寫註解的一派
相對的,這派人認為好的程式碼應該是自解釋的,不需要註解,主要論點為:
- 程式碼即文件:好的程式碼應該是自解釋的,如果你需要註解來解釋你的程式碼,那麼問題可能出在程式碼本身。
- 增加維護難度:註解需要維護,不一致的註解比沒有註解更糟,因為它會導致誤解和混淆。
- 過度依賴註解:過多的註解可能會讓開發者過度依賴於它們來理解程式碼,忽視了提高程式碼品質的重要性。
作者的觀點與我的看法
細看這兩派的主張,我們可以看出,都有一定的道理——不然也不會爭論不休了。
作者觀點
我們先來看看本書作者的觀點(引用難免斷章取義,完整上下文請見本書第 15 頁),下面內容主要是回應「不寫註解的一派」:
但我倒是認為事情沒那麼絕對。無論程式碼寫得多好,多麼「自說明」,跟讀程式碼相比,讀註解通常讓人覺得更輕鬆。
註解會讓人們覺得親切(尤其當註解是中文時),高品質的指引性註解確實會讓程式碼更易讀。有時抽象一個新函式,不見得就一定比一行註解加上幾行程式碼更好。
第一段引用,其實就跟我在「Python docstring 之我見」中的主張一致:
在我看來,無論程式寫得如何簡潔易讀,對一些比較複雜的函式或類別而言,docstring 終究是不可少的。因為文字的詮釋能力和程式碼相比,絕不在同一個層次,相信這也是為何 docstring 會有屬於自己的獨立 PEP 加以規範的理由。
我的看法與結論
既然都寫了這篇文章,想當然爾我是「支持」寫註解的一派。
不過,反對派說註解會增加維護難度,以及可能讓人更加依賴註解而忽略了提升程式碼品質,我覺得這些擔憂也非常真實。
所以,對我來說,問題不在於「要不要寫註解?」,而是「怎麼樣才能寫好註解?」。這也是本文想回答的問題。
我的結論是:寫註解,但也要適度。並保持對「讀者意識」的敏感:不寫多餘的註解,也不寫自言自語的註解。
延伸閱讀
相關文章