Refactoring

A complex code usually is a very long method doing a lot of things. The first thing we do is to find the correspondent test for that method, make sure tests are passing, then using some of the following techniques to refactor and make sure tests still pass.

def price
  order.quantity * order.itemPrice - [0, order.quantity - 500].max * order.itemPrice * 0.05 + [order.quantity * order.itemPrice * 0.1, 100].min
end

=>

def price
  base_price = order.quantity * order.itemPrice
  discount_by_quantity = [0, order.quantity - 500].max * order.itemPrice * 0.05
  shipping_cost = [order.quantity * order.itemPrice * 0.1, 100].min

  basePrice - quantityDiscount + shipping;
end

Once we got lots of variables in a long method, we can extract to a method.

Increase readability by naming what we are doing, foster reuse of methods.

def price
  basePrice - quantityDiscount + shipping;
end

private

def base_price
  order.quantity * order.itemPrice
end

def discount_by_quantity
  [0, order.quantity - 500].max * order.itemPrice * 0.05
end

def shipping_cost
  [order.quantity * order.itemPrice * 0.1, 100].min
end

A lot of times in Rails, we want to do something when user is not signed in. People may add logics in user model or in controller. But a better way is to introduce a Guest.

def current_user
  super || Guest.new
end

class Guest
  def do_something
  end
end

def some_method_requires_signed_in
  current_user.do_something
end

instead of

def some_method_requires_signed_in
  if current_user.signed_in?
    current_user.do_something
  else
    current_user.do_something_when_not_signed_in
  end
end

Pass no more than four parameters into a method. Hash options are parameters.

You can wrap long list of parameters to a Parameter Object and use it accordingly.

def complex_method(a, b, c, d, e = {})
end
class Parameter
  attr_reader :a, :b, :c, :d, :e
  def initialize(a:, b:, c:, d:, e:)
    @a = a
    @b = b
    @c = c
    @d = d
    @e = e
  end
end

def complex_method(parameter)
  # parameter.a, parameter.b, parameter.c ...
end

Replace conditionals!

if type == :some_condition
  some_condition_plan.cost
elsif type == :another_condition
  another_condition_plan.cost
else
  default_plan.cost
end
plan = find_plan(type)
plan.cost