Replacing An If Statement With An Object

I found myself writing this code yesterday:

def to_s
  if status == AssessmentStatus::Open
    date = get_assessment_short_date(:date_opened)
    "#{assessment.name}, Opened #{date}"
  else
    date = get_assessment_short_date(:date_closed)
    "#{assessment.name}, Closed #{date}"
  end
end

 

I wrote it and moved on. It worked and it did what I needed it to do – format a string to show when an assessment was opened, or when it was closed, depending on the current state of the assessment. A few moments later, I found myself writing the same code in my LabOrder class. I needed to know the date of the order vs the date it was received, depending on the state of the object.

When I saw myself writing this for the second time, I realized I was missing an abstraction. I needed to stop writing procedural code and start writing objects instead.

 

Not Quite A State Machine

Remembering this little quote and re-examining the code I was writing, I thought about creating a simple state machine in my app. I would only have a few transitions – from open to closed, or from ordered to received – but it seemed like a nice clean way to handle things. After doing a bit of looking around at the various state machine implementations for ruby, though, I decided I didn’t need that much complexity in my app. I already have the business logic of transitioning from Open to Closed, for my assessment, wrapped up in my Assessment object. It would be easier to just move my object’s `status` attribute into a class that contained the actual status and the date that the status occurred. (Note that I also renamed the field to `state` instead of `status`, on the Assessment.)

Here’s what that class looks like:

class AssessmentStatus
  include Mongoid::Document

  Open = "open"
  Closed = "closed"

  embedded_in :assessment, :inverse_of => :state
  
  field :status, :default => Open
  field :date, :type => DateTime, :default => DateTime.now
end

 

It’s a very simple class with no real logic in it at this point. However, it gives me a starting point to move forward in the event that I do find myself needing a complete state machine. For now, though, having the status and date stored in it’s own class allows me to change my `.to_s` method on the Assessment class:

def to_s
  "#{assessment.name} #{state.status} #{state.date.short_date}"
end

 

(Note the `.short_date` method on my date field is a helper method that I created in a DateTimeFormats module, which gives me several date and time formats that I use throughout my app)

 

“An Object Is A Choice”

I don’t remember who said this originally, but it’s been quoted on twitter recently and it has stuck with me for a few weeks now. When I first read this statement, I misunderstood the context. I thought it was saying you can choose to create an object, or not; that there is no hard and fast rule that says everything must be an object. While this interpretation may hold some ground on, too, I think the real intention of this statement was to say that you can write better code by removing flow-control logic (if statements, switch statements, etc) and replace them with objects. Stated a little differently, an object can be used in place of flow-control logic.

That’s not to say you should never use an if statement, though. There are times when you don’t necessarily need an object; when the if-statement or switch statement is sufficient for your design. Remember, though, that there are trade-offs that need to be accounted for. A good guideline to know when you should have an object instead of an if or switch statement, is when you see the same if or switch statement in more than one place. Where two or more ifs are repeated, an object should be present.

 


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Analysis and Design, AntiPatterns, Principles and Patterns, Ruby. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Anonymous

    hmm… conversation sounds vaguely familiar :)

    • http://mutedsolutions.com Derick Bailey

      what? no… just because we talked about these very things at the CTXDNUG meeting you presented at? :)

      • Anonymous

        oh yeah… that was it. Nice example.

  • http://profiles.google.com/rod.hyde Rod Hyde

    “I think the real intention of this statement was to say that you can write better code by removing flow-control logic (if statements, switch statements, etc) and replace them with objects.”

    Perhaps I’m getting old, but doesn’t this argument also apply to methods, ie, you can write better code by removing flow-control and replacing it with a call to a method.

    If you find that you’re repeating yourself, replace the repetition with something that expresses the intent of what you’re doing rather than the mechanics of what you’re doing.

    • http://mutedsolutions.com Derick Bailey

      +1 for expressiveness and cleaning up repeated code. I do that a lot. However, moving an if statement into a method doesn’t remove the if statement, it only moves it. A method can’t substitute for an if statement, it can only encapsulate the if statement. An object (or set of objects), however, can replace an if statement entirely.

      • Carlos Ribas

        Does using a set of objects remove an if statement? Or does it move the if statement to wherever the instance is created?

        • http://mutedsolutions.com Derick Bailey

          it’s entirely possible that you would just move the if statement. that depends on the design of your higher level objects in some cases.

          in the example I’ve shown here, I’ve clearly removed the if statement, altogether. however, if you were using simple factories to build the correct object at runtime, you may use an if or switch statement in the factory. In that case, I’d say it’s acceptable because the flow-control logic is well encapsulated in an object where an if or switch statement is appropriate.

          if you’re using an IoC container, though, then you don’t really know or care if there is an if-statement because it’s external code. the end result in this case would be that your code, which is what you care about, has the if statement removed.

          again, not all if-statements or switch statements are bad, though. there are times when the additional “complexity” (in understanding, abstraction, and other aspects) may be over kill.

          they’re all just guidelines, not rules. learn them as if they were rules, then learn when, where, and why it’s ok to break them.

          • Carlos Ribas

            Agreed on all counts. Sorry, my inability to mentality parse Ruby code made me miss the fact that you’re really talking about top-down development vs. OOP.

            Your example of moving logic into a re-usable class does in fact eliminate your if statement — its just one that shouldn’t have existed in an OOP environment to begin with.

            That’s the part I missed — I got it in my head that you were eliminating an domain condition from a method body into a class by moving the conditional to a factory someplace and that sounded like “moving” the if rather than removing it.

            Sorry! Feel free to ignore me now.