Configuring Rubocop in Code Base

Configuring Rubocop in Code Base

Rubocop is a code-style linter for Ruby based on the official Ruby style guide. Rubocop supports autocorrection & is flexible in terms of the configuration of various cops based on our preferences.

In this blog post, we discuss the various steps on how we can improve code quality using Rubocop in our codebase.

Why do we need Rubocop?

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

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

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

Rubocop Config

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

  1. rubocop-performance: Performance optimization analysis.

  2. rubocop-rails: Rails best practices & coding convention.

  3. rubocop-rspec: Rspec (used for Ruby code testing) specific code analysis.

The next step is to create the .rubocop.yml which contains the cops to be enabled/disabled, 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

Rubocop TODO Approach

For existing projects, RuboCop provides a file .rubocop_todo.yml which 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 docs.rubocop.org/rubocop/1.36/index.html.

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

Configuring Cops

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.

  1. Style/RedundantReturn: In Ruby, the last line in a method is something that is returned, so the official style guide recommends avoiding returns unless necessary. However, we disabled this Cop for the following reasons:
  • 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

  1. Style/WordArray: For defining an array, the Style Guide recommends using the % 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
  1. Style/SymbolArray: The configuration for this is similar to Style/WordArray.
Style/SymbolArray:
  EnforcedStyle: brackets
  1. Metrics/MethodLength: The use of this cop to check if the length of a method exceeds some maximum value. The default is. 10 but you can take a look at your codebase and analyze the suitable number for it.

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 them with the entire team.

Maintaining the Standard

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:

  1. Make use of pre-commit hooks that run Rubocop across all the configured Cops.

  2. 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 at HackerRank by cleaning up the .rubocop_todo.yml file.

Screenshot 2022-09-24 at 3.53.23 PM.png

I hope this article helps to configure Rubocop in your code bases & improve the code quality.

Did you find this article valuable?

Support Shloka Shah by becoming a sponsor. Any amount is appreciated!