Names should be as short as possible while still being clear

Ben Hoyt

名前は明確さを保てる範囲で、できるだけ短く

原文は Ben Hoyt により に公開されました。 このブログを購読する

このミニ記事は、PythonのAPI設計について私が行ったトークの一部に基づいていますが、要点はプログラミング言語を問わず当てはまると思います。

Netscapeで働いていたプログラマーのフィル・カールトンは、有名な言葉を残しています。「コンピュータサイエンスで難しいことは2つだけある、キャッシュの無効化と名前付けだ」

何かに名前を付けること自体は実際には簡単です。難しいのは、それをうまく名付けることです。もし名前を付けることがもっと難しければ、良い名前を選ぶのにもっと時間をかけるのかもしれません。

確かに、短すぎる名前を使う開発者もいます。しかし、よりありがちな間違いは、長すぎる名前を使ってしまうことだと私は思います。

長すぎる名前は、重要な情報を見えにくくしてしまいます。例えば、大学を出たばかりのボブが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」という単語が、モジュール名と関数名で2回出てきます。さらに「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では、1行のリスト内包表記やジェネレータ式をよく使います。そういった場合には、さらに踏み込んで1文字の変数名を使ってもかまいません――2つの使用箇所が同じ1行に収まっているなら、ffilefile_objectと同じくらい明確な場合もあります。

例えば、内側のループをCounter.updateとジェネレータ式、1文字の変数名を使って書き換えることもできます。

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

最後に、Goプロジェクトのテックリードであるラズ・コックスの言葉を引用して締めくくりたいと思います。彼は自身の変数命名の哲学をこう述べています。

名前の長さは、その名前が持つ情報量を超えるべきではない。 … グローバルな名前は、より多様な文脈で現れるため、相対的により多くの情報を伝える必要があります。それでも、短く的確な名前は、長々とした名前よりも多くのことを語りうるのです。acquiretake_ownershipを比べてみればよいでしょう。すべての名前に意味を持たせましょう。

最後の一文は詩的です。ただ、私自身のより散文的な「変数命名の哲学」を改めて述べて締めくくりたいと思います。

名前は、明確さを保てる範囲で可能な限り短くあるべきです。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント