別做搶活的導演:代碼中的抽象層次原則
??在電影片場,一個優秀的導演是如何工作的?
??他會跟攝影指導說:“我希望這個鏡頭能傳達出主角內心的孤獨和絕望感,我們用冷色調,構圖要空曠一些。” (這是頂層意圖和藝術方向)
??然后,攝影指導會把這個抽象的“意圖”翻譯成具體的執行方案,告訴燈光師:“我們需要一個頂光,用低色溫的光源,把周圍環境的亮度降下來,人物面部的陰影要硬一些。” (這是中層技術方案)
??最后,燈光師會指揮工作人員:“把那盞2K的菲涅爾透鏡燈吊起來,裝上1/4的藍色色紙,用黑旗把左邊的光擋掉。” (這是底層具體操作)
??這是一個權責清晰、溝通高效的體系。每一層的人都專注于自己該干的事。那么能夠營造出最終成功的作品。

??現在,想象一個糟糕的、愛搶活的導演。他會在片場大喊:
??“我要孤獨感!小王,去,把那盞2K的菲涅爾燈給我裝上1/4的藍色色紙!……不對,咱們這個鏡頭的構圖是不是太空了?演員,你的情緒再絕望一點!”
??當導演本人直接跳下去指揮燈光師助理“換色紙”時,會打亂了整個創作的“抽象層次”。他的思維在“最終藝術效果”和“具體燈具型號”之間來回跳躍,導致的結果就是:在場的所有人(包括他自己)都感到混亂,無法集中精力完成自己層級的本職工作,最終的藝術效果也必然大打折扣。
軟件工程層面的抽象層次混亂
??最近修改codebase中的一些代碼,注意到有很多代碼存在多個抽象層次混用的狀況,需要修改的Bug正好和該問題有關,不貼原代碼,通過AI,創造出風格類似的代碼,如下:
def register_user(username, email, password, user_type):
# 第1層:業務邏輯層 - 業務規則判斷
if user_type == 'premium':
if len(username) < 6:
return {'success': False, 'error': 'Premium用戶名至少6位'}
# 業務規則:premium用戶需要特殊驗證
special_validation_needed = True
else:
special_validation_needed = False
# 第2層:數據處理層 - 數據驗證和轉換
# 郵箱格式驗證
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
return {'success': False, 'error': '郵箱格式不正確'}
# 密碼強度檢查
if len(password) < 8 or not re.search(r'[A-Z]', password) or not re.search(r'[0-9]', password):
return {'success': False, 'error': '密碼必須至少8位且包含大寫字母和數字'}
# 密碼加密
password_hash = hashlib.sha256(password.encode()).hexdigest()
# 第3層:底層技術細節 - 數據庫操作
try:
# 直接在業務函數中處理數據庫連接
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# 檢查用戶是否已存在
cursor.execute("SELECT id FROM users WHERE username = ? OR email = ?",
(username, email))
if cursor.fetchone():
conn.close()
return {'success': False, 'error': '用戶名或郵箱已存在'}
# 插入用戶數據
cursor.execute("""
INSERT INTO users (username, email, password_hash, user_type, created_at)
VALUES (?, ?, ?, ?, ?)
""", (username, email, password_hash, user_type, datetime.now()))
user_id = cursor.lastrowid
conn.commit()
conn.close()
except sqlite3.Error as e:
# 業務層被迫處理數據庫技術細節
return {'success': False, 'error': f'數據庫錯誤: {str(e)}'}
# 第3層:底層技術細節 - 郵件發送
try:
# 業務函數直接處理SMTP配置
smtp_server = smtplib.SMTP('smtp.gmail.com', 587)
smtp_server.starttls()
smtp_server.login('your_email@gmail.com', 'your_password')
# 根據用戶類型發送不同郵件(業務邏輯與技術細節混雜)
if user_type == 'premium':
subject = "歡迎成為Premium用戶!"
body = f"親愛的 {username},感謝您成為我們的Premium用戶..."
else:
subject = "歡迎注冊!"
body = f"親愛的 {username},感謝您的注冊..."
msg = MIMEText(body)
msg['Subject'] = subject
msg['From'] = 'your_email@gmail.com'
msg['To'] = email
smtp_server.send_message(msg)
smtp_server.quit()
except smtplib.SMTPException as e:
# 業務層被迫處理郵件服務器技術細節
# 這里還要考慮:郵件發送失敗了,要不要回滾數據庫?
pass # 先忽略郵件發送失敗
# 第1層:業務邏輯層 - 返回業務結果
return {
'success': True,
'user_id': user_id,
'message': f'{"Premium" if user_type == "premium" else "普通"}用戶注冊成功'
??整個代碼從抽象角度,可以看做是一個業務單元的完整操作,示例代碼是一次用戶注冊:
-
輸入驗證與規則檢查 - 根據用戶類型執行不同的業務規則校驗,對郵箱格式、密碼強度等關鍵數據進行合法性驗證。
-
數據轉換與持久化 - 將原始密碼轉換為哈希值,檢查用戶唯一性約束,將用戶信息存儲到數據庫中獲取用戶ID。
-
外部服務交互 - 根據用戶類型構造相應的歡迎郵件內容,通過SMTP服務發送通知郵件完成用戶體驗閉環。
-
結果封裝與返回 - 將操作結果、用戶ID、狀態信息統一封裝成標準響應格式,為上層調用提供清晰的執行反饋。
??可以看到,該函數的功能橫跨了三個抽象層面,從最上層的注冊用戶業務邏輯,到對于用戶對象的處,再到非常細節的服務交互,數據驗證以及SQL處理,需要橫跨多個抽象層級。
??這種體感,在閱讀該代碼時,會感覺抽象層次非常混亂,考慮業務邏輯的同時,還需要看到密碼長度規則的細節,如果這類代碼讀的多,很容易迷失在細節中,總結來看,會有下面問題:
-
代碼可讀性極差,難以理解(“認知過載”):大腦必須在不同的抽象思維層面之間頻繁切換,思維需要跨層次, 這種感覺很擰巴, 就像你想規劃一個旅行,規劃行程的過程中,同時考慮是不是要提前值機,選哪個座位,而不是更high level的去哪玩。
-
代碼難以維護和修改:在一個混合了不同抽象層次的“大泥球”函數中,各個部分緊密地耦合在一起。任何微小的改動都可能引發意想不到的連鎖反應。
-
代碼難以測試:函數不僅圈復雜度高,同時又存在很多外部依賴,單元測試會非常困難。
-
代碼復用性極低:整個代碼僅適用于單一流程(用戶注冊),其他模塊想要引用,就會陷入到香蕉大猩猩問題 :你想要一個香蕉,但你得到的是一個大猩猩拿著香蕉,以及整個叢林。
單一抽象層次原則 (Single Level of Abstraction Principle, SLAP)
??如何解決?Uncle Bob在Clean Code中提出過一個SLAP原則,簡單來說就是在一個函數(或者方法),所有代碼語句都應該在同一個“抽象級別”上。在我看來類的也應該遵循該原則。
??在開頭的故事中,導演、攝影指導、燈光師所需要面對的問題分別位于三個層次,如下圖:

??每個角色僅考慮當前抽象層次的問題,使得分工協作效率大增。
??題外話,軟件工程還有一個說法,如果希望做好當前層級的事,需要對下一層級也有了解(例如,寫SQL可以不懂優化器,但懂了優化器可以寫出更好的SQL)。所以希望用AI寫出好的代碼,AI下層的軟件工程理論變的更加重要,否則生成的代碼對于大型項目可能是維護性災難。
??因此回到開頭的函數register_user,按開頭電影導演故事的抽象層次拆分,分為3部分
業務協調層 - 最高抽象層次
??這一層是整個業務流程的“導演”或“總指揮”。它的核心職責是編排和協調,而非執行。就像導演告訴攝影指導“我需要孤獨感”,register_user函數通過調用User.create, user.save和_send_welcome_email等一系列高層次的指令,清晰地描述了“用戶注冊”這個業務故事的“What”,而不是“How”。
# ============================================================================
# 第1層:業務協調層 - 最高抽象層次
# ============================================================================
def register_user(username: str, email: str, password: str, user_type: str) -> Dict[str, Any]:
"""用戶注冊主流程 - 業務協調,最高抽象層次"""
# 創建用戶(內部會驗證)
user_result = User.create(username, email, password, user_type)
if not user_result.success:
return {'success': False, 'error': user_result.error}
user = user_result.user
# 保存用戶
save_result = user.save()
if not save_result.success:
return {'success': False, 'error': save_result.error}
# 發送歡迎郵件
_send_welcome_email(user)
# 返回成功結果
return _create_success_response(user)
def _send_welcome_email(user: 'User') -> None:
"""發送歡迎郵件 - 協調層職責"""
try:
email_content = _get_welcome_email_content(user)
email_sender = EmailSender()
email_sender.send(user.email, email_content)
except Exception:
# 郵件發送失敗不影響注冊
pass
def _get_welcome_email_content(user: 'User') -> Dict[str, str]:
"""獲取歡迎郵件內容"""
return user.get_welcome_email_content()
def _create_success_response(user: 'User') -> Dict[str, Any]:
"""創建成功響應"""
user_type_display = "Premium" if user.user_type == "premium" else "普通"
return {
'success': True,
'user_id': user.user_id,
'message': f'{user_type_display}用戶注冊成功'
}
業務對象層 - 中抽象層次,健壯的業務對象
??這一層是業務邏輯的“核心承載者”,如同電影拍攝中的“攝影指導”,負責將導演的抽象意圖轉化為具體可執行的“拍攝方案”。在這里,核心是User這個業務對象。
??它不再是一個簡單的貧血數據類,而是一個健壯的、自洽的實體。它封裝了與“用戶”相關的所有業務規則和數據操作:
# ============================================================================
# 第2層:業務對象層 - 中抽象層次,健壯的業務對象
# ============================================================================
@dataclass
class UserCreationResult:
"""用戶創建結果"""
success: bool
user: Optional['User'] = None
error: Optional[str] = None
@dataclass
class SaveResult:
"""保存結果"""
success: bool
user_id: Optional[int] = None
error: Optional[str] = None
class User:
"""用戶 - 核心業務對象,內部驗證保證健壯性"""
def __init__(self, username: str, email: str, password_hash: str, user_type: str):
self.username = username
self.email = email
self.password_hash = password_hash
self.user_type = user_type
self.user_id = None
self.created_at = datetime.now()
@classmethod
def create(cls, username: str, email: str, password: str, user_type: str) -> UserCreationResult:
"""創建用戶 - 內部驗證保證健壯性"""
# 驗證用戶類型
if user_type not in ['regular', 'premium']:
return UserCreationResult(success=False, error='無效的用戶類型')
# 驗證用戶名
username_error = cls._validate_username(username, user_type)
if username_error:
return UserCreationResult(success=False, error=username_error)
# 驗證郵箱
email_error = cls._validate_email(email)
if email_error:
return UserCreationResult(success=False, error=email_error)
# 驗證密碼
password_error = cls._validate_password(password)
if password_error:
return UserCreationResult(success=False, error=password_error)
# 檢查用戶是否已存在
if UserRepository().exists(username, email):
return UserCreationResult(success=False, error='用戶名或郵箱已存在')
# 創建用戶對象
password_hash = cls._hash_password(password)
user = cls(username, email, password_hash, user_type)
return UserCreationResult(success=True, user=user)
def save(self) -> SaveResult:
"""保存用戶到數據庫"""
try:
repository = UserRepository()
self.user_id = repository.add(self)
return SaveResult(success=True, user_id=self.user_id)
except Exception as e:
return SaveResult(success=False, error=f'保存用戶失敗: {str(e)}')
@staticmethod
def _validate_username(username: str, user_type: str) -> Optional[str]:
"""驗證用戶名"""
if not username or not username.strip():
return '用戶名不能為空'
if user_type == 'premium' and len(username) < 6:
return 'Premium用戶名至少6位'
return None
@staticmethod
def _validate_email(email: str) -> Optional[str]:
"""驗證郵箱"""
if not email or not email.strip():
return '郵箱不能為空'
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
return '郵箱格式不正確'
return None
@staticmethod
def _validate_password(password: str) -> Optional[str]:
"""驗證密碼"""
if not password:
return '密碼不能為空'
if len(password) < 8:
return '密碼必須至少8位'
if not re.search(r'[A-Z]', password):
return '密碼必須包含大寫字母'
if not re.search(r'[0-9]', password):
return '密碼必須包含數字'
return None
def get_welcome_email_content(self) -> Dict[str, str]:
"""獲取歡迎郵件內容"""
if self.user_type == 'premium':
return {
'subject': '歡迎成為Premium用戶!',
'body': f'親愛的 {self.username},感謝您成為我們的Premium用戶...'
}
else:
return {
'subject': '歡迎注冊!',
'body': f'親愛的 {self.username},感謝您的注冊...'
}
@staticmethod
def _hash_password(password: str) -> str:
"""生成密碼哈希"""
return hashlib.sha256(password.encode()).hexdigest()
基礎設施層 - 最低抽象層次,純技術實現
??這一層是整個體系的“燈光師”和“場務”,負責所有具體的“臟活累活”。它包含了與外部世界(如數據庫、文件系統、郵件服務器等)打交道的所有技術實現。
============================================================================
# 第3層:基礎設施層 - 最低抽象層次,純技術實現
# ============================================================================
class UserRepository:
"""用戶倉儲 - 業務場景的數據訪問"""
def __init__(self):
self.db_path = 'users.db'
def exists(self, username: str, email: str) -> bool:
"""檢查用戶是否存在"""
try:
with self._connect() as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT id FROM users WHERE username = ? OR email = ?",
(username, email)
)
return cursor.fetchone() is not None
except Exception:
return False # 數據庫錯誤時保守處理
def add(self, user: User) -> int:
"""添加用戶記錄"""
with self._connect() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO users (username, email, password_hash, user_type, created_at)
VALUES (?, ?, ?, ?, ?)
""", (user.username, user.email, user.password_hash,
user.user_type, user.created_at))
return cursor.lastrowid
def _connect(self):
"""創建數據庫連接"""
return sqlite3.connect(self.db_path)
class EmailSender:
"""郵件發送器 - 純技術實現,不包含業務邏輯"""
def __init__(self):
self.smtp_host = 'smtp.gmail.com'
self.smtp_port = 587
self.username = 'your_email@gmail.com'
self.password = 'your_password'
self.from_email = 'your_email@gmail.com'
def send(self, to_email: str, content: Dict[str, str]) -> None:
"""發送郵件"""
if not to_email or not content:
raise ValueError("郵件地址和內容不能為空")
message = self._create_message(to_email, content)
with self._connect() as smtp:
smtp.send_message(message)
def _create_message(self, to_email: str, content: Dict[str, str]) -> MIMEText:
"""創建郵件消息"""
msg = MIMEText(content.get('body', ''))
msg['Subject'] = content.get('subject', '')
msg['From'] = self.from_email
msg['To'] = to_email
return msg
def _connect(self):
"""創建SMTP連接"""
smtp = smtplib.SMTP(self.smtp_host, self.smtp_port)
smtp.starttls()
smtp.login(self.username, self.password)
return smtp
整個實現的架構如圖:

小結
??就像一個好導演絕不會親自去調燈光色紙一樣,好的代碼也應該各司其職、層次分明。當你的函數既要考慮"用戶注冊的業務邏輯",又要糾結"SMTP服務器配置"時,你就成了那個"愛搶活的導演"——看似很忙很全能,實際上把整個劇組都搞得一團糟。
??這 個問題在AI時代變得更加微妙。AI擅長生成局部完美的代碼片段,就像一個技藝精湛但缺乏大局觀的"萬能助理"——它能幫你寫出完美的SMTP配置,也能生成漂亮的密碼驗證邏輯,但它不會主動告訴你"這些東西不應該混在一個函數里"。AI越強大,開發者的架構思維就越重要
??在AI可以秒生代碼的今天,真正的價值不在于寫得多快,而在于想得多清楚——畢竟,沒有人希望維護一個由AI生成的"意大利面條式巨無霸函數",那種感覺就像拿到了一個大猩猩、香蕉和整個叢林的打包組合...而你只是想吃個香蕉。
浙公網安備 33010602011771號