Is there any tooling or recommended way to find dead code? As my application starts to grow things can get a little messy especially with autogenerated contexts in phoenix.
Dialyzer is a static analysis tool that identifies software discrepancies, such as definite type errors, code that has become dead or unreachable because of programming error, and unnecessary tests, in single Erlang modules or entire (sets of) applications.
The compiler gives you a warning for unreachable functions. That means private functions which are not called in the module where these are defined are throwing such warnings. But public functions that are never called anywhere are not checked by the compiler.
I am (very) slowly working on that one to work with compiler tracer. I can push my branch (I thought it is pushed) where I am trying to implement such and if you have any PR then it will be more than welcome.
You might also be able to use Erlangâs Xref in a Mix Task. It has options:
locals_not_used(*)
Returns a list of local functions that have not been locally used.
exports_not_used
Returns a list of exported functions that have not been externally used. Note that in modules mode, M:behaviour_info/1 is never reported as unused.
The only way I can think of doing it would be to instrument all the code, then run the service and subject it to a barrage of automated testing then finally have it produce a report ala coveralls detailing code that hadnât been executed.
The problem is, even coveralls isnât great, particularly when macros are being used.
Maybe someone could implement a compiler plugin that could instrument the code in this manner.
In the world of Java such a thing is relativelyly trivial, simply use BCEL (byte code enhancement library) and implement an agent which instruments class files with the extra instructions as they are loaded in.
From what I understand, Erlang doesnât provide such a facility, at least in a way that doesnât involve learning how the compiler works.
The problem with these approaches is that quite a lot of dynamic invocation (:erlang.apply/3) happens in Erlang/Elixir projects and dialyser/xref and friends canât be aware of whatâs being invoked when the call site is dynamic.
My initial interest was in trying to prune the Riak codebase as it was littered with so much half-implemented, never invoked, rewritten 4 times, copied and pasted cruft that open source development would struggle to get anywhere with it. Dead code detector would have allowed to delete a huge portion of dead code paths.
Hey @hauleth I was trying your branch over the weekend but couldnât get it to return any results I have a reasonably large codebase that could be a good test here. Let me know if you are interested on syncing for this.
In some codebases in the past, Iâve also added probes to the code, delivering an event if a code is used, to assess if the code path is never actually used. It takes a bit of time to get a correct assessment that way, but it helps when everybody is gone
Interesting, the thing Iâm dealing with right now is I have a phoenix context that really bubble up in size, especially with all the pre-generated code. There are technically tests for all that code that might or might not actually be used.
Sure! It can take various forms depending on the context. The first step is to identify a good âhot spotâ candidate, something that you have doubt about.
If itâs a top-level something, it is easy. It can get more hairy if it is lower-level stuff, which in some cases require to create proxies on top of objects, or use (outside of Elixir, havenât used that in Elixir yet!) AOP (aspect-oriented-programming) interceptors.
To actually track the event I use various techniques: it can be just a simple âmagic wordâ in the logs (but then I make sure to avoid logging at a costly place), or use some form of counter depending on what is available in the system (e.g. statsd or any other metric system).
I usually deploy to production for a long time (can be as long as. months if needed, on long-term maintenance apps), and make sure that the information is captured.
You need to make sure the âprobeâ wonât take your system down in a way or another!
Eventually, we look for rows in public_functions.txt not present in called_functions.txt
comm -13 called_functions.txt public_functions.txt
This approach is useful but gives a lot of false negatives.
Mainly because it doesnât care of modules (if a function A.foo() is called somewhere, then any foo function declared in any other module is considered as non-dead)
I think for this to be effective, you would ideally want to grep over the AST, not the source code, as you might get into situations where you might be calling dynamically the function name.