Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 チェックロジックを記述する必要がなくなります。これは、よりクリーンで安全なコードパスを提供します。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

  1. Pythonにおける関数の戻り値と None:

    • Pythonの関数は、明示的に return ステートメントで値を返さない場合、または return の後に何も指定しない場合、自動的に None を返します。
    • None はPythonにおける「何もない」ことを表す特別なオブジェクトです。他のプログラミング言語における nullnil に相当します。
    • None は、ブールコンテキストでは False と評価されますが、それ自体は辞書やリストのようなコレクション型ではありません。したがって、None に対して辞書やリストの操作を行おうとすると、TypeError が発生します。
  2. Pythonの try-except ブロック:

    • try-except ブロックは、例外処理のためのPythonの構文です。
    • try ブロック内のコードで例外が発生した場合、その例外は except ブロックで捕捉され、指定された例外処理が実行されます。
    • このコミットでは、open() 関数が CONTRIBUTORS ファイルを見つけられない場合に発生する IOError などの例外を捕捉するために使用されています。
  3. Goプロジェクトのコードレビュープロセスと CONTRIBUTORS ファイル:

    • Goプロジェクトは、GoogleのGerritベースのコードレビューシステムを使用しています。
    • CONTRIBUTORS ファイルは、プロジェクトに貢献した人々のリストを管理するために使用されるテキストファイルです。このファイルは、著作権表示や貢献者のクレジット表示など、法的な側面やプロジェクトの透明性において重要な役割を果たすことがあります。
    • lib/codereview/codereview.py のようなスクリプトは、この CONTRIBUTORS ファイルを読み込み、コードレビューの承認プロセスや、貢献者の統計情報生成などに利用される可能性があります。
  4. 堅牢なプログラミング(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(): のようなループを記述していたとします。contributorsNone の場合、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行目の returnreturn {} に変更されています。

コアとなるコードの解説

変更された行は、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 を回避できます。

関連リンク

参考にした情報源リンク

  • Python公式ドキュメント: None について
  • Python公式ドキュメント: try ステートメント (例外処理)
  • Goプロジェクトのコードレビュープロセスに関する一般的な情報 (Gerritなど)
  • PythonのEAFP (Easier to Ask for Forgiveness than Permission) 原則に関するプログラミングのベストプラクティス