本文是Euro Python 2021大會演講Python Anti-Patterns的學習筆記,這篇演講的作者是Vinicius Gubiani Ferreira,
英文簡介
By Vinicius Gubiani Ferreira
Most people heard at least once or focused really hard on studying design patterns. But did you know there are also lots of anti-patterns we should try to avoid?
In this talk I intend to present some of the most known anti-design patterns that we’ll promise never to use … but for some reason or slip end up using it anyway. I will start showing more generic anti-patterns that apply to all languages and software in general, and move on to Python specifics, by using The Little Book of Python Anti-Patterns.
Since there will be Python code, attendees should fell confort with some basic to intermediate Python knowledge. For example: know about classes, constructors, parameters/arguments, etc.
翻譯
很多人聽說過或者努力學習過設計模式,但是你知道還有我們應該避免的反面模式么?
在這次演講中,我會展示一些常見的反面模式,并且,我們要保證以后避免使用,我會從各種語言都有的反面模式開始講起,然后再介紹Python的反面模式,我使用的書是《The Little Book of Python Anti-Patterns》
既然我們講的是Python,與會者應該有Python的初級或者中級水平,比如,了解類,構造器,引數,等,
作者簡介
Vinicius Gubiani Ferreira 是AZION的高級后端開發工程師,AZION是一家severless and 邊緣計算公司,致力于讓網路更快,更安全,他也是python的葡萄牙語的翻譯者,業余愛好有喝啤酒,騎自行車等,
(中國人一定會裝逼的說,我喜歡文學,藝術,我知道茴香豆的茴字有100種寫法,,,)
通用反面模式
首先,我們要明白什么是模式,模式就是針對反復出現的問題的通用的解決方案,至少出現三次才叫反復出現,模式會被大規模的采用,可以認為是一種內聚方法,模式是可靠,高效的,
而反面模式一開始看起來挺好,直到出現問題,有時候,反面模式不僅不能解決問題,反而會引發更大的問題,
《Anti-Patterns》這本書,介紹了三種反面模式:
- Development 開發
- Architechure 架構
- Project Management 專案管理
Boat Anchor 船錨, 是指哪些已經不需要的廢代碼,這里的解決方案就是盡快洗掉它們,
Spaghetti Code 意大利面代碼,指那些沒有結構和清晰度的代碼,這些代碼很難維護,解決的方法就是重構,
God Code 上帝代碼,看起來有一些結構,但是有限,比如,一個檔案500行代碼,解決的方法就是拆分,之后就會易于維護了,
Vendor Lock-in 供應商鎖定, 代碼邏輯對特定供應商的依賴太重,有一天,我們決定不在使用這個供應商了,這時候,我們的麻煩就大了,解決的方法是,抽象出一個抽象層,我們的代碼只和抽象層打交道,當供應商換了,我們只需要重寫抽象的具體實作,上層建筑無需更改,
Cargo Cult Programming 盲目信仰, 有人從Stackoverflow復制了一段代碼,還沒看明白,就盲目的放到專案里了,出了問題也不知道怎么回事,解決的方案就是,要明白自己的代碼要干什么,要花時間去看懂,
Premature optimization 不成熟的優化, 有人說他是萬惡之源(The root of all evil), 97%的情況,我們不需要優化,解決的方法就是先要找出性能的指標和瓶頸,如果我們做不成熟的優化,那么我們的代碼會變得沒有必要的復雜,
Magic Numbers 魔術數字, 他們看起來完全隨機,但是如果我們改了這些數字,就會引發很多奇怪的bug,兩個解決方案,要么加上注釋,或者使用列舉,
Gold plating 鍍金, 開發一些客戶不需要的功能,解決的方法就是開會,開會,開會,
(我懷疑你一個程式員,能推翻專案經理的決定么?)
Python 反面模式
沒有例外型別
Bad code sample
try:
do_somthing()
except:
pass
Good code sample
try:
do_somthing()
except ValueError:
logging.exception("Caught error!")
pass
我們不應該捕獲了所有例外,然后假裝無事發生,我們應該把例外記錄下來,不然我們就不知道發生了什么,我們尤其應該捕獲特定的例外,而不是所有例外,
處理檔案時不使用內容管理器
Bad code sample
f = open("file.txt", "r")
content = f.read()
1 / 0
f.close()
Good code sample
with open("file.txt", "r") as f:
content = f.read()
1 / 0
當我們讀寫檔案的時候,我們可能發生任何錯誤,如果我們不將資料寫入檔案(關閉檔案),資料可能會丟失,檔案也可能會被污染,用了內容管理器,無論是否例外,檔案都會被關閉,
(在.py檔案中,我一定會使用內容管理器,只有在jupyter notebook中,我才會省略內容管理器,我的習慣是在jupyter notebook探索,當然探索的代碼都不是正式代碼,等探索跑通了,在寫成正式的python檔案,)
回傳多種型別
Bad code sample
def get_secret_code(password):
if password != "bicycle":
return None
return "42"
Good code sample
def get_secret_code(password):
if password != "bicycle":
raise ValueError
return "42"
如果有多種回傳型別,會難為維護,最好只使用一種型別回傳,空值或者找不到資料可以拋例外,
在類的外面使用被保護的成員物件
Bad code sample
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
tr = Rectangle(5, 6)
memberprint(
"Widht:{:d}".format(r._width)
)
Good code sample
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
@property
def width(self):
return self._width
tr = Rectangle(5, 6)
memberprint(
"Widht:{:d}".format(r.width)
)
在其他語言中,被保護的成員,在外部是無法被訪問的,但是python里面,你還是可以訪問,不過,這并不代表你就應該去訪問,還是應該遵守軟體開發的基本守則,
應該實作公共的getter和setter,
使用關鍵字
Bad code sample
list = [1, 2, 3, 4]
cars = list()
Good code sample
numbers = [1, 2, 3, 4]
cars = list()
更夸張的是,你居然可以賦值給關鍵字,或者已經使用的名字,這樣做可能對整個系統造成不可預料的破壞,
使用tab,甚至tab和空格混合
Bad code sample
# Indentation with tabs
Good code sample
# Indentation with 4 spaces
(PEP 8里面說了,使用4個空格來縮進代碼,如果你不遵守,說明你沒讀PEP 8,我給你個鏈接,你去讀讀吧,)
https://pep8.org/
用空格的程式員比用tab的程式員每年多2萬美元工資,
這里有一篇stackoverflow的文章,這這么說的,
https://stackoverflow.blog/2017/06/15/developers-use-spaces-make-money-use-tabs/

For…Else
Bad code sample
my_list = [1, 2, 3, 4]
magic_number = 4
found = False
for number in my_list:
if number == magic_number:
found = True
print("Magic number found")
break
if not found:
print("Magic number not found")
Good code sample
my_list = [1, 2, 3, 4]
magic_number = 4
for number in my_list:
if number == magic_number:
print("Magic number found")
break
else:
print("Magic number not found")
上面的bad code就是我們通常的寫法,在其他語言里,我們都會這樣寫,不過,python有更好的解決方案,就是在for后面跟一個else,如果for回圈執行完畢,沒有跳出,則執行else,
(我看到這里有些頭大,我經常看歐美的程式員大叔的視頻,每一個人說到這里的時候,都不建議使用For…Else,在他之前,我應該遇到3個人專家,反對for…else了,反正我不會用For…else,你用不用,隨你,很多時候,尤其在中國,因為專案的需要,老板隨便叫兩個人來,就讓你維護python代碼了,他不管你們有沒有學過python,寫For…Else是為難后面的程式員,所以我不寫,當然,也指不定哪天,我“想通”了,這個是爭議話題,我覺得,老師在介紹爭議話題的時候,應該介紹反面的觀點,而不是灌輸和一言堂,)
不用get()獲取默認值
Bad code sample
dictionary = {"message": "Hello!"}
data = ""
if "message" in dictionary:
data = dictionary["message"]
print(data)
Good code sample
dictionary = {"message": "Hello!"}
data = dictionary.get("message", "")
print(data)
Python的原則是easier to ask forgiveness than permission
而Java的原則是look before you leap,
后面的代碼看起來舒服多了,
用通用符參考所有成員
Bad code sample
from math import *
Good code sample
from math import ceil
如果你參考幾個庫,那么這幾個庫里面很可能有同名成員,那你就麻煩大了,
使用全域變數
Bad code sample
WIDTH = 0
HEIGHT = 0
def area(w, h):
global WIDTH
global HEIGHT
WIDTH = w
HEIGHT = h
return WIDTH * HEIGHT
Good code sample
WIDTH = 0
HEIGHT = 0
class Rectangle(w, h):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
盡量避免使用全域變數
用一個字符命名變數
Bad code sample
l = [1, 2, 3, 4]
Good code sample
car_ids = [1, 2, 3, 4]
盡量使用有明確含義的名字,而不是模棱兩可的名字,
錯誤的比較方法
Bad code sample
flag = True
if flag == True:
print("This works!")
Good code sample
flag = True
if flag:
print("This works!")
if flag is True:
print("This works!")
如果要檢查一個值是否為True,只需要把這個值直接作為條件即可,比較的時候,有 == 運算子 和 is 運算子,他們的含義并不一樣,True 等于 1 ,但是True不是1,所以 True == 1 回傳 True,True is 1 回傳 False,
用type比較型別
Bad code sample
c = Circle(2)
r = Rectangle(3, 4)
if type(r) is not type(c):
print("object types do not match")
Good code sample
r = Rectangle(3, 4)
if isinstance(r, types.ListType):
print("object r is a list")
isinstance會檢查一個變數的型別以及其所有的父類,而直接比較type,比較的是當前的類,
(這里我沒看出來bad code哪里不好了,如果它的目的是比較兩個形狀是不是同樣的形狀,那么我認為它可以這樣寫,)
函式回傳值沒有使用named tuple
Bad code sample
def get_name():
return "Rechard", "Jones"
name = get_name()
# no idea which is which
print(name[0], name[1])
Good code sample
from collections import namedtuple
def get_name():
name = namedtupe(
"name":["first", "last"]
)
return name("Richard", "Jones")
name = get_name()
print(name.first, name.last)
不區分單復數
作者并沒有提到這個,但是我發現這點是中國程式員常犯的錯誤,
Bad code sample
car_id = [1, 2, 3, 4]
sheep = ['喜羊羊', '美羊羊']
for isheep in sheep:
print(f"咩,我是{isheep}")
Good code sample
car_ids = [1, 2, 3, 4]
sheep_list = ['喜羊羊', '美羊羊']
for sheep in sheep_list:
print(f"咩,我是{sheep}")
上面的錯誤代碼中,list不區分單復數,以至于到了要遍歷list的時候,只能在sheep前面加i了,我的做法是,盡量區分單復數,當單復數同形的時候,復數就叫xxx_list,單數還叫xxx,
參考
大會頁面:
https://ep2021.europython.eu/talks/yXoQCzR-python-anti-patterns/
視頻地址(優酷):
https://v.youku.com/v_show/id_XNTgxMTU5NjEwOA==.html
PDF地址:
https://ep2021.europython.eu/media/conference/slides/yXoQCzR-python-anti-patterns.pdf
《Python反面模式小冊子》:
https://github.com/quantifiedcode/python-anti-patterns
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/307293.html
標籤:python
