Is there any tooling or recommended way to find dead code?

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.

GitHub - hauleth/mix_unused: Find unused functions in your project is kinda what I was looking for but it seems is no longer working nor maintained.

Thanks in advance! :smiley:

6 Likes

Doesn’t the compiler warn you about unused functions? Have you looked into it?

1 Like

As far as i know, Erlang -- dialyzer does that.

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.

Seems like exactly what you need.

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.

3 Likes

Exactly I’m trying to figure out a way to check for unused public functions

I think @hauleth can say something about the state of :mix_unused. Or he will be except a PR from you, if you need some changes in the lib.

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.

6 Likes

That would be awesome, more than happy to help if I can.

1 Like

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. 

This will return a lot of false positives due to macros and other autogenerated functions in Elixir.

2 Likes

Makes sense, I’ve only used it in Erlang projects.

1 Like

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.

2 Likes

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.

Cheers

Is there any tooling or recommended way to find dead code?

Typically to assess that I use first test coverage (GitHub - parroty/excoveralls: Coverage report tool for Elixir with coveralls.io integration.), then a bit of manual investigation, plus the compiler warnings.

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 :smile:

1 Like

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.

Can you elaborate on the probing approach ?

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!

FWIW, I did a bit of googling on the idea of AOP and someone published this (GitHub - nobrick/exaop: A minimal elixir library for aspect-oriented programming.), I will have to experiment and see how this works, but it could be an idea for more advanced cases.

Hope this helps!

1 Like