Names should be as short as possible while still being clear

Ben Hoyt

名稱應該在保持清晰的前提下越短越好

原文由 Ben Hoyt 發布,訂閱此部落格

這篇短文是基於我之前關於Python API 設計的一場演講的其中一部分,但我認為其核心觀點適用於各種程式語言。

曾在 Netscape 任職的程式設計師 Phil Karlton曾有一句名言:「在電腦科學中只有兩件困難的事:快取失效和命名。」

其實,要幫東西命名很容易。困難的是要命名得。或許如果命名本身更困難一點,我們反而會花更多時間去挑選好名字。

有些開發者確實會使用過短的名稱。然而,我認為更常見的錯誤是使用過長的名稱。

過長的名稱會讓人更難看清重點資訊。舉例來說,想像一下如果剛從大學畢業的 Bob 來設計Requests這個函式庫,而他的某位老師曾說過「幫函式取個能解釋它在做什麼的名字」。

這建議本身不算糟,但他卻聽成了「幫函式取個能解釋它所做的一切的名字」,於是寫出了這樣的程式碼:

# requests.py
def send_get_request_and_receive_response(url):
    ...

實際使用時會長這樣:

import requests

response = requests.send_get_request_and_receive_response(url)

那這樣有什麼問題呢?多餘的文字並沒有增加任何資訊。事實上,它們反而降低了清晰度:requests.get()一目了然、易於閱讀,而使用較長的名稱時,重點——也就是它是「get」方法這件事——反而被埋在名稱中間。

「request」這個字出現了兩次,一次在模組名稱中,一次在函式名稱中。還有「send」和「receive response」。但這個函式庫的核心目的本來就是發送請求並接收回應,所以這些也都是多餘的。

最後剩下簡潔有力的requests.get()——它清楚扼要地表達了「發送一個 GET 請求」。你不可能再寫得更短了。我的意思是,你當然可以把它寫成requests.g()——但那就太荒謬了。

強烈主張在設計模組時,應該讓使用者以「模組名稱加上點號前綴」的方式來呼叫。Requests 就是遵循這個原則:單獨的get()本身沒什麼意義,但requests.get()就清晰俐落。你也不會遇到名稱衝突,因為模組名稱就在那裡幫你區分。

換句話說,不要為了鉅細靡遺地解釋功能而命名;要讓名稱在上下文中清楚易懂。

使用簡潔的名稱不僅在設計函式庫時很重要,在撰寫一般程式碼時也同樣重要。區域變數的名稱通常可以很短,因為上下文已經由周圍的程式碼——也就是它們所在的函式或腳本——提供了。

我們來看一個例子。最近我在一個repository_statistics.py腳本中看到了這段程式碼:

repository_index_retriever = RepositoryIndexRetriever(args.architecture)
repository_statistics_processor = RepositoryStatisticsProcessor()
for repository_object in repository_index_retriever.retrieve_repositories():
    for repository_file in repository_object.repository_files:
        repository_statistics_processor.increment_repository_file_count(repository_file.name)

for repository_file_name, count in repository_statistics_processor.get_top_n_repositories(10):
    print(repository_file_name, count)

這麼多又長又大的詞!程式碼簡單的邏輯全被淹沒在冗長的文字裡了。

以下是我會如何重寫這段程式碼(對了,RepositoryStatisticsProcessor其實只是包裝得比較華麗的collections.Counter,所以我們就直接用它吧):

fetcher = repolib.IndexFetcher(args.arch)
counts = collections.Counter()
for repo in fetcher.get_repos():
    for file in repo.files:
        counts[file.name] += 1

for repo, count in counts.most_common(10):
    print(repo, count)

結構相同,但邏輯現在清楚多了。

而在 Python 中,你經常會寫單行的串列生成式(list comprehension)或產生器表達式(generator expression)。在寫這些時,你甚至可以更進一步使用單字母的變數名稱——當兩次使用都在同一行時,f可能就跟filefile_object一樣清楚。

舉例來說,我們可以把內層迴圈改成使用Counter.update搭配產生器表達式和單字母變數名稱:

counts.update(f.name for f in repo.files)

最後,我想以 Go 專案的技術負責人 Russ Cox 的一句話作結,他這樣闡述他的變數命名哲學

名稱的長度不應超過它所承載的資訊量。 … 全域名稱必須傳達相對更多的資訊,因為它們出現在更多樣的上下文中。即便如此,一個簡短而精確的名稱,往往比冗長的名稱更能表達意思:比較一下acquiretake_ownership。讓每個名稱都有意義。

最後那句話簡直是詩。但我還是要用自己比較平實的「變數命名哲學」來作結:

名稱應該在保持清晰的前提下越短越好。

本文章由 muse-spark-1.2-contributor 進行翻譯

留言