GMOメイクショップでエンジニアをしている黒木です。
22年に新卒で入社し、Goでバックエンドの開発をしています。
入社後にGoを学び始めて、学びたての頃は、よく循環参照のエラーを起こしていました。
この経験を踏まえて、Goを学びたての頃にこれを知っていれば循環参照は怖くないという思いを元に循環参照が起きた場合、どこが原因で循環参照が起きているかの特定方法と、解決法をご紹介いたします。
ゴール
Goを使っている方なら一度は、import cycle not allowed
というエラーに出会ったことはないでしょうか。
このエラーは、パッケージが互いに参照し合っているため発生します。
とは言っても、Goを学びたての頃は、どこが原因なの? ということが多々あると思います。今回は、パッケージ間の依存関係をグラフ化することで、どこが原因かを特定して、循環参照を解決してみたいと思います。
どうやるか
godepgraphというツールを使って、プロジェクト内の依存関係をグラフ化します。その後、グラフから循環参照の原因を特定し、循環参照を解消するところまでやってみます。
まず始めに
循環参照とは、複数のパッケージが互いに参照し合うことです。パッケージ間の依存関係を有向グラフで表した際に、始点と終点が同じになる経路、いわゆる閉路が存在すると、循環参照が発生します。
循環参照が起こる例を挙げると、以下のような状態になります。packageA を始点として、packageB を経由し、packageA が終点となります。始点と終点が同じ状態です。
このような状態でも、循環参照が発生します。packageA を始点として、 packageB、packageC を経由し、packageA が終点となります。
反対に、循環参照が起こらない例は、以下のような状態になります。packageAを始点、packageBが終点となっているような、始点と終点が異なる状態です。
循環参照が起こるコードを書いてみる
今回は、packageA・packageB・packageCを定義し、packageA → packageB → packageC → packageAのような依存関係があるコードを実装します。
import-cycle-example
というモジュールを作り、進めていきます。
まずは、packageA/packageA.go
を定義します。
続いてp
ackageB/packageB.go
、packageC/packageC.go
を定義します。
そして最後にmain.goを定義し、まずはpackageAのCalled()をコールします。
プログラムを実行すると、循環参照が起こりました。
依存関係をグラフ化して解決する
小規模なプロジェクトであれば、依存関係をグラフ化して、解消するのがおすすめです。 まずは、依存関係のグラフ化のためにgodepgraphをインストールします。
インストールが完了したら、プロジェクト配下で依存関係をグラフ化します。godepgraph モジュール名 | dot -Tpng -o グラフを出力するファイル名
に沿って、コマンドを実行します。
出力されたグラフは以下のようになります。
このグラフから、packageA → packageB → packageC → packageAのような依存関係になっていることが分かります。
packageAが始点となり、packageB、packageCを経由して、packageAが終点になる経路になっていることから、循環参照していることがわかります。
今回は、以下のようにpackageCからpackageAの参照を削除して、閉路にならないようにします。
packageC/packageC.go
を以下のように修正します。
プログラムを実行すると、エラーが消えて、循環参照が解消されました。
再度依存関係を、グラフ化してみましょう。
packageAが始点となり、packageCが終点になっているので、循環参照が解消されたことがわかります。
一方で、大規模なプロジェクトでこの方法を使用すると、どうなるでしょうか。弊社のmakeshopというプロダクトで、依存関係をグラフ化してみます。
パッケージ数が多いため、グラフから依存関係を把握するのは、得策ではないように感じます
循環参照が起こないようにするには
ルールに沿ってコーディングすれば、循環参照が起きることはなくなります。その一例として参考までに、クリーンアーキテクチャという設計思想があります。
弊社のmakeshopというサービスでは、クリーンアーキテクチャの思想に習って、ディレクトリが組まれています。
クリーンアーキテクチャは、プロダクトをいくつかのレイヤーに分類します。外側のレイヤーから内側のレイヤーへの依存だけが許可されているので、双方向に依存し合うことはありません。
出典:https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
したがって、このルールにしたがってコーディングできれば、大規模なプロジェクトでも循環参照が起きることはなくなります。
まとめ
循環参照は、Goを学びたての頃にはよくつまづくところですが、原因がわかれば怖くありません。
今回紹介したこの方法以外にも、循環参照を解決する方法はたくさんありますが、少しでもご参考になれば幸いです。
◆ 他のBlogはこちらから⇒ https://tech.makeshop.co.jp/ ◆
よろしければ感想などメンバーと話してみませんか?ご応募お待ちしています。