學習過程中,我們經常會閱讀他人寫的程式碼,如果注意觀察就會發現,好的程式碼本身就是一份文件,解決同樣的問題,不同的人編寫的程式碼,其可讀性千差萬別。
有些人的設計風格和程式碼風格猶如熱刀切黃油,從頂層到底層的程式碼看下來酣暢淋漓,注釋詳盡又精簡;深入到細節程式碼,無需註釋也能理解清清楚楚。而有些人,程式碼勉勉強強能跑起來,遇到稍微複雜的情況就會出崩潰,且程式碼中處處都是堆積在一起的變數、函數和類,很難理清程式碼的實現思路。
Python 創始人 Guido van Rossum(吉多·范羅蘇姆)說過,程式碼的閱讀頻率遠高於編寫程式碼的頻率。畢竟是在編寫程式碼的時候,我們自己也需要對程式碼進行反複閱讀和偵錯,來確認程式碼能夠按照期望執行。
本節,在讀者學會如何使用 Puython 函數的基礎上,教大家怎麼才能合理分解程式碼,提高程式碼的可讀性。
首先,大家在程式設計過程中,一定要圍繞一個中心思想:
不寫重複性的程式碼。因為,重複程式碼往往是可以通過使用條件、迴圈、建構函式和類(後續章節會做詳細介紹)來解決的。
例如,仔細觀察下面的程式碼:
if i_am_rich:
money = 100
send(money)
else:
money = 10
send(money)
這段程式碼中,同樣的 send 語句出現了兩次,其實它完全可以進行合併,把程式碼改造成下面這樣:
if i_am_rich:
money = 100
else:
money = 10
send(money)
與此同時,
還要學會刻意地減少程式碼的疊代層數,盡可能讓 Python 程式碼扁平化。例如:
def send(money):
if is_server_dead:
LOG('server dead')
return
else:
if is_server_timed_out:
LOG('server timed out')
return
else:
result = get_result_from_server()
if result == MONEY_IS_NOT_ENOUGH:
LOG('you do not have enough money')
return
else:
if result == TRANSACTION_SUCCEED:
LOG('OK')
return
else:
LOG('something wrong')
return
上面這段程式碼層層縮排,如果我們沒有比較強的邏輯分析能力,理清這段程式碼是比較困難。其實,這段程式碼完全可以改成如下這樣:
def send(money):
if is_server_dead:
LOG('server dead')
return
if is_server_timed_out:
LOG('server timed out')
return
result = get_result_from_server()
if result == MONET_IS_NOT_ENOUGH:
LOG('you do not have enough money')
return
if result == TRANSACTION_SUCCEED:
LOG('OK')
return
LOG('something wrong')
可以看到,所有的判斷語句都位於同一層級,同之前的程式碼格式相比,程式碼層次清晰了很多。
另外,
在使用函數時,函數的粒度應該盡可能細,不要讓一個函數做太多的事情。往往一個複雜的函數,我們要盡可能地把它拆分成幾個功能簡單的函數,然後合併起來。
如何拆分函數呢?這裡,舉一個二分搜尋的例子。給定一個非遞減整數陣列,和一個 target 值,要求你找到陣列中最小的一個數 x,滿足 x*x > target,如果不存在,則返回 -1。
大家不妨先獨立完成,寫完後再對照著來看下面的程式碼,找出自己的問題:
def solve(arr, target):
l, r = 0, len(arr) - 1
ret = -1
while l <= r:
m = (l + r) // 2
if arr[m] * arr[m] > target:
ret = m
r = m - 1
else:
l = m + 1
if ret == -1:
return -1
else:
return arr[ret]
print(solve([1, 2, 3, 4, 5, 6], 8))
print(solve([1, 2, 3, 4, 5, 6], 9))
print(solve([1, 2, 3, 4, 5, 6], 0))
print(solve([1, 2, 3, 4, 5, 6], 40))
對於上面這樣的寫法,應付演算法比賽和面試已經綽綽有餘。但如果從工程的角度考慮,還需要進行深度優化:
def comp(x, target):
return x * x > target
def binary_search(arr, target):
l, r = 0, len(arr) - 1
ret = -1
while l <= r:
m = (l + r) // 2
if comp(arr[m], target):
ret = m
r = m - 1
else:
l = m + 1
return ret
def solve(arr, target):
id = binary_search(arr, target)
if id != -1:
return arr[id]
return -1
print(solve([1, 2, 3, 4, 5, 6], 8))
print(solve([1, 2, 3, 4, 5, 6], 9))
print(solve([1, 2, 3, 4, 5, 6], 0))
print(solve([1, 2, 3, 4, 5, 6], 40))
在這段程式碼中,我們把不同功能的程式碼單獨提取出來作為獨立的函數。其中,comp() 函數作為核心判斷,提取出來之後,可以讓整個程式更清晰;同時,還把二分搜尋的主程式提取了出來,只負責二分搜尋;最後的 solve() 函數拿到結果,決定返回不存在,還是返回值。這樣一來,每個函數各司其職,閱讀性也能得到一定提高。