如何在Go中避免导入循环?有什么好的建议吗?

45

我已经在Go项目上工作了一个月。好处是Go非常高效。但是,一个月的开发后,我已经有了成千上万行代码和许多packages。避免导入循环对我来说是一个重大问题,每当我遇到导入循环错误时,一开始我就不知道问题可能出在哪里。

Go编译器也只提供非常简单的提示,往往不能快速定位问题,像这样:main.go:7:3: import cycle not allowed。它只能帮助你知道哪个文件可能会引起问题,但不能给出更深入的提示。由于import关系随着代码增长变得越来越复杂,我迫切想知道如何更有效地避免Go中的导入循环。非常感谢您的帮助。

4个回答

59
go list -f '{{join .Deps "\n"}}' <import-path>

将显示<import-path>包的导入依赖项 - 或者如果<import-path>为空,则在当前目录中显示。或者

go list -f '{{join .DepsErrors "\n"}}' <import-path>

希望这些信息对您有用。另请参阅输出:

hopefully shows some useful information in your case. See also the output of

go help list

有关 go list 工具的更多信息,请参阅。


3
知道这个肯定很好,其实我甚至不知道有go list这个命令。 - mna
1
较新版本的Go语言会提供更多有关导入循环来源的信息。 - MattyW

44
为了补充jnml的回答(帮助“调试”循环引用问题),您可以使用依赖反转来打破这些循环,再加上依赖注入。对于一个应用程序,我总是尽量遵循干净架构的指导方针 - 请参见这里以获取Go特定示例 - 我发现Go的接口“非声明式实现”(即,你不必显式地说type MyStruct struct implements IfceSomething)使得这非常简单。
所以,如果您有包A -> B -> C -> A,您可以在包C中创建InterfaceA(一些相关的名称,显然更多与行为相关而不是与包相关:),并使其依赖于此接口,而不是包A,并确保包A“实现”此接口。

然后你只需要在某个时候提供一个 A 到 C 的具体实现(这里有很多可能性,我通常会在知道所有依赖项的主要包中编写这个“粘合”代码)。


12

随着代码的增长,导入关系变得越来越复杂,我很想知道如何更有效地避免 Go 中的循环依赖。

另一个选项是通过 CLI 工具 godepgraph 可视化项目中的依赖关系。您可以使用以下命令进行安装:

go get -u github.com/kisielk/godepgraph

然后使用另一个CLI工具graphvis来查找应用程序中的导入循环,借助这些工具,您可以可视化包依赖关系:

godepgraph -s path/to/my/package | dot -Tpng -o godepgraph.png
open ./godepgraph.png

查找代码中的循环: 在此输入图片描述


0

除了使用go list工具来确认您已知的依赖项之外,从架构角度来看,您还需要确保您的依赖树足够深,以便通过构建子组件来了解循环。如果模块中存在导入循环,则应清楚存在的循环。应该有足够的模块化(树深度),以便轻松移动这些依赖项。

Model -> Field  (Uses A) -- Needs to import "System"
Model -> System (Defines A) -- But needs to import "Field"
----------Move type A Struct A.go to top of module----------
----------This is what the Model Dir looks like now---------
Model -> A
Model -> Field
Model -> System

现在依赖关系已经分离,子组件可以自由地使用 A。这可能无法帮助可视化依赖关系或提供良好的基于工具的解决方案,但是如果您足够解耦逻辑并构建出子组件,您应该很快就能收敛到循环中。否则,如果您正在使用树形可视化工具,我会说这是最后的选择,也是设计不良/子组件不足的结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接