Styling guidelines for ruby by Shopify,Airbnb, and Github
The best guidelines style for ruby suggested by big companies like Shopify, Airbnb, and Github
August 12, 2020 • 33 min read • RailsRefactoring
When we come to the term of best practice or best guidance, we properly follow the experts in that field. As a Ruby developer, my experts are the seniors in big companies like Github, Airbnb, and Shopify who are using Ruby as the main language and they're the largest companies for Ruby out there.
Before we go into Ruby styles, I would like to encourage you to use RuboCop, the most recommended tool for Ruby projects. It helps you adopt best practices for Ruby styling. To know how to install and use RuboCop, please refer to RuboCop's official documentation.
Bring your ☕ and let's startGeneral Coding Style
- Github
- 1) Use soft-tabs with a two space indent.
- 2) Keep lines fewer than 80 characters.
- 3) Never leave trailing whitespace.
- 4) End each file with a blank newline.
- 5) Use spaces around operators, after commas, colons and semicolons, around { and before }.
- Shopify
- 1) Make all lines of your methods operate on the same level of abstraction. (Single Level of Abstraction Principle)
- 2) Code in a functional way. Avoid mutation (side effects) when you can.
- 3) Do not program defensively. see
- 4) Do not mutate arguments unless that is the purpose of the method.
- 5) Do not mess around in / monkeypatch core classes when writing libraries.
- 6) Keep the code simple.
- 7) Don't overdesign.
- 8) Don't underdesign.
- 9) Avoid bugs.
- 10) Be consistent.
- 11) Use common sense.
- Airbnb
- Nothing ✍ from Airbnb
Syntax
Shopify- Use :: only to reference constants (this includes classes and modules) and constructors (like Array() or Nokogiri::HTML()). Do not use :: for regular method invocation.
- Avoid using :: for defining classes and modules, or for inheritance, since constant lookup will not search in parent classes/modules.
# bad
module A
FOO = 'test'
end
class A::B
puts FOO # this will raise a NameError exception
end
# good
module A
FOO = 'test'
class B
puts FOO
end
end
- Use def with parentheses when there are parameters. Omit the parentheses when the method doesn't accept any parameters.
- Never use for, unless you know exactly why.
- Never use then.
- Favour the ternary operator(?:) over if/then/else/end constructs.
# bad
result = if some_condition then something else something_else end
# good
result = some_condition ? something : something_else
- Use one expression per branch in a ternary operator. This also means that ternary operators must not be nested. Prefer if/else constructs in these cases.
- Use when x then ... for one-line cases.
- Use ! instead of not.
- Prefer &&/|| over and/or. More info on and/or for control flow.
- Avoid multiline ?: (the ternary operator); use if/unless instead.
- Favour unless over if for negative conditions.
- Do not use unless with else. Rewrite these with the positive case first.
- Use parentheses around the arguments of method invocations. Omit parentheses when not providing arguments. Also omit parentheses when the invocation is single-line and the method:
# bad
class User
include(Bar)
has_many(:posts)
end
# good
class User
include Bar
has_many :posts
SomeClass.some_method(:foo)
end
- Avoid return where not required for control flow.
- Avoid
self
where not required (it is only required when calling a self write accessor). - Use ||= to initialize variables only if they're not already initialized.
- Don't use ||= to initialize boolean variables (consider what would happen if the current value happened to be false).
# bad - would set enabled to true even if it was false
@enabled ||= true
# good
@enabled = true if enabled.nil?
# also valid - defined? workaround
@enabled = true unless defined?(@enabled)
- Do not put a space between a method name and the opening parenthesis.
- Use the new lambda literal syntax.
# bad
l = lambda { |a, b| a + b }
l.call(1, 2)
l = lambda do |a, b|
tmp = a * 7
tmp * b / 50
end
# good
l = ->(a, b) { a + b }
l.call(1, 2)
l = ->(a, b) do
tmp = a * 7
tmp * b / 50
end
- Prefer proc over Proc.new
- Prefix with
_
unused block parameters and local variables. It's also acceptable to use just_
.
# bad
def compute_thing(thing)
if thing[:foo]
update_with_bar(thing)
if thing[:foo][:bar]
partial_compute(thing)
else
re_compute(thing)
end
end
end
# good
def compute_thing(thing)
return unless thing[:foo]
update_with_bar(thing[:foo])
return re_compute(thing) unless thing[:foo][:bar]
partial_compute(thing)
end
- Avoid hashes-as-optional-parameters in general. Does the method do too much?
- Prefer keyword arguments over options hash.
- Prefer map over
collect
,find
overdetect
,select
overfind_all
,size
overlength
. - Prefer
Time
overDateTime
since it supports proper time zones instead of UTC offsets More info.
- Use def with parentheses when there are arguments. Omit the parentheses when the method doesn't accept any arguments.
def some_method
# body omitted
end
def some_method_with_arguments(arg1, arg2)
# body omitted
end
- Never use
for
, unless you know exactly why. Most of the time iterators should be used instead.for
is implemented in terms ofeach
(so you're adding a level of indirection), but with a twist -for
doesn't introduce a new scope (unlikeeach
) and variables defined in its block will be visible outside it.
arr = [1, 2, 3]
# bad
for elem in arr do
puts elem
end
# good
arr.each { |elem| puts elem }
- Never use then for multi-line if/unless.
# bad
if some_condition then
# body omitted
end
# good
if some_condition
# body omitted
end
- Avoid the ternary operator (?:) except in cases where all expressions are extremely trivial. However, do use the ternary operator(?:) over if/then/else/end constructs for single line conditionals
# bad
result = if some_condition then something else something_else end
# good
result = some_condition ? something : something_else
- Use one expression per branch in a ternary operator. This also means that ternary operators must not be nested. Prefer
if/else
constructs in these cases.
# bad
some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else
# good
if some_condition
nested_condition ? nested_something : nested_something_else
else
something_else
end
- The
and
andor
keywords are banned. It's just not worth it. Always use&&
and||
instead. - Avoid multi-line
?:
(the ternary operator), useif/unless
instead. - Favor modifier
if/unless
usage when you have a single-line body.
# bad
if some_condition
do_something
end
# good
do_something if some_condition
- Never use
unless
withelse
. Rewrite these with the positive case first
# bad
unless success?
puts "failure"
else
puts "success"
end
# good
if success?
puts "success"
else
puts "failure"
end
- Don't use parentheses around the condition of an
if/unless/while
.
# bad
if (x > 10)
# body omitted
end
# good
if x > 10
# body omitted
end
Prefer {...}
over do...end
for single-line blocks. Avoid using {...}
for multi-line blocks (multiline chaining is always ugly). Always use do...end
for "control flow" and "method definitions" (e.g., in Rakefiles and certain DSLs). Avoid do...end
when chaining.
names = ["Bozhidar", "Steve", "Sarah"]
# good
names.each { |name| puts name }
# bad
names.each do |name|
puts name
end
# good
names.select { |name| name.start_with?("S") }.map { |name| name.upcase }
# bad
names.select do |name|
name.start_with?("S")
end.map { |name| name.upcase }
Some will argue that multiline chaining would look OK with the use of {...}
, but they should ask themselves - is this code really readable and can't the block's contents be extracted into nifty methods?
- Avoid return where not required.
# bad
def some_method(some_arr)
return some_arr.size
end
# good
def some_method(some_arr)
some_arr.size
end
- Use spaces around the
=
operator when assigning default values to method parameters:
# bad
def some_method(arg1=:default, arg2=nil, arg3=[])
# do something...
end
# good
def some_method(arg1 = :default, arg2 = nil, arg3 = [])
# do something...
end
While several Ruby books suggest the first style, the second is much more prominent in practice (and arguably a bit more readable).
- Use ||= freely to initialize variables.
# set name to Bozhidar, only if it's nil or false
name ||= "Bozhidar"
- Don't use
||=
to initialize boolean variables. (Consider what would happen if the current value happened to befalse
.)
# bad - would set enabled to true even if it was false
enabled ||= true
# good
enabled = true if enabled.nil?
- Never put a space between a method name and the opening parenthesis
# bad
f (3 + 2) + 1
# good
f(3 + 2) + 1
Airbnb
- Never use
for
, unless you know exactly why. Most of the time iterators should be used instead.for
is implemented in terms ofeach
(so you're adding a level of indirection), but with a twist - for doesn't introduce a new scope (unlikeeach
) and variables defined in its block will be visible outside it.[link]
arr = [1, 2, 3]
# bad
for elem in arr do
puts elem
end
# good
arr.each { |elem| puts elem }
Prefer {...}
over do...end
for single-line blocks. Avoid using {...}
for multi-line blocks (multiline chaining is always ugly). Always use do...end
for "control flow" and "method definitions" (e.g., in Rakefiles and certain DSLs). Avoid do...end
when chaining.
names = ["Bozhidar", "Steve", "Sarah"]
# good
names.each { |name| puts name }
# bad
names.each do |name| puts name end
# good
names.each do |name|
puts name
puts 'yay!'
end
# bad
names.each { |name|
puts name
puts 'yay!'
}
# good
names.select { |name| name.start_with?("S") }.map { |name| name.upcase }
# bad
names.select do |name|
name.start_with?("S")
end.map { |name| name.upcase }
Some will argue that multiline chaining would look okay with the use of {...}
, but they should ask themselves if this code is really readable and whether the block's content can be extracted into nifty methods.
- Use shorthand self assignment operators whenever applicable.
# bad
x = x + y
x = x * y
x = x**y
x = x / y
x = x || y
x = x && y
# good
x += y
x *= y
x **= y
x /= y
x ||= y
x &&= y
- Avoid semicolons except for in single line class definitions. When it is appropriate to use a semicolon, it should be directly adjacent to the statement it terminates: there should be no space before the semicolon.
# bad
puts 'foobar'; # superfluous semicolon
puts 'foo'; puts 'bar' # two expressions on the same line
# good
puts 'foobar'
puts 'foo'
puts 'bar'
puts 'foo', 'bar' # this applies to puts in particular
- Use
::
only to reference constants (this includes classes and modules) and constructors (like Array() or Nokogiri::HTML()). Do not use::
for regular method invocation.
# bad
SomeClass::some_method
some_object::some_method
# good
SomeClass.some_method
some_object.some_method
SomeModule::SomeClass::SOME_CONST
SomeModule::SomeClass()
# bad
def some_method(some_arr)
return some_arr.size
end
# good
def some_method(some_arr)
some_arr.size
end
- Avoid return where not required
# bad
def some_method(some_arr)
return some_arr.size
end
# good
def some_method(some_arr)
some_arr.size
end
- Don't use the return value of
=
in conditionals
# bad - shows intended use of assignment
if (v = array.grep(/foo/))
...
end
# bad
if v = array.grep(/foo/)
...
end
# good
v = array.grep(/foo/)
if v
...
end
- When defining an object of any mutable type meant to be a constant, make sure to call freeze on it. Common examples are strings, arrays, and hashesMore on this.
The reason is that Ruby constants are actually mutable. Calling freeze
ensures they are not mutated and are therefore truly constant and attempting to modify them will raise an exception. For strings, this allows older versions of Ruby below 2.2 to intern them.
# bad
class Color
RED = 'red'
BLUE = 'blue'
GREEN = 'green'
ALL_COLORS = [
RED,
BLUE,
GREEN,
]
COLOR_TO_RGB = {
RED => 0xFF0000,
BLUE => 0x0000FF,
GREEN => 0x00FF00,
}
end
# good
class Color
RED = 'red'.freeze
BLUE = 'blue'.freeze
GREEN = 'green'.freeze
ALL_COLORS = [
RED,
BLUE,
GREEN,
].freeze
COLOR_TO_RGB = {
RED => 0xFF0000,
BLUE => 0x0000FF,
GREEN => 0x00FF00,
}.freeze
end
Naming
Shopify- Use
snake_case
for symbols, methods, and variables. - Use
CamelCase
for classes and modules (keep acronyms like HTTP, RFC, XML uppercase). - Use
snake_case
for naming files and directories, e.g.,hello_world.rb
. - Aim to have just a single class/module per source file. Name the file as the class/module, but replace
CamelCase
withsnake_case
. - Use
SCREAMING_SNAKE_CASE
for other constants. - When using inject with short blocks, name the arguments according to what is being injected, e.g.,
|hash, e|
(mnemonic: hash, element). - When defining binary operators, name the parameter
other
(<<
and[]
are exceptions to the rule since their semantics are different). - The names of predicate methods (methods that return a boolean value) should end in a question mark (i.e.,
Array#empty?
). Methods that don't return a boolean shouldn't end in a question mark. - Method names should not be prefixed with
is_
. E.g., preferempty?
overis_empty?
. - Avoid magic numbers. Use a constant and give it a useful name.
- Avoid nomenclature that has (or could be interpreted to have) discriminatory origins.
- Use
snake_case
for methods and variables. - Use
CamelCase
for classes and modules (keep acronyms like HTTP, RFC, XML uppercase). - Use
SCREAMING_SNAKE_CASE
for other constants. - The names of predicate methods (methods that return a boolean value) should end in a question mark (i.e.,
Array#empty?
). - The names of potentially "dangerous" methods (i.e., methods that modify self or the arguments,
exit!
, etc.) should end with an exclamation mark. Bang methods should only exist if a non-bang method exists. More on this.
- Use
snake_case
for methods and variables. - Use
CamelCase
for classes and modules (keep acronyms like HTTP, RFC, XML uppercase). - Use
SCREAMING_SNAKE_CASE
for other constants. - The names of predicate methods (methods that return a boolean value) should end in a question mark (i.e.,
Array#empty?
). - The names of potentially "dangerous" methods (i.e., methods that modify self or the arguments,
exit!
, etc.) should end with an exclamation mark. Bang methods should only exist if a non-bang method exists. More on this. - Name throwaway variables
_
.
version = '3.2.1'
major_version, minor_version, _ = version.split('.')
Shopify
Classes & Modules
-
Prefer modules to classes with only class methods. Classes should be used only when it makes sense to create instances out of them.
-
Favour the use of
extend self
overmodule_function
when you want to turn a module's instance methods into class methods.
# bad
module SomeModule
module_function
def some_method
end
def some_other_method
end
end
# good
module SomeModule
extend self
def some_method
end
def some_other_method
end
end
- Use a
{class << self}
block overdef self
. when defining class methods, and group them together within a single block.
# bad
class SomeClass
def self.method1
end
def method2
end
private
def method3
end
def self.method4 # this is actually not private
end
end
# good
class SomeClass
class << self
def method1
end
private
def method4
end
end
def method2
end
private
def method3
end
end
-
When designing class hierarchies make sure that they conform to the Liskov Substitution Principle.
-
Use the attr family of methods to define trivial accessors or mutators.
# bad
class Person
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
def first_name
@first_name
end
def last_name
@last_name
end
end
# good
class Person
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
- Avoid the use of
attr
. Useattr_reader
andattr_accessor
instead - Avoid the usage of class (
@@
) variables due to their "nasty" behavior in inheritance. - Indent the
public
,protected
, andprivate
methods as much as the method definitions they apply to. Leave one blank line above the visibility modifier and one blank line below in order to emphasize that it applies to all methods below it.
class SomeClass
def public_method
# ...
end
private
def private_method
# ...
end
def another_private_method
# ...
end
end
- Avoid alias when alias_method will do
- Avoid the usage of class (@@) variables due to their unusual behavior in inheritance
class Parent
@@class_var = "parent"
def self.print_class_var
puts @@class_var
end
end
class Child < Parent
@@class_var = "child"
end
Parent.print_class_var # => will print "child"
As you can see all the classes in a class hierarchy actually share one class variable. Class instance variables should usually be preferred over class variables.
- Use def
self.method
to define singleton methods. This makes the methods more resistant to refactoring changes.
class TestClass
# bad
def TestClass.some_method
# body omitted
end
# good
def self.some_other_method
# body omitted
end
- Avoid
{class << self}
except when necessary, e.g. single accessors and aliased attributes.
class TestClass
# bad
class << self
def first_method
# body omitted
end
def second_method_etc
# body omitted
end
end
# good
class << self
attr_accessor :per_page
alias_method :nwo, :find_by_name_with_owner
end
def self.first_method
# body omitted
end
def self.second_method_etc
# body omitted
end
end
- Avoid explicit use of self as the recipient of internal class or instance messages unless to specify a method shadowed by a variable.
class SomeClass
attr_accessor :message
def greeting(name)
message = "Hi #{name}" # local variable in Ruby, not attribute writer
self.message = message
end
end
Airbnb
Same as Github classes
Exceptions
Shopify- Signal exceptions using the
raise
method. - Don't specify
RuntimeError
explicitly in the two argument version ofraise
.
# bad
raise RuntimeError, 'message'
# good - signals a RuntimeError by default
raise 'message'
- Prefer supplying an exception class and a message as two separate arguments to
raise
, instead of an exception instance.
# bad
raise SomeException.new('message')
# Note that there is no way to do `raise SomeException.new('message'), backtrace`.
# good
raise SomeException, 'message'
# Consistent with `raise SomeException, 'message', backtrace`.
- Do not return from an
ensure
block. If you explicitly return from a method inside anensure
block, the return will take precedence over any exception being raised, and the method will return as if no exception had been raised at all. In effect, the exception will be silently thrown away.
def foo
raise
ensure
return 'very bad idea'
end
- Use implicit begin blocks where possible.
# bad
def foo
begin
# main logic goes here
rescue
# failure handling goes here
end
end
# good
def foo
# main logic goes here
rescue
# failure handling goes here
end
- Don't suppress exceptions
# bad
begin
# an exception occurs here
rescue SomeError
# the rescue clause does absolutely nothing
end
# bad - `rescue nil` swallows all errors, including syntax errors, and
# makes them hard to track down.
do_something rescue nil
- Avoid using
rescue
in its modifier form.
# bad - this catches exceptions of StandardError class and its descendant classes
read_file rescue handle_error($!)
# good - this catches only the exceptions of Errno::ENOENT class and its descendant classes
def foo
read_file
rescue Errno::ENOENT => error
handle_error(error)
end
- Avoid rescuing the
Exception
class.
# bad
begin
# calls to exit and kill signals will be caught (except kill -9)
exit
rescue Exception
puts "you didn't really want to exit, right?"
# exception handling
end
# good
begin
# a blind rescue rescues from StandardError, not Exception.
rescue => error
# exception handling
end
-
Favour the use of exceptions from the standard library over introducing new exception classes.
-
Don't use single letter variables for exceptions (
error
isn't that hard to type).
# bad
begin
# an exception occurs here
rescue => e
# exception handling
end
# good
begin
# an exception occurs here
rescue => error
# exception handling
end
Github
- Don't use exceptions for flow of control.
# bad
begin
n / d
rescue ZeroDivisionError
puts "Cannot divide by 0!"
end
# good
if d.zero?
puts "Cannot divide by 0!"
else
n / d
end
- Rescue specific exceptions, not StandardError or its superclasses.
# bad
begin
# an exception occurs here
rescue
# exception handling
end
# still bad
begin
# an exception occurs here
rescue Exception
# exception handling
end
Airbnb
- Don't use exceptions for flow of control
# bad
begin
n / d
rescue ZeroDivisionError
puts "Cannot divide by 0!"
end
# good
if d.zero?
puts "Cannot divide by 0!"
else
n / d
end
- Avoid rescuing the
Exception
class
# bad
begin
# an exception occurs here
rescue Exception
# exception handling
end
# good
begin
# an exception occurs here
rescue StandardError
# exception handling
end
# acceptable
begin
# an exception occurs here
rescue
# exception handling
end
- Don't specify
RuntimeError
explicitly in the two argument version of raise. Prefer error sub-classes for clarity and explicit error creation
# bad
raise RuntimeError, 'message'
# better - RuntimeError is implicit here
raise 'message'
# best
class MyExplicitError < RuntimeError; end
raise MyExplicitError
- Prefer supplying an exception class and a message as two separate arguments to
raise
, instead of an exception instance.
# bad
raise SomeException.new('message')
# Note that there is no way to do `raise SomeException.new('message'), backtrace`.
# good
raise SomeException, 'message'
# Consistent with `raise SomeException, 'message', backtrace`.
- Avoid using rescue in its modifier form
# bad
read_file rescue handle_error($!)
# good
begin
read_file
rescue Errno:ENOENT => ex
handle_error(ex)
end
Collections
Shopify- Prefer literal array and hash creation notation (unless you need to pass parameters to their constructors, that is).
# bad
arr = Array.new
hash = Hash.new
# good
arr = []
hash = {}
- Prefer the literal array syntax to
%w
, except when it reads substantially more clearly in context.
# bad
STATES = %w(draft open closed)
# good
STATES = ['draft', 'open', 'closed']
- Usage of trailing comma in multi-line collection literals is encouraged. It makes diffs smaller and more meaningful.
# not encouraged
{
foo: :bar,
baz: :toto
}
# encouraged
{
foo: :bar,
baz: :toto,
}
- Prefer the literal array syntax to
%i
# bad
STATES = %i(draft open closed)
# good
STATES = [:draft, :open, :closed]
- When accessing the first or last element from an array, prefer
first
orlast
over[0]
or[-1]
- Avoid the use of mutable objects as hash keys
- Use the Ruby 1.9 hash literal syntax when your hash keys are symbols
- Don't mix the Ruby 1.9 hash syntax with hash rockets in the same hash literal. When you've got keys that are not symbols stick to the hash rockets syntax.
# bad
{ a: 1, 'b' => 2 }
# good
{ :a => 1, 'b' => 2 }
- Use
Hash#key?
instead ofHash#has_key?
andHash#value?
instead ofHash#has_value?
. As noted here by Matz, the longer forms are considered deprecated.
# bad
hash.has_key?(:test)
hash.has_value?(value)
# good
hash.key?(:test)
hash.value?(value)
- Use
Hash#fetch
when dealing with hash keys that should be present.
heroes = { batman: 'Bruce Wayne', superman: 'Clark Kent' }
# bad - if we make a mistake we might not spot it right away
heroes[:batman] # => "Bruce Wayne"
heroes[:supermann] # => nil
# good - fetch raises a KeyError making the problem obvious
heroes.fetch(:supermann)
- Introduce default values for hash keys via
Hash#fetch
as opposed to using custom logic.
batman = { name: 'Bruce Wayne', is_evil: false }
# bad - if we just use || operator with falsy value we won't get the expected result
batman[:is_evil] || true # => true
# good - fetch work correctly with falsy values
batman.fetch(:is_evil, true) # => false
Closing ]
and }
must be on the line after the last element when opening brace is on a separate line from the first element.
# bad
[
1,
2]
{
a: 1,
b: 2}
# good
[
1,
2,
]
{
a: 1,
b: 2,
}
Github
- Prefer
%w
to the literal array syntax when you need an array of strings.
# bad
STATES = ["draft", "open", "closed"]
# good
STATES = %w(draft open closed)
-
Use
Set
instead ofArray
when dealing with unique elements.Set
implements a collection of unordered values with no duplicates. This is a hybrid ofArray
's intuitive inter-operation facilities andHash
's fast lookup. -
Use symbols instead of strings as hash keys.
# bad
hash = { "one" => 1, "two" => 2, "three" => 3 }
# good
hash = { :one => 1, :two => 2, :three => 3 }
Airbnb
-
Prefer
map
overcollect
-
Prefer
detect
overfind
. The use offind
is ambiguous with regard to ActiveRecord's find method -detect
makes clear that you're working with a Ruby collection, not an AR object. -
Prefer
reduce
overinject
-
Prefer
size
over eitherlength
orcount
for performance reasons. -
Prefer literal array and hash creation notation unless you need to pass parameters to their constructors.
# bad
arr = Array.new
hash = Hash.new
# good
arr = []
hash = {}
# good because constructor requires parameters
x = Hash.new { |h, k| h[k] = {} }
- Favor Array#join over Array#* for clarity.
# bad
%w(one two three) * ', '
# => 'one, two, three'
# good
%w(one two three).join(', ')
# => 'one, two, three'
- Use symbols instead of strings as hash keys.
# bad
hash = { 'one' => 1, 'two' => 2, 'three' => 3 }
# good
hash = { :one => 1, :two => 2, :three => 3 }
- Relatedly, use plain symbols instead of string symbols when possible.
# bad
:"symbol"
# good
:symbol
- Use multi-line hashes when it makes the code more readable, and use trailing commas to ensure that parameter changes don't cause extraneous diff lines when the logic has not otherwise changed.
hash = {
:protocol => 'https',
:only_path => false,
:controller => :users,
:action => :set_password,
:redirect => @redirect_url,
:secret => @secret,
}
- Use a trailing comma in an Array that spans more than 1 line.
# good
array = [1, 2, 3]
# good
array = [
"car",
"bear",
"plane",
"zoo",
]
Strings
Shopify- Prefer string interpolation and string formatting instead of string concatenation:
# bad
email_with_name = user.name + ' <' + user.email + '>'
# good
email_with_name = "#{user.name} <#{user.email}>"
# good
email_with_name = format('%s <%s>', user.name, user.email)
- With interpolated expressions, there should be no padded-spacing inside the braces.
# bad
"From: #{ user.first_name }, #{ user.last_name }"
# good
"From: #{user.first_name}, #{user.last_name}"
-
Adopt a consistent string literal quoting style
-
Don't use the character literal syntax
?x
. Since Ruby 1.9 it's basically redundant -?x
would interpreted as'x'
(a string with a single character in it). -
Don't leave out
around instance and global variables being interpolated into a string.
class Person
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
# bad - valid, but awkward
def to_s
"#@first_name #@last_name"
end
# good
def to_s
"#{@first_name} #{@last_name}"
end
end
$global = 0
# bad
puts "$global = #$global"
# fine, but don't use globals
puts "$global = #{$global}"
- Don't use
Object#to_s
on interpolated objects. It's invoked on them automatically.
# bad
message = "This is the #{result.to_s}."
# good
message = "This is the #{result}."
- Don't use
String#gsub
in scenarios in which you can use a faster more specialized alternative.
url = 'http://example.com'
str = 'lisp-case-rules'
# bad
url.gsub('http://', 'https://')
str.gsub('-', '_')
str.gsub(/[aeiou]/, '')
# good
url.sub('http://', 'https://')
str.tr('-', '_')
str.delete('aeiou')
- When using heredocs for multi-line strings keep in mind the fact that they preserve leading whitespace. It's a good practice to employ some margin based on which to trim the excessive whitespace.
code = <<-END.gsub(/^\s+\|/, '')
|def test
| some_method
| other_method
|end
END
# => "def test\n some_method\n other_method\nend\n"
# In Rails you can use `#strip_heredoc` to achieve the same result
code = <<-END.strip_heredoc
def test
some_method
other_method
end
END
# => "def test\n some_method\n other_method\nend\n"
- In Ruby 2.3, prefer "squiggly heredoc" syntax, which has the same semantics as strip_heredoc from Rails:
code = <<~END
def test
some_method
other_method
end
END
# => "def test\n some_method\n other_method\nend\n"
Github
- Prefer string interpolation instead of string concatenation:
# bad
email_with_name = user.name + " <" + user.email + ">"
# good
email_with_name = "#{user.name} <#{user.email}>"
- Use double-quoted strings. Interpolation and escaped characters will always work without a delimiter change, and
'
is a lot more common than"
in string literals.
# bad
name = 'Bozhidar'
# good
name = "Bozhidar"
- Avoid using
String#+
when you need to construct large data chunks. Instead, use{String#<<}
. Concatenation mutates the string instance in-place and is always faster thanString#+
, which creates a bunch of new string objects.
# good and also fast
html = ""
html << "<h1>Page title</h1>"
paragraphs.each do |paragraph|
html << "<p>#{paragraph}</p>"
end
Airbnb
- Use \ at the end of the line instead of + or {<<} to concatenate multi-line strings.
# bad
"Some string is really long and " +
"spans multiple lines."
"Some string is really long and " <<
"spans multiple lines."
# good
"Some string is really long and " \
"spans multiple lines."
Regular Expressions
Shopify- Don't use regular expressions if you just need plain text search in string:
string['text']
- Use non-capturing groups when you don't use the captured result.
# bad
/(first|second)/
# good
/(?:first|second)/
- Don't use the cryptic Perl-legacy variables denoting last regexp group matches
($1, $2, etc)
. UseRegexp#match
instead.
# bad
/(regexp)/ =~ string
process $1
# good
/(regexp)/.match(string)[1]
- Avoid using numbered groups as it can be hard to track what they contain. Named groups can be used instead.
# bad
/(regexp)/ =~ string
...
process Regexp.last_match(1)
# good
/(?<meaningful_var>regexp)/ =~ string
...
process meaningful_var
- Be careful with
^
and$
as they match start/end of line, not string endings. If you want to match the whole string use:\A
and\z
(not to be confused with\Z
which is the equivalent of/\n?\z/)
.
string = "some injection\nusername"
string[/^username$/] # matches
string[/\Ausername\z/] # doesn't match
Github
- Avoid using $1-9 as it can be hard to track what they contain. Named groups can be used instead.
# bad
/(regexp)/ =~ string
...
process $1
# good
/(?<meaningful_var>regexp)/ =~ string
...
process meaningful_var
- Use
x
modifier for complex regexps. This makes them more readable and you can add some useful comments. Just be careful as spaces are ignored.
regexp = %r{
start # some text
\s # white space char
(group) # first group
(?:alt1|alt2) # some alternation
end
}x
Airbnb
Same Github's style
Percent Literals
Shopify-
Use
%()
(it's a shorthand for%Q
) for single-line strings which require both interpolation and embedded double-quotes. For multi-line strings, prefer heredocs. -
Avoid
%q
unless you have a string with both'
and"
in it. Regular string literals are more readable and should be preferred unless a lot of characters would have to be escaped in them. -
Use
%r
only for regular expressions matching at least one '/' character.
# bad
%r{\s+}
# good
%r{^/(.*)$}
%r{^/blog/2011/(.*)$}
- Avoid the use of
%s
. Use:"some string"
to create a symbol with spaces in it. - Prefer
()
as delimiters for all % literals, except, as often occurs in regular expressions, when parentheses appear inside the literal. Use the first of()
,,
[]
,{<>}
which does not appear inside the literal.
- Use
%w
freely
STATES = %w(draft open closed)
- Use
%()
for single-line strings which require both interpolation and embedded double-quotes. For multi-line strings, prefer heredocs.
# bad (no interpolation needed)
%(<div class="text">Some text</div>)
# should be "<div class=\"text\">Some text</div>"
# bad (no double-quotes)
%(This is #{quality} style)
# should be "This is #{quality} style"
# bad (multiple lines)
%(<div>\n<span class="big">#{exclamation}</span>\n</div>)
# should be a heredoc.
# good (requires interpolation, has quotes, single line)
%(<tr><td class="name">#{name}</td>)
- Use
%r
only for regular expressions matching more than one'/'
character.
# bad
%r(\s+)
# still bad
%r(^/(.*)$)
# should be /^\/(.*)$/
# good
%r(^/blog/2011/(.*)$)
Airbnb
Same Github's style
Testing
- Treat test code like any other code you write. This means: keep readability, maintainability, complexity, etc. in mind.
- Minitest is the preferred test framework.
- A test case should only test a single aspect of your code.
- A good test case consists of three parts:
- Setup of the environment
- The action that is the subject of the test
- Asserting that the action did what you expect it do to.
Consider separating these parts by a newline for readability, especially when your environment setup is complicated and you want to run multiple assertions afterwards.
test 'sending a password reset email clears the password hash and set a reset token' do
user = User.create!(email: 'bob@example.com')
user.mark_as_verified
user.send_password_reset_email
assert_nil user.password_hash
refute_nil user.reset_token
end
- A complex test should be split into multiple simpler tests that test functionality in isolation.
- Prefer using
test 'foo'-style
syntax to define test cases overdef test_foo
. - Prefer using assertion methods that will yield a more descriptive error message.
# bad
assert user.valid?
assert user.name == 'tobi'
# good
assert_predicate user, :valid?
assert_equal 'tobi', user.name
- Avoid using
assert_nothing_raised
. Use a positive assertion instead. - Prefer using assertions over expectations. Expectations lead to more brittle tests, especially in combination with singleton objects.
# bad
StatsD.expects(:increment).with('metric')
do_something
# good
assert_statsd_increment('metric') do
do_something
end
Rails
- When immediately returning after calling
render
orredirect_to
, putreturn
on the next line, not the same line
# bad
render :text => 'Howdy' and return
# good
render :text => 'Howdy'
return
# still bad
render :text => 'Howdy' and return if foo.present?
# good
if foo.present?
render :text => 'Howdy'
return
end
- When defining ActiveRecord model scopes, wrap the relation in a
lambda
. A naked relation forces a database connection to be established at class load time (instance startup).
# bad
scope :foo, where(:bar => 1)
# good
scope :foo, -> { where(:bar => 1) }
Be Consistent
If you're editing code, take a few minutes to look at the code around you and determine its style. If they use spaces around all their arithmetic operators, you should too. If their comments have little boxes of hash marks around them, make your comments have little boxes of hash marks around them too.
The point of having style guidelines is to have a common vocabulary of coding so people can concentrate on what you're saying rather than on how you're saying it. We present global style rules here so people know the vocabulary, but the local style is also important. If code you add to a file looks drastically different from the existing code around it, it throws readers out of their rhythm when they go to read it. Avoid this.
Above all else
Follow your ❤️