Let's make our codebase better. Here's my three step program:
- Prevent new bad code from getting in
- Make the bad code that already got in, not bad
- Feel great because our codebase is great (you look great today)
- Styles (community-driven standards for writing commit messages, javascript, ruby, scss, etc)
- Linters (programs that use static analysis to check your text files for errors and bad code styles)
- Dependencies (we have lot's of them; some of them are bad, sometimes; when they fail, we get in trouble)
When you write to communicate (which you should be doing ALL THE TIME if you are writing code...that will be read potentially many times by potentially many developers, including future you), you should aim to write idiomatically. There are standards and conventions that have been set in place by our wider community of peers (https://github.com/bbatsov/ruby-style-guide). If we apply these standards, some good stuff will happen, not least of which: it will become easier for new developers to understand our code base, and (by regularly enforcing these styles) it will become easier for us to understand other ruby code. Aren't there a lot of standards for styling code? How can I remember them allZOMG? Yes, there are a lot. So what should we do with the these repetitive and exacting menial tasks? MAKE ROBOTS DO THEM!
- Source: https://github.com/bbatsov/rubocop/
- Docs: https://rubocop.readthedocs.io/
- Autobiography: "A Ruby static code analyzer, based on the community Ruby style guide."
So yeah, essentially, Rubocop is a gem that uses static analysis (parses the contents of your files without running them) to identify bad code styles. It has a number of sensible defaults that can be configured to our hearts' content by specifying configuration options in a file called 'rubocop.yml'. Here's how you use it in large code repositories that have not historically had a large emphasis on code styles (like ours):
- Install rubocop using bundler (
bundle install rubocop
) - Run Rubocop for the first time (
rubocop
). This will cause Rubocop to produce a staggering (read: embarassing) number of warnings. - Generate a Rubocop configuration file (.rubocop_todo.yml) to ignore all of these warnings (for now) (
rubocop --auto-gen-config
). Now when we run Rubocop, there are no warnings! Are we done?! (no) - Whenever we write new code, we can run Rubocop and ensure that no new offenses are checked into our code base.
- Whenever we have spare time to refactor, we remove an ignore block form .rubocop_todo.yml and correct the warnings (whilst humming 99 Bottles of Beer on the Wall).
- Repeat steps 4 and 5 until the rubocop_todo.yml is empty.
- Repeat steps 4 until cosmic heat death.
More tools to the rescue! Don't worry, you can forget about this one (until you commit). Let's use an oft-forgotten git utility: hooks (https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks). Don't mess with native hit hooks. There's a gem for that.
- Source/Docs: https://github.com/brigade/overcommit
- Autobiography: "A fully configurable and extendable Git hook manager."
So yeah, essentially, overcommit uses git hooks, but let's you write ruby code to run custom checks on your codebase. Also, unlike other git hook gems, the configuration files live in source (which you check in), so everyone shares the same checks. Here's how you use it:
- Forget that it exists
- Write your code
- Commit your code
- If your commit fails due to a failed check, fix it, yo
- If your commit is successful, but you see a CommitMsg warning, that means you screwed up the formatting on your commit message. Fix it with
git commit --amend
, ya goober. - If your commit is successful, and you don't see any warnings, you win! Ship it.
Overcommit loads a file called '.overcommit.yml' in order to determine what checks to run. At any time, you can check this file at the root directory of our project, but for the sake of explanation, here are the current checks at the time of this documents writing:
Preample: most of these checks that care about files will only look at staged files. You would have to run overcommit --run
in order to see warnings for the entire codebase.
PreCommit checks:
- RuboCop: runs rubocop over files that have been staged
- BundleAudit: checks dependencies for security vulnerabilities
- BundleCheck: ensures that Gemfile and Gemfile.lock are nSync
CommitMsg checks:
- SingleLineSubject: commit messages should include a subject line followed by an empty line followed by an optional body with additional details
- HardTabs: please just don't
- RussianNovel: for funzies; don't write more than 30 lines
Potentially useful hooks:
- BundleOutdated: checks for newer gem versions
- CoffeeLint/HamlLint/EtcLint: runs linters
- ForbiddenBranches: prevents committing code to protected branches (must be merged)
- RailsBestPractices: runs files through rails standards checks (https://github.com/railsbp/rails_best_practices)
- TrailingWhitespace
For all of your hook related needs, please see the out-of-the-box checks that overcommit offers (https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook), or be even more awesome, and write your own! (http://www.guoxiang.me/posts/28-how-to-write-a-custom-overcommit-precommit-git-hook-in-4-steps)