Python 工匠Python 工匠

這是《Python 工匠|案例、技巧與開發實戰》筆記的第 3 篇,你可以把它當作是一則重點整理,加上我個人的開發經驗與心得。

如第一篇所言,這是一本關於「Clean Code in Python」的書。

上一篇我整理書中了第 13 章「有關單元測試的建議」的內容(以及我的看法)。這次我們要再回到一開始,繼續學習如何寫好程式碼。

本文整理自書中的第 1 章第 3 節,為了不過度重複書中內容,下筆點到為止,並加上我自己的理解。


變數是程式碼中的基本元素,它們幾乎無所不在

老實說,看這本書之前,我也想沒到,原來變數的使用有這麼多的學問!

以下就讓我們來一探究竟。

一、保持變數的一致性

這裡有兩種一致性:

  1. 名字一致性:在同一專案、模組、函式中,變數名稱應該保持一致。意思是,對「同一件事物」,不要用不同名稱。
  2. 型態一致性:變數的型態(型別)應該保持一致。意思是,不要把同一個變數,重新指向不同型態的資料。

老實說,這兩件事,就我的經驗,如果團隊沒有特別要求,那就一定會有人違反。

乍看這好像也沒什麼,但當你的程式碼規模變得越來越龐大,這些不一致就會變成隱藏的 bug——魔鬼真的藏在細節裡。

所幸 Mypy 不允許你變更變數型態,它的檢查確保了型態一致性。但命名上的一致性,只能靠人來保證。

關於「一致性」的議題,我們還會另篇討論——這真的非常重要。

重新把變數指向不同型態的資料

這真的是一個很常見的「巨大壞味道」。當你在程式碼中看到這樣的事情,就應該立刻警覺

雖然許多時候它可能不會「真的」造成問題(所以才不受重視),但這樣的寫法會讓你的程式碼變得難以理解——為什麼前面是 list,後面卻變成了 dict?

而真正造成問題的時候,又很難 debug。

以前我也會做這種事,而且從 Python 語法角度,這「完全合法」呀!

後來自己被「坑」到以後,才理解這樣的寫法是多麼危險——難怪 Mypy 會禁止這樣的行為。

一個簡單避免這類行為的手段,就是重新命名一個新變數吧!還有安裝 Mypy,讓工具來幫你檢查。


二、變數宣告儘量靠近使用處

這是一個好習慣,也是讓程式碼更易讀的方法。

如果我還要跨越好幾百行才能找到一個變數是怎麼宣告的,那會讓人絕望。

當然幾百行是誇張了,但如果是長函式,然後變數宣告在函式的開頭,那至少也是幾十行的事——太累了。

我們希望變數的宣告與使用能夠愈近愈好。

這樣的好處是,你可以快速理解這個變數是幹嘛的,而不用一直往上找。


三、宣告「臨時變數」提升可讀性

我覺得這是平時開發中,非常容易忽略的一點——因為這很反人性

當你在寫程式碼時,通常你會想到什麼就寫什麼,而不會去想到「這裡可以宣告一個臨時變數,讓程式碼更好讀」。

這真的很難XD

我們直接看書中的例子:

1
2
3
4
# 為所有性別為女性,或者等級大於 3 的活躍使用者發放 10000 個金幣
if user.is_active and (user.sex == 'female' or user.level > 3):
user.add_coins(10000)
return

因為直接照著「業務邏輯」寫,程式碼隨著業務邏輯的複雜度增加,變得難以閱讀

此時,我們可以宣告一個「臨時變數」,讓程式碼更易讀:

1
2
3
4
5
6
7
# 為所有性別為女性,或者等級大於 3 的活躍使用者發放 10000 個金幣
user_is_eligible = user.is_active and (
user.sex == 'female' or user.level > 3):

if user_is_eligible:
user.add_coins(10000)
return

我覺得這真的堪稱神來一筆——這樣的寫法,讓程式碼更易讀,也更易維護

你看完可能覺得,這好像也沒什麼,難嗎?

我覺得其中的難點在於:你恰好意識到,臨時變數在這個場景的必要性。

這需要很細微的心思,與為他人著想的精神。因為你自己寫、自己讀,其實不需要這麼麻煩。

我稱這樣的臨時變數為——良心事業


四、能不宣告變數就別宣告

當然你看完上一條建議,決心想要好好適時宣告變數,當一個「貼心的人」的時候。

這條建議就是要防止你矯枉過正,濫用上述建議XD

這正是程式設計可以稱為「藝術」的地方——平衡

定義臨時變數可以提高可讀性。但有時,把不必要的東西賦值成臨時變數反而會讓程式碼顯得囉嗦

1
2
3
4
5
6
7
8
9
10
def get_best_trip_by_user_id(user_id):

# OS:『嗯,這個值未來說不定會修改/二次使用』,讓我們先把它定義成變數吧!
user = get_user(user_id)
trip = get_best_trip(user_id)
result = {
'user': user,
'trip': trip
}
return result

其實,這段程式碼裡的三個臨時變數完全可以去掉,變成這樣:

1
2
3
4
5
def get_best_trip_by_user_id(user_id):
return {
'user': get_user(user_id),
'trip': get_best_trip(user_id)
}

簡言之,變數是「我現在就需要」才宣告——包括臨時變數。


五、空行也是一種「註解」

說真的,我本來是打算把這段的標題作為文章標題,但這樣顯然有點「文不對題」。

空行是程式碼的一部分,它可以讓程式碼更易讀

看看書上怎麼說:

程式碼裡的註解不只是那些常規的描述性語句,有時候,沒有一個字元的空行,也算得上一種特殊的「註解」。

在寫程式碼時,我們可以適當地在程式碼中插入空行,把程式碼按不同的邏輯塊分隔開,這樣能有效提升程式碼的可讀性。

這些道理大家都懂,但真正做到的人,可能還不算太多。

部分開發者對於「空行」的態度,還是比較隨心所欲,未必是著眼於程式碼的可讀

因此讓人感覺缺乏一致性:相同的情況,有時候會空行,有時候又不會。

所以我才會很想把本段作為文章標題,來強調「空行」的重要性XD