wiki

View on GitHub

Ruby coding convention

Mở đầu

Phạm vi bài viết này chọn lọc những quy ước thường gặp nhất khi dùng Ruby trong software development. Có cả những quy ước chủ quan dựa trên kinh nghiệm của người viết và tham khảo các styleguide được đánh giá cao ở trên Github

General

sum = 1 + 2
a, b = 1, 2
1 > 2 ? true : false; puts "Hello World"
[1, 2, 3].each { |e| puts e }
method_call(arg).other
[1, 2, 3].length
!array.include? element
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
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
# Bad Ex (viết 1 mạch quá 80 kí tự)
def send_mail(source)
  Mailer.deliver(to: 'bob@example.com', from: 'us@example.com', subject: 'Important message', body: source.text)
end

# Bad (Trong trường hợp tên class và tên hàm quá dài dẫn đến indent xấu)
# Trang https://github.com/bbatsov/ruby-style-guide định nghĩa TH này là Good
def send_mail(source)
  Mailer.deliver(to: 'bob@example.com',
                 from: 'us@example.com',
                 subject: 'Important message',
                 body: source.text)
end

# Good
def send_mail(source)
  Mailer.deliver(
    to: 'bob@example.com',
    from: 'us@example.com',
    subject: 'Important message',
    body: source.text)

Loop

arr = [1, 2, 3]

# Bad
for elem in arr do
  puts elem
end

# Good
arr.each { |elem| puts elem }

Nguyên nhân là do biến elem được truyền trong scope nên sẽ không có thể gọi được từ bên ngoài nữa

# bad
while x > 5 do
  # body omitted
end

until x > 5 do
  # body omitted
end

# good
while x > 5
  # body omitted
end

until x > 5
  # body omitted
end
# Bad
while some_condition
  do_something
end

# Good
do_something while some_condition
# bad
while true
  do_something
end

until false
  do_something
end

# good
loop do
  do_something
end

Conditional Syntax

# Bad
if some_condition then
end

# Good
if some_condition
end
# Bad
result = if some_condition then something else something_else end

# Good
result = some_condition ? something : something_else
# 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
# Bad
if some_condition
end

# Good
do_something if some_condition
# Bad
do_something if !some_condition

# Good
do_something unless some_condition
# Bad
unless success?
  puts 'failure'
else
  puts 'success'
end

# Good
if success?
  puts 'success'
else
  puts 'failure'
end
# Bad
if (x > 10)
end

# Good
if x > 10
end
# Bad
if (v = array.grep(/foo/)) ...

# Bad
if v = array.grep(/foo/) ...

# Bad
if (v = self.next_value) == "hello" ...

# Good
v = self.next_value
if v == 'hello' ...

Strings

# bad
email_with_name = user.name + " <" + user.email + ">"

# good
email_with_name = "#{user.name} <#{user.email}>"
# Good
name = 'Bozhidar'

# Bad
name = "Bozhidar"
str <<-HEREDOC
        Subscription expiring soon!
        Your free trial will expire in #{days_until_expiration} days.
        Please update your billing information.
      HEREDOC

Block

names = ['Bozhidar', 'Steve', 'Sarah']

# Good
names.each { |name| puts name }

# Bad
names.each do |name|
  puts name
end

# Bad
names.each { |name| puts sugoku.nagai.shori(fukuzatsu.na.shori(name)) }

# Good
names.each do |name|
  name = fukuzatsu.na.shori(name)
  name = sugoku.nagai.shori(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 }
# Bad
result = hash.map { |k, v| v + 1 }

# Good
result = hash.map { |_, v| v + 1 }

Method

# Bad
def some_method()
end

# Good
def some_method
end

# Bad
def some_method_with_args arg1, arg2
end

# Good
def some_method_with_args(arg1, arg2)
end
def some_method
  data = initialize(options)

  data.manipulate!

  data.result
end

def some_method
  result
end
# Bad
def some_method(some_arr)
  return some_arr.size
end

# Good
def some_method(some_arr)
  some_arr.size
end
# Bad
def some_method(arg1=:default, arg2=nil, arg3=[])
end

# Good
def some_method(arg1 = :default, arg2 = nil, arg3 = [])
end
# Bad
def too_much; something; something_else; end

# Good
def some_method
  body
end

Variables

name ||= 'Bozhidar' # name = Bozhidar nếu name == nil
# Bad
enabled ||= true

# Good
enabled = true if enabled.nil?

Array, Hash

# bad
STATES = ["draft", "open", "closed"]

# good
STATES = %w(draft open closed)
# Bad
hash = { :one => 1, :two => 2 }

# Good
hash = { one: 1, two: 2 }

Keyword Arguments

Keyword Arguments là tính năng từ Ruby 2.0 cho phép truyền 1 hash các option vào làm arguments của method.

Thay vì sử dụng

def remove_member(user, skip_membership_check=false)
  # ...
end
# Giá trị true ở phía dưới khá khó hiểu
remove_member(user, true)

Ta có thể viết lại đẹp hơn với keyword arguments

def remove_member(user, skip_membership_check: false)
  # ...
end
remove_member user, skip_membership_check: true

Lambda

# Bad
lambda = lambda { |a, b| a + b }
lambda.call(1, 2)

# Good
lambda = ->(a, b) { a + b }
lambda.(1, 2)

Numbers

# bad
rand(6) + 1

# good
rand(1..6)
nil.to_i #=> 0

Classes

class TestClass
  # bad
  def TestClass.some_method
    # body omitted
  end

  # good
  def self.some_other_method
    # body omitted
  end
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'
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
class SomeClass
  def public_method
    # ...
  end

  private
  def private_method
    # ...
  end
end
# bad
class FooError < StandardError
end

# Good
class FooError < StandardError; end

Rules đặt tên

class Array
  def flatten_once!
    res = []

    each do |e|
      [*e].each { |f| res << f }
    end

    replace(res)
  end

  def flatten_once
    dup.flatten_once!
  end
end

Exceptions

# bad
fail SomeException, 'message'

# good
raise SomeException, 'message'
# 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`.
# 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

Comments

# bad
counter += 1 # Increments counter by one.