[インデックス 17779] ファイルの概要
このコミットは、Goプロジェクトのコードレビューツールの一部である lib/codereview/codereview.py
における小さな、しかし重要な変更を導入しています。具体的には、CONTRIBUTORS
ファイルが見つからない場合に、関数が None
を返す代わりに空の辞書を返すように修正されています。これにより、後続のコードでの None
チェックが不要になり、より堅牢で予測可能な動作が保証されます。
コミット
commit b6c0c4228d62406fbe24c4357410813715fdb75d
Author: Francesc Campoy <campoy@golang.org>
Date: Thu Oct 10 17:16:17 2013 -0700
lib/codereview: return an empty list when CONTRIBUTORS is not found instead of None.
R=adg, campoy, r
CC=golang-dev
https://golang.org/cl/14419059
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b6c0c4228d62406fbe24c4357410813715fdb75d
元コミット内容
lib/codereview: return an empty list when CONTRIBUTORS is not found instead of None.
このコミットメッセージは、lib/codereview
ディレクトリ内のコードレビュー関連スクリプトにおいて、CONTRIBUTORS
ファイルが見つからない場合に、関数が None
ではなく空のリスト(実際にはコードの変更内容から空の辞書 {}
)を返すように変更されたことを示しています。
変更の背景
この変更の背景には、Pythonにおける関数の戻り値の扱いと、それによって引き起こされる可能性のあるランタイムエラーの回避があります。
Goプロジェクトのような大規模なオープンソースプロジェクトでは、コードの貢献者(コントリビューター)を管理するための CONTRIBUTORS
ファイルが存在することが一般的です。このファイルには、プロジェクトに貢献した人々の情報が記載されており、コードレビューシステムがこの情報を利用することがあります。
lib/codereview/codereview.py
は、Goプロジェクトのコードレビュープロセスを支援するPythonスクリプトの一部です。ReadContributors
関数は、この CONTRIBUTORS
ファイルを読み込み、その内容を処理することを目的としています。
元の実装では、CONTRIBUTORS
ファイルを開く際に例外(例えば、ファイルが存在しない IOError
など)が発生した場合、関数は明示的に return
ステートメントのみを実行していました。Pythonでは、return
の後に値が指定されていない場合、関数は暗黙的に None
を返します。
この None
という戻り値は、ReadContributors
関数の呼び出し元で問題を引き起こす可能性がありました。呼び出し元が None
が返されることを想定しておらず、返された値が辞書であることを前提として処理を続行した場合、None
オブジェクトに対して辞書操作(例: len()
, for key in dict:
, dict[key]
など)を行おうとすると、TypeError
などのランタイムエラーが発生します。
このコミットは、このような潜在的なエラーを回避し、コードの堅牢性を向上させるために行われました。CONTRIBUTORS
ファイルが見つからないという「正常ではないが予期される」状況において、None
ではなく空の辞書を返すことで、呼び出し元は常に辞書型のオブジェクトを受け取ることが保証され、追加の None
チェックロジックを記述する必要がなくなります。これは、よりクリーンで安全なコードパスを提供します。
前提知識の解説
このコミットを理解するためには、以下の前提知識が役立ちます。
-
Pythonにおける関数の戻り値と
None
:- Pythonの関数は、明示的に
return
ステートメントで値を返さない場合、またはreturn
の後に何も指定しない場合、自動的にNone
を返します。 None
はPythonにおける「何もない」ことを表す特別なオブジェクトです。他のプログラミング言語におけるnull
やnil
に相当します。None
は、ブールコンテキストではFalse
と評価されますが、それ自体は辞書やリストのようなコレクション型ではありません。したがって、None
に対して辞書やリストの操作を行おうとすると、TypeError
が発生します。
- Pythonの関数は、明示的に
-
Pythonの
try-except
ブロック:try-except
ブロックは、例外処理のためのPythonの構文です。try
ブロック内のコードで例外が発生した場合、その例外はexcept
ブロックで捕捉され、指定された例外処理が実行されます。- このコミットでは、
open()
関数がCONTRIBUTORS
ファイルを見つけられない場合に発生するIOError
などの例外を捕捉するために使用されています。
-
Goプロジェクトのコードレビュープロセスと
CONTRIBUTORS
ファイル:- Goプロジェクトは、GoogleのGerritベースのコードレビューシステムを使用しています。
CONTRIBUTORS
ファイルは、プロジェクトに貢献した人々のリストを管理するために使用されるテキストファイルです。このファイルは、著作権表示や貢献者のクレジット表示など、法的な側面やプロジェクトの透明性において重要な役割を果たすことがあります。lib/codereview/codereview.py
のようなスクリプトは、このCONTRIBUTORS
ファイルを読み込み、コードレビューの承認プロセスや、貢献者の統計情報生成などに利用される可能性があります。
-
堅牢なプログラミング(Robust Programming):
- 堅牢なプログラミングとは、予期せぬ入力やエラー条件に対しても、プログラムがクラッシュしたり、不正な状態になったりすることなく、適切に動作し続けるように設計することです。
- このコミットは、ファイルが見つからないという「予期されるエラー条件」に対して、
None
を返すことで後続の処理でエラーを引き起こすのではなく、空の辞書を返すことで、呼び出し元が常に有効な(ただし空の)辞書を扱えるようにするという、堅牢性向上の典型的な例です。
技術的詳細
このコミットの技術的な詳細は、Pythonの例外処理と関数の戻り値のセマンティクスに集約されます。
変更は lib/codereview/codereview.py
ファイル内の ReadContributors
関数にあります。この関数は、repo.root + '/CONTRIBUTORS'
というパスにある CONTRIBUTORS
ファイルを開こうとします。
元のコードでは、ファイルを開く操作が try
ブロック内にあり、何らかの例外(例えば、ファイルが存在しない場合の IOError
や、アクセス権の問題など)が発生した場合、except
ブロックが実行されます。
# 元のコードの一部
try:
f = open(repo.root + '/CONTRIBUTORS', 'r')
except:
ui.write("warning: cannot open %s: %s\\n" % (opening, ExceptionDetail()))
return # ここで暗黙的に None が返される
この return
ステートメントは、関数を即座に終了させますが、明示的な戻り値が指定されていないため、Pythonのデフォルトの動作として None
が呼び出し元に返されます。
問題は、ReadContributors
関数の呼び出し元が、常に辞書型のオブジェクトが返されることを期待している場合に発生します。例えば、呼び出し元が contributors = ReadContributors(...)
の後に for name, email in contributors.items():
のようなループを記述していたとします。contributors
が None
の場合、None.items()
は AttributeError: 'NoneType' object has no attribute 'items'
というエラーを引き起こします。
このコミットによる変更は、この問題を解決します。
# 変更後のコードの一部
try:
f = open(repo.root + '/CONTRIBUTORS', 'r')
except:
ui.write("warning: cannot open %s: %s\\n" % (opening, ExceptionDetail()))
return {} # ここで明示的に空の辞書が返される
return {}
とすることで、例外が発生した場合でも、ReadContributors
関数は常に辞書型のオブジェクト(この場合は空の辞書)を返します。これにより、呼び出し元は返された値が常に辞書であることを安全に仮定でき、追加の None
チェックロジックを記述する必要がなくなります。
これは、Pythonの「EAFP (Easier to Ask for Forgiveness than Permission)」原則に沿った変更とも言えます。つまり、操作を試みて、失敗した場合に例外を処理する(try-except
)というアプローチを取りつつ、失敗した場合の戻り値を、呼び出し元が扱いやすい一貫した型にすることで、後続のコードの複雑さを軽減しています。
コアとなるコードの変更箇所
変更は lib/codereview/codereview.py
ファイルの1箇所のみです。
--- a/lib/codereview/codereview.py
+++ b/lib/codereview/codereview.py
@@ -984,7 +984,7 @@ def ReadContributors(ui, repo):\n \t\tf = open(repo.root + \'/CONTRIBUTORS\', \'r\')\n \texcept:\n \t\tui.write("warning: cannot open %s: %s\\n" % (opening, ExceptionDetail()))\n-\t\treturn\n+\t\treturn {}\n \n \tcontributors = {}\n \tfor line in f:
具体的には、986行目の return
が return {}
に変更されています。
コアとなるコードの解説
変更された行は、ReadContributors
関数内の try-except
ブロックの except
節にあります。
def ReadContributors(ui, repo):
# ... (ファイルのオープンを試みる部分)
try:
f = open(repo.root + '/CONTRIBUTORS', 'r')
except:
# ファイルのオープンに失敗した場合の処理
ui.write("warning: cannot open %s: %s\\n" % (opening, ExceptionDetail()))
return {} # ここが変更点:None ではなく空の辞書を返す
contributors = {}
for line in f:
# ... (CONTRIBUTORS ファイルの内容を解析する部分)
pass # 実際にはここでファイルの内容を解析し、contributors 辞書に格納する
return contributors
この変更により、CONTRIBUTORS
ファイルの読み込みに失敗した場合(例えば、ファイルが存在しない、読み取り権限がないなどの理由で open()
が例外を発生させた場合)、関数は警告メッセージを ui
オブジェクトに書き込んだ後、空の辞書 {}
を返して終了します。
この修正の意図は、ReadContributors
の呼び出し元が、ファイルが見つからない場合でも常に辞書型のオブジェクトを受け取ることを保証することです。これにより、呼び出し元は返されたオブジェクトが None
であるかどうかを明示的にチェックする必要がなくなり、コードの簡潔さと堅牢性が向上します。例えば、呼び出し元が返された contributors
オブジェクトに対して for key, value in contributors.items():
のようなイテレーションを行う場合、contributors
が空の辞書であればループは単に実行されず、None
であれば発生する AttributeError
を回避できます。
関連リンク
- GoプロジェクトのGitHubリポジトリ: https://github.com/golang/go
- このコミットのGerrit Code Reviewリンク: https://golang.org/cl/14419059
参考にした情報源リンク
- Python公式ドキュメント:
None
について - Python公式ドキュメント:
try
ステートメント (例外処理) - Goプロジェクトのコードレビュープロセスに関する一般的な情報 (Gerritなど)
- PythonのEAFP (Easier to Ask for Forgiveness than Permission) 原則に関するプログラミングのベストプラクティス