I’m confronted with a case where mix xref graph --label compile-connected --fail-above 0
would force me to refactor something which seems unnecessary. I’ll give a simplified example, starting with an expected case and then showing my case.
Expected case
Here is a clear transitive compile-time dependency, where A
depends on B
which depends on C
.
(Note: pretend that each of these modules is in its own file.)
- If
C.hello/0
changes, that meansB
needs to recompile andB.hello/0
changes - If
B.hello/0
changes, that meansA
needs to recompile andA.hello/0
changes
defmodule A do
@hello B.hello()
def hello, do: @hello
end
defmodule B do
@hello C.hello()
def hello, do: @hello
end
defmodule C do
def hello, do: "hello"
end
We can find this out like this:
$ mix xref graph --label compile-connected --fail-above 0
lib/a.ex
└── lib/b.ex (compile)
** (Mix) Too many references (found: 1, permitted: 0)
If we want to know “why is it bad that A
depends on B
? What does B
depend on?”, we can check:
$ mix xref graph --source lib/b.ex
lib/b.ex
└── lib/c.ex (compile)
(Tangential point: it would be nice if the first command would show this automatically; its output implies “A
shouldn’t depend on B
” when maybe the truth is that B
shouldn’t depend on C
.)
A More Confusing Case
Here is a more confusing case, like the one I’m facing:
- If
C.world/0
changes, that meansB
needs to recompile andB.world/0
changes - If
B.world/0
changes, I’m pretty sure that A isn’t going to compile any differently, but because theA
depends at compile time onB
forB.hello/0
, it will get (needlessly?) recompiled.
defmodule A do
@hello B.hello()
def hello, do: @hello
end
defmodule B do
@hello "hello"
def hello, do: @hello
def world, do: C.world()
end
defmodule C do
def world, do: "world"
end
The mix xref
commands above see these two situations as the same, even though to me they seem quite different. This seems like a shortcoming of mix xref
and/or the compile process.
Am I wrong?