Skip to content

Instantly share code, notes, and snippets.

@tute
Last active August 31, 2021 14:54
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tute/6374237 to your computer and use it in GitHub Desktop.
Save tute/6374237 to your computer and use it in GitHub Desktop.
Notes/Patterns for Refactoring Code

Intention Revealing Methods

Intention revealing method is simple and yet I see it frequently slip through programmers' code. Developers don't like lengthy methods, or find it inconvenient to read through chubby if-else branches, and if they are nice enough they'll leave comments like those.

If we change spaces by underscores in the comments, delete the comment characters, and define the resulting methods in the same file (as private helpers for example), we get code that explains itself, instead of through verbose long methods, or human code comments which get stale.

Intention revealing methods is the most basic, no brain-teaser, easiest rule that I know. Combine it with Sandi Metz's rule of a maximum of 5 lines per method and you'll get simple code that explains itself, that is a pleasure to read, improving communication and productivity of the team (even when it's only yourself).

Sample Code

if current_user && current_user.admitted_at < 1.week.ago
  # When user is admitted at least a week ago we show it's active projects and
  # admitted-only ones
  @active_projects = current_user.projects.active
  @promo_projects = Project.promo
else
  # If not admitted we show some featured projects, and set a marketing flash
  # message when user is new
  if current_user && current_user.created_at > 1.week.ago
    flash[:info] = 'Sign up for having your own projects, and see promo ones!'
  end
  @featured_projects = Project.active.featured
end
if user_already_admitted?
  set_user_projects
else
  set_featured_projects
  upsell_if_new_user
end

private

def set_user_projects
  @active_projects = current_user.projects.active
  @promo_projects = Project.promo
end

def set_featured_projects
  @featured_projects = Project.active.featured
end

def upsell_if_new_user
  return if current_user.nil?
  if current_user.created_at > 1.week.ago
    flash[:info] = 'Sign up for having your own projects, and see promo ones!'
  end
end

def user_already_admitted?
  return false if current_user.nil?
  current_user.admitted_at < 1.week.ago
end

Interestings

Having semantic names instead of code also helps to spot when we work at different levels of abstraction in the same place, unveiling next steps in improving the code structure.

References

http://ntcoding.blogspot.com.ar/2011/05/clean-code-tricks-intention-revealing.html http://www.codinghorror.com/blog/2008/07/coding-without-comments.html

Refactoring code with Service Objects

aka Put chubby classes on a diet

  1. Put some business logic in controllers and some in models
  2. Move all this logic to the models
  3. End up with fat models (-cohesion / +responsibilities / +coupling)
  4. Move on to greener fields. Or start having some good fun!

If we force an operation into an object that doesn't fit the object's definition, the object loses its conceptual clarity and becomes hard to understand or refactor. And because these operations often draw together many domain objects, coordinating them and putting them into action, the added responsibility will create dependencies on all those objects, tangling concepts that could be understood independently.

When an operation is an important domain concept, a Service forms a natural part of a design. Declared in the model as a Service, rather than as a phony object that doesn't actually represent anything, the standalone operation will not mislead anyone.

Idea

Handle business logic in more, smaller objects.

Definition

A Service is an operation offered as an interface that stands alone in the model, without encapsulating state. The name service emphasizes the relationship with other objects. Unlike Entities and Value Objects, it is defined purely in terms of what it can do for a client. A Service tends to be named as a verb rather than a noun.

Sample Code

(TODO: take BraintreeService example)

References

Refactoring code with Special Case Objects

aka Get rid of ifs and trys from your code

Nulls are awkward things in object-oriented programs. Since a variable can contain null, you may run into a runtime error by invoking a message on null, which will get you a nice, friendly stack trace.

If it's possible for a variable to be null, you have to remember to surround it with null test code so you'll do the right thing if a null is present.

Idea

Instead of returning null or some odd value, return a Special Case that has the same interface as what the caller expects.

Definition

A subclass that provides special behavior for particular cases.

Sample Code

class User
  def active_subscription
    subscriptions.active.first
    # turns into ->
    subscriptions.active.first || Null::Subscription.new
  end
<%# View code %>
<% last_subscription = user.subscriptions.last %>
<td><%= last_subscription.try(:name) || "none" %></td>
<td><%= last_subscription.try(:status) || "not started" %></td>
<td><%= last_subscription.try(:updated_at) || 'N/A' %></td>
<td><%= last_subscription.try(:length_in_days) || 'Unknown' %> days</td>
<%# turns into -> %>
<td><%= user.last_subscription.name %></td>
<td><%= user.last_subscription.status %></td>
<td><%= user.last_subscription.updated_at %></td>
<td><%= user.last_subscription.length_in_days %> days</td>
### Controller code
if @subscription && @subscription.cancel!
# turns into ->
if @subscription.cancel!
### With a Special Case Object like
module Null
  class Subscription
    def name
      'none'
    end
    ...

Interestings

The Objective-C language takes another approach to this problem and does not invoke methods on nil but instead returns nil for all such invocations.

References

Copy link

ghost commented Aug 29, 2013

class NilClass
  def method_missing(*)
    # obj-c style.
  end
end

problem solved! (joke).
looking forward to this one.

@tute
Copy link
Author

tute commented Aug 29, 2013

We have to patch ActiveSupport @robgleeson:

irb(main):006:0> nil.a
=> nil
irb(main):007:0> nil.b
=> nil
irb(main):008:0> nil.c
=> nil
irb(main):009:0> nil.a.b.c
=> nil

;-)

Copy link

ghost commented Aug 29, 2013

active_support-objective_c gem. :D

@phillbaker
Copy link

Actually I really like the ability to call methods on nil in Objective C. It makes sense in that language.

Also, http://nickknowlson.com/blog/2013/04/16/why-maybe-is-better-than-null/ <- good read

@tute
Copy link
Author

tute commented Dec 17, 2014

@phillbaker have you seen https://github.com/thoughtbot/wrapped, "the maybe functor for Ruby"?

@jontonsoup
Copy link

jontonsoup commented Apr 20, 2017

Hah! I was googling on intention revealing methods to send something good to someone who wanted to learn more and I found this!

Hope you are doing well. @tute

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment