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

[インデックス 11474] ファイルの概要

このコミットは、Go言語のコードレビューツールである codereview.py スクリプトに対する変更です。具体的には、lib/codereview/codereview.py ファイルが修正されています。このファイルは、Goプロジェクトにおけるコードのフォーマットチェック(gofmt)を自動化し、コードレビュープロセスに統合するためのロジックを含んでいます。

コミット

このコミットは、codereview ツールが gofmt を実行する際に、テストファイル(test/ ディレクトリ以下のファイル)を無視するように変更します。ただし、ベンチマークテストファイル(test/bench/ 以下)は引き続き gofmt の対象とします。これにより、テストコードのフォーマットが gofmt の厳格なルールに常に従う必要がなくなり、開発の柔軟性が向上します。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/deeb1b36ddd7a59871d7e6bb088cf06c71da5ebd

元コミット内容

codereview: ignore test files during 'hg gofmt'

R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5581047

変更の背景

Go言語の公式ツールセットには、コードの自動フォーマットツールである gofmt が含まれています。gofmt は、Goコードの標準的なフォーマットを強制し、コードベース全体の一貫性を保つ上で非常に重要な役割を果たします。Goコミュニティでは、gofmt を通したコードの自動フォーマットが強く推奨されており、多くのプロジェクトでCI/CDパイプラインの一部として組み込まれています。

しかし、テストファイル(通常 _test.go で終わるファイル)は、本番コードとは異なる性質を持つことがあります。例えば、特定のテストケースを意図的に読みにくく記述したり、特定のフォーマットルールから逸脱した記述を許容したりする場合があります。gofmt がテストファイルに対しても厳格に適用されると、開発者がテストコードの可読性や特定のテストシナリオの表現のために、gofmt のルールに反する記述をせざるを得ない場合に不便が生じます。

このコミットの背景には、gofmt の適用範囲に関する実用的な考慮事項があります。特に、Goプロジェクトのコードレビュープロセスで使用される codereview ツールが gofmt を実行する際に、テストファイルが常にフォーマットチェックの対象となることで、不必要な警告やエラーが発生し、開発者のワークフローを妨げる可能性がありました。ベンチマークテストファイル(test/bench/ 以下)は、パフォーマンス測定の正確性を期すために、本番コードに近い厳格なフォーマットが求められる場合があるため、例外的に gofmt の対象として残されています。

この変更は、gofmt の恩恵を受けつつも、テストコードの記述における柔軟性を確保し、開発者の生産性を向上させることを目的としています。

前提知識の解説

gofmt

gofmt は、Go言語のソースコードを自動的にフォーマットするツールです。Goの標準ライブラリの一部として提供されており、Go言語のコードスタイルガイドラインに準拠した一貫したコードフォーマットを強制します。gofmt を実行すると、インデント、スペース、改行などが自動的に調整され、Goコミュニティ全体で統一されたコードスタイルが維持されます。これにより、コードの可読性が向上し、異なる開発者間でのコードスタイルの議論が不要になります。

codereview ツール

codereview は、Goプロジェクトで利用されていたコードレビューシステムの一部を構成するPythonスクリプトです。これは、Mercurial (hg) リポジトリと連携し、変更セットの作成、レビューコメントの管理、そしてコードの品質チェック(gofmt の実行を含む)を自動化する役割を担っていました。このツールは、Googleの内部コードレビューシステム(Mondrian)にインスパイアされており、Goプロジェクトの初期段階で広く利用されていました。現在では、Goプロジェクトのコードレビューは主にGerritを通じて行われていますが、このコミットが作成された時点では codereview.py が重要な役割を果たしていました。

Goのテストファイル命名規則

Go言語では、テストファイルは通常、テスト対象のソースファイルと同じディレクトリに配置され、ファイル名の末尾に _test.go を付けます(例: my_package.go のテストは my_package_test.go)。これにより、Goツールチェーンは自動的にテストファイルを認識し、go test コマンドで実行できるようになります。また、ベンチマークテストファイルは _test.go ファイル内に記述され、通常 Benchmark プレフィックスを持つ関数として定義されます。

Pythonのリスト内包表記

このコミットの変更箇所では、Pythonのリスト内包表記が使用されています。リスト内包表記は、既存のリストから新しいリストを作成するための簡潔な構文です。例えば、[f for f in files if condition(f)] は、files リストの各要素 f に対して condition(f) が真である場合にのみ、その f を新しいリストに含めます。

技術的詳細

このコミットの技術的な核心は、codereview.py スクリプト内で gofmt の対象となるファイルをフィルタリングするロジックの変更にあります。

変更前は、CheckGofmt 関数と gofmt 関数(codereview コマンドの gofmt サブコマンドに対応)の両方で、gofmt を適用するファイルを決定するためのフィルタリングロジックが直接記述されていました。このロジックは以下の条件に基づいていました。

  1. ファイルが .go 拡張子を持つこと (f.endswith('.go'))。
  2. ファイルパスが test/ で始まらないこと、または test/bench/ で始まること (not f.startswith('test/') or f.startswith('test/bench/'))。

この条件は、test/ ディレクトリ以下のファイルは通常無視するが、test/bench/ ディレクトリ以下のファイルは例外的に含める、という意図を表現しています。

このコミットでは、この重複するフィルタリングロジックを gofmt_required という新しいヘルパー関数に抽出し、再利用するように変更されました。

gofmt_required 関数の導入

新しく導入された gofmt_required 関数は、ファイルのリストを受け取り、gofmt の適用が必要なファイルのみをフィルタリングして返します。この関数は、以前のフィルタリングロジックをそのままカプセル化しています。

def gofmt_required(files):
    return [f for f in files if (not f.startswith('test/') or f.startswith('test/bench/')) and f.endswith('.go')]

CheckGofmt 関数の変更

CheckGofmt 関数は、コードレビュー時に gofmt のフォーマットチェックを実行する部分です。変更前は、ファイルのフィルタリングが直接行われていました。

# 変更前
files = [f for f in files if (not f.startswith('test/') or f.startswith('test/bench/')) and f.endswith('.go')]

# 変更後
files = gofmt_required(files)

この変更により、CheckGofmtgofmt_required 関数を呼び出すだけで、gofmt の対象となるファイルを簡単に取得できるようになりました。

gofmt 関数の変更

gofmt 関数(codereview gofmt コマンドの実行ロジック)も同様に、ファイルのフィルタリングロジックを gofmt_required 関数に置き換えました。

# 変更前
files = [f for f in files if f.endswith(".go")] # この行は、元のコミットメッセージのdiffと少し異なりますが、
                                                # 実際のdiffではより複雑なフィルタリングが適用されていました。
                                                # コミットの意図としては、gofmt_requiredに集約することです。

# 変更後
files = gofmt_required(files)

この変更により、gofmt コマンドも一貫した方法でファイルをフィルタリングするようになりました。

コードの重複排除と保守性の向上

この変更の主な技術的メリットは、コードの重複を排除し、保守性を向上させた点にあります。同じフィルタリングロジックが複数の場所に散らばっていると、将来的にそのロジックを変更する必要が生じた場合に、すべての箇所を修正する必要があり、エラーのリスクが高まります。gofmt_required 関数にロジックをカプセル化することで、gofmt の対象ファイルに関するルールを変更する際には、この関数を修正するだけで済むようになります。これは、ソフトウェア開発におけるDRY (Don't Repeat Yourself) 原則の良い例です。

コアとなるコードの変更箇所

--- a/lib/codereview/codereview.py
+++ b/lib/codereview/codereview.py
@@ -895,7 +895,7 @@ def CheckFormat(ui, repo, files, just_warn=False):
 
 # Check that gofmt run on the list of files does not change them
 def CheckGofmt(ui, repo, files, just_warn):\n-\tfiles = [f for f in files if (not f.startswith(\'test/\') or f.startswith(\'test/bench/\')) and f.endswith(\'.go\')]\n+\tfiles = gofmt_required(files)\n \tif not files:\n \t\treturn\n \tcwd = os.getcwd()\n@@ -1749,7 +1749,7 @@ def gofmt(ui, repo, *pats, **opts):\n \t\treturn codereview_disabled\n \n \tfiles = ChangedExistingFiles(ui, repo, pats, opts)\n-\tfiles = [f for f in files if f.endswith(\".go\")]\n+\tfiles = gofmt_required(files)\n \tif not files:\n \t\treturn \"no modified go files\"\n \tcwd = os.getcwd()\n@@ -1766,6 +1766,9 @@ def gofmt(ui, repo, *pats, **opts):\n \t\traise hg_util.Abort(\"gofmt: \" + ExceptionDetail())\n \treturn\n \n+def gofmt_required(files):\n+\treturn [f for f in files if (not f.startswith(\'test/\') or f.startswith(\'test/bench/\')) and f.endswith(\'.go\')]\n+\n #######################################################################\n # hg mail

コアとなるコードの解説

上記のdiffは、lib/codereview/codereview.py ファイルにおける3つの主要な変更を示しています。

  1. CheckGofmt 関数の変更:

    • 変更前: files = [f for f in files if (not f.startswith('test/') or f.startswith('test/bench/')) and f.endswith('.go')]
      • この行は、渡された files リストから、gofmt を適用すべきファイルをフィルタリングしています。条件は、「test/ で始まらない、または test/bench/ で始まる」かつ「.go で終わる」ファイルです。
    • 変更後: files = gofmt_required(files)
      • フィルタリングロジックが gofmt_required という新しいヘルパー関数に抽出され、その関数を呼び出す形に変更されました。これにより、CheckGofmt 関数の内部がより簡潔になり、フィルタリングの具体的なロジックが抽象化されました。
  2. gofmt 関数の変更:

    • 変更前: files = [f for f in files if f.endswith(".go")]
      • この行も、gofmt コマンドが処理するファイルをフィルタリングしています。元のdiffでは .go ファイルのみを対象としていますが、これはコミットの意図(テストファイルを無視する)を完全に反映していません。実際の変更は、CheckGofmt と同様に、より複雑なフィルタリングロジックを gofmt_required に集約することを目指しています。
    • 変更後: files = gofmt_required(files)
      • ここでも、フィルタリングロジックが gofmt_required 関数に置き換えられ、コードの重複が解消されました。
  3. gofmt_required 関数の新規追加:

    • def gofmt_required(files):
    • return [f for f in files if (not f.startswith('test/') or f.startswith('test/bench/')) and f.endswith('.go')]
      • この新しい関数は、gofmt を適用すべきファイルを決定するための共通のフィルタリングロジックをカプセル化しています。これにより、同じロジックが CheckGofmtgofmt の両方で再利用され、コードの重複が排除され、将来的なメンテナンスが容易になりました。

これらの変更は、gofmt の適用範囲をより正確に制御し、特にテストファイルに対する gofmt の強制を緩和することで、開発者のワークフローを改善することを目的としています。同時に、コードの構造を改善し、重複を排除することで、codereview.py スクリプト自体の保守性も向上させています。

関連リンク

  • Go言語公式ドキュメント - gofmt: gofmt の詳細については、Go言語の公式ドキュメントを参照してください。
  • Go言語のテスト: Go言語におけるテストの書き方や慣習については、公式のTestingパッケージのドキュメントが参考になります。
  • Goのコードレビュープロセス (Gerrit): 現在のGoプロジェクトのコードレビューはGerritで行われています。当時の codereview.py とは異なりますが、Goのコードレビュー文化を理解する上で参考になります。

参考にした情報源リンク

  • コミット情報: /home/orange/Project/comemo/commit_data/11474.txt
  • GitHubコミットページ: https://github.com/golang/go/commit/deeb1b36ddd7a59871d7e6bb088cf06c71da5ebd
  • Go言語の公式ドキュメント (gofmt, testing パッケージ)
  • Pythonのリスト内包表記に関する一般的な知識
  • Goプロジェクトの歴史とコードレビューツールの変遷に関する一般的な知識 (Web検索による)
  • Mercurial (hg) に関する一般的な知識 (Web検索による)