jollygoodcode.github.io icon indicating copy to clipboard operation
jollygoodcode.github.io copied to clipboard

Find Unused Code

Open JuanitoFatas opened this issue 8 years ago • 19 comments

We will use a CLI tool to find unused code automatically 🔎🔎🔎.

Unused

Unused is a CLI tool to find unused code, written in 100% Haskell by awesome Josh Clayton.

Install Unused on macOS

$ brew tap joshuaclayton/formulae
$ brew install unused

Ctags for macOS

Install Ctags via homebrew instead of using system built-in older Ctags:

$ brew install ctags

If you having problem that your Ctags does not load homebrew-installed one, add an alias (zsh):

alias ctags="`brew --prefix`/bin/ctags"

Generate index file

For Ruby files here is an example:

$ ctags -f .git/tags -R $(git ls-files | grep .rb)

A .tags file under .git folder will be generated, which contains the index of your Ruby code. Don't forget to gitignore your tags file.

Find Unused Code

$ unused -s -g none
* -s,--single-occurrence   Display only single occurrences
* -g,--group-by ARG        [Allowed: directory, term, file, none] Group results

unused will do the hard work, find where unused code are, and report to you:

BulkCustomerDashboard                     1, 1  app/dashboards/bulk_customer_dashboard.rb  used once
UserSerializer                            1, 1  app/serializers/user_serializer.rb         used once
authorized_repos                          1, 1  lib/github_api.rb                          used once
create_subscription_record                1, 1  app/services/repo_subscriber.rb            used once
excluded_file?                            1, 1  lib/ext/scss-lint/config.rb                used once
has_something?                            1, 1  spec/models/linter/ruby_spec.rb            used once
register_email                            1, 1  spec/models/linter/ruby_spec.rb            used once
stub_commit                               1, 1  spec/services/build_runner_spec.rb         used once
stub_customer_with_discount_find_request  1, 1  spec/support/helpers/stripe_api_helper.rb  used once
stubbed_style_checker_with_config_file    1, 1  spec/services/build_runner_spec.rb         used once
token=                                    1, 1  app/models/user.rb                         used once
user_names                                1, 1  spec/models/linter/ruby_spec.rb            used once
verified_request?                         1, 1  app/controllers/application_controller.rb  used once

Wow, unused code found, delete them and then sent a Pull Request!

Happy Housekeeping! 🏡

Note that Unused is meant to guide, though, not give definitive answers to what can be removed. -- Josh Clayton ‏@joshuaclayton

JuanitoFatas avatar Jun 30 '16 17:06 JuanitoFatas

Thanks for this HowTo! One remark, for me the definition of alias for ctags was not necessary, since brew automatically linked it correctly.

Aqualon avatar Jun 30 '16 18:06 Aqualon

@JuanitoFatas nice post! A couple things:

  • if the ctags file exists at .git/tags, tags, or tmp/tags within the repo, unused will load it automatically, meaning you won't have to pipe it in, so all that'd be needed is to run unused
  • -s will filter so only single-occurrence tokens are displayed, allowing you to omit piping to grep
  • -g none will also remove the grouping, so if you want individual lines without headings, you can disable those as well (a side-effect of using grep)

joshuaclayton avatar Jun 30 '16 19:06 joshuaclayton

@joshuaclayton for me using tags file doesn't work. I use ctags -f tags -R $(git ls-files | grep .rb) and then call unused and it does not find anything. Weird thing is, if I use the cat $file | unused --stdin way, it works fine for a tags file starting with a ., but for files without I just get "Unused found no results". If I move the file outside of my project directory and cat it from there, it also works.

Any clue what's going on there? Running on OS X 10.11.5. btw.

Aqualon avatar Jun 30 '16 21:06 Aqualon

@Aqualon interesting; if you cd into the project directory (where you're running unused) and run cat tags | wc -l, what is returned?

When unused can't find a tags file, it'll tell you (screenshot below). If it's saying there aren't results, it's often that the file exists but doesn't contain much/anything (often due to misconfiguration from ctags).

tmux

Let me know if anything looks off in the generated tags file - that'd be my first guess.

joshuaclayton avatar Jun 30 '16 21:06 joshuaclayton

@joshuaclayton it finds 1823 lines and for both cases (the .tags file piped into unused and just having the same file as tags) I get the output Unused: analyzing 919 terms.

I think I have an idea what could be going on. Can it be that unused scans all files in the project directory again and without tags file in .gitignore it scans it too and thus gets confused? After I added it to .gitignore unused works. And that's why it maybe also works, if the file is outside the project directory and not inside.

Aqualon avatar Jun 30 '16 22:06 Aqualon

@Aqualon yes, that'd probably do it; a tags file is (as far as I've ever known) always something you'd want git to ignore, since each developer may have different ways of generating tags on their machine.

It might be good for unused to count the tokens, and if it's non-zero, issue a message if nothing comes up - thoughts?

For projects with literally no unused code, it might be confusing - but for all others, it might be helpful to provide insight as to why nothing's showing up.

joshuaclayton avatar Jun 30 '16 22:06 joshuaclayton

@JuanitoFatas @joshuaclayton what would be the alias option for a fish shell?

askl56 avatar Jul 01 '16 01:07 askl56

@askl56 if your $PATH is configured correctly (e.g. /usr/local/bin has higher priority than /usr/bin) you shouldn't have to write an alias at all.

That said, https://fishshell.com/docs/current/commands.html#alias looks like a good place to start (I haven't used fish myself)

joshuaclayton avatar Jul 01 '16 01:07 joshuaclayton

Nah I know about aliases, the problem is that the ` syntax doesn't work in fish (within the quotes)

askl56 avatar Jul 01 '16 01:07 askl56

@askl56 ah - I'd check $PATH first, packages installed with Homebrew should take precedence over any system-installed binaries.

@JuanitoFatas the alias line seems confusing and totally unnecessary when $PATH is configured correctly; thoughts on removing that bit of the post too?

joshuaclayton avatar Jul 01 '16 02:07 joshuaclayton

So @joshuaclayton what would be the command without the alias?

askl56 avatar Jul 01 '16 02:07 askl56

@askl56 The alias is necessary if your $PATH is misconfigured. which ctags should return the Homebrew-installed version. If it does, nothing else is required and you can ignore the alias section entirely. If it doesn't, follow Homebrew's instructions to configure your $PATH correctly.

joshuaclayton avatar Jul 01 '16 02:07 joshuaclayton

@JuanitoFatas nice post! A couple things:

if the ctags file exists at .git/tags, tags, or tmp/tags within the repo, unused will load it automatically, meaning you won't have to pipe it in, so all that'd be needed is to run unused -s will filter so only single-occurrence tokens are displayed, allowing you to omit piping to grep -g none will also remove the grouping, so if you want individual lines without headings, you can disable those as well (a side-effect of using grep)

Thanks for the tips, I edited the post, much appreciated!

JuanitoFatas avatar Jul 01 '16 06:07 JuanitoFatas

The alias is necessary if your $PATH is misconfigured. which ctags should return the Homebrew-installed version. If it does, nothing else is required and you can ignore the alias section entirely. If it doesn't, follow Homebrew's instructions to configure your $PATH correctly.

@joshuaclayton After fixed my $PATH, I don't need the alias, too. Thanks! 👍

JuanitoFatas avatar Jul 01 '16 06:07 JuanitoFatas

@joshuaclayton maybe a hint in the readme which files are ignored by default (e.g. to me it looks like all dot-files are not scanned) and that the tags file should be in .gitignore? Programmatically you could check for the default run, if the found tags file is not ignored by git and thus could lead to not finding things? (I'm quite new to the whole ctags thing, so don't know what others would expect).

Aqualon avatar Jul 01 '16 06:07 Aqualon

@JuanitoFatas good post! I'm currently trying this out on a number of ruby codebases and ran into this Github issue/post. I realize that this might be a dumb question, but just for curiosity's sake: what do the 2 number columns mean (e.g. 1,1)? They sometimes differ but at least one of them refers to the number of occurrences of the class/method, but I don't know what the other one is for.

@joshuaclayton nice tool btw!

padi avatar Jul 21 '16 12:07 padi

this might be a dumb question

This is a good question! 😊

what do the 2 number columns mean (e.g. 1,1)?

The first 1 is how many times it occurs, second 1 is how many matches.

For example, in my project, I ran unused, I had this one from output:

fetch!  2, 3  spec/models/rubygem_spec.rb  only the definition and corresponding tests exist

Then I do a search fetch! in my editor (against .rb files only, since I only created ctags for .rb files), the result is:

3 matches across 2 files.

Hope this helps.

JuanitoFatas avatar Jul 21 '16 13:07 JuanitoFatas

@JuanitoFatas @padi spot on!

I display the numbers because there are times where there are multiple occurrences but it's still flagged as high-likelihood for removal (e.g. an instance method where the only use looks to be in the unit tests).

@padi I'd love to hear any thoughts on how to make this more clear; interested in opening an issue?

joshuaclayton avatar Jul 21 '16 13:07 joshuaclayton

So, does the occurring mean in how many files does the tag/method/class occur, regardless of how many times it appeared on the same file?

@JuanitoFatas now that I remember, long time no see from rubyconf.ph. @joshuaclayton thanks. I made an issue as a point of discussion. https://github.com/joshuaclayton/unused/issues/60

padi avatar Jul 21 '16 22:07 padi