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

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

このコミットは、cmd/godocツールにおけるディレクトリ読み込みの不具合を修正するものです。具体的には、以前の変更(CL 5783076)によって導入された誤ったロジックを元に戻し、godocがGoファイルを含まないディレクトリを正しく処理できるように修正しています。特に、/docパスに対する特殊な処理("doc hack")が誤りであったことを指摘し、既存のコードが本来意図していた挙動に戻すことで問題を解決しています。

コミット

commit da8efae9fe6a3d5f5e6dffc2c70c835fa6724cbb
Author: Russ Cox <rsc@golang.org>
Date:   Mon Mar 12 13:10:37 2012 -0400

    cmd/godoc: fix directory read
    
    Undo CL 5783076 and apply correct fix.
    
    The /doc hack is wrong.  The code to handle this case was
    already there and just needs a simple fix:
    
            // We didn't find any directories containing Go files.
            // If some directory returned successfully, use that.
    -       if len(all) == 0 && first != nil {
    +       if !haveGo {
                    for _, d := range first {
                            haveName[d.Name()] = true
                            all = append(all, d)
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5783079

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

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

元コミット内容

cmd/godoc: fix directory read

Undo CL 5783076 and apply correct fix.

The /doc hack is wrong. The code to handle this case was
already there and just needs a simple fix:

        // We didn't find any directories containing Go files.
        // If some directory returned successfully, use that.
-       if len(all) == 0 && first != nil {
+       if !haveGo {
                for _, d := range first {
                        haveName[d.Name()] = true
                        all = append(all, d)

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5783079

変更の背景

このコミットは、Go言語のドキュメンテーションツールであるgodocReadDir関数における不具合を修正するために行われました。以前の変更(CL 5783076)が、特定の条件下でディレクトリの内容を正しく読み取れない問題を引き起こしていました。特に、Goファイルを含まないディレクトリの処理や、/docパスに対する不適切な特殊処理(コミットメッセージで「/doc hack」と表現されているもの)が問題の原因でした。

godocは、Goのソースコードからドキュメンテーションを生成し、Webブラウザで閲覧可能にするツールです。そのため、ファイルシステムを正確に走査し、ディレクトリ内のファイルを適切に識別する能力が不可欠です。CL 5783076は、おそらく特定のケースを解決しようとしたものの、より広範なディレクトリ読み込みロジックを壊してしまったか、あるいは不必要な複雑さを導入してしまったと考えられます。

このコミットの目的は、その誤った変更を元に戻し、godocReadDir関数が、Goファイルが存在しないディレクトリであっても、そのサブディレクトリや関連ファイルを適切にリストアップできるようにすることです。コミットメッセージが示唆するように、この問題に対処するための正しいロジックは既に存在しており、以前の変更がそれを妨げていたため、その変更を元に戻すことが「正しい修正」とされています。

前提知識の解説

godoc

godocは、Go言語の公式ドキュメンテーションツールです。Goのソースコード(.goファイル)からコメントや関数シグネチャなどを解析し、自動的にAPIドキュメンテーションを生成します。このドキュメンテーションは、コマンドラインから参照できるだけでなく、HTTPサーバーとして起動してWebブラウザ経由で閲覧することも可能です。godocは、Goの標準ライブラリのドキュメンテーションを提供するために広く利用されており、Go開発者にとって非常に重要なツールです。

os.FileInfo

Go言語のosパッケージは、オペレーティングシステムとのインタラクションを提供します。os.FileInfoインターフェースは、ファイルやディレクトリに関する情報(名前、サイズ、パーミッション、最終更新時刻など)を抽象的に表現するためのものです。os.ReadDiros.Statなどの関数は、このos.FileInfoインターフェースを実装した値を返します。godocReadDir関数も、内部でファイルシステムを読み取る際にos.FileInfoのリストを扱います。

strings.HasSuffix

stringsパッケージは、文字列操作のためのユーティリティ関数を提供します。strings.HasSuffix(s, suffix string) bool関数は、文字列sが指定されたsuffixで終わるかどうかを判定します。このコミットのコードでは、ファイル名が.goで終わるかどうかをチェックするために使用されています。

append関数

Go言語の組み込み関数であるappendは、スライスに要素を追加するために使用されます。append(slice []T, elems ...T) []Tのように使用し、新しい要素を追加したスライスを返します。元のスライスの容量が不足している場合は、より大きな容量を持つ新しい基底配列が割り当てられ、要素がコピーされます。

map (連想配列/ハッシュマップ)

Go言語のmapは、キーと値のペアを格納するデータ構造で、他の言語における連想配列やハッシュマップに相当します。キーは一意であり、それに対応する値に高速にアクセスできます。このコミットのコードでは、haveNameというmap[string]bool型の変数が使用されており、これは既に処理されたファイルやディレクトリの名前を追跡し、重複を防ぐために使われています。

GoにおけるCL (Change List)

Goプロジェクトの開発では、Gerritというコードレビューシステムが使われています。Gerritでは、一連の変更は「Change List (CL)」として管理されます。各CLは、一つ以上のコミットから構成されることがあり、レビューと承認を経てGoのリポジトリにマージされます。コミットメッセージに記載されているCL 5783076CL 5783079は、Gerritにおける特定の変更セットを指します。

技術的詳細

このコミットの核心は、src/cmd/godoc/filesystem.go内のnameSpace.ReadDirメソッドのロジック修正にあります。このメソッドは、godocがファイルシステムを仮想的に読み取るためのインターフェースを提供します。

元のコード(CL 5783076によって導入されたと思われる部分)では、useFilesというフラグが導入され、特に/docパスに対しては常にファイルを含めるという特殊なロジックが適用されていました。これはコミットメッセージで「/doc hack」と批判されている部分です。

修正前のコードの主要な問題点は以下の2点です。

  1. /docパスの特殊処理 (useFilesフラグ):

    		useFiles := false
    
    		// Always include all files under /doc.
    		if path == "/doc" || strings.HasPrefix(path, "/doc/") {
    			useFiles = true // always include docs
    		}
    

    このロジックは、/doc以下のパスでは無条件にすべてのファイルを含めるようにしていました。しかし、godocの本来の意図は、Goファイルが存在しないディレクトリではサブディレクトリのみをリストアップし、Goファイルが存在するディレクトリではそのディレクトリ内のGoファイルとサブディレクトリをリストアップすることです。この「hack」は、その本来のロジックを歪めていました。

  2. Goファイルが見つからなかった場合のフォールバックロジックの誤り:

    	// We didn't find any directories containing Go files.
    	// If some directory returned successfully, use that.
    	if len(all) == 0 && first != nil {
    		for _, d := range first {
    			haveName[d.Name()] = true
    			all = append(all, d)
    		}
    	}
    

    この部分では、allスライス(最終的に返されるファイル情報のリスト)が空であり、かつfirst(最初に成功したディレクトリ読み込みの結果)が存在する場合に、firstの内容をallに追加していました。しかし、godocのロジックでは、haveGoというフラグが、現在のディレクトリまたはそのサブディレクトリにGoファイルが見つかったかどうかを示します。Goファイルが見つからなかった場合でも、サブディレクトリは表示されるべきです。len(all) == 0という条件は、allがGoファイルを含むディレクトリからのエントリでまだ埋められていない場合にのみ適用されるべきであり、haveGoの状態と連動させるべきでした。

このコミットは、これらの問題を以下のように修正しています。

  1. /docパスの特殊処理の削除: useFilesフラグの初期化と、/docパスに対する条件分岐が削除されました。これにより、godocはすべてのパスに対して一貫したディレクトリ読み込みロジックを適用するようになります。

  2. Goファイルが見つからなかった場合のフォールバックロジックの修正: if len(all) == 0 && first != nilという条件が、より適切で意図を反映したif !haveGoに変更されました。

    • haveGoは、nameSpace.ReadDirが走査したディレクトリツリーのどこかでGoファイルが見つかった場合にtrueになるフラグです。
    • !haveGoという条件は、「Goファイルが全く見つからなかった場合」を正確に表します。この場合、first(最初に読み込みに成功したディレクトリの内容)をallに追加することで、Goファイルがないディレクトリでもサブディレクトリが適切に表示されるようになります。
    • さらに、haveName[d.Name()]のチェックが追加され、firstからallに要素を追加する際に、既にallに含まれている(例えば、別のパスから追加された)エントリの重複を防ぐようになりました。これは、haveNameマップが既に処理された名前を追跡しているため、より堅牢な重複排除メカニズムを提供します。

これらの変更により、godocはファイルシステムをより正確に、かつ意図された通りに走査できるようになり、Goファイルが存在しないディレクトリでも適切な表示が行われるようになります。

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

diff --git a/src/cmd/godoc/filesystem.go b/src/cmd/godoc/filesystem.go
index 869e23ca25..e7092ff287 100644
--- a/src/cmd/godoc/filesystem.go
+++ b/src/cmd/godoc/filesystem.go
@@ -420,17 +420,11 @@ func (ns nameSpace) ReadDir(path string) ([]os.FileInfo, error) {
 			first = dir
 		}\n 
-		useFiles := false
-
-		// Always include all files under /doc.
-		if path == "/doc" || strings.HasPrefix(path, "/doc/") {
-			useFiles = true // always include docs
-		}
-
 		// If we don't yet have Go files in 'all' and this directory
 		// has some, add all the files from this directory.
 		// Otherwise, only add subdirectories.
-		if !useFiles && !haveGo {
+		useFiles := false
+		if !haveGo {
 			for _, d := range dir {
 				if strings.HasSuffix(d.Name(), ".go") {
 					useFiles = true
@@ -451,10 +445,12 @@ func (ns nameSpace) ReadDir(path string) ([]os.FileInfo, error) {
 
 	// We didn't find any directories containing Go files.
 	// If some directory returned successfully, use that.
-	if len(all) == 0 && first != nil {
+	if !haveGo {
 		for _, d := range first {
-\t\t\thaveName[d.Name()] = true
-\t\t\tall = append(all, d)
+\t\t\tif !haveName[d.Name()] {\n+\t\t\t\thaveName[d.Name()] = true
+\t\t\t\tall = append(all, d)
+\t\t\t}\n 		}
 	}
 

コアとなるコードの解説

このコミットは、src/cmd/godoc/filesystem.goファイルのnameSpace.ReadDir関数に対して行われた変更です。

  1. /docパスの特殊処理の削除:

    -		useFiles := false
    -
    -		// Always include all files under /doc.
    -		if path == "/doc" || strings.HasPrefix(path, "/doc/") {
    -			useFiles = true // always include docs
    -		}
    

    この部分では、以前のコミットで導入されたuseFiles変数の初期化と、/docパスに対する特殊な条件分岐が削除されています。これにより、godoc/docパスに対しても他のパスと同様の一般的なファイル読み込みロジックを適用するようになります。コミットメッセージにある「/doc hack」がこの部分を指しており、これが不適切であると判断されたため削除されました。

  2. useFiles変数の再定義と条件の変更:

    -		if !useFiles && !haveGo {
    +		useFiles := false
    +		if !haveGo {
    

    useFiles変数が削除されたため、その後のif !useFiles && !haveGoという条件式は無効になります。この変更では、useFilesが再度falseで初期化され、条件式から!useFilesが削除されています。これにより、このブロックはhaveGo(Goファイルが見つかったかどうか)の状態のみに基づいて実行されるようになります。つまり、「まだGoファイルが見つかっていない場合」に、現在のディレクトリ内のGoファイルをallに追加するかどうかを判断するロジックが、よりシンプルかつ意図通りに動作するようになります。

  3. Goファイルが見つからなかった場合のフォールバックロジックの修正:

    -	if len(all) == 0 && first != nil {
    +	if !haveGo {
     		for _, d := range first {
    -\t\t\thaveName[d.Name()] = true
    -\t\t\tall = append(all, d)
    +\t\t\tif !haveName[d.Name()] {\n+\t\t\t\thaveName[d.Name()] = true
    +\t\t\t\tall = append(all, d)
    +\t\t\t}\n 		}
     	}
    

    この変更は、ReadDir関数がGoファイルを含むディレクトリを全く見つけられなかった場合のフォールバックロジックを修正しています。

    • 条件式の変更: if len(all) == 0 && first != nilからif !haveGoに変更されました。
      • 元の条件len(all) == 0 && first != nilは、「allスライスが空(Goファイルがまだ追加されていない)であり、かつfirst(最初に成功したディレクトリ読み込みの結果)が存在する場合」を意味していました。これは、Goファイルが見つからなかった場合にサブディレクトリを表示するという意図を完全にカバーしていませんでした。
      • 新しい条件!haveGoは、「これまでにGoファイルが全く見つからなかった場合」を直接的に示します。この場合、firstに含まれるエントリ(通常はサブディレクトリ)をallに追加することで、Goファイルがないディレクトリでもサブディレクトリが正しく表示されるようになります。
    • 重複排除の追加: for _, d := range firstループ内で、if !haveName[d.Name()]という条件が追加されました。
      • これは、firstからallに要素を追加する際に、既にhaveNameマップに登録されている(つまり、既に処理済みまたは追加済みの)エントリをスキップするためのものです。これにより、同じファイルやディレクトリが複数回allスライスに追加されるのを防ぎ、結果の正確性と効率性を向上させます。

これらの変更により、godocのディレクトリ読み込みロジックはより堅牢になり、Goファイルが存在しないディレクトリの表示や、/docパスの処理が意図通りに行われるようになりました。

関連リンク

参考にした情報源リンク