這是《Python 工匠|案例、技巧與開發實戰》筆記的第 2 篇,你可以把它當作是一則重點整理,加上我個人的開發經驗與心得。
如第一篇所言,這是一本關於「Clean Code in Python」的書。
這二篇,我將整理書中第 13 章「有關單元測試的建議」的內容(以及我的看法)。我覺得真的寫得太好了,值得你了解。
話不多說,直接開始。
- 《Python 工匠》筆記(一)如何寫好註解
- 《Python 工匠》筆記(二)對「單元測試」的看法與建議
作者一開始就點了出關於「單元測試」的一個奇特現象:
雖然好像人人都認為單元測試很有用,但在實際工作中,有完善單元測試的專案仍然是個稀奇的東西。
這一點也不誇張,甚至可以說是公開的秘密。
單元測試,大家都說重要,但真的有在寫的團隊、專案、公司,卻不如想像中多。往往都是靠 QA 把關——把整合測試當作單元測試在測😎(我就待過這樣的公司)
很多公司都是在口頭上強調單元測試的重要性,但實際的執行力度卻大打折扣,就像是那些年初設定的新年決心,說的比做的好聽。
姑且不說公司如何如何,其實就連開發者自己,也常常逃避寫單元測試。
書中舉出了實務上,開發人員不想寫單元測試的 3 個常見理由——以及這些「理由」為什麼不成立的理由:
- 「時程緊迫沒時間寫測試」:寫單元測試看上去要多花費時間,但其實會在未來節約你的時間。
- 「模組複雜沒辦法寫測試」:也許這正代表了你的程式設計有問題,需要調整。
- 「模組簡單不需要測試」:是否應該寫單元測試,和模組簡單或複雜沒有任何關係。
第一個理由無疑是我們最常聽到的說法,但我們都知道,它往往只是一個藉口——就算真的有時間,說這種話的人也不會寫測試的啦!
拋開這些似是而非,各種不寫測試的牽強藉口,作者提出了 5 個,寫單元測試前值得先有的重要理解。
接下來,讓我們一一說明。
寫單元測試可以節約你「整體」的開發時間。這個說法,我相信大部分的人應該都能夠接受——無論有沒有寫過測試。
那具體節約了哪些時間呢?主要有兩種:
第一種情況,可謂天天都在發生——加了新東西,怎麼舊的就壞了!
不少人計算自己開發時間的方式,是只算「自己寫程式、 完成新功能」的時間,至於後續的 debug(更別說自己寫的爛扣造成後續無數次 debug 的時間),這些都不算!
你可能聽過下列對白:
RD:「新功能我做完了,你測試一下吧!」
QA:「(經過一番測試)這 API 回傳不太對耶?還有這個參數好像少了一個?」
RD:「怎麼可能?」
好一句「怎麼可能」。
此時,QA 心中想的應不是「測一下」,而是「測幹譙(台語)」。
作者相信,你因為沒寫單元測試而耗費去處理後續問題的時間,絕對遠遠超過你寫單元測試的時間——我完全認同。這也許是「扁鵲梗:軟體工程師版」可以得到大量共鳴的原因。
當然,我也理解,寫單元測試通常不算是一件很有趣的事。但話說回來,當「有趣」和「重要」不可兼得的時候,我們也只能選擇後者。
誰叫我們是稱職的軟體工程師呢?
有單元測試第二個重大好處,就是它給你重構的勇氣。
假設你要對某個模組做大規模的重構,那麼,這個模組是否有單元測試,對應的重構難度天差地別。對於沒有任何單元測試的模組來說,重構是地獄難度。
在這種環境下,每當你調整任何程式,都必須仔細找到模組的每一個被引用處,小心翼翼地手動測試每一個場景。稍有不慎,重構就會引入新 bug,好心辦壞事。
簡言之,對於複雜模組的重構,沒有單元測試是不可能的,這已經不是「有沒有時間」的問題了。
「先幫我 review 下剛提交的這個 PR,功能已經全實現好了。單元測試我等等補上來!」
這樣說法的背後,透露著一種思維:單元測試是「多」的,完全是「附屬」地位。所以可以事後再「補」。
單元測試被當成了一種驗證正確性的事後工具,對開發功能程式沒有任何影響,因此,人們總是可以在完成開發後再補上測試。
但作者不這麼看:
但事實是,單元測試不光能驗證程式的正確性,還能極大地幫助你改進程式設計。但這種幫助有一個前提,那就是你必須在寫程式的同時寫單元測試。
當開發功能與寫測試同步進行時,你會來回切換自己的角色,分別作為程式的設計者和使用者,不斷從程式裡找出問題,調整設計。經過多次調整與打磨後,你的程式會變得更好、更具擴展性。
好吧!我承認,我自己也沒有很好地做到這點。
雖然很少提出要「補」測試,但我的開發還是處於「先寫功能再寫測試」的傳統習慣——尤其是專案的早期,API 還沒有完全底定的時候。
但我也相信,帶著「測試思維」來寫程式,絕對能夠讓程式碼的品質更上一階,這點我並不懷疑。
而要帶著測試思維寫程式,最簡單的方法,就是一邊寫程式一邊寫測試!
說到這裡,你應該很容易聯想到 TDD。
本書第 412 頁,作者寫了一個小專欄,專門討論他對 TDD 的看法。一言以蔽之就是:不一定要完全按照 TDD 的流程寫程式,但 TDD 的思維與習慣,值得你培養!
有興趣的讀者,可以自行參考書中內容。
如果你認同了前述「一、二」的核心看法,那這個道理應該是自然而然的。
那些難寫測試的程式,本身很可能就有問題!我們看書中的一個例子:
當模組相依了一個全域物件時,寫單元測試就會變得很難。全域物件的基本特徵決定了它在記憶體中永遠只會存在一份。而在寫單元測試時,為了驗證程式在不同場景下的行為,我們需要用到多份不同的全域物件。這時,全域物件的唯一性就會成為寫測試最大的阻礙。
這類的例子真的多不勝數。大部分的時候,我們遇到這類情況,往往就是去「更改測試的邏輯」讓測試變得「剛好能夠通過」。
而帶來的結果往往是,測試的有效性降低!因為這個測試邏輯與方法和被測試的程式碼已經太過耦合了,可說是為了它「量身訂做」的測試。
如此一來,只要未來程式碼稍有變動,該測試也很可能就過不了。
現在我們知道,有時候不妨多想一下:「為什麼這麼難測試?」,並試著去重構原來的程式碼——而不是顧著修改測試函式本身。
因此,每當你發現很難為程式寫測試時,就應該意識到程式設計可能存在問題,需要努力調整設計,讓程式變得更容易測試。
即使有在寫測試,這些測試程式碼,往往也被當作「二等公民」對待。
書中舉出了,把測試視為二等公民,因而「另眼看待(看輕)」它們的三個特徵:
其中潛藏並透露出的心態,用一句話來講就是:「測試的程式碼終究只是一個附屬品,差不多就好了,不要要求太多!」
作者當然不認同這樣的心態,並建議你:
像應用程式一樣對待測試程式。
和前述第二點一樣,我肯定認同這樣的看法,雖然還無法做到 100 分。
不過 80 分絕對是有的!
所以我在 code review 時,對於 pytest 的 fixture 設計,以及 fixtures 在測試函式中的引入與使用方式,會非常仔細審查。
你知道,測試寫的爛,也是一種「業障」。
總之,千萬別小看了測試程式碼。
我個人覺得這段非常非常精彩,哈哈哈!
說起來很奇怪,在單元測試領域有非常多的理論與說法。人們總是樂於發表種對單元測試的見解,在文章、演講以及與同事的交談中,你常常能聽到這些話:
看到書中的這段,我真的會笑死XDDD——因為這個「怪現象」竟是如此的真實。
哪怕還沒有開始寫測試之前,我就已經看過不少這類言論。說真的,這些言論——或者說「信仰」——恐怕或多或少增加了想要入門測試的人,在心理上的門檻。
好像你不把測試做到 100 分、盡善盡美,就乾脆不要寫測試了——我覺得這不是一種健康的姿態。
針對這現象,作者認為:
這些觀點各自都有許多狂熱的追隨者,但我有個建議:你應該了解這些理論,越多越好,但是千萬不要陷入教條主義。
因為在現實世界裡,每個人參與的專案千差萬別,別人的理論不一定適用於你,如果盲目遵從,反而會給自己增加麻煩。
而我覺得,認知並了解到這個現象的存在,主要是為了「降低不必要的心理負擔」,進而認真看待自己已經寫好的測試,並對此感到欣慰。(但不自滿)
這很重要。
以我自己為例,目前工作上專案的測試覆蓋率大概在 60-75% 之間。而我清楚,想要從 75% 繼續再往上提升的話,必須要做很多「細節」工作。
比如為每一個自定義的 API 錯誤寫測試,確認錯誤訊息符合期待。(我們會適時地寫這些測試,但不是每一個都如此)
其中的投入與產出,不僅不成比例(投入多、效益有限),而且還可能造成開發者對於「寫單元測試」這件事感到厭煩。
所以並沒有嚴格要求覆蓋率要達到 100%——我想這也不太現實。
然而,每次看到文章或教學中的「100% 主義」,我還是多少會擔心:「難道我們做的真的還不夠好嗎?」——現在看來,大可不必。
單元測試領域的理論確實很多,這剛好說明了一件事,那就是要做好單元測試真的很難。要更好地實踐單元測試,你要做的第一件事就是拋棄教條主義,腳踏實地,不斷尋求最合適當前專案的測試方案,這樣才能最大地享受單元測試的好處。
但我還是不禁好奇!為什麼「測試」這領域,就是有這麼多狂熱信徒?
這個議題,我們直接看看 ChatGPT 怎麼說吧!
以下內容整理自 ChatGPT(我劃了一些重點,以粗體字表示),我覺得分析得挺不錯,供有興趣的讀者參考。
這問題真是直擊核心,「測試」領域確實滋生了許多信仰狂熱者,原因有幾個方面:
這些原因綜合在一起,就創造了一個完美的環境,讓「測試」領域裡的信仰狂熱者茁壯成長。
]]>這是《人生 4 千個禮拜》筆記的第 2 篇,你可以把它當作是一則重點整理,加上大量我個人的經驗與想法。
上一篇我們提到,「生產力」一詞在這個時代是如何地被重視——甚至有點扭曲。
以及如果你試圖用各種方法(尤其是那些筆記軟體與提升效率的工具)來增加生產力,反而很可能會掉入所謂的「效率陷阱」。
如果你也認同「過度追求效率很可能適得其反」這個命題,那很慶幸,我們已經達成了最重要的共識。這也是我給這本書高評價的原因。
- 《人生 4 千個禮拜》筆記(一)病態的生產力
- 《人生 4 千個禮拜》筆記(二)抗拒「重要性中等」的誘惑
如第一篇所言,本書可以認為是作者「對自己與『提高生產力』這個議題的重新觀察、自我反省」。
我希望這不是一篇普通的閱讀筆記或導讀,而是要強調我對本書最認同、最在乎的部分——我是如何在書中看到過去的自己。當然,這肯定包含了我自己的解讀。
因此,這篇文章,我想要繼續對作者追問:
好,我現在知道過度追求生產力的可能代價了,那要怎麼想、怎麼做會更好?
本文整理自書中的第 3 到 4 章。
本書第 3 章的標題為「面對有限性」。
我知道,你可能會想:「噢!又來了!」然後皺起眉頭。心裡想到的是,在無數的書籍或文章中,那些提醒你要「珍惜現在、直面生命的有限」的雞湯內容。
好吧,至少我就是這樣覺得,覺得這個標題也未免太老生常談了!
但本文還是整理了其中的一段,因為我需要它,為接下來的內容作鋪墊。一言以蔽之,本章的重點就是:
因為人生有限,你必須做出選擇。不能想要這個,又想要那個。
在這個前提下,作者是這麼說的:
做選擇(從當前的選項中挑一個)成為一種肯定,根本算不上挫敗。這是一種正面的投入,你決定要用某段時間做這件事、不做那件事(不只一件,而是不計其數的其他事情),因為你判定這件事是目前最重要的一件。
請記得「最重要」三個字。
覺得自己事情太多,老是做不完,需要加強時間管理,或學習特定的生產力工具來提高產出嗎?先等等!看看作者怎麼說。
關於「時間管理」,到底怎麼做才稱得上「有效」,作者提出了一個有趣且令人信服的切入點:
不論是哪一種間管理技巧,真正的有效評估指標是那項技巧是否協助你忽視應該忽視的事物。
我非常喜歡這個切入點。
因此,重點不是根治拖延症,而是以更明智的方式選擇要延後哪些事,專注於眼前最重要的事。
當然,這樣的看法並不算新穎。但是,能夠把「有效忽略不夠重要事物」這個指標提升到時間管理方法論的「首要」考慮事項,我個人十分贊同。
接下來的內容,會結合我個人的經驗、想法。
還是要再提到「人生管理系統」,不過這次我們不需要了解其中的細節。
這種承諾你可以完成很多事的「生產力方法論」,也就是上述的「系統」,無疑是現代「生產力至上」這類價值觀的縮影。
你說它不好嗎?可能也不盡然。我相信這類「系統」確實可能讓你做完更多事情——但也僅止於此。
這類系統與其背後的價值觀,可大致濃縮為:
用厲害的方法,做完很多事。
而推廣這類方法論的生產力 KOL 們,會不斷明示、暗示你:「一旦擁有能做完很多事的能力,你會從瞎忙中脫解,你的人生將從此不同。」
其實這樣的論述和更早開始流行的「時間管理」一樣,都是給你一個美好的承諾。
其中的差別或許是,他們更加強調「工具」的重要性——並提供一整套有關「如何善用工具」的學習方案。(可能是文章、書籍、影片、課程、社群,可能是免費或付費)
上述「多多益善」命題是否為真、是否有效,可以先不論。這裡想從另一個角度切入,說說我認為更值得推崇的看法。
我的核心看法很簡單:放下「想做完很多事」的渴望。接受書中的核心觀點,理解人生有限,並做出取捨。然後專注於那些「最要緊」的事就好。
必須強調,捨棄一部分重要的事情,這個過程可能會讓人很不舒服。如果你沒有這樣的感受,那可能是因為你還沒有真正做出取捨。
對此,我要更進一步闡明:
我不認為「能做完很多事」的能力,能夠改變你的人生。相反的,能做出取捨,專注於關鍵事物的人,更有機會變得不同。
換句話說,我認為「取捨」——與取捨後的「專注」——才是最重要的能力,而不是靠工具、系統、時間管理,試圖去完成更多事情。
而所謂取捨的能力,講白了,就是本段標題說的,「忽視應該忽視的事物」的能力。
「忽視應該忽視的事物」的能力,究竟要如何培養?老實說,我自己也還在摸索、思考與歸納的路上。
我感覺自己還不是一個,能夠果斷取捨然後篤定前行的人——我總是太 FOMO。
不過,《為什麼你「不需要」所謂的人生管理系統》中關於「局部實現」的闡述,仍然值得我們參考:
局部實現,是一種心法或價值觀,說穿了沒什麼,就是「緊扣著需求尋找並選定方法,需要多少才投入多少」。
想做多少再投入多少,確保自己做的事,都是真正重要的。這樣的思考與價值觀,或許更加簡潔、有力。
相信本書作者,也會認同這樣的觀點。
書中舉了一個例子(作者說,話是誰講的並不重要XD)來表達,為了真正做到取捨,你應該怎麼看待,那些「重要但不是最重要」的事情。
據說這是股神巴菲特(Warren Buffett)的故事,反正有一次,巴菲特的私人駕駛請教他,怎樣才能排定優先順序。
他要駕駛找出人生最重要的二十五件事,接著依序排列,從最重要的排到最不重要的。巴菲特說,在規畫時間時,應該安排好單子上的前五名。
至於剩下的二十件事,和駕駛以為會聽到的建議不一樣。據說巴菲特解釋,那二十件事不是重要性居次、有機會就去做的事。
錯,錯,錯!
事實上,駕駛應該不惜一切代價,努力避免去做那二十件事,因為對那名駕駛而言,那二十個目標沒有重要到構成人生的核心,吸引力卻大到足以讓他分心,以至於沒去做最重要的事。
「沒有重要到構成人生的核心,吸引力卻大到足以讓他分心」,這真的是血淋淋的教訓!而且我相信,不論任何人,都一定能夠想起,自己曾經做過了多少這樣的事情。
當然,人生畢竟不是理論,縱使扣除休息與休閒,當我們想要「有所作為」的時候,也不可能真的只做最重要的五件事——太難了!
但是,至少每隔一段時間自我檢視、反省一次:「我是不是又把時間花在『重要但不是最重要』的事情上了?」,肯定會很有收獲。
但我還是想再次提醒你——也提醒自己。想要真心誠意,盡可能緊扣著「最重要」的事努力實踐。就要一定程度放下「我要做很多事」的渴望。
也就是抗拒「重要性中等」的誘惑。
這並不容易!如前所述,真正的取捨是個讓人「不太舒服」的過程。
因此,我能斗膽斷言:深信自己「可以透過學習工具、方法論,完成大量重要事情」的人,99% 都是要落空的。
不過落空的結局有三個截然不同的版本:
上面提到的數字是 99% 而不是 100,看樣子,我還是相信有 1% 的人會成功?
是的!我確實相信,有極少數人能夠做到。但我奉勸你不要追求,企圖成為這鳳毛麟角的 1%——因為真的沒有必要。
]]>這是《Python 工匠|案例、技巧與開發實戰》筆記的第 1 篇,你可以把它當作是一則重點整理,加上我個人的開發經驗與心得。
從書名推敲,我們並不容易知道本書的主題為何。事實上,和《Python 功力提升的樂趣》類似,這是一本關於「Clean Code in Python」的書。
而且我認為它的難度適中(好吧,後半部難度比較高,而且本書「不適合」初學者),非常推薦看完《Python 功力提升的樂趣》後,想要更進一步寫出 Pythonic 程式碼的讀者與開發人員。
我覺得,兩本恰恰都是屬於「從書名看很容易被忽略」的好書。因此,作為喜歡本書的讀者,我覺得自己有義務,向你們轉述書中一些值得傳誦的內容。
這也是「閱讀筆記」系列的核心精神。
- 《Python 工匠》筆記(一)如何寫好註解
- 《Python 工匠》筆記(二)對「單元測試」的看法與建議
本文整理第一章的其中一部分——關於「如何寫好註解」的討論。
之所以要特別寫成筆記,是因為這是我目前看過的書中,討論註解時講最得好的一本。尤其是一些使用上的建議,和我的開發經驗與價值觀可謂非常吻合!
本書作者朱雷(piglei),擁有超過 10 年的 Python 開發經驗,精通 Python 語言特性,對如何開發高品質的大型 Python 專案有獨到見解。
其實我一直也想整理一篇關於寫好註解的基本守則,但遲遲沒有行動。但現在有這本書,我只要整理書中的內容,並加上自己的看法即可。
有關 Python 註解或 docstring 的討論,我在過去多篇文章中都有提到,可參考如下:
開始正文。
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」,這需要再構思一番。但常見的錯誤不外乎:
總的來說,無論是「自言自語」還是「抽象層次混亂」,它們的本質都差不多——這些註解都像是寫給「現在的自己」看的筆記。
但就像前面說的,現在的自己是最不需要看註解的人!所以才說是「弄錯受眾」了。
最後不免俗地,我們要討論,提到「程式碼註解」就一定避不開的問題:
到底要不要寫註解?
寫程式到底要不要寫註解,一直有兩派說法,是個老掉牙又爭論不休的問題。我們先來看看這兩派的觀點。( ChatGPT 整理)
這派人認為註解是程式碼的一部分,應該寫註解,主要論點為:
相對的,這派人認為好的程式碼應該是自解釋的,不需要註解,主要論點為:
細看這兩派的主張,我們可以看出,都有一定的道理——不然也不會爭論不休了。
我們先來看看本書作者的觀點(引用難免斷章取義,完整上下文請見本書第 15 頁),下面內容主要是回應「不寫註解的一派」:
但我倒是認為事情沒那麼絕對。無論程式碼寫得多好,多麼「自說明」,跟讀程式碼相比,讀註解通常讓人覺得更輕鬆。
註解會讓人們覺得親切(尤其當註解是中文時),高品質的指引性註解確實會讓程式碼更易讀。有時抽象一個新函式,不見得就一定比一行註解加上幾行程式碼更好。
第一段引用,其實就跟我在「Python docstring 之我見」中的主張一致:
在我看來,無論程式寫得如何簡潔易讀,對一些比較複雜的函式或類別而言,docstring 終究是不可少的。因為文字的詮釋能力和程式碼相比,絕不在同一個層次,相信這也是為何 docstring 會有屬於自己的獨立 PEP 加以規範的理由。
既然都寫了這篇文章,想當然爾我是「支持」寫註解的一派。
不過,反對派說註解會增加維護難度,以及可能讓人更加依賴註解而忽略了提升程式碼品質,我覺得這些擔憂也非常真實。
所以,對我來說,問題不在於「要不要寫註解?」,而是「怎麼樣才能寫好註解?」。這也是本文想回答的問題。
我的結論是:寫註解,但也要適度。並保持對「讀者意識」的敏感:不寫多餘的註解,也不寫自言自語的註解。
在〈使用 Notion 滿 3 年,為何我仍「不推薦」用它來管理你的人生?〉一文與其所屬的「Simple Notion 系列」中,我已用不少幅篇,明確表達我對這類「大而全」生產力系統的隱憂與質疑。
在其它文章中,也有過類似討論:
本文將進行一次「總結式」的整理,重新闡述不用這類系統的三大理由。並提出我認為相對可行的替代方案:局部實現。
本文想表達一個核心看法:打造大而全的人生管理系統,期望它全面提升你的生產力,可能不是一個好主意。
我知道,在這個資訊爆炸、快速變遷的時代,追求生產力幾乎可說是一種「顯學」。
這類系統也許為「少部分人」帶來了可觀的成就、自我掌控感,甚至是安全感。
因此他們能夠信誓旦旦地告訴你:「相信我!這是一個超棒的方法,而且你也能夠!」
老實說,連我自己都很難完全抗拒「高效、生產力」這類的主題,我想這是所有懷抱著自我期許之人的本能吧!
所以我並不打算阻止你去追求、建立屬於自己的生產力方法論。本文想要提醒的是——這些方法最好不要是「大而全」的那種,而是要能夠做到「取捨」。
不過,人生之難,就難在取捨。
本文目標讀者有二:
對於這兩類讀者,我希望通過我的經驗和觀察,提供一個不同於「主流」的視角。
一言以蔽之,我相信對大部分人而言,真正有效的提升並不在於建立一個龐大而複雜的系統。反而一些簡單的做法,往往更有效。
文章一開始,我們需要對本文中提到的這類「系統」——或者應該稱為「提高生產力的方法論」,做出基本的定義。
從上古時期的 GTD、到現在的 PARA,以至於你在網路上、YouTube 影片中,所看到任何關於建立一套「流程」來組織與管理你的生產力活動(比如工作、學習、寫作),都算。
這些系統、方法論,往往都不打算只解決你一、兩個小問題,而是雄心壯志地宣稱可以改變你的人生。
藉由全方位自我管理,帶給你不同於以往的全新做事思維,讓你從混亂、瞎忙、事情做不完的焦頭爛額中解脫出來。
從此過上「高效、優雅、有目標」的充實生活。
聽起來對我們的人生真的很有幫助呢!☺️
所以我們姑且稱它為「人生管理系統」。
在這個強調終身學習的時代,這類系統也很常以「個人知識管理系統」的形式活躍,從 GTD(把事情做完)到 PARA(整理知識、有效學習),能看出時代的變遷。
這些方法論,往往具備兩個特徵,這也是它們可以自稱為「系統」的主要原因。
所謂的複雜,指的是你幾乎很難一看就學會。往往還需要看書、上課,或者看一堆教學影片,才能慢慢領略其中具體的做法與精神。
總之,這個「系統」本身就是一套需要學習的方法。
毫無疑問,在這個自媒體時代,這類系統的「最佳代言人」,就是眾多生產力 KOL。可能是一個 YouTuber,或生產力部落客。
他們會在 YouTube 或自己的部落格上,分享關於「如何建立與使用」這類系統的文章影片。再多一點,則是會有自己的課程或書籍。
除了複雜,這類系統的另一個明顯的特徵——「流程化」。
遵循一定的規則、順序、條件,就像生產線一樣,把知識——或任何其它你需要處理、學習的事物——當作原料,透過一套完整的工具使用流程,讓它們變成「你的東西」,或產出相對應的成果。
想當然爾,這不會是簡單的幾個步驟,而是一整套理論(甚至說是「信仰」可能也不為過),加上各式各樣實踐上的細節、建議所組成。
什麼!GTD、PARA 你都沒聽過?沒關係,因為本文要討論的,不是任何具體的生產力方法,也不是它們究竟有沒有效,而是更高層次的價值觀議題。
如「生產力軟體的戰國時代」中所說,上述的系統或方法論,無一例外都是透過某一款筆記軟體(有時甚至是一整套工具)來實現,無論是 Notion 或 Evernote。
這可以理解,畢竟理論終歸是理論,還是需要工具來落實。以前可能是紙筆,但在這個時代,軟體、app 才是我們最常用的工具。
而本文要強調的則是「為什麼不要這麼用」這些軟體——不要在這些軟體之上,或好些軟體之間,建立一個複雜的工作流程。
不要在這些軟體之上,或好些軟體之間,建立一個複雜的工作流程。
為什麼不?
開頭的 3 篇文章中,都有提到不這麼做的理由,讓我搭配引述,重新為你疏理一番。
歸納起來,主要有下列 3 個理由。
特別提醒:用來落實本文中「系統」的工具,並不限於 Notion,只不過 Notion 可能是最常被使用、提及的工具而已。
在「維持工作流持續運轉,是個『體力活』」中,我是這麼說的:
很遺憾,對這套「系統」每一天的落實、記錄,大多仍須由你「手動」執行。
哪怕 Notion 有 API 整合,可以省下一些時間。網路上也有眾多模板,供你直接套用。但是,你設計出來的工作流,其中最核心的部分,往往只能由你自己——手動維護。
而這些手動的功夫,勢必將帶來大量的時間、精力的消耗。系統愈大、愈是精巧,成本就愈高。
我認為對大部分人而言,如此巨大的投入,幾乎很難獲得「相應」的回報。
為什麼?
因為你其實不需要這麼多回報。
大部分時候,產出價值的關鍵在於「把事情做好」,而不是「做好多事情」。
而就我所知,要把一些事情做好,並不需要太複雜的工具或管理技巧。
不過話說回來,如果你的野心更大,想要「把好多事情都做好」,那可能確實需要這類系統。
然而,這樣的想法,也有其背後的隱憂——讓我們看第二個理由。
時間成本,是採用「人生管理系統」的一大門檻,而且光是需要投入的時間成本,其實就足以勸退一半以上的嘗試者。
其中的理由可想而知:因為太累了嘛!🤣
但是!如果你動力過人、積極實踐,並沒有因為成本而卻步。那第二個理由則是我們更需要關心的。
我在「效率陷阱」中提到:
有時候,這些額外增加的效率(生產力)就像是超商的「第二件 6 折」零食。平時你知道零食不健康,所以不會多買。
但有了這個折扣,我們往往忍不住,買更多——內心還覺得很划算。
正如前述,這些事如果不是「必須的」,那「提高對此事的生產力」,往往只是讓你花費更多時間,做那些本來就不需要做的事!——就像多吃了一堆零食。
換句話說,如果你真的能「把好多事情都做好」,那你也很可能——且幾乎不可避免地——會增加做「不重要瑣事」的數量與比例。
而這些瑣事對整體產出的價值,當然是「有害」的!
你乍聽可能覺得:「會嗎?事情是否重要,我一直都是了然於心的啊!」
但我想說,現實恐怕沒有這般美好。
接下來的第三個理由,將會一定程度論證這一點:我們對事物重要性的判斷,並不總是那麼敏感。
還記得十多年前,超商非常流行的「集點公仔收集」活動嗎?
年輕的讀者可能沒什麼印象,這個熱潮當時真的流行了好幾年才慢慢消退。
仔細想想,為什麼會這麼流行?是因為這些公仔、飾品真的很吸引人嗎?這只是一部分原因。當然,也不乏對特定主題本來就有興趣的愛好者。
但其中一個不容忽視的要素是——我們對於「事物完整性」的本能追求:
而純喫茶則是運用包裝上的瓢蟲元素,推出了一系列不同品種花紋的公仔,不少人也是特別去購買產品來完成收集的目標。
「完成收集目標」意謂著,為什麼收集並不那麼重要,重要的是它必須被完成。
可想而知,對完整性的本能偏好,很可能會讓我們忽略,追求完整究竟是為了什麼?
那這個本能和「系統」有什麼關係?
系統,特別是那些複雜的「人生管理系統」,它的設計往往很容易激發我們追求完整的本能。
回顧一下我在〈使用 Notion 滿 3 年,為何我仍「不推薦」用它來管理你的人生?〉提過的,那些鼓吹你入坑「人生管理系統」的迷人標語:
就算沒有真的實踐過,我們光看這些宣傳詞,想必也能合理推測:
這樣的系統,勢必要追求一定的完整性,才能有效應對不同面向上的各種需求——畢竟它可是要改變你的人生!
結合不同功能模組(在 Notion 中就是許多 database),形成一套完整的「工作流」、「系統」,正是它們主要的設計思路。
這種大而全的設計思路,讓我們不自覺地想要填滿每一個環節上的空白、講究每一個角落的細節——即使這些細節對我們的整體目標並不重要。
就像那些公仔收集活動一樣,系統觸發了我們對「完整性」的渴望,讓我們陷入了一種錯誤的追求方向——「我要把它們都收集完!」
在大系統中,我們可能花很多時間思考系統本身的設計、優化管理方法,而忽略了真正對我們重要的事情,也就是系統「本來」想達到的目的。
追求完整的本能,再結合複雜的系統設計,最終可能導致我們的生產力「下降」,而非提升。
到頭來,我們花費了太多時間在維護、優化系統,而非利用它來提升我們的生活品質或工作效率。
這是我曾有過的經驗,不知你是否也熟悉?
因此,與其說是完整的悖論,或許更適合稱之為——完整的「詛咒」。
我們舉一個典型且普遍的例子來感受一下,「追求完整」導致你花時間在鎖事上,真的比想像中的更容易發生。
「用 Notion 做閱讀書籍的管理」這個需求場景,絕對是一搜尋就一大堆。在 YouTube 上搜尋「Notion 閱讀筆記」之類的關鍵字,能找到的相關影片更是多不勝數。
甚至不用搜尋,Notion 官方已經幫你收集了大量用戶提供的模板,供你直接套用:
並不是說,用 Notion 整理自己的閱讀清單、筆記有什麼問題。這絕對是好事。
但如果你也有過類似的嘗試——用 Notion database 管理自己的閱讀清單。應該很容易發現,這個過程一不小心就可能會「走火入魔」。
一開始是書名、作者這類基本欄位,但隨著這個「系統」愈來感完整,你會很想要為它加入更多「點綴式」的資訊欄位,包括一些與排版設計有關的元素,比如封面、icon、顏色等等。
簡言之,基於一種「完整記錄」的偏好,你可能會增加一些實際上沒多少用處,但會「讓人感覺很好、很完整」的欄位。
這些欄位在 Notion 閱讀管理資料庫中經常出現,但它們更多是為了追求「完整性」,而非實際的閱讀效益而設計,比如:
這些欄位雖然可以讓你的閱讀資料庫看起來很全面,但實際上,對提升閱讀體驗或增進理解往往沒有太大幫助(甚至可以說毫無關係)。反而讓你花費更多時間在製作、管理這些數據,而非真正的閱讀思考。
讀書本來沒那麼複雜,不外乎明白一些事,然後有所行動。但工具可能讓它變得複雜,而「系統」更是如此。
工具可能讓閱讀變得複雜,而系統更是如此
最壞的情況是,我們被這些強大系統所吞噬,淪為「為系統工作」的人,而非利用系統的人。
當然,上述只是最壞情況,而且情況總是在變動,不能一概而論。
但話說回來,那些知名的生產力達人,他們好像並沒有這樣的困擾?
確實,因為這些人是「凌駕於系統之上」的人。
下一段我們要討論,這類人和一般人,究竟有什麼不同。
下面舉例的瓦基和電腦玩物,都是我認為「健康」的生產力達人。以他們為例,有助於我們理解「生產力達人」的特質,以及為什麼——你可能無法成為他們。
在「你是哪一種人?」中,我使用了「生產力狂魔」一詞來形容那些擁有強大生產力且能持續貫徹的人:
你得承認,有些人就是紀律良好且生產力過人,一樣時間他們能產出數倍成果,且品質有保證。比如閱讀前哨站站長「瓦基」,在台積電當工程師時,他能利用下班時間完成大量書籍閱讀與心得輸出,週而復始,筆耕不綴,進而實踐了自己的志業。
這裡改用「生產力達人」這個詞彙,更加中性。
上述的瓦基,或我們熟悉的電腦玩物站長,他們都擁有驚人的生產力,更可貴的是——持續產出的能力。
我相信,他們不太會受困於系統,因為:
這樣的人有著很強的自我調節能力,能持續地調整自己的行為,讓自己不斷進步——近乎本能。
相比於一般人,他們擁有「更遠大的目標」,他們是「利用系統」的人。
不過,我同時也相信,這樣的人,只是少數。
沒錯,我真心認為,這樣的人只是少數,而且只會是少數。
生產力達人當然不是天生的,但必定是經過不斷自我追求、精進而煉成。(不然這世界也太不公平😂)
而我認為,這種不斷追求生產力的動力與本能——很大程度是天生的。
這是一種非常強烈的價值觀偏好。
對我們這些普通人來說,要達到生產力達人的輸出量,肯定相當困難,但並非不可能。然而,如果要「持續」為之,沒有一定的動力與本能,恐怕還是不行的。
大部分人對生產力的渴求,並沒有那麼強烈。
這就是為什麼我們需要更實用、更適合自己的管理方法,而非盲目追求那些「強大」的系統。
畢竟,最有效的生產力提升方法,應該是能幫助我們專注於真正重要的事情,而不是讓我們陷入無止境的系統維護、資訊收集和管理壓力中。
絕對有人需要大系統,而且能利用它們做的很好。只不過,大部分人如果也想按照相同的方式來提升生產力,基於上述三大理由,恐怕將適得其反。
因此,我相信,對大多數人來說,一個更現實且可行的方法是「局部實現」。
局部實現,是一種心法或價值觀,說穿了沒什麼,就是「緊扣著需求尋找並選定方法,需要多少才投入多少」。
更具體地說,它有兩大核心內涵。
如果當前方法已能做出 80 分成果,一定程度滿足需求了,就不要再繼續追尋「更有效方法」——宜適可而止。
必須強調,這裡不是指結果一律只追求 80 分就好。
而是,如果結果已經有 80 分,但你想要進一步提升,建議不要試圖從「手段、方法」上去改進。更不需要去建立一個複雜的系統。
無論什麼系統,對於已有 80 分的產出結果,很可能已不構成影響。再從方法上改進,企圖更進一步提升產出品質,往往只是緣木求魚。
「系統」二字最讓我害怕的,就是它隱約暗示著系統的各個元件之間,存在著巧妙的「關聯」,所以它們能共同組合成一個「工作流」。
我的建議是:謹慎關聯。
可以的話,不要關聯,因為所有關聯都是有「代價」的。
我還是要用 Notion 舉例,因為我比較熟。Notion 的資料庫關聯,無疑是強大的功能,但真的不宜濫用。
何謂濫用?那就是本來一個 db 就能完成的需求,因為可以「關聯」,所以你決定把它拆成兩個甚至更多 db 來做,因為這樣更條理分明,也更有「組合後的整體美感」——我就幹過這種事!而且還不止一次。
最後我往往還是把 db 砍到剩 1 個。因為實在太煩了。
相關內容:
為什麼總是忍不住想要關聯呢?
我想你也看出來了,對「關聯」的渴望,正是「追求完整」的本能所致——我們很喜歡事物看起來是一個整體,更甚於只是一堆零散的部分。
所以必須把它們關聯起來。
追求完整往往帶來不必要的複雜,而「關聯」正是複雜小惡魔們誕生的溫床😈
在我看來,對付複雜的最佳思維就是:各個擊破就好——不關聯,不整體。方法簡單、有效,能「恰如其分」解決問題,就是足夠好的方法。
當然,這會破壞一個大系統的「美感與整體感」,使之不再完整。
對,我就是要破懷這樣的美感。
因為這種美感,正是讓我們「感覺良好」的來源,卻也是陷入困境的開端。
接下來以我自己為例,再聊聊更多「局部實現」的具體實作。
我目前的主力筆記軟體包括了 Notion 與 Logseq。說真的,這兩款軟體,都能創造出非常複雜的系統——它們在功能上皆強大而靈活。
但無論 Notion 還是 Logseq,「局部實現」策略都是可行的。
這意味著你可以在這些平台上建立特定的、專注於某一個或幾個目標的小流程,而不是一個涵蓋所有事情的龐大系統。
Notion 中,我目前有在用的,只有一個 database。用來管理我的寫作(主要)與個人 side project。
Notion 寫作管理看板
Notion 的簡易看板對我來說完全夠用,我不需要 Trello 這類更專業的專案管理軟體。
附帶一提,如果你有更加專業的「看板」需求,尤其是工作上的專案管理,千萬不要用 Notion,會很痛苦。
我怎麼知道?望向工作上的 Notion 看板……🥲
關於這個看板與 Notion 寫作,可參考:
沒了,我主要就用 Notion 管理與進行「寫作」這一件事而已。
我以前也把很多事塞進 Notion,但我發現它能勝任的部分不多。很多事它都能做,但做的不是特別好——尤其是學習。
現在這樣的 Notion 用法,讓我非常喜歡且滿意。而設計上述看板需花多久的時間呢?應該不到半小時。
使用看板的過程中,我嘗試過上述的「把一個資料庫拆成兩個,再互相關聯」,但始終沒有成功。(前後還試了兩次!😱)
用了一個月左右,我發現這樣簡單的看板,效果竟超乎想像的好——我的「發文紀律」明顯提升了!
既然如此,這方法已經足夠,依「局部實現」思維,我也該適可而止了。
前面提到,Notion 對我的學習效果不是很好。
但我做筆記,最大的訴求就是學習,尤其是學習程式。所以我後來改用了 Logseq。
把學習用筆記軟體從 Notion 換成 Logseq 的具體緣由,我會再另篇討論。
而我現在用 Logseq 的學習方式,也很簡單:
容我引用第二篇中的一段:
程式開發時勤做筆記,把筆記切成一張張閃卡(flashcard),每天複習一些卡片。不用多,10-20 張就夠了
對我而言,是不錯的學習方式(我以前還很不以為然呢 XD)
經實測,它有助於我於更靈活運用這些開發知識。簡言之,我比以前「更容易想起」實作上要注意的細節,與其中的原理——因為我踩過這些坑、做了筆記,然後用閃卡複習
Logseq 對我當然還有別的重要使用方式,比如記錄我的晚餐與當次用餐心得!但主要的大目標,就只有學習。
我從來不覺得自己正在使用什麼「系統」。
對我來說,筆記就只是筆記而已。
上述這些方法,說真的,都滿「平淡」的。我也不知道怎麼樣才能把它們寫得更 fancy 一點——可能是因為我沒有「系統」可言。
採用「局部實現」思維,意味著你可以選擇最適合當前需求的工具和方法,從而使工具真正為你所用。因為這些方法都比較「小」,要用不用,皆操之在己。
雖然系統提倡者們都不約而同地宣稱:精緻而完善的系統,能使人找回生活的主導權!(此時文案旁通常還要搭配一則漂亮的圖表,讓你感受「系統的整體之美」)
但我不這麼看。
我認為,相比於大系統,使用小方法的局部實現,更容易讓我專注於事物的本質,而非被系統所束縛。
回想一下,學習所謂「人生管理系統」的過程中,你是否曾有過這樣的自我懷疑:
我覺得這東西真的很棒!但是我不夠認真,所以還用不好。
相信我——
那很可能不是你的問題。
我誠摯認為,不值得再花費時間精力去維護那些「看起來很棒」,但實際效果極其有限的功能模組。比如上述「不必要的欄位」。
人生苦短,你完全可以把精力集中在真正重要的事情上,直接提高結果的品質。
我本來想要使用「擁抱」這個詞,但這詞常常被濫用在一些心靈雞湯中,情感上也不是非常協調。
因為殘缺或遺憾,都不是我們喜歡的東西(我們喜歡完整),要「擁抱」它們,本身就已違反人性——太虛假。
而「接受」一詞則更貼切些,帶有幾分不捨,與幾分坦然,很適合。
我相信,我們面對「大系統」的心態,也是如此。
前面講的我自己的局部實現案例,可以說,沒什麼特別之處,任何人都能做到。
不過連我自己在這個過程中,都還是很容易受到「完整性」的誘惑。
我可是寫了這篇七千字長文,不斷提醒你要小心「完整性誘惑」的作者,但我自己還是會受到誘惑。只能說,本能的力量,真的很強大。
當然我並非「系統愛好者」,所以不太可能被誘惑到去建立一個完整的系統。但不得不承認,我也常常很想要去「優化方法」。
在此,我想要再次告訴你,也提醒我自己:方法真的沒那麼重要。
大系統、工作流,真正讓人著迷的,往往是這些系統「本身」,而非其產出的結果。
人生管理系統就像一座壯麗的城堡,它的外觀、內部設計、各個房間的佈置,無不讓人陶醉其中。但這座城堡的主人——也就是你——卻未必快樂。
就像畫框與畫的關係,我們被精緻的畫框所吸引,甚至忘記了畫框的存在目的,是為了襯托畫作。
當然,話說回來,沒有方法確實不妥,但方法的改進,應適可而止。
上述看板就是很好的例子,它實在很簡單,簡單到不值一提,但對我寫作紀律的提升,大概從 0 分提高到了 60 分。
我還要繼續改進這個看板嗎?或許可以,或許不必。
寫文章並不是我的工作,如果能擁有 60 分的寫作紀律,且能夠持續保持,那我已相當自豪。這意味著我的「寫作畫框」已經足夠好了。
我認為,保持方法的殘缺與遺憾,是一種「高明的妥協」。
小方法比大系統更容易掌握,更可能讓我們專注於「畫作」本身。
接受殘缺並不意味著放棄進步,而是在進步的過程中,試著學會區分:哪些只是手段,而哪些才是本質。
手段可以殘缺。唯有本質的完整,才值得我們一心追求——至死方休。
擺脫對系統的「依賴」後,甚至會發現,我們的生產力其實已經有了顯著的提升。這樣的提升,來自於我們對「自身局限」的理解和接受。
這種心態的轉變,不僅提高了生產力,更重要的是:減少了不必要的壓力和焦慮。
]]>前陣子,我得知了「Not By AI」這個徽章的存在。
它提倡為你的創作(以下都指文章)加上這個標籤,聲明是由人類所寫。
我們先看一下這個徽章的創立使命(中文為 Google 翻譯):
使命
「Not By AI」徽章的創建是為了鼓勵更多的人製作原創內容並幫助用戶識別人類生成的內容。最終目標是確保人類繼續進步。
專家估計,到 2025 年,90% 的線上內容將由人工智慧產生。隨著人工智慧產生內容的激增,值得注意的是人工智慧是根據人類生成的內容進行訓練的。如果人類僅僅依靠人工智慧來產生前進的內容,那麼人工智慧產生的任何新內容都可能只是過去的內容的回收。這可能對人類進步構成重大障礙。只有限制對人工智慧的依賴並繼續創造原創內容才能推動我們作為一個物種向前發展。
我乍一看覺得有點心動,而且作為一個長期創作的部落格作者,像「Not By AI」這種「自我主張型」的標籤,僅是在本能情感上,就有股莫名的吸引力。
但同時又覺得,好像哪裡怪怪的。
這篇文章與其說是討論「Not By AI」徽章的必要性,不如說是討論背後透露出的更大議題:「在人工智慧時代,我們該如何看待自己與 AI 的關係?」。
而「Not By AI」則是一個支點,讓我們可以從中展開討論。
換言之,它提供了一個具體的情境與切入視角,讓我們可以挖掘並獲得自己的思考、喜好、想法,進而發現自己在這個議題上的相關立場。
而本文表述我的立場。
雖然我在情感上很想要直接使用這個徽章,但我的理性與直覺卻告訴我:先等等。
這個時候該怎麼辦?以前,你可以慢慢獨立思考,把一些可能的思考素材與蛛絲馬跡寫下來。但現在你有更好的夥伴了!——ChatGPT。
這個議題,我們直接問問 AI 吧!看看「它」怎麼說(這是我自己訂製的 GPT,回答的口吻比較機車一點,請多多擔待🙏):
這個「Not By AI」徽章,看起來像是一種懷舊情懷和對 AI 的不信任感結合的產物。實話實說,這種做法有點自欺欺人。當然,表明你的內容是純手工、未經 AI 處理的,對某些讀者來說可能有點吸引力,就像有機食品對某些消費者來說更有吸引力一樣。但別自欺了,這不過是一種行銷手段,實際上對內容的品質沒有任何保證。
使命中提到限制對 AI 的依賴並繼續創造原創內容是推動人類進步的關鍵。這種觀點忽略了技術與人類創意的互補性。僅僅通過拒絕或限制 AI 的使用,無法推動人類在藝術、科學或任何其他領域的真正進步。相反,我們應該學會如何更好地利用 AI 來擴展我們的創作能力和視野。
我的建議?專注於創作高品質、有價值的內容。如果你的內容夠好,無論是不是 AI 生成的,讀者都會欣賞。別太在意這些浮華的徽章。真正的價值在於你的內容和你作為開發者的專業知識,而不是某個標籤。讓你的創作自己說話吧!
我覺得它講得很好,讓我豁然開朗。
這也是為什麼,在面對那些我認為重要的課題時,我一定會先跟 AI 討論一番——它帶給我的啟發,往往超乎想像。
參考上述 AI 的回答,我大致得出了自己的結論。
同時也明白,為什麼一開始,我雖然受到「Not By AI」徽章在情感上的吸引,但心裡卻始終覺得不自然、不自在。
我的看法如下。
為了闡述好這個命題,我得從文章「作者」與「讀者」兩個角度分別討論。讀者角度,再依照不同的「閱讀目的」,說明為何大部分時候,讀者並不那麼在乎,文章是人還是 AI 寫的。
身為作者,我非常希望自己的文章能夠做到邏輯清晰、文筆生動、內容對讀者有價值,最好還很有個人特色!
這是為什麼我看到「Not By AI」的號召時,會覺得心動:「作為茫茫人海中的一員,我想要被看見、被記得!」
但,這也只是我從作者角度的個人期望而已。
更多時候,我是一個讀者。
當我是讀者,對於眼前這篇文章是人還是 AI 寫的,依不同的閱讀目的,我會有不同的在意程度。
看這類文章,我通常是因為「喜歡或認同該作者」,比如《斑馬通信》。此時我的確會在意,文章是出自人類之手。
這或許是「Not By AI」徽章對身為讀者的我而言,少數的價值。
但若再深挖一尺,會進一步發現:與其說我在意「文章是人類寫的」這件事,還不如說我真正在意的是「文章是『這個人』寫的」。
換句話說,如果文章是別人代筆,即使代筆的是人類,那也是不行的!
前述文章只佔一小部分,我看的文章有 9 成以上,都屬於「實用性」文章。
這類文章,我更在乎寫得好不好、對我是否有幫助。作者是誰,則相對次要。
當然,能寫出好文章的作者,絕對值得被記住、讚賞、追蹤。我也希望能夠看到更多好文章。
好的作者,我會記得,慢慢的,他們的文章就變成了第一類文章。
但,這個作者,就非得是個「人」嗎?就算是 AI 寫的,只要寫得好,那為什麼我不追蹤、不多看呢?只因為文章不是出自人類之手?
這個理由,略嫌薄弱。
綜上所述,作為一個讀者,我會記得那些,帶給我心情與智識上滿足感的作者們——但不是只有人類才能辦到。
AI 也常常帶給我智識上的滿足感、讓我感到欣慰。
如果「解決」二字太沉重,我們可以解讀成「改善」。
老實說,我覺得「Not By AI」徽章能夠改善的,大概只有「身為作者的焦慮感」——我們真的很怕被 AI 取代!
然而,為文章加上「Not By AI」標籤,對讀者而言,究竟可以改善什麼問題呢?
顯然「Not By AI」徽章對改善文章「真實性」部分,幫不上忙。
因為不管是 AI 還是人類創造的內容,都有錯誤或造假的可能。現有的內容農場,不就是源自人類的惡意與對內容的不尊重?
甚至,這種標籤還可能會給讀者一種虛假的安全感,使我們降低身為一個讀者,對內容品質、真實的思考敏感度——人類寫的應該更可靠的,對吧?
想當然爾,「Not By AI」不能保證內容的品質或創新,畢竟這個徽章本來就不是拿來聲明與內容品質相關的事項。
但是,如前所述,身為讀者,我更在乎的是內容本身。好的文章應該是具有深度、引人思考,或者提供新的視角,而這些特性與其是否由 AI 創作無關。
看完一篇文章,我看到了作者放的「Not By AI」徽章,我依舊不禁會想:「所以呢?這個『Not By AI』聲明,對我有實質幫助嗎?」
我們當然可以說,上述這些問題,本來就不是「Not By AI」徽章的重點。畢竟它只是一個「聲明」,而非「解決方案」。
這個徽章的重點,就是它本身——強調由「人類」所創作。
我可以理解,但仍不禁好奇,這種強調,對讀者而言,真的有那麼重要嗎?讀者真的會因為看到「Not By AI」徽章,而更願意閱讀這篇文章嗎?
退萬步言,假設讀者真的更在乎「人類」創作的內容。可是,讀者要如何驗證,這個徽章「說的是真的」呢?
比如,我可以為文章標示「Not By AI」,卻透過 AI 幫我產生大部分內容,只要修改到讓讀者看不出來就好。
總的來說,我覺得「Not By AI」唯一的價值就是……讓作者感受到一絲心安與自豪。
寫出一篇文章,絕對值得自豪。但我認為這種自豪感,應該來自於對內容本身的投入,而非只因為「這是我親手寫的喔!」。
所以,儘管本站的任一篇文章,都能夠符合「The Not By AI 90% Rule」(9 成以上的內容由人類創作),我依舊不會放上「Not By AI」徽章。
我認為一個作者在創作與呈現作品時,強調「人類 vs AI」的二分法,對讀者而言,並沒有太大價值。
在我看來,文章內容真正的二分法,只有「好看 vs 不好看」而已。
如果真的想為自己的文章宣誓點什麼,除了「Not By AI」,我覺得也不妨考慮「本文寫了 4 小時」、「用生命創作」等徽章——我一定對你刮目相看👀
我倒是不反對作者為自己的文章加上「Not By AI」標籤,甚至還會因此而多看兩眼。只是我知道,那就只是一個標籤而已。
作為一個讀者,我真正在乎的,只有作品本身帶給我的連結與共鳴,無論是人類創作,還是 AI 生成。
當然,有些事物是人類專屬的,比如某篇食記與對某道料理的感受。如果你告訴我這是「AI 親自去試吃的心得」,那我會感到十分無言🐸
但問題的關鍵始終不在於「誰寫的」,而在於「寫得怎麼樣」。
一篇文章能否觸動人心、提供有價值的見解,或帶來新的洞察,永遠是最重要的。
而一個簡單的「Not By AI」標籤,永遠無法帶給你這些答案或線索。
我並非一個 AI 樂觀主義者。
真要說的話,我應該屬於悲觀的那一方,身為一個軟體工程師,我時常擔憂,未來可能被 AI 取代。
但是,就像我在「AI 輔助寫 Code」中說的:
不同的時代有不同的努力方式,作為一個 AI 時代的開發者,我們要學習與 AI 共舞。
活在這個時代,我所能選擇最好的努力方式,就是用心和 AI 協作。
事實上,本文的創作過程,就是我和 ChatGPT 不斷對話的過程。
我花費了大量時間與它交談、討論,甚至讓它挑戰我的觀點。如果沒有 AI,這篇文章會比你現在看到的更平庸且無聊得多——連我自己都不想看。
所以,我無法發自內心認為,為自己的文章加上「Not By AI」標籤,是一件值得自豪的事。(但我覺得這是一件「可愛」的事,所以我不反對你這麼做)
畢竟,一篇文章的好壞,最終還是要由讀者來評價。
]]>這是 Django Tutorial 系列連載的第 2 篇。
搭配學習的範例程式碼,可參考 GitHub 專案:Django-Tutorial。更多 Django 教學,請見「Django 文章總覽」。
- Django ORM:一對一、一對多外鍵教學(上)前言與關聯設定
- Django ORM:一對一、一對多外鍵教學(中)反向關聯
上一篇我們介紹完 Django ORM 的關聯設定,接下來本應該要進入查詢部分。不過,由於「反向關聯」在 ORM 查詢中扮演著十分重要的角色。
所以我決定專門寫這個(中)篇,好好介紹 Django ORM 中的「反向關聯」。
本文可以視為是第一篇的「補充」——對「反向關聯屬性」多加著墨。
不用說,這篇文章需要你先充分理解第一篇的內容,再行閱讀。尤其是該文中的這三個部分:
請務必熟悉。
我們可以從「正向關聯」來比較反向關聯,會更好理解。
在 Django ORM 中,正向關聯意指那些由我們明示定義的欄位,比如ForeignKey
、OneToOneField
等欄位。
1 | class Comment(models.Model): |
正向關聯屬性的最大特色是,它對應著資料庫 table 中的特定外鍵欄位。換句話說,它和 model 中的其它欄位一樣,都是「實體」的。
而反向關聯是「虛擬」的。
所謂的「虛擬」,是指反向關聯並不直接對應於資料庫中的一個實際欄位。它只存在於 Django ORM 層面上,作為模型關係的一部分。
這種設計使得我們可以在不增加額外資料庫欄位的情況下,輕鬆地管理和查詢模型間的關係與對應的實例。
換言之,即使沒有反向關聯,我們還是可以透過標準 ORM 語法,查詢想要的資料——只是比較麻煩!
反向關聯在「快速獲得關聯實例」這個需求場景,大大突顯了 ORM 查詢相對於原生 SQL 查詢的便利性。也讓你多增加了一個使用 ORM 的理由。
根據關聯的類型,反向關聯的「屬性值」會有所不同。
在一對一關係中,反向關聯屬性返回的是一個的關聯模型實例。在一對多關係中,返回的是一個QuerySet
(嚴格來說其實是關係管理器——RelatedManager
),代表所有相關聯的模型實例集合。
這種彈性使得反向關聯成為 Django ORM 中一個極其強大且靈活的存在。
好,講完了定義,我們趕緊來看,反向關聯屬性在實務上究竟是如何被使用。以及使用上的注意事項。
不過在此之前,我必須對原來範例程式碼中的 model 結構,做出一些調整。
我要變更其中的「一對一」model 關係,因為原來的設計有兩個比較大的缺陷。我們先看看舊的程式碼:
1 | class Post(models.Model): |
第一個缺陷,是 Post 和 Title 的一對一關係,有違現實。
把 Title 變成一個關聯物件,是非常少見的。這不僅會讓讀者「難以想像」,無法感同身受。後續使用這模型來實作 API 時,這個設計不良問題會更加突顯。
所以,還是改成比較符合現實的版本——但又不能脫離「部落格文章關係模型」這個大框架,如何再想一個更「有感」的一對一關係,不禁又讓我苦思了一段時間。
和 ChatGPT 討論很多可能,始終找不到非常適合的例子。最後我決定這樣:把 Title 改為 Post 的一個欄位,這是比較尋常且合理的做法。
一對一部分,改用「Subtitle」模型替代。這樣一來,Post 就有了一個「可選」的副標題。
沒錯,為了突顯「反向關聯不存在」這個議題,Subtitle 必須是「可選」的。也就是不一定每篇文章都要有關聯的 Subtitle。
原來的「Post - Title」關聯,就不是「可選」的——即兩者都一定要有。不能呈現一對一關係不存在時的情境,這是舊程式碼第二個缺陷。
我們來到更新後的業務邏輯中。想像每一篇部落格文章,都一定會有標題,所以標題現在只是 Post 模型的一個欄位而已。
但是,如果你願意,你可以為這篇文章加上「副標題」,也就是關聯 Subtitle。這完全是「可選」的,加不加隨你。
如果你用過寫作平台 Medium,就知道它的文章正是「副標題可選」的設定。
雖然新設計還是不盡理想,因為它和 Title 一樣,其實也可以只是 Post 的一個欄位就好。但無論如何,Subtitle 主要是為了讓本文可以舉例,它的存在價值與合理性,我們暫不深究。
這裡我們只需知曉一件事:Post 可能有關聯的 Subtitle,也可能沒有。且兩者是一對一關係。
如上述修正後,新的models.py
內容如下:
1 | # 文章 |
不知道你覺得如何?我感覺更加井然有序了!很適合用來作為教學文章解說的範例。
接下來,我們就用上述的程式碼實例,來一一解說反向關聯。
請看 Post 與 Subtitle 這兩個模型,它們是典型的一對一關係。
Subtitle 實例有著「正向關聯」屬性——post
,關聯某個 Post 模型實例。
Post 實例有著「反向關聯」屬性——subtitle
,來自related_name='subtitle'
。不過,即使你沒有特別定義related_name
,這裡的「預設」反向關聯屬性名稱,也是subtitle
(即關聯模型名稱的「小寫」型態)。
假設 Post 模型的實例為post_1
。
想要查詢post_1
所關聯的 Subtile 實例,有兩種方法,分別是一般查詢,與反向關聯查詢。
一般查詢方式如下:
1 | subtitle = Subtitle.objects.filter(post=post_1) |
如前所述,即使沒有反向關聯,我們一樣可以得到我們想查詢的資料。
但透過反向關聯屬性,則會更加方便:
1 | subtitle = post_1.subtitle |
一對一反向關聯有一個重點,那就是「關聯物件不存在時的錯誤處理」。
兩個「可以」建立關聯的模型物件,並非「一定要」建立關聯。因此,post_1
的反向關聯屬性,不一定總是對應著一個 Subtitle 實例——有可能關聯不存在。
當關聯不存在時,訪問post_1.subtitle
會引發RelatedObjectDoesNotExist
例外。
考慮到「關聯不存在」的可能,我們的程式常常會這樣寫:
1 | try: |
其中ObjectDoesNotExist
是RelatedObjectDoesNotExist
的父類別,因為你無法直接引用RelatedObjectDoesNotExist
。
這種寫法雖然不算優雅,但它明確地表達了你的意圖。
一對多關係,我們要把目光放到 Post 與 Comment 這兩個模型。
其中 Post 是「一方」,而 Comment 則是「多方」。
一對多關係中,ForeignKey
欄位肯定是實作在「多方」,所以上述程式碼,定義這個欄位的模型是 Comment:
1 | class Comment(models.Model): |
一樣,我們假設上述模型實例分別為post_1
、comment_1
。
其中comment_1
有正向關聯屬性post
(即 ForeignKey)。而post_1
有「一對多」反向關聯屬性comments
(從related_name='comments'
獲得)——注意這個複數。
同理,想獲得post_1
所有關聯 Comment 實例,有一般查詢和透過反向關聯查詢。
一般查詢:
1 | comments = Comment.objects.filter(post=post_1) |
反向關聯查詢:
1 | comments = post_1.comments.all() |
訪問一對一反向關聯屬性,會得到兩種可能:
RelatedObjectDoesNotExist
例外。而一對多的反向關聯,則只有一種可能:關係管理器物件(RelatedManager
)。
RelatedManager
和Manager
(即.objects
的屬性值)類似,都是獲取 QuerySet 的「入口」。所以上述的post_1.comments.all()
需要最後的all()
方法,透過「關係管理器」再獲取「由關聯實例組成的 QuerySet」。
換句話說,光是呼叫post_1.comments
本身,你只會得到「關係管理器」物件。記住這點,這將影響你對於「關聯不存在」時的處理。
如果post_1
有可能還沒有任何 Comment 關聯實例。那我們應該怎麼樣在程式中考慮進去呢?
我們第一個想到的可能是這樣:
1 | comments = post_1.comments.all() |
上面這樣寫確實是可以的,當 QuerySet 為空,會被視為 falsy。不過更好、更 Django 的寫法則是:
1 | if post_1.comments.exists(): |
然後千萬不要寫:
1 | comments = post_1.comments |
因為此時的comments
變數內容是一個關係管理器物件,它一定會被視為 truthy。意即這個判斷條件永遠會成立。
如果你想以「反向關聯是否存在」作為查詢的條件,比如我想查詢「沒有留言的文章」有哪些,要怎麼做呢?答案是——isnull
。
1 | posts_without_comments = Post.object.filter(comments__isnull=True) |
注意其中的雙底線,這是典型的 ORM 查詢條件使用方式。
同理,我想要查詢「有副標題(subtitle)的文章」則是:
1 | posts_with_subtitle = Post.object.filter(subtitle__isnull=False) |
可以看到,上面兩個例子,都用透過 Post 的「反向關聯屬性」來進行過濾查詢——就像是一個普通的欄位一樣。
Django ORM 中的反向關聯,是 ORM 皇冠上的一顆明珠。
為什麼說 ORM 相對於原生 SQL 查詢更加優雅?從上述程式碼範例中,反向關聯無疑是最好的答案——像post_1.subtitle
和post_1.comments.exists()
這樣的語句,不僅簡潔,且非常可讀。
善用反向關聯,你將成為更加道地、成熟的 Django 開發者。
我很少為自己的文章寫「後記」,不過仔細想想,為這篇好不容易才產出的文章寫點後記,也是值得的。
平常開發使用 Django ORM,其實也沒有想太多,只要「熟悉、習慣」就好。有時候用久了,對於常見的元素,甚至就不怎麼思考了。
但是!寫文章就不同了。為了向讀者解釋其中的細節,思考上不能草草帶過,至少要了解文中提到的部分,大概是怎麼一回事。
為了完成這篇文章,我實際上做了這些事:
總之,寫一篇文章真是不容易呀!希望它對你有所幫助。
]]>這是《人生 4 千個禮拜》筆記的第 1 篇,你可以把它當作是一則重點整理,加上大量我個人的經驗與想法。
我認為,如果你不是書中描寫的這類人——在某些方面、某種程度上對追求生產力有過一定追求的人。那看本書很可能只覺得它像是一連串的高級雞湯文。
比如「接受人生有限」這樣的話,你可能早就聽過無數次,甚至已經麻木了。
然而,作者在書中把這類人在「生產力追求路上」所伴隨的種種「荒謬」與「弔詭」之處,精準地勾勒出來,令人拍案叫絕。
或許你不是這樣的人,沒關係,因為我(某種程度)是。
所以我會試著把這樣的努力與隨之而來的困境,盡可能展現出來,好比是種自嘲。就像〈使用 Notion 滿 3 年,為何我仍「不推薦」用它來管理你的人生?〉中的我。
- 《人生 4 千個禮拜》筆記(一)病態的生產力
- 《人生 4 千個禮拜》筆記(二)抗拒「重要性中等」的誘惑
和「系列:Python 功力提升的樂趣」一樣,我們只講重點。
再次提醒,本文中有相當部分屬於我自己的看法,而非單純書中觀點的整理。我會適度明示地區分這兩者,還請讀者留意。
本文整理書中的前言到第 2 章。
本書的確又是一本談善用時間的書,但宗旨是我們所知的時間管理大慘敗,我們需要停止假裝那些方法有用。
作者 Oliver Burkeman 是《衛報》的專欄作家,寫的正是有關「如何提高生產力」、「時間管理」等主題。
只不過他後來發現:這些所謂的「提高」,並不像想像中的那般美好。因此才有了本書的誕生。
換言之,本書可說是他對自己與「提高生產力」這個議題的重新觀察、自我反省。
當然,也不是每一個人張口閉口都是生產力。但不可諱言地,現代人對生產力的重視與追求,確實與日俱增。
以往書店的書架上,總有一櫃是滿滿的「自我提升」、「成功學」。現在當然還是有,但就我的觀察,它們更多被一大票「工具方法論」的書所取代了。比如電腦玩物站長的《防彈筆記法》。
這是一個從抽象到具體的演化過程,也不難理解其中的緣由。
畢竟,無論是成功學還是自我提升,都是高度的抽象概念,很難有具體的方法可以直接照著做,而結果又很難驗證,它們都太空泛了!
這些內容已經無法滿足現代人,我們想要更加「看得到、摸得著」的東西,最好能立刻看到這些方法、工具的效果——於是我們有了「生產力」。
從抽象的「成功、自我提升」到具體的「提高生產力、時間管理」。
最近幾年,我們被活出百分之百全效生活的建議給淹沒,市面上的書籍鼓勵我們做到《極度生產力》,《一週工作 4 小時》,還要《更聰明、更快、更好》。
這些書暗示著:我們應該不斷地改進自己,讓自己變得更有效率,更有生產力。
五花八門的網站要我們化身為「生活駭客」,好讓完成日常事務的時間能少個幾秒鐘(「生活駭客」一詞本身就是古怪的建議,彷彿你的人生頂多稱得上某種老是出錯的裝置,需要加以調整,不再處於未達最佳運轉的狀態)。
你對上述現象可能不陌生,又或許你並不是這樣的人,甚至感到嗤之以鼻。
但這個世界上,確實有這麼一部分人(包括我),時常在乎「怎麼做會比較快?還能再快嗎?」等議題。
雖然我自認為不是一個追求極致生產力的人——遠遠不是。
相關文章:11,我絕不當資訊的聚合者
然而,我發現,追求生產力並不是只有「實踐」這個面向而已。有時候光是「空想」,就是一種貪婪。
而這樣的貪婪會帶來所謂的「FOMO」(這裡指對資訊的錯失恐懼)與明顯的焦慮感。從這個角度看,我依舊深陷其中。所以我需要這本書。
這部分不是書中內容,而是我的看法。
生產力其實也不是什麼新概念,就算是幾千年前的人,也會追求怎麼「怎麼做事更快、更好」——只不過他們可能不是用「生產力」這個詞而已。
那為何現在「生產力」一詞已經成為顯學?
仔細想想,「生產力」一詞大概是這近 10 年才比較頻繁被使用、強調。這其中的差別究竟是什麼?
我認為關鍵的差別是「工具」——工具進化了。
工具,尤其是電腦、網路時代的核心工具——軟體,和以往相比,已經大大地進化了。我們使用各式各樣軟體,來完成工作上的簡報、提案、包括我的程式碼。
這些軟體讓我們更有效率地完成任務,並且增加了我們的工作能力。當然,這也意味著我們需要學習和掌握更多的軟體技能。
說穿了,我們其實就是把「使用這些軟體的產出成果」視為生產力。
而所謂「提高生產力」,某種程度就是指:提高我們對這些軟體的使用技巧、方法。
這些軟體對現代人而言太過尋常,以致「生產力」一詞也變得如此普及、朗朗上口。
這樣的論述可能不夠全面,但我覺得在大方向上確實如此,你說呢?
而在各式工具軟體中,哪一種更能成為生產力的代名詞?
如果是 2000 到 2010 年,所謂的生產力軟體,大概是指微軟 Office 系列——忘掉那個美好的上古時代吧!
2010 年代至今,毫無疑問,生產力軟體的代名詞,就是「筆記軟體」。
當然還有各式各樣讓你工作、生活更加「方便」的軟體,如:
但筆記軟體絕對是其中的主角。
筆記軟體是現代生產力舞台的主角,這點你可能很難否認。
比如時間管理,你聽過 GTD。目標管理,你聽過 OKR。甚至還有關於個人知識管理的 PARA Method、卡片盒筆記法。太多太多了。
凡此種種,當你決定要實踐的時候,你幾乎都必須選擇一套筆記軟體來使用。(當然也是可以用紙本啦!不過,呃…你懂的)
難怪現代生產力 KOL 們,多離不開教你怎麼「高效使用」某某筆記軟體。彷彿不好好學習、善用這些工具,就要被時代拋棄。
說得有點遠了,讓我們回到本書。
作者並沒有提及筆記軟體,但他也認為,善用各種技巧、產品,能讓你做事更快、更有效率,當下能帶給你強烈的掌控感——這種感覺真的很好!
問題不在於這些技巧與產品沒用。它們確實有用。你可以完成更多事,趕去開更多會,送孩子去更多課後活動,替你的雇主賺更多錢。
但接下來的故事可能就沒那麼迷人了。
然而矛盾的是,成功後,我們得到的只有感到更忙碌、更焦慮,某方面來講還更空虛。
生產力最大的弔詭在於,當你學習了新技巧,新工具,把以往要花上 2 小時的事,在半小時內俐落地完成之後——你發現新任務以更快的速度進來了!
這描述有點誇張,但也有真實之處。
誇張的部分在於,不是每件都能夠藉由工具、技巧,大幅縮短所需要的時間。有些事——尤其是那些困難的事——就是需要佔用你大量的心力與精神。
能夠大幅縮短的,往往是一些瑣事。當然,我認為,能夠減少做瑣事的時間,絕對是好的、有益的。只要你確保你做的是「必要」的瑣事。
而真實之處在於,我們都知道,工作——長期而言——是做不完的。你能夠做得更快,往往就會做得更多。
美國人類學家愛德華.霍爾(Edward T. Hall)談到現代世界的時間,感覺就像是永遠不會停下的輸送帶,我們一送出完成的工作,就會冒出新工作。
所謂變得「更有生產力」,似乎只是加快輸送帶的運轉速度,而不是讓我們變得比以前更加從容、有餘裕。
這裡的關鍵命題應該是:這些多出來的時間、多完成的工作,對你而言是否有足夠的價值?如果有,那生產力仍是值得追求的。
所以,我倒不認為作者說的「感到更忙碌、更焦慮,某方面來講還更空虛」現象一定會發生——這個描述有點太片面、太戲劇化了。
不過接下來的「效率陷阱」,則是我認為本書必讀的部分。
效率陷阱最糟糕的地方,在於「質」也會受到影響,你愈是努力塞進每一件事,你用在最沒意義的事情上的時間,反而會增多。
換言之,一旦你做某些事情的速度變快、變容易,你很可能會傾向做更多。
這真是一個巨大的諷刺,卻又如此的真實。
有時候,這些額外增加的效率(生產力)就像是超商的「第二件 6 折」零食。平時你知道零食不健康,所以不會多買。
但有了這個折扣,我們往往忍不住,買更多——內心還覺得很划算。
正如前述,這些事如果不是「必須的」,那「提高對此事的生產力」,往往只是讓你花費更多時間,做那些本來就不需要做的事!——就像多吃了一堆零食。
我在〈少則得,多則惑:使用 Notion 時的兩個常見陷阱〉提到的兩種典型情況,就屬於不必要的瑣事——卻能夠消耗你大量心力☺️
難怪文中不禁感嘆:
但如果你讓我選,我寧可沒有這些自由——因為一不小心,就容易沉迷其中。
看到這裡,你可能還是有點不服,覺得會掉入上述的陷阱的人,肯定是因為沒有分清楚事情的輕重緩急。
換句話說,只要把事情分清楚,好好規畫、時時提醒自己,就不會掉入這樣的陷阱。
老實說,在看完本書之前,我也是這麼想的。
但我覺得,我們往往還是低估了「完整性的誘惑」,與隨之而來的時間浪費。
書中這兩段話讓我特別有感:
你要是採用超級有雄心壯志的時間管理系統,也就是那種承諾能搞定整張待辦清單的方法,你最後大概沒機會處理清單上最重要的事項。
上述「超級有雄心壯志的時間管理系統」,不正是所謂的「人生管理系統」?
相關文章:為什麼你「不需要」所謂的人生管理系統
我在〈使用 Notion 滿 3 年,為何我仍「不推薦」用它來管理你的人生?〉中不建議你用 Notion 建立一個大而全的「人生管理系統」,主要理由是它的「維護成本太高」。
維護成本太高,是我認為這類「人生管理系統」的第一個重大隱憂。
而本書作者提出了一個「更加高級」的切入視角。
換句話說,縱使「人生管理系統」的維護成本為 0(這當然不可能,不過這樣的假設有助於你理解,這類系統的「危險」之處),擁有這樣的系統,也未必是全是好事。
你愈是堅定地說服自己,有足夠時間做每一件事,你就愈不會感到有必要質疑,從事某項活動是不是善用你一部分時間的最佳方法。
於是待辦事項愈堆愈多。
仔細想想,這不正是另一種——無所不能的「詛咒」?
]]>Ruff 從去年(2022)6 月正式開源起算,已過去一年半,知道此工具的開發者也愈來愈多。雖然在去年就聽過它,但我卻一直沒有任何行動。
隨著前陣子 v0.1.0 的發布(先別覺得這版本號怎麼乍看像早期測試版本🤣,畢竟前一版可是 v0.0.292),我覺得時機已到,所以進行了一番研究、嘗試,於是有了本文的誕生。
工作上,我們團隊使用的 linter、formatter 分別是常見的 Flake8、isort、yapf。這在以往的文章有多次提及。
隨著 Ruff 的聲量與能見度日漸提高,作為一個 Code Formatting 愛好者,我自然也是躍躍欲試——最好是能夠直接應用到工作開發上!
因為團隊還不大(含我共 3 個後端開發者),所以改用 Ruff 是完全有可能的。
不過經過一番研究與考慮,我還是暫時推遲了工作上對 Ruff 的採用(只是把 formatter 從 yapf 換成 Black),原因會在文末說明。
但是!我還是很推薦,從現在就開始使用 Ruff 作為你「個人開發」的預設 linter 甚至 formatter。而 Ruff 究竟有哪些吸引人之處?且容我娓娓道來。
我在〈Python 開發:pre-commit 設定 Git Hooks 教學〉曾提過:
考慮到「pre-commit」這個主題已經有數篇文章珠玉在前,我下筆前都已拜讀完。本文的論述重心會盡可能與這些文章錯開,或乾脆直接引用,以降低不必要的重複感。
而 Ruff 也是如此,推薦你先讀過這篇〈新世代的 Python Linter - Ruff〉,然後再繼續閱讀本文,將有助於獲得更全面的理解。
接下來對 Ruff 的介紹,會有不同的側重與著墨,與更多的設定細節。
本文主要分為三大部分:
一開始不免俗地先講述我研究與使用 Ruff 的動機與理由,接著介紹 Ruff 的主要功能——linter 與 formatter。這也是它的最大賣點。
而「設定篇」著重的,則是在初步了解 Ruff 之後,如果真要採用它,我們還需要處理好哪些環節,才能在開發中流暢地使用它。
畢竟,開發工具的設定與整合,可算是本站文章的一大類型。☺️
本文的目標讀者有三:
這篇文章是寫給「已經用 Python 開發一段時間」的人,而非 Python 初學者。
不過話說回來,如果初學者一開始就肯使用 Ruff 好好規範自己所寫的 Python code,不正是一個絕佳的開端嗎?😎
什麼是 Ruff,一句話說就是:
An extremely fast Python linter and code formatter, written in Rust.
而我認為採用 Ruff 主要基於下面兩大理由。
下圖來自 Ruff 的 GitHub 首頁,很好表達了為何要採用 Ruff——因為快。
用 Rust 寫的,快,自然不在話下。
而「極致的快」在很多時候會大幅改變你做事的方式——不過這對於大型專案可能才更有感。
對於中、小型專案,我覺得以下第二個理由更加重要。
不止是速度,Ruff 的「整合」能力也不可小覷。
按照官方文件,有了 Ruff 後,以下的工具都不必再安裝了:
想想是不是有一點「pyproject.toml」企圖統一天下,整合眾多工具設定檔的味道呢?
上述這些工具,尤其是最常用的 Flake8、isort、Black,如果現在只需要用一個工具就能實現,而且還更快、更好。
那我們何樂而不為?
本文中所提到的 Ruff 設定具體內容,包括 pyproject、VS Code、pre-commit 部分,都會加到 Django-Tutorial 這個專案中。
它是我「Django Tutorial」系列文章的範例程式碼,恰好也適合作為其它教學文章的實際素材展示。
git clone
本專案後,可以直接git checkout
到02-ruff
分支。這個分支所在的 commit,就是本文的具體設定內容與改動。
你也可以直接在 GitHub 上查看 commit 內容。
前言結束,接下來我們進入正題。首先是對 Ruff 的整體介紹。
Ruff 是一個 Python linter + formatter,雖然大部分人可能只使用它的 linter 部分,因為 formatter 還處於 beta 階段。
Ruff formatter 是一個相對獨立的功能,在開源後才開始建構。你完全可以只用 Ruff 的 linter,而依舊使用 Black 或 yapf 來格式化程式碼。
不過,即使只看 Ruff 的 linter 部分,它也並不是一個「單純的 linter」而已。因為它的實際行為超過了靜態分析(static analysis)。
所以,想要全面了解 Ruff,我們需要具體知曉:它究竟能夠做到哪些事、取代哪些工具。
Ruff 的核心部分就是它的 linter,這點無庸置疑。
而 Flake8 作為 Python 開發中最流行的 linter,自然是 Ruff 首要取代的目標。所以 Ruff 連錯誤代碼都盡可能與 Flake8 一致,也是考慮到遷移的成本。
不僅如此,Ruff 還能取代一眾「帶有 formatter 功能的 linter」,isort 就是其中的代表。而常見的 pyupgrade 也是。
這就有趣了,從這點可知,即使你沒有使用 Ruff 的 formatter 功能,它的 linter 部分還是帶有一定的 format 能力——其實就是 autofix。
這個特性真的很方便,但也帶來了一定的複雜。
方便的是,你可以只安裝 Ruff linter,就獲得多種 linter 附帶的 format 行為。而複雜則在於:設定上的細節也比一般 linter 更多。
我原本以為 Ruff(不考慮ruff-format
部分)只是一個比較快的靜態分析工具,顯然事實並非如此。
總之,我們只要記得:Ruff linter 有著簡易 format(autofix)能力——它是個簡易版的 formatter。
這和 Flake8 只做單純的靜態分析不同,Ruff linter 在檢查過程中,能夠直接對程式碼進行修改。當然,如果你不喜歡,這功能是可以關閉的。
我相信從 Ruff 的開源之初,就已經想過要成為一個 All-in-One 工具。畢竟 Rust 這麼快,只做 linter 未免太可惜了!
和 linter 不同,formatter 具有強烈的排它性。不同的 formatter 之間,沒有相容可言。不像 linter 還可以疊加使用——如果你不嫌煩XD。
如果寫一個全新的 formatter,就必須要有足夠的理由,讓開發者願意放棄當前方案,採用你的新工具——這很不容易。
比較可行的做法,是相容並取代市場上現有的 formatter。
既然要選一個,那當然是選 Black——目前最流行的 Python formatter。
Black Formatter 相關文章:
因此,和對待 Flake8 一樣,Ruff formatter 必須與 Black 格式化後的結果,有高度的相容(一致)。否則你換了它的 formatter,卻帶來一堆格式化結果改動,絕對會造成遷移負擔與採用意願的下降。
按照官方文件中「Black compatibility 」這段可知:
Specifically, the formatter is intended to emit near-identical output when run over Black-formatted code. When run over extensive Black-formatted projects like Django and Zulip, > 99.9% of lines are formatted identically. When migrating an existing project from Black to Ruff, you should expect to see a few differences on the margins, but the vast majority of your code should be unchanged.
兩者的相容性(一致性)高達 99.9% 以上,基本可以放心遷移。
附帶一提,如果你是從 yapf 遷移到 Black,就能夠明顯感受到格式化風格差異所帶來的困擾——程式碼變動的地方太多了!
這種情況最好還是獨立一個分支或 commit,一次就把所有格式化差異都處理掉。而不要直接繼續開發,讓開發中的程式碼、檔案隨著開發進度被新 formatter 自動格式化,因為這會很影響 code review——格式化變動和開發變動混雜。
The Ruff formatter is available as a production-ready Beta as of Ruff v0.1.2.
引言中的「production-ready Beta」超連結,指向 Ruff Formatter 的官方介紹文章與發展現況總結。
所謂的 beta 並不是還有很多 bug,更像是「設計與方向上」還沒有完全底定——比如下面要討論的「單、雙引號」議題。所以才會在前面加上「production-ready」。
如果你想要在個人的生產環境中使用,我相信是沒什麼問題。畢竟 FastAPI 專案已經直接使用了!
不過如果是公司專案,我還是會選擇觀望,不急於一時。
Python 允許開發者自由選擇要在程式碼中使用單引號或雙引號。
只有在比如 docstring 這種連續使用 3 個引號的場景——即""" <內容> """
——時,慣例上要求使用雙引號。見 PEP 257:
For consistency, always use
"""triple double quotes"""
around docstrings.
如果你對 Black 有一些了解,應該會知道,早期 Black 是完全不管你習慣用單引號還是雙引號,它一律把你的 Python 程式碼格式化為雙引號!
這小小的硬性規則帶來了巨大的反彈,畢竟 Python 開發者中想必有不少人和我一樣,是「單引號」的支持者。
最後,Black 開發團隊也不得不妥協(這是 Black 少數的妥協,因為該工具本身就是以「不妥協」為賣點、slogan🤣),加入了skip-string-normalization
選項。
而 Ruff linter 作為 Black 的替代方案,也會遇到相同的「困境」。不同於 Black,目前 Ruff linter 提供的是quote-style
這個選項:
1 | quote-style = "single" |
即使你選了 single,在上述的慣例部分,Ruff 還是會格式化為雙引號,不用擔心。
至於要不要像 Black 加上skip-string-normalization
,目前似乎還沒有定論。
整體而言,這個議題仍然在持續中,有興趣可以關注這個 GitHub Issue 討論串。
這裡只是提醒你,採用 Ruff formatter 會有「單、雙引號」議題(而且和 Black 的處理方式不完全相同)。
至少對我而言,這非常重要。
接下來,我們要進入 Ruff 的「設定篇」。
當要採用一個新的 linter、formatter 時(尤其站在團隊開發考量),以下這三個部分的支援成熟度,是我一定會慎重考慮的。
不用說,複雜的工具都一定有自己的設定檔,讓你可以客製化一些需求。如果能支援 pyproject.toml 則會更受到我的青睞。
它能讓你在寫程式的同時,就能夠看到 linter 發出的警告,而不必等到 commit 之際才被 pre-commit 擋下來。
相較於 linter,formatter 更需要有自己的 VS Code 套件。讓你能直接在 VS Code 中進行格式化,而不用透過 CLI 指令或等到 pre-commit 時才自動修正。
而且,雖然你可以只用 Ruff 的 linter 功能,但因為有「autofix」存在,本質上它也是一個簡易(附帶)的格式化器,所以最好有 VS Code 整合。
pre-commit 是團隊協作中一道重要的關卡,我在「為什麼要使用 pre-commit?」中已有相當的闡述。
對於一個開發工具,我們主要關心的是:「它是否提供 pre-commit hook?」
以上 3 點,Ruff 都有完整支援。下面就來一一解說。
Ruff 總共支援三種設定檔:pyproject.toml
、ruff.toml
、.ruff.toml
。
如果設定不算多,那我一律推薦放pyproject.toml
。反之如果有大量的客製,那獨立一個設定檔可能是比較好的選擇。
如果不知道怎麼開始,參考文件是最快上手的方式。文件中的範例內容同時有著註解式的解說。
不過大部分時候,我們只需要設定一些基本的項目。比如以下是我目前的設定,包含了 linter 與 formatter 部分:
1 | [tool.ruff] |
首先,因為在pyproject.toml
中,所以設定的 key 定是[tool.xxx]
格式。
Linter 部分,一些基本的設定比如line-length
和 Flake8 類似。其中select
和沒有列出的ignore
相對重要。
Ruff 預設只會顯示 E
和 F
系列的錯誤訊息(而 Flake8 還有 W
系列)。想要增加或排除特定部分的錯誤訊息警示(包含 autofix),就得透過上述兩個欄位調整。比如:
1 | [tool.ruff.lint] |
在我的設定中,select
加上了I
和UP
。分別代表了 isort 和 pyupgrade。一旦你開啟了它們,Ruff 就會提示相關錯誤,並在有錯誤時自動修正。(autofix 預設為開啟)
因為開啟了UP
,所以我必須設定target-version
(這裡為py310
),意味著 Ruff 會將程式碼中舊的寫法自動轉換(autofix)為 Python 3.10 的寫法。
總之,可設定的項目非常豐富。
Ruff 的 VS Code 套件在 2022 年 12 月首次發表。
說真的,如果一個全新的 linter 或 formatter 沒有相關的 VS Code 套件,我絕不會考慮使用。
套件的重要性前面有提過,在此再次闡述:
絕大部分情況下,linter 提示的錯誤都會直接被 formatter 自動修正,感覺上沒有開啟 linter 提示似乎也無妨?
但是,兩者在少數時候會有不同的行為,所以我認為 linter 的提示仍是必要的。
當然,另一方面也因為我已習慣看 linter 提示👀——沒有會很不自在!
套件安裝後就可以直接使用,如果你的專案中已有專屬的 Ruff 設定檔,我覺得不需要再特別設定 VS Code Ruff 套件部分。
不過還是提供我的設定:
1 | "ruff.lint.args": [ |
Ruff 套件我覺得不設定也沒關係,有設定檔就夠了。
除非你不想要為每個專案一一建立 Ruff 設定檔,那就還是得弄一下(會套用到每一個專案)。同時也要考慮不同專案間的設定衝突問題——使用者全局設定 vs 專案設定。
相對的,VS Code 的 Python 部分則建議一定要設定。
這部分的具體內容,Ruff 套件首頁也有完整說明。我們直接看最完整的版本:
1 | "[python]": { |
效果:
editor.formatOnSave
:存檔時自動格式化(對所有 formatter 都有效)。source.fixAll
:存檔時自動 fix。(類似 pre-commit 時,hook 的自動修正)source.organizeImports
:存檔時自動排序 imports。我想上述這些 Python 設定才是 Ruff 在 VS Code 中流暢使用的重點。
還有一個小細節,就是專案之間的切換問題。
因為不一定每個專案都用 Ruff。比如我,只在個人專案使用 Ruff,但工作上還沒有。或是相反的情況。
此時記得要把可能發生衝突的 VS Code 套件(主要是 linter 和 isort)在「工作區」範圍內停用!做法如下圖。不然 linter 部分很可能一起運作,產生意料之外的結果。
Formatter 部分,因為每個專案只能選定一種格式化器,比較沒有衝突問題。
pre-commit 設定相對單純,更細部的行為,hook 會自動讀取設定檔中的內容。
1 | - repo: https://github.com/astral-sh/ruff-pre-commit |
這裡只要注意版本和 hook 的 id 即可。
Ruff 的設定真的滿多樣且可以很複雜——儘管它的預設值已能滿足大多數人。
如果你不清楚究竟有哪些項目可以調整,又想了解更多。除了研究官方文件、Github 首頁的 README 外,去看看那些已經採用了 Ruff 的開源專案的設定檔,也是很好的學習!
比如,我就習慣參考 FastAPI 的 Ruff 設定:
1 | [tool.ruff] |
而且 FastAPI 真的很貼心,還加了註解!
結語之前,講講我在一番研究後,仍未建議團隊在此刻就採用 Ruff 的理由。
放到最後不是為了賣關子,而是看到這裡,你應該比較能夠理解其中的顧慮。
主要有三個。
首先我覺得 Ruff 的設定太多樣了,畢竟它一口氣整合了這麼多的工具。
而且要同時考慮 pyproject.toml、VS Code、pre-commit 的整合,加上各種 linter 在設定上的開關,想想有點頭痛。
這會帶來一定的認知負擔,同事可能會覺得「搞這些真的有必要嗎?」——其實我也這麼想🤣
我們目前的專案最多只能算中型,用「Flake8 + isort + Black Formatter」經典組合已能運作良好。改用 Ruff,可能看不出太大差別。
如果看不出差別,設定又要重新調整、學習,難免讓人卻步。
說起來,前兩個理由並不算什麼重大阻礙,只是也沒有明顯的動力。
我打算等 Ruff formatter 的「beta」字樣拿掉後,直接「一換三」一次到位。
所以,不妨讓子彈再飛一會兒。
Ruff 現階段對我的重要性,顯然還遠不如 Poetry 套件管理器。但這樣的工具依舊讓人興奮且充滿期待。
我並不認為目前的 Python 開發一定要用上 Ruff,但值得你嘗試一下。
如果你也是一個 Code Formatting 愛好者,那麼 Ruff 絕對是一個值得你花時間研究的工具。
更重要的是,由 Rust 所引發的 Python 生態革命,現在才剛剛開始。
]]>你現在看網站的選單,已經沒有「發文計畫」了。
至於那是什麼,可以參考上一期〈23,Blog 新增「發文計畫」與我的思路〉。
這篇就來解釋,為什麼它失敗了,與我的一點想法。
先簡單回顧一下,發文計畫的兩大初衷:
從這一個多月的實施結果來看,上述這兩件事情,基本都搞砸了。
首先是可預見,這確實有一定的效果。畢竟我把接下來的要寫的東西,都先公開其中。
但是,很多文章(尤其是較長的內容)的發文日期,卻是一延再延,如此一來,即使可預見,也沒有太大的參考價值。
既然文章發布日可以一延再延,那公開承諾就沒有了原來想像的拘束力(至少在心理上沒有足夠的拘束力)。
如此一來,想透過公開承諾培養一定紀律的期待,也就落了空。
其實,並不是所有的文章都是一延再延。
有一部分文章,反而因為有了這份「發文行事曆」,而事先完成了!這實在超乎了我的預料,可謂意外的驚喜。
這是我少數能感受到「計畫」強大威力的時刻。
不過,即使已經完稿,我通常還是會等到上面寫的發文日期才正式公開。
這是好的部分。
但是,那些真正「困難」的文章(主要是技術相關的長篇文章),我卻還是一拖再拖,直接架空了這個行事曆。
我也真正了解到,除非我有辦法真正降低這些主題的寫作門檻,或許找到其它更有效的創作手段。
不然面對這些棘手的主題,常常就是一逃再逃。
本來我是這麼說的:
公開承諾會強化其中的痛苦,讓我對這樣的拖延感到壓力。
確實如此,但從結果看來,這樣的壓力,還是不足以讓我按時完成!
既然事與願違,索性還是撤掉了。
此外,網站的選單部分我一向是寸土寸金,捨不得增加太多雜訊。
既然發文計畫無法達到當初的期待,又佔據了選單列表,我還是選擇拿掉。不再公開。
儘管如此,我發現「發文行事曆」確實是很棒的規劃工具,尤其是可以「拖曳」這個特性,令人愛不釋手。
一直把日期後延,對讀者很不好意思,所以我只能「關門」。但如果只有我自己的話,行事曆仍不失為稱手好用的發文管理利器!
這和「僅把待發表文章列在清單中」的感覺,截然不同。推薦你也試試。
整體而言,我目前的寫作還是略嫌發散了,主要受到三股勢力的牽引:
上述三者雖然不算涇渭分明,但仍有一定的排它性。
然而時間就這麼多,我必須有所取捨。而現在的我,還做得不夠好。
講白了就是太貪心!
所以我應該會減少發文量。把主要精力集中在中長篇內容,尤其是那些我常常想要逃避的主題。
這類文章往往需要很大的心力來創作,但也是真正具有「自我代表性」的作品。
至於發文頻率,則不再強求。
]]>這是《Python 功力提升的樂趣:寫出乾淨程式碼的最佳實務》閱讀筆記的第 3 篇,也是最後一篇。
你可以把它當作是一則重點整理,加上我個人的開發經驗與心得。
本文整理書中的第 10、11 章,而且篇幅幾乎集中在前者。畢竟無論什麼語言,「寫好函式」這件事總是如此重要,Python 自然也不例外。
- 《Python 功力提升的樂趣》筆記(一)Black、命名、壞味道
- 《Python 功力提升的樂趣》筆記(二)Pythonic、行話、陷阱
- 《Python 功力提升的樂趣》筆記(三)函式、註解、docstring
有效率的函式(或說「好的」函式)需要你在「命名、規模大小(行數)、參數數量和複雜性」之間,做出許多決定和取捨。
這無疑是極具挑戰的事——尤其是取捨。
人生之難,就難在取捨。
本章探討的正是這些取捨之間的利弊得失,以及編寫函式的重要原則。不用說,絕對是關鍵的一章。
我們都聽過「函式應該盡可能簡單、一次只做一次件事」之類的建言,也表示認同。從這個精神出發,太大或太複雜的函式就應該要進行拆分。
但!事實是,有效拆分函式是一件耗神、講究細節,且沒有標準答案的事。以致於我們即使知道,也很難完全貫徹,包括我自己。
出於各種原因,我們常常對現實世界作出一定的妥協。
有些人覺得任何函式都不應該超過 20 行,甚至 10、5 行😂。因為函式「短」往往有下面這些優點:
但小也有缺點:
這些「缺點」往往也解釋了為何我們不一定那麼積極拆分函式,讓每一個函式都符合「一次只做一件事」原則。
尤其是小函式造成的大量命名問題,對於命名很講究的我而言,有時確實感到棘手。
函式原則上還是應該要盡可能單純一點,該拆就要拆,但不一定要很短。而且其中必然會有很多挑戰。
從「功能」上去劃分界限、拆分函式,會更有意義與指導性,與可行性。
作者認為,一味追求短函式,確實可以讓各別函式變得簡單,但卻很可能讓程式的「整體」變得複雜,適得其反。
他的經驗是,理想情況下,函式最好少於 30 行,最多不超過 200 行。讓函式在合理情況下盡可能短少,但不只是為了短少而縮減。
對此,我想說:
這真的好重要啊!(吶喊)
卻常常沒有被好好遵守。
簡言之,為確保函式的「可預測性」,我們應該努力讓函式只回傳「單一資料型別」的值。比如總是回傳整數或字串,而不要有時回傳字串,有時則回傳布林值。
這不一定容易做到,但我更常遇到的情況是:明明有替代方案讓回傳型別單一化,卻沒有善用。
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
,然後再決定是否進行後續的操作。其實「return
型別不確定」肯定不只發生在例外處理,但它確實「很常發生」在例外處理,因為我看過不止一次類似的真實案例。
很多人本能地會這樣寫,這往往是因為不習慣、不擅長處理錯誤,所以想要用自己平常更熟悉的False
。
除此之外,還有「函式正常執行回傳一個類別物件,失敗時回傳一個 Python tuple——包含錯誤代碼和錯誤訊息」這種非常反直覺的設計。彷彿是在告訴我們:
這個函式的回傳值,可能是一個物件,也可能是一個 tuple,你自己判斷吧!
會寫出這樣的函式,原因諸多——工作很忙、重構太麻煩了,要新增什麼功能我直接「加上去」就好!
充滿了技術債味的寫法。
這類「雙型別 return」的函式,對於函式使用者(有可能是你的同事)的認知,有著「更高的要求」——呼叫方必須很了解這個函式的怪異行為,才能正確使用與處理後續衍生的問題。
這在多行或有多個 return 值的複雜函式時,真是一場災難。
期望他人知道自己做了什麼「特別的事」,不是我所知曉的軟體開發之道。
當函式具有這種「雙型別 return」的特性時,會明顯增加呼叫方的「認知負擔」。
這使得程式不僅難以閱讀和維護,也容易出錯,因為未來的維護者或其他團隊成員很可能不知道這個函式的「獨特」行為。
無論何時,我們都不應該寫這樣的程式。
寫好函式的重點實在太多了,而本文的篇幅有限,只能擇要為之。
我也講講我認為函式的撰寫中,最重要的兩點。
至少遵守這兩點,你的同事會很感激你。
其一是盡可能地寫 docstring,這不容易,畢竟維護 docstring 也需要心力。
Docstring 就跟所有開發文件一樣——自己很懶得寫,但如果我想調用別人寫好的程式時,卻希望它們越詳細越好。
而且 docstring 也不是有寫就行,還需要從「讀者(也就是你的同事)」的角度去思考與表達。不然看起來會很像開發者的自言自語——沒人看得懂。
然而我也承認,為所有的模組、類別、函式寫 docstring,未免有點不切實際。在眾多函式中,下列兩種是我認為一定要寫 docstring 的:
畢竟,看有描述性的文字,總比看一長串程式碼,要簡單且友善得多。
上述兩種情況,若不寫 docstring,那麼閱讀程式碼的成本就會大大提升。這相當於在告訴你的同事:
我離開公司後,誰還想維護這段程式碼,先學習通靈之術吧!
關於我對 docstring 的其它討論,可以參考這兩個部分:
我的觀點一向如此:不寫好 docstring,就稱不上是一流的 Python 開發者。
其二,好的函式要「言行一致」。
你可能會想:
這不是理所當然的嗎?
對,它「本應該」是理所當然的,畢竟這不就是函式命名的基本目的?——用來描述函式的行為。
但我們可以回想一下自己在工作中遇到的各式各樣函式,究竟有多少比例,是真正做到「言行一致」?我覺得可能只有一半。
或許你會認為「一半」也太誇張了!但我並不這麼想。
「言行不一致」通常有下面幾種症狀:
如果你不能從一個函式的名稱中有效理解並推測它應有的行為,那麼這個函式基本上就是失敗(或不健康)的。
很多時候,函式最初可能是「言行一致」的,但隨著後來的修改、刪除、擴充,實際上做的事情變更了,但命名卻沒有跟著改變、重構。
這些言行不一的函式,充滿誤導性,不斷地挑戰著你的認知、推理能力,更增加了維護成本。
這樣的例子還少嗎?恐怕每天都在發生。
這章我只摘錄書中的一段話——我特別欣賞與認同的部分:
好的注釋對程式設計師在未來閱讀並理解程式碼作用時提供了簡潔、有用和準確的資訊。這些注釋應該用來解說程式設計師原本的意圖,並總結某程式碼的作用,而不是只對某行程式碼進行解說。
注釋有時會詳細描述程式設計師在編寫程式碼時所得到的經驗教訓,這些寶貴的資訊可以讓將來的維護者不必再次經歷這些苦難。
說的太好了!
團隊寫程式,是關於溝通的藝術,畢竟《人月神話》已經告訴我們:人多不一定比較快。
溝通不止發生在會議、Jira、Slack 和規格文件上,程式之內也有著大量的溝通,註解是如此,docstring 亦是如此。
永遠不要低估「對這些細節的用心」所帶能來的巨大影響力。
優秀的工程師絕不可能輕忽它們。
]]>