英文:
Any good advice about how to avoid import cycle in Go?
问题
我在一个月的时间里一直在做一个Go项目。好处是Go非常高效。但是在一个月的开发中,我已经有了成千上万行的代码和许多packages
。为了避免导入循环对我来说是一个主要问题,每当我遇到导入循环错误时,我一开始就不知道问题可能在哪里。
Go编译器也只有非常简单的提示,总是不足以快速定位问题,比如:main.go:7:3: import cycle not allowed
。它只会告诉你可能导致问题的文件,没有更深入的信息。由于import
关系随着代码的增长变得越来越复杂,我渴望知道如何更有效地避免Go中的导入循环。非常感谢任何帮助。
英文:
I'm working on a Go project for a month. The good thing is Go is really highly efficient. But after a month of development I've already got thousands lines of code and many packages
. To avoid import cycle is a major issue for me that anytime I got a import cycle error, I have no idea where the problem may be at first time.
The Go compiler also only have very simple notice that always not good enough to locate issue quickly like: main.go:7:3: import cycle not allowed
. It will only help you to know which file may cause the problem but nothing more deeply. Since import
relationship just become more and more complex while code grows, I'm eager to know how to avoid import cycle more efficiently in Go. Any help is much appreciated.
答案1
得分: 59
go list -f '{{join .Deps "\n"}}'
将显示位于<import-path>
的包的导入依赖项 - 如果<import-path>
为空,则在当前目录中显示。或者
go list -f '{{join .DepsErrors "\n"}}'
希望在您的情况下显示一些有用的信息。还可以查看以下命令的输出:
go help list
以获取有关go list工具的其他信息。
英文:
go list -f '{{join .Deps "\n"}}' <import-path>
Will show import dependencies for package at <import-path>
- or in current directory if <import-path>
is left empty. Alternatively
go list -f '{{join .DepsErrors "\n"}}' <import-path>
hopefully shows some useful information in your case. See also the output of
go help list
for additional information about the go list tool.
答案2
得分: 44
为了补充jnml的答案(帮助“调试”循环引用问题),您可以使用依赖倒置来打破这些循环,结合依赖注入。对于一个应用程序,我总是尽量遵循“干净架构”的指导原则-参见这里的Go特定示例-我发现Go的“非声明性实现”接口(也就是说,您不必显式地说“type MyStruct struct implements IfceSomething”)使得这非常简单。
因此,如果您有包A -> B -> C -> A
,您可以在包C中创建InterfaceA
(一些相关的名称,显然与包相关的行为更相关:)并使其依赖于此接口而不是包A,并确保包A“实现”此接口。
然后,您只需在某个时候为C提供A的具体实现(这里有很多可能性,我通常在了解所有依赖项的主包中编写这个“粘合”代码)。
英文:
To complement on jnml's answer (which helps "debug" circular references problems), you can use dependency inversion to break those cycles, coupled with dependency injection. For an application, I always try to follow the guidelines of the Clean Architecture - see here for a Go-specific example - and I find that Go's "non-declarative implementation" of interfaces (that is, you don't have to explicitly say type MyStruct struct implements IfceSomething
) makes this very simple.
So, if you have packages A -> B -> C -> A
, you create InterfaceA
(some relevant name, obviously, more behaviour-related than package-related in package C and make it depend on this interface instead of on package A, and you make sure package A "implements" this interface.
Then you just have to provide a concrete implementation of A to C at some point (many possibilities here, I usually do this "glue" code in the main package that knows about all dependencies).
答案3
得分: 12
另一个选择是可视化项目中的依赖关系。可以使用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
英文:
> Since import relationship just become more and more complex while code
> grows, I'm eager to know how to avoid import cycle more efficiently in
> Go.
Another option is to visualize the dependencies in your project. This can be done with CLI tool godepgraph.
You can install it with:
go get -u github.com/kisielk/godepgraph
And then use it for finding import cycles in your app with help of another CLI tool graphvis.
Having this tools you can visualize package dependencies:
godepgraph -s path/to/my/package | dot -Tpng -o godepgraph.png
open ./godepgraph.png
答案4
得分: 0
作为使用go list工具的附带说明,该工具可以确认您已经了解的依赖关系,从架构的角度来看,您希望确保您的依赖树足够深,以便通过构建子组件来了解循环依赖。如果模块中存在导入循环,则应清楚地显示存在的循环。应该有足够的模块化(树深度),以便无缝移动这些依赖项。
Model -> Field(使用A)-- 需要导入"System"
Model -> System(定义A)-- 但需要导入"Field"
----------将类型A的结构A.go移动到模块顶部----------
----------现在模型目录的样子如下---------
Model -> A
Model -> Field
Model -> System
现在依赖关系已经分离,子组件可以自由使用A。这可能无法帮助可视化依赖关系或成为一个好的基于工具的解决方案,但再次强调,如果您足够解耦逻辑并构建子组件,您应该很快找到循环。否则,如果您使用树形可视化工具,我会说这是最后的手段,是设计不良/子组件不足的结果。
英文:
As an aside to using the go list tool, which may confirm dependencies you already know about, from a architectural view you want to make sure that your dependency tree is deep enough that you can find out about cycles by building out sub-components. If there is a import cycle in a module then the cycle that exists should be clear. There should be enough modularity (tree depth) that moving these dependencies around works seamlessly.
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
Now dependencies have been separated and the children can use A freely. This may not help with visualizing the dependencies or be a nice tool based solution, but once again, if you decouple your logic enough and build out the sub-components you should converge on the cycle pretty quickly. Otherwise if you're using a tree visualizer I would say that's a last resort and a result of poor design/not enough sub-components.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论