jollygoodcode / jollygoodcode.github.io

:thought_balloon: Jolly Good Blog
https://jollygoodcode.github.io
137 stars 7 forks source link

Find Unused Code #20

Open JuanitoFatas opened 8 years ago

JuanitoFatas commented 8 years ago

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

Aqualon commented 8 years ago

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

joshuaclayton commented 8 years ago

@JuanitoFatas nice post! A couple things:

Aqualon commented 8 years ago

@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.

joshuaclayton commented 8 years ago

@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.

Aqualon commented 8 years ago

@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.

joshuaclayton commented 8 years ago

@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.

askl56 commented 8 years ago

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

joshuaclayton commented 8 years ago

@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)

askl56 commented 8 years ago

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

joshuaclayton commented 8 years ago

@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?

askl56 commented 8 years ago

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

joshuaclayton commented 8 years ago

@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.

JuanitoFatas commented 8 years ago

@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 commented 8 years ago

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! ๐Ÿ‘

Aqualon commented 8 years ago

@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).

padi commented 8 years ago

@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!

JuanitoFatas commented 8 years ago

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.

joshuaclayton commented 8 years ago

@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?

padi commented 8 years ago

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