Invoca Ruby Style Guide initially forked from https://github.com/bbatsov/ruby-style-guide.
The contents of this styleguide are to be enforced by (rubocop)[https://github.com/bbatsov/rubocop] and automated in the code review process by (HoundCI)[https://houndci.com/]
Any adjustments to this styleguide should be captured in the .rubocop.yml
config.
- Source Code Layout
- Syntax
- Naming
- Comments
- Annotations
- Classes
- Exceptions
- Collections
- Strings
- Regular Expressions
- Metaprogramming
- Misc
# good
def some_method
do_something
end
# bad - four spaces
def some_method
do_something
end
and before }
. (But there is no need for spaces inside the empty hash {}
.)
a, b = 1, 2 + 3
location = { :city => 'Santa Barbara', :state => 'CA' }
size > 10 ? 'large' : 'small'
[1, 2, 3].each { |e| puts e }
params = {}
The only exception is when using the exponent operator:
# bad
e = M * c ** 2
# good
e = M * c**2
some(arg).other
[1, 2, 3].length
collection = []
with this one, but it's the style established in both the "The Ruby Programming Language" and "Programming Ruby".
case
when song.name == 'Misty'
puts 'Not again!'
when song.duration > 120
puts 'Too long!'
when Time.now.hour > 21
puts "It's too late"
else
song.play
end
kind = case year
when 1850..1889 then 'Blues'
when 1890..1909 then 'Ragtime'
when 1910..1929 then 'New Orleans Jazz'
when 1930..1939 then 'Swing'
when 1940..1950 then 'Bebop'
else 'Jazz'
end
def some_method
data = initialize(options)
data.manipulate!
data.result
end
def some_method
result
end
# starting point (line is too long)
def send_mail(source)
Mailer.deliver(to: 'bob@example.com', from: 'us@example.com', subject: 'Important message', body: source.text)
end
# good (normal indent)
def send_mail(source)
Mailer.deliver(
to: 'bob@example.com',
from: 'us@example.com',
subject: 'Important message',
body: source.text)
end
# bad (double indent)
def send_mail(source)
Mailer.deliver(
to: 'bob@example.com',
from: 'us@example.com',
subject: 'Important message',
body: source.text)
end
# bad
def send_mail(source)
Mailer.deliver(to: 'bob@example.com',
from: 'us@example.com',
subject: 'Important message',
body: source.text)
end
# bad
collection
.transformA { ... }
.transformB { ... }
# good
collection
.transformA { ... }
.transformB { ... }
# good
while myvariable
.a
.b
# do something
end
# good
myvariable = Thing
.a
.b
.c
Don't put an empty line between the comment block and the def
.
Omit the parentheses when the method doesn't accept any arguments.
def some_method
...
end
def some_method_with_arguments(arg1, arg2)
...
end
Most of the time iterators should be used instead. for
is implemented in terms of each
(so
you're adding a level of indirection), but with a twist - for
doesn't introduce a new scope (unlike each
) 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 }
# bad
if some_condition then
...
end
# good
if some_condition
...
end
This also means that ternary operators must not be nested. Prefer
if/else
constructs in these cases.
Avoid multi-line ternary; use if/unless
instead.
# 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
# boolean expression
if document.text_changed? || document.settings_changed?
document.save!
end
# control flow
document.saved? or document.save!
Beware: and/or
have lower precedence than =
!
flag = top_of_page? or reset_page
# is equivalent to
(flag = top_of_page?) or reset_page
Only use trailing if/unless
when they are rare footnotes that can be ignored in the usual, "go-right" case.
That is, the statement you start with should almost always execute.
(A good alternative for assertions and other one-line code that rarely executes is control-flow and/or
.)
# bad -- the raise rarely executes
raise ArgumentError, "name must be provided" unless name.present?
# good
name.present? or raise ArgumentError, "name must be provided"
# good -- the unless is a rare footnote
format(page) unless page.already_formatted?
# good -- the if is almost always true
send_notification(users) if users.any?
# 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
, unless the condition contains an assignment.
(see "Using the return value of =
" below).
# bad
if (x > 10)
...
end
# good
if x > 10
...
end
# ok
if (x = self.next_value)
...
end
# bad
while some_condition
do_something
end
# good
do_something while some_condition
# bad
do_something while !some_condition
# good
do_something until some_condition
(e.g. Rake, Rails, RSpec), methods that are with
"keyword" status in Ruby (e.g. attr_reader
, puts
) and attribute
access methods. It is preferred to use parentheses around the arguments of all other
method invocations.
class Person
attr_reader :name, :age
...
end
temperance = Person.new('Temperance', 30)
temperance.name
puts temperance.age
x = Math.sin(y)
array.delete(e)
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 blocks contents be extracted into nifty methods?
(Omitting return
is more succinct and declarative, and your code will still work if you refactor it into a block later.)
# bad
def some_method(some_arr)
return some_arr.size
end
# good
def some_method(some_arr)
some_arr.size
end
# bad
def click_url
return "http://#{click_domain}/c" if click_domain.nonblank?
return click_url_for_hostname(vanity_domain) if vanity_domain.nonblank?
click_url_for_hostname(CLICK_URL_DEFAULT_DOMAIN)
end
# good
def click_url
if click_domain.nonblank?
"http://#{click_domain}/c"
elsif vanity_domain.nonblank?
click_url_for_hostname(vanity_domain)
else
click_url_for_hostname(CLICK_URL_DEFAULT_DOMAIN)
end
end
# bad
def should_redirect?
return false unless rendered?
return true if redirect_location.nonblank?
return true if global_redirect?
return false
end
# good
def should_redirect?
!rendered && (redirect_location.nonblank? || global_redirect?)
end
# better still
def have_redirect?
redirect_location.nonblank? || global_redirect?
end
def should_redirect?
!rendered && have_redirect?
end
# bad
def ready?
if self.last_reviewed_at > self.last_updated_at
self.worker.update(self.content, self.options)
self.status = :in_progress
end
self.status == :verified
end
# good
def ready?
if last_reviewed_at > last_updated_at
worker.update(content, options)
self.status = :in_progress
end
status == :verified
end
class Foo
attr_accessor :options
# ok
def initialize(options)
self.options = options
# both options and self.options are equivalent here
end
# bad
def do_something(options = {})
unless options[:when] == :later
output(self.options[:message])
end
end
# good
def do_something(params = {})
unless params[:when] == :later
output(options[:message])
end
end
end
# 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).
# bad
result = 1 \
- 2
# better
result = 1 -
2
But surround the assignment with parentheses to make it clear you are not mistakenly using =
when you meant ==
.
# good - shows intended use of assignment
if (v = array.grep(/foo/)) ...
# bad
if v = array.grep(/foo/) ...
# also good - shows intended use of assignment and has correct precedence.
if (v = next_value) == 'hello' ...
(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?
They are cryptic and global.
# bad
f (3 + 2) + 1
# good
f(3 + 2) + 1
# bad
hash = { :one => 1, :two => 2 }
# good
hash = { one: 1, two: 2 }
This is because lambda
s enforce argument list cardinality and have unsurprising return
semantics.
(Only use proc
if you really need a return statement that returns from the enclosing code.)
More details here.
This is because stabby lambda arguments are treated just like regular method arguments. For example, these special arguments work for stabby lambda but not lambda
or Proc.new
:
- default arguments
- keyword arguments
- block (
&
) argument
# bad
lam = lambda { |a, b| a + (b || 0) }
lam.call(1, 2)
# good
lam = ->(a, b = 0) { a + b }
lam.call(1, 2)
# bad
result = hash.map { |k, v| v + 1 }
# good
result = hash.map { |_, v| v + 1 }
Use CamelCase
for classes and modules. Keep acronyms like HTTP, RFC, XML uppercase when possible. (Upper case words cause problems for rails routes.)
(e.g. Array#empty?
)
(e.g. methods that have side-effects like mutating a variable or changing the process flow)
map
overcollect
reduce
overinject
find
overdetect
select
overfind_all
size
overlength
Company names with capitals in the middle (e.g. RingRevenue) should drop the inner capitalization so that rails string helpers don't insert underscores in the middle
Examples:
- RingRevenue: Constantize: Ringrevenue, underscored: ringrevenue
- HubSpot => Hubspot hubspot
- HubspotIntegration => hubspot_integration
Good code is its own best documentation. As you're about to add a comment, ask yourself, "How can I improve the code so that this comment isn't needed?" Improve the code and then document it to make it even clearer.
-- Steve McConnell
Try to make your classes as [SOLID](http://en.wikipedia.org/wiki/SOLID_(object-oriented_design\)) as possible.
Use @@ variables when you want process-wide globals (such as for a process-wide cache).
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.
Assign proper visibility levels to methods (private
, protected
) in accordance with their intended usage.
Indent the public
, protected
, and private
methods as much the method definitions they apply to. Leave one blank line above and below them.
class SomeClass
def public_method
...
end
private
def private_method
...
end
end
Use class << self
or def self.method
to define singleton methods so you don't have to repeat the class name (Don't Repeat Yourself).
class TestClass
# bad
def TestClass.some_method
...
end
# good
def self.some_other_method
...
end
# best
# this form lets you define many class methods,
# and public/private
# and attr_reader work as expected.
class << self
def first_method
...
end
private
def second_method_etc
...
end
end
end
If you explicitly return from a
method inside an ensure
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
begin
fail
ensure
return 'very bad idea'
end
end
# 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
Mitigate the proliferation of begin
blocks by using contingency methods (a term coined by Avdi Grimm).
# bad
begin
something_that_might_fail
rescue IOError
# handle IOError
end
begin
something_else_that_might_fail
rescue IOError
# handle IOError
end
# good
def with_io_error_handling
yield
rescue IOError
# handle IOError
end
with_io_error_handling { something_that_might_fail }
with_io_error_handling { something_else_that_might_fail }
# bad
begin
# an exception occurs here
rescue SomeError
# the rescue clause does absolutely nothing
end
# bad
do_something rescue nil
# 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. This will trap signals and calls to exit
, requiring you to kill -9
the process.
# 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 as many
# programmers assume.
rescue => e
# exception handling
end
# also good
begin
# an exception occurs here
rescue StandardError => e
# exception handling
end
# bad
begin
# some code
rescue Exception => e
# some handling
rescue StandardError => e
# some handling
end
# good
begin
# some code
rescue StandardError => e
# some handling
rescue Exception => e
# some handling
end
f = File.open('testfile')
begin
# .. process
rescue
# .. handle error
ensure
f.close unless f.nil?
end
Use exceptions from the standard library for simple cases so you can avoid introducing new exception classes.
Set
implements a collection of unordered values with no duplicates. This
is a hybrid of Array
's intuitive inter-operation facilities and
Hash
's fast lookup.
Define them as ['a', 'b']
or 'a b'.split(' ')
# bad
email_with_name = user.name + ' <' + user.email + '>'
# good
email_with_name = "#{user.name} <#{user.email}>"
String#+
, avoids mutation (which is good in a functional way) but therefore runs slower since it creates a new string object.
# faster
html = ''
paragraphs.each do |paragraph|
html << "<p>#{paragraph}</p>"
end
# more functional but slower
html = ''
paragraphs.each do |paragraph|
html += "<p>#{paragraph}</p>"
end
# most functional
html = paragraphs.map do |paragraph|
"<p>#{paragraph}</p>"
end.join
# bad
if command[/quit/]
...
end
# good
if command['quit']
...
end
match = string[/regexp/] # get content of matched regexp
first_group = string[/text(grp)/, 1] # get content of captured group
string[/text (grp)/, 1] = 'replace' # string => 'text replace'
Avoid using $1-9 as it can be hard to track what they contain and they live globally. Numbered indexes or named groups can be used instead.
# bad
/\A(https?):/ =~ url
...
setup_connection($1)
# good
protocol = url[/\A(https?):/, 1]
...
setup_connection(protocol)
# good
/\A(?<protocol>https?):/ =~ url
...
setup_connection(protocol)
Be careful not to use ^
and $
for anchors (like in other languages) because in Ruby they also match newlines.
For anchors, 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/] # don't match
Use x
modifier for complex regexps so that you can use whitespace and comments to make them more readable.
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
For example, deliver_<mail_message>
in TMail was completely
unnecessary since it was equivalent to simply deliver(:mail_message)
.
Generally across all code in the process including other gems and libraries (separation of concerns!):
# bad
class Fixnum
def days
...
end
end
- when you use the string-interpolated form, always supply
__FILE__
and__LINE__
, so that your backtraces make sense:
class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__
define_method
is preferable toclass_eval { def ... }
Backtraces become messy; the behavior is not listed in #methods
; misspelled method calls might silently work (nukes.launch_state = false
).
Consider using delegation, proxy, or define_method
instead. If you must, use method_missing
,
- be sure to also define
respond_to_missing?
- only catch methods with a well-defined prefix, such as
find_by_*
-- make your code as assertive as possible. - call
super
at the end of your statement - delegate to a reusable, testable, non-magical method:
# bad
def method_missing?(meth, *args, &block)
if /^find_by_(?<prop>.*)/ =~ meth
# ... lots of code to do a find_by
else
super
end
end
# good
def method_missing?(meth, *args, &block)
if /^find_by_(?<prop>.*)/ =~ meth
find_by(prop, *args, &block)
else
super
end
end
# best of all, though, would be to define_method as each findable attribute is declared
Try an editor that will show you these when you save.
When code patterns are repeated, use separate lines and extra whitespace when practical to align columns so that the code is tabular.
This makes the patterns obvious which helps to spot/prevent bugs.
# bad
PROMOTIONAL_METHODS = [
[ 'review_site', 'Content / Review Site'],
[ 'coupon_site', 'Discount / Coupon Site' ],
[ 'display', 'Display' ],
[ 'email', 'Email' ],
[ 'rewards', 'Rewards / Incentive' ],
[ 'leads', 'Lead Form / Co Reg' ],
[ 'search', 'Search' ],
[ 'social_media', 'Social Media' ],
[ 'software', 'Software' ],
[ 'other', 'Other' ]
],
...
# good
PROMOTIONAL_METHODS = [
[ 'review_site', 'Content / Review Site'],
[ 'coupon_site', 'Discount / Coupon Site' ],
[ 'display', 'Display' ],
[ 'email', 'Email' ],
[ 'rewards', 'Rewards / Incentive' ],
[ 'leads', 'Lead Form / Co Reg' ],
[ 'search', 'Search' ],
[ 'social_media', 'Social Media' ],
[ 'software', 'Software' ],
[ 'other', 'Other' ]
],
...
# bad
params = {
'phone' => calling_phone_number.to_param,
'LastName' => last_name,
'FirstName' => first_name,
'Address' => primary_address,
'ApartmentNum' => secondary_address,
'City' => city_name,
'State' => state,
'Zipcode' => zip
}
...
# good
params = {
'phone' => calling_phone_number.to_param,
'LastName' => last_name,
'FirstName' => first_name,
'Address' => primary_address,
'ApartmentNum' => secondary_address,
'City' => city_name,
'State' => state,
'Zipcode' => zip
}
avoid side-effects like object mutation unless required by performance concerns. Mutating arguments is a side-effect so don't do it unless that is the sole purpose of the method.
- Don't put required parameters into options hashes.
Ideally, most methods will be shorter than 5 lines of code. Comments and empty lines do not count.
ruby -s
for trivial command line options.