對於處理主要業務邏輯的程式碼來說,判斷錯誤、處理異常的程式碼屬於"防禦性程式碼"。而EAFP、LBYL是兩種截然不同的防禦型程式碼撰寫風格。
EAFP的全名是Easier to Ask for Forgiveness than Permission,是 Python 提倡的防禦型程式碼風格,鼓勵工程師直接編寫主要業務邏輯,不需要在執行主要邏輯前多做異常及錯誤的檢查,真的發生再處理就好。
user = {"name": "馬斯克"}
try:
send_email(user["email"])
send_sms(user["mobile"])
except KeyError:
print("Key is not exist"
從這段 Python 程式碼可以看到,我們在存取 user 字典前,不事先使用if判斷要存取的 key 是否存在於字典中,直接執行主要業務邏輯(send_email、send_sms),將所有key相關的錯誤丟到 except 區塊統一處理。
我們再來看看另一種防禦性程式碼風格: LBYL(Look Before You Leap),這種風格的程式碼要求工程師在撰寫主要業務邏輯之前,必須想清楚可能的錯誤並預先排除。
user = {"name": "馬斯克"}
if user.get("email"):
send_email(user["email"])
else:
print("Key is not exist: email")
if user.get("mobile"):
send_sms(user["mobile"])
else:
print("Key is not exist: mobile")
LBYL 的程式碼風格則是與上面這段程式碼類似,先用 if 確定 key 確實存在 user 字典裡,才取出需要的 user 資料執行主要業務邏輯(send_email、send_sms)。
EAFP 風格的程式碼主要業務邏輯與防禦型程式碼分離,程式碼清晰、可讀性較佳,工程師較容易專注處理主要業務邏輯。
EAFP 風格的程式碼在異常狀況發生時,try/except 相關的 call stack 處理是比 if 慢的,但是只有發生異常時要處理。
相反的 LBYL 不管有沒有發生異常,都要花費固定成本執行 if 一次。因此在效率上可以說是不相上下。
if user.get("email"):
# 假如發生 context switch, user的email被其他process刪除
send_email(user["email"])
else:
print("Key is not exist: email")
如果程式碼是運行在 Multi-process 或是 Multi-thread 等非同步場景之下,極有可能在if 敘述執行後發生 Context Switch,這個狀況下通過前一行 if 敘述的檢查並無法保證接下來一定可以存取到對應的資料。
這種狀況下 LBYL 風格的if檢查敘述必須搭配鎖相關的原子操作的機制才能發揮原本的檢查效果,而 EAFP 的 try/except 本身就已經涵蓋了這種狀況。
Python 作為動態型別的程式語言,配合其完善的異常處理系統,強烈鼓勵開發者採用 EAFP風格編寫防禦性程式碼。
此外,在涉及多進程或多線程的應用場景中,採用 EAFP 風格的防禦性程式碼不僅提升了程式碼的可讀性,還能有效應對非同步操作中可能出現的問題,帶來實質的效益。