Rubocop is a code-style linter for Ruby based on the official
In this blog post, we discuss the various steps on how we can improve code quality using Rubocop in our codebase.
Creating a Standard: There are various patterns in which developers write code. To harmonize the coding style, we can use Rubocop. By enabling and disabling rules with Rubocop, we can discuss with the team what good practices we need to follow.
Ease the process of understanding & reviewing code: Sometimes, when a PR is sent for review, the reviewer might spend time commenting on the code style/format, such as extra whitespaces and missing lines at the end. By integrating Rubocop into pre-commit hooks and GitHub Actions, the developer can be made aware of offenses even before the PR is reviewed.
Best Practices: As Ruby beginners, we may not be aware of the practices used by the Ruby community. In the form of various gems, Rubocop provides us with an excellent starting point for understanding all of them.
In order to integrate Rubocop into our codebase, add the following gems to your Gemfile
gem 'rubocop', '1.23.0', require: false
gem 'rubocop-performance', '~> 1.12.0', require: false
gem 'rubocop-rails', '~> 2.12.0', require: false
gem 'rubocop-rake', '~> 0.6.0', require: false
gem 'rubocop-rspec', '~> 2.6.0', require: false
The next step is to create the .rubocop.yml
which contains the cops to be enabled/disabled, and the files that we don't want to run specific cops on.
An example can look like this
require:
- rubocop-performance
- rubocop-rake
- rubocop-rails
- rubocop-rspec
AllCops:
DisabledByDefault: false
NewCops: enable
TargetRubyVersion: 2.6.3
Exclude:
- db/schema.rb
- node_modules/**/*
- vendor/**/*
Layout/FirstArgumentIndentation:
Exclude:
- spec/controllers/api/payments_controller_spec.rb
what this piece of code does is,
excludes all files under AllCops::Exclude when running Rubocop.
disables running of Cop Layout/FirstArgumentIndentation
on the files under Exclude.
For existing projects, RuboCop provides a file .rubocop_todo.yml
that records all the offenses from our codebase. Rather than having to fix everything at once, we can fix each offense as we touch the corresponding piece of code.
In order to generate the todo file use the command rubocop --auto-gen-config --exclude-limit 10000
which runs rubocop on your entire codebase and adds the offenses in the code as disabled.
--exclude-limit 10000
is a hack to make sure that Rubocop will not disable any cops in the .rubocop_todo.yml file
.
An example of how the .rubocop_todo.yml
will look like
# This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 10000`
# on 2022-09-24 07:33:55 UTC using RuboCop version 1.23.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 8
# Cop supports --auto-correct.
# Configuration parameters: IndentationWidth.
Layout/AssignmentIndentation:
Exclude:
- 'app/controllers/home_controller.rb'
- 'app/helpers/application_helper.rb'
- 'app/helpers/regex_helper.rb'
The next step is to let rubocop
know to ignore the files in .rubocop_todo.yml
. You can do this by inheriting the file in the main config.
inherit_from: .rubocop_todo.yml
inherit_mode:
merge:
- Exclude
require:
- rubocop-performance
- rubocop-rake
- rubocop-rails
- rubocop-rspec
...
This is how your .rubocop.yml
will look after configuring the todo file. Then when we run rubocop, it pulls the configuration from .rubocop.yml
, which then pulls the configuration from .rubocop_todo.yml
, which contains all the excluded files. Therefore, we won't see any offenses.
An added todo file does not mean that the offenses have been solved, it simply indicates that we have suppressed the warnings we were receiving repeatedly. As a result, the offenses remain present and now it is our responsibility to rectify each one individually.
The approach that we have been following at HackerRank to solve these offenses includes the following steps:
Pick up a cop with offenses in the .rubocop-todo.yml
file.
Read the documentation at
Check if the Cop supports Safe Autocorrection.
Check the default enforced style for the Cop. Check if the Style is shared across various languages. The reason for this is to bring consistency among our developers.
If we change the enforced style, drop a note to the Engineering Team.
Finally, create a PR.
It is important to not overcompensate for old code with many violations but to ensure that the new code adheres to the standards enforced by Rubocop config and to incrementally improve it.
As I was cleaning up the .rubocop-todo.yml
file at HackerRank, we discovered that some cops needed configs different from what the official Ruby style guide recommends. Listed below are a few examples of those, along with the reason and how you can modify the defaults.
Almost all languages adhere to explicit returns, which makes transitioning between them easier for the developer.
It simplifies the code at first glance and doesn't require any previous language-specific knowledge. When I first started with Ruby, I wasn't aware of this, so when my code stopped working, I assumed a return statement was missing. Eventually, I realized that it wasn't the return statement but something else.
You can disable this cop using the following piece of code in .rubocop.yml
Style/RedundantReturn:
Enabled: false
%
format for strings that don't have any spaces. In the case of spaces using the %w
will be considered an offense.
# bad
STATES = ['draft', 'open', 'closed']
# good
STATES = %w[draft open closed]
# bad (contains spaces)
STR = %w[foo\ bar baz\ quux]
# good (contains spaces)
STR = ['foo bar', 'baz quux']
→ There is a problem with this format if the strings have spaces (which is highly likely in a large codebase), there will be two different formats used in code: "Brackets" for strings with spaces and "%" for strings without spaces.
→ Additionally, brackets are a common format across many languages, so if a newcomer is trying to understand your code, they won't have to learn all kinds of syntax first. We can bring consistency to our developers by using brackets.
For the above two reasons, we configured the default to Brackets
. You can use the following piece in .rubocop.yml
.
Style/WordArray:
EnforcedStyle: brackets
Style/WordArray
.
Style/SymbolArray:
EnforcedStyle: brackets
It also provides a configuration like CountAsOne which is available for array, hash, and heredoc where each literal will be counted as one line regardless of its actual size & IgnoredMethods where we can provide the list of methods that we want the cop to ignore.
The approach we have been following includes:
Taking the number for which most methods have offenses.
Set that as the Max limit.
Add the remaining methods as offenses.
Gradually decrease the limit.
The goal was to avoid unnecessarily high lines of code in new methods and also not have to worry too much about refactoring old code.
Metrics/MethodLength:
Max: 100
CountAsOne: ['array', 'hash', 'heredoc']
IgnoredMethods: ['a','b']
These are some Cops which we have configured differently compared to the default Style Guide after discussing with the entire team.
Now that we have the configuration in place, we need to make sure that the new code written doesn't contain the offenses.
To do this we can do the following:
Make use of pre-commit hooks that run Rubocop across all the configured Cops.
Setup Code Climate/Github Actions that report offenses on the PR. You can also make these checks as required to pass for merging the code.
PS: The following screenshot is a trend on how we have improved the code quality atHackerRank by cleaning up the .rubocop_todo.yml
file.
I hope this article helps to configure Rubocop in your code bases & improve the code quality.
Also Published Here