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

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

このコミットは、Go言語の仕様書(doc/go_spec.html)を更新し、循環インポート(cyclic imports)が不正であることを明示的に記述するものです。具体的には、パッケージが直接的または間接的に自身をインポートすること、およびエクスポートされた識別子を参照せずにパッケージを直接インポートすることが不正であるというルールを明確化しています。

コミット

commit 4be38dde841ea5581661ca327e11b4199b69a460
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Mar 4 12:59:40 2013 -0800

    spec: cyclic imports are illegal
    
    Fixes #4976.
    
    R=r
    CC=golang-dev
    https://golang.org/cl/7421050

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

https://github.com/golang/go/commit/4be38dde841ea5581661ca327e11b4199b69a460

元コミット内容

spec: cyclic imports are illegal

Fixes #4976.

R=r
CC=golang-dev
https://golang.org/cl/7421050

変更の背景

このコミットの背景には、Go言語におけるパッケージの依存関係の明確化と、循環インポートがもたらす問題への対処があります。Go言語は、クリーンでモジュール化されたコードと高速なコンパイル時間を奨励するために、循環インポートを許可していません。

コミットメッセージにある「Fixes #4976」は、GoのIssueトラッカーにおける4976番の課題を解決することを示しています。この課題は、Goの仕様書において循環インポートの禁止が十分に明確に記述されていない、あるいはその定義が曖昧であるという問題提起であったと推測されます。Goコンパイラは循環インポートを検出すると「import cycle not allowed」というエラーを発生させますが、このエラーの根拠となる仕様の記述をより厳密にすることが求められていました。

循環インポートは、ソフトウェア設計において密結合(tight coupling)の兆候であり、コードの理解、テスト、保守を困難にします。また、コンパイル時の依存関係解決を複雑にし、ビルド時間の増加や、場合によっては無限ループのような問題を引き起こす可能性があります。Go言語の設計思想として、このような問題を未然に防ぎ、より健全なパッケージ構造を促進するために、仕様レベルで循環インポートを禁止することが重要であると判断されました。

前提知識の解説

パッケージとインポート

Go言語では、コードは「パッケージ」という単位で組織されます。パッケージは関連する機能の集合であり、他のパッケージから利用されることを目的としています。あるパッケージが別のパッケージの機能を利用するためには、import宣言を用いてそのパッケージをインポートする必要があります。

例:

import "fmt" // fmtパッケージをインポート
import "net/http" // net/httpパッケージをインポート

循環インポート(Cyclic Imports / Import Cycles)

循環インポートとは、複数のパッケージが互いに直接的または間接的に依存し合う状態を指します。

  • 直接的な循環インポート: パッケージAがパッケージBをインポートし、同時にパッケージBがパッケージAをインポートするケース。
    • package A imports package B
    • package B imports package A
  • 間接的な循環インポート: パッケージAがパッケージBをインポートし、パッケージBがパッケージCをインポートし、パッケージCがパッケージAをインポートするケース。
    • package A imports package B
    • package B imports package C
    • package C imports package A

Go言語における循環インポートの禁止

Go言語は、設計上、循環インポートを厳しく禁止しています。これは、以下の理由に基づいています。

  1. コンパイルの複雑性: 循環依存があると、コンパイラはどのパッケージを最初にコンパイルすべきかを決定できなくなります。依存関係グラフにサイクルが存在すると、トポロジカルソートが不可能になります。
  2. 初期化順序の曖昧さ: Goでは、パッケージの初期化はインポート順序に依存します。循環依存があると、初期化の順序が曖昧になり、予期せぬ動作やバグの原因となる可能性があります。
  3. コードの密結合: 循環インポートは、パッケージ間の密結合を示唆しています。これは、各パッケージが独立した責任を持つというモジュール設計の原則に反します。密結合なコードは、変更が他の部分に予期せぬ影響を与えやすく、テストや保守が困難になります。
  4. 可読性と理解の低下: 循環依存があるコードベースは、依存関係を追跡するのが難しく、コードの全体像を把握するのを妨げます。

Goコンパイラは、循環インポートを検出するとコンパイルエラーを発生させ、開発者にその解消を促します。

副作用のためのインポート

Goでは、パッケージの初期化関数(init()関数)を実行するためだけにパッケージをインポートすることが可能です。この場合、インポートされたパッケージの識別子をコード内で直接使用する必要がないため、ブランク識別子(_)を用いてインポートします。

例:

import _ "github.com/go-sql-driver/mysql" // データベースドライバの登録など、副作用のためにインポート

このコミットの変更前も、この「副作用のためのインポート」は許可されていました。

技術的詳細

このコミットは、Go言語の公式仕様書であるdoc/go_spec.htmlを修正することで、循環インポートに関するルールをより厳密に定義しています。

変更前は、仕様書には「パッケージが自身をインポートすること、またはエクスポートされた識別子を参照せずにパッケージをインポートすることは不正である」と記述されていました。この記述は、直接的な自己インポートと、副作用を伴わない未使用インポートを禁止していましたが、間接的な循環インポートについては明示的に触れていませんでした。

今回の変更では、以下の2点が明確化されました。

  1. 「直接的または間接的に」の追加: 「パッケージが自身をインポートすること」の後に「直接的または間接的に(directly or indirectly)」という文言が追加されました。これにより、A -> B -> C -> A のような間接的な循環インポートも明確に禁止されることが仕様として明記されました。これは、Goコンパイラが既にそのように動作していたことを、仕様書に反映させたものです。
  2. 「直接的に」の追加: 「エクスポートされた識別子を参照せずにパッケージをインポートすること」の前に「直接的に(directly)」という文言が追加されました。これは、副作用のためのインポート(import _ "package")が引き続き許可されることを明確にするためのものです。つまり、ブランク識別子を使ってインポートする場合は、そのパッケージの識別子を参照しなくても合法である、という既存のルールを再確認しています。

この仕様の明確化は、Go言語の設計原則である「シンプルさ」と「明確さ」を追求する一環です。開発者がGoのパッケージシステムをより正確に理解し、循環依存による問題を回避するための指針となります。

循環インポートの解決策としては、一般的に以下の方法が推奨されます。

  • パッケージの再構成: 循環の原因となっている機能や型を、新しい独立したパッケージに移動させる。これにより、依存関係の方向を一方通行にする。
  • インターフェースの導入(依存性逆転の原則): 密結合な関係にあるパッケージ間にインターフェースを導入し、具体的な実装ではなく抽象に依存させることで、循環を解消する。
  • パッケージの結合: もし2つのパッケージが非常に密接に関連しており、分離することが不自然であるならば、それらを一つのパッケージに統合することも検討する。

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

変更はdoc/go_spec.htmlファイル内の、インポート宣言に関するセクションです。

--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1,6 +1,6 @@
 <!--{
  	"Title": "The Go Programming Language Specification",
- 	"Subtitle": "Version of March 1, 2013",
+ 	"Subtitle": "Version of March 4, 2013",
  	"Path": "/ref/spec"
 }-->
 
@@ -5367,7 +5367,8 @@ import . "lib/math"         Sin
 <p>
 An import declaration declares a dependency relation between
 the importing and imported package.
-It is illegal for a package to import itself or to import a package without
+It is illegal for a package to import itself, directly or indirectly,
+or to directly import a package without
 referring to any of its exported identifiers. To import a package solely for
 its side-effects (initialization), use the <a href="#Blank_identifier">blank</a>
 identifier as explicit package name:

コアとなるコードの解説

変更された行は以下の部分です。

-It is illegal for a package to import itself or to import a package without
+It is illegal for a package to import itself, directly or indirectly,
+or to directly import a package without

この変更により、Go言語の仕様書におけるインポートの合法性に関する記述が更新されました。

  1. Subtitleの変更:

    • "Version of March 1, 2013" から "Version of March 4, 2013" へと変更されています。これは、仕様書のバージョン日付をコミット日に合わせて更新したものです。
  2. 主要な仕様変更:

    • 変更前: It is illegal for a package to import itself or to import a package without referring to any of its exported identifiers.
      • 「パッケージが自身をインポートすること」
      • 「エクスポートされた識別子を参照せずにパッケージをインポートすること」
      • これらが不正であるとされていました。
    • 変更後: It is illegal for a package to import itself, directly or indirectly, or to directly import a package without referring to any of its exported identifiers.
      • 「パッケージが自身を直接的または間接的にインポートすること」
      • 直接的にエクスポートされた識別子を参照せずにパッケージをインポートすること」
      • これらが不正であると明確化されました。

この修正は、Go言語のパッケージシステムにおける循環インポートの禁止を、より包括的かつ厳密に定義し、開発者が誤解する余地をなくすことを目的としています。特に「直接的または間接的に」という文言の追加は、Goコンパイラが既に実装している循環インポート検出ロジックと仕様との整合性を高める上で重要です。

関連リンク

参考にした情報源リンク