Docstring 的重要性——《Python 功力提升的樂趣》筆記
文章目錄
Python 功力提升的樂趣
2024/06/30
:新增「心得與總結」篇章,本書全系列正式完結。
2024/05/05
:重新編輯全文並刪除部分內容,使文章更緊湊。
這是《Python 功力提升的樂趣:寫出乾淨程式碼的最佳實務》閱讀筆記的第 3 篇,你可以把它當作是一則重點整理,加上我個人的開發經驗與心得。
本文整理書中的第 10、11 章,且篇幅幾乎集中在前者。無論什麼語言,「寫好函式」這件事總是如此重要,Python 自然也不例外。
系列:Python 功力提升的樂趣
- 使用 Black 格式化程式碼——《Python 功力提升的樂趣》筆記
- 如何寫出 Pythonic 程式碼——《Python 功力提升的樂趣》筆記
- Docstring 的重要性——《Python 功力提升的樂趣》筆記
- 《Python 功力提升的樂趣》心得:Python 開發 Clean Code 入門指南
第 10 章:寫出有效率的函式
有效率的函式(或說「好的」函式)需要你在「命名、規模大小(行數)、參數數量和複雜性」之間,做出許多決定和取捨。
本章探討的正是這些取捨之間的利弊得失,以及編寫函式的重要原則。
不用說,這是關鍵的一章。
函式的規模:小就是好?
我們都聽過「函式應該盡可能簡單、一次只做一次件事」之類的建言,也表示認同。從這個精神出發,太大或太複雜的函式就應該要進行拆分。
但!事實是,有效拆分函式是一件耗神、講究細節,且沒有標準答案的事。以致於我們即使知道,也很難完全貫徹,包括我自己。
出於各種原因,我們常常對現實世界作出一定的妥協。
小函式的優點
有些人覺得任何函式都不應該超過 20 行,甚至 10、5 行😂。因為函式「短」往往有下面這些優點:
- 單一函式容易理解
- 較少的參數(這確實非常重要!)
- 易於測試與除錯
小函式的缺點
但小也有缺點:
- 一樣的邏輯,更小的函式也意味著「更多」的小函式
- 函式愈多,結構就愈複雜。即「函式間」的關係會變得更加複雜
- 愈多函式,函式間的精準命名將成為巨大的挑戰——這真的很困難!
這些「缺點」往往也解釋了為何我們不一定那麼積極拆分函式,讓每一個函式都符合「一次只做一件事」原則。
尤其是小函式造成的大量命名問題,對於命名很講究的我而言,有時確實感到棘手。
小結:小不等於短
函式原則上還是應該要盡可能單純一點,該拆就要拆,但不一定要很短。而且其中必然會有很多挑戰。
從「功能」上去劃分界限、拆分函式,會更有意義與指導性,與可行性。
作者認為,一味追求短函式,確實可以讓各別函式變得簡單,但卻很可能讓程式的「整體」變得複雜,適得其反。
他的經驗是,理想情況下,函式最好少於 30 行,最多不超過 200 行。讓函式在合理情況下盡可能短少,但不只是為了短少而縮減。
return 應該要有相同的資料型別
對此,我想說:
這真的好重要啊!(吶喊)
卻常常沒有被好好遵守。
簡言之,為確保函式的「可預測性」,我們應該努力讓函式只回傳「單一資料型別」的值。比如總是回傳整數或字串,而不要有時回傳字串,有時則回傳布林值。
這不一定容易做到,但我更常遇到的情況是:明明有替代方案讓回傳型別單一化,卻沒有善用。
以False
代替raise
最常見的例子就是:該拋出錯誤時候,卻只用return False
替代。
意即,當函式正常執行時,回傳一般正常的 output 值。但當執行失敗時,卻是回傳False
——這簡直令人髮指,而且我相信你一定看過這樣的函式。
必須說明,發生錯誤時不拋出而選擇return False
未必總是不好的,就像 Django REST framework 序列化器的is_valid
方法,預設也是返回一個布林值(可以用raise_exception=True
參數改為直接拋出錯誤),方便你進行更多後續操作。
不過,如果你選擇在遇到錯誤時return False
,則應該在函式正確執行時,return True
,以保持回傳型別的一致性。
而且函式命名也要跟著配合,讓人一看就知曉該函式、方法會返回一個布林值,比如上述的is_valid
,或常見的has_permission
、is_authenticated
等。這些都是常見的最佳實踐。
錯誤示範
我們看一下這個錯誤示範:假設我們有一個函式,目的是從一個 JSON 文件中讀取設定資訊。
如果讀取成功,它會返回一個 Python 字典;如果讀取失敗,它會捕捉異常——並返回False
。
1 | import json |
這個函式在except
區塊裡面直接返回False
,這會導致以下問題:
- 資料型別不一致:正常情況下返回一個字典,異常情況下返回
False
(布林值)。 - 誤導函式使用者:使用者可能會誤以為
False
是一個有效的設定(只是設定值為False
),進而嘗試在其上進行操作,導致更多的錯誤。 - 後續處理困難:函式使用者必須額外檢查返回值是否為
False
,然後再決定是否進行後續的操作。
「return
型別不確定」不只發生在例外處理,但它確實很常發生在例外處理,我看過不止一次類似的真實案例。
除此之外,還有「函式正常執行回傳一個類別物件,失敗時回傳一個 Python tuple——包含錯誤代碼和錯誤訊息」這種非常反直覺的設計。彷彿是在告訴我們:
這個函式的回傳值,可能是一個物件,也可能是一個 tuple,你自己判斷吧!
會寫出這樣的函式,原因諸多——工作很忙、重構太麻煩了,要新增什麼功能我直接「加上去」就好!
這些無疑都是充滿了「技術債味」的做法。
技術債與認知負擔
這類「雙型別 return」函式,對於函式使用者(比如你的同事)的認知與理解,有著「更高的要求」——呼叫方必須很了解這個函式的怪異行為,才能正確使用與處理後續衍生的問題。
這在多行或有多個 return 值的複雜函式時,真是一場災難。
期望他人知道自己做了什麼「特別的事」,不是我所知曉的軟體開發之道。
當函式具有這種「雙型別 return」的特性時,會明顯增加呼叫方的「認知負擔」。
這使得程式不僅難以閱讀和維護,也容易出錯,因為未來的維護者或其他團隊成員很可能不知道這個函式的「獨特」行為。
無論何時,我們都不應該寫這樣的程式。
我對寫好函式的基本看法
寫好函式的重點實在太多了,而本文的篇幅有限,只能擇要為之。
我也講講我認為函式的撰寫中,最重要的兩點。
至少遵守這兩點,你的同事會很感激你。
一、Docstring 真的很重要
盡量寫 docstring,儘管這真的不容易,畢竟維護 docstring 也需要心力。
Docstring 就跟所有開發文件一樣——自己很懶得寫,但如果我想調用別人寫好的程式時,卻希望它們越詳細越好。
而且 docstring 也不是有寫就行,還需要從「讀者(也就是你的同事)」的角度去思考與表達。不然看起來會很像開發者的自言自語——沒人看得懂。
何時建議寫 docstring?
我承認,要為「所有」的模組、類別、函式寫 docstring,未免有點不切實際。在眾多函式中,下列兩種是我認為一定要寫 docstring 的:
- 專案「自定義」成份濃厚:除了開發者本人,沒人知道這段程式在幹什麼。這通常源於特殊的業務需求,而且往往行數超多、邏輯超手刻,各種 if/else、for 迴圈滿天飛,aka——沒人想看的程式碼。
- 流程相對複雜的函式:愈複雜就愈難理解,這時候 docstring 就是你的好朋友。用文字描述函式的輸入、輸出、邏輯,能大大提升我們理解程式碼的效率。
畢竟,看有描述性的文字,總比看一長串程式碼,要簡單且友善得多。
上述兩種情況,若不寫 docstring,那麼閱讀程式碼的成本就會大大提升。這相當於在告訴你的同事:
我離開公司後,誰還想維護這段程式碼,先學習通靈術吧!
關於我對 docstring 的其它討論,可參考下面內容:
我的觀點一向如此:不寫好 docstring,就稱不上是一流的 Python 開發者。
二、函式的行為與命名要一致
其二,好的函式要「言行一致」。
你可能會想:
這不是理所當然的嗎?
對,它「本應該」是理所當然的,畢竟這不就是函式命名的基本目的?——用來描述函式的行為。
但我們可以回想一下自己在工作中遇到的各式各樣函式,究竟有多少比例,是真正做到「言行一致」?我覺得可能只有一半。
或許你會認為「一半」也太誇張了!但我並不這麼想。
「言行不一致」通常有下面幾種症狀:
- 函式名稱只表達了函式「部分」的行為。也就是函式做了超過它宣稱要做的事,比如「驗證欄位」函式,竟然還把驗證資料格式化了!
- 函式名稱「言過其實」,說要驗證加格式化,結果只做了一半。
- 名稱太模糊、缺乏業務邏輯描述、濫用技術詞彙等等,根本看不懂它在說什麼,更別說言行一致了。
如果你不能從一個函式的名稱中有效理解並推測它應有的行為,那麼這個函式基本上就是失敗(或不健康)的。
很多時候,函式最初可能是「言行一致」的,但隨著後來的修改、刪除、擴充,實際上做的事情變更了,但命名卻沒有跟著改變、重構。
這些言行不一的函式,充滿誤導性,不斷地挑戰著你的認知、推理能力,更增加了維護成本。
這樣的例子還少嗎?恐怕每天都在發生。
第 11 章:注釋、docstring 和 type hints
這章我只摘錄書中的一段話——我特別欣賞與認同的部分:
好的注釋對程式設計師在未來閱讀並理解程式碼作用時提供了簡潔、有用和準確的資訊。這些注釋應該用來解說程式設計師原本的意圖,並總結某程式碼的作用,而不是只對某行程式碼進行解說。
注釋有時會詳細描述程式設計師在編寫程式碼時所得到的經驗教訓,這些寶貴的資訊可以讓將來的維護者不必再次經歷這些苦難。
說的太好了!
團隊寫程式,是關於溝通的藝術,畢竟《人月神話》已經告訴我們:人多不一定比較快。
溝通不止發生在會議、Jira、Slack 和規格文件上,程式之內也有著大量的溝通,註解是如此,docstring 亦是如此。
永遠不要低估「對這些細節的用心」所帶能來的巨大影響力。
優秀的工程師絕不可能輕忽它們。
相關文章