What is Replace Conditional with Polymorphism Refactoring? How is it implemented in Ruby?
The Replace Conditional with Polymorphism Refactoring is rather simple and it is pretty much exactly what it sounds like. You have a method with a conditional like this:
def speed
case @type
when :european then base_speed
when :african then base_speed - load_factor * @number_of_coconuts
when :norwegian_blue then if nailed? then 0 else base_speed(@voltage) end
end
and you replace it with polymorphism like this:
class European
def speed
base_speed
end
end
class African
def speed
base_speed - load_factor * @number_coconuts
end
end
class NorwegianBlue
def speed
if nailed? then 0 else base_speed(@voltage)
end
end
You can apply the Refactoring again to NorwegianBlue#speed
by creating a subclass of NorwegianBlue
:
class NorwegianBlue
def speed
base_speed(@voltage)
end
end
class NailedNorwegianBlue < NorwegianBlue
def speed
0
end
end
Voilà, all your conditionals are gone.
You might ask yourself: does this always work? Can I always replace an if
with message dispatch? And the answer is: yes, you can! In fact, if you didn't have if
, you can implement it yourself using nothing but message dispatch. (This is, in fact, how Smalltalk does it, there are no conditionals in Smalltalk.)
class TrueClass
def iff(thn:, els: ->{})
thn.()
end
def &
yield
end
def |
self
end
def !
false
end
end
class FalseClass
def iff(thn:, els: ->{})
els.()
end
def &
self
end
def |
yield
end
def !
true
end
end
(3 > 4).iff(thn: ->{ 'three is bigger than four' }, els: ->{ 'four is bigger than three' } )
# => 'four is bigger than three'
true.& { puts 'Hello' }
# Hello
true.| { puts 'Hello' }
# => true
Also relevant: the Anti-IF
Campaign
Replace conditional with polymorphism - nice in theory but not practical
You're right - "the conditionals are getting pushed up to the top of the chain" - but there's no "just" about it. It's very powerful. As @thkala says, you just make the choice once; from there on out, the object knows how to go about its business. The approach you describe - BaseAction, ViewAction, and the rest - is a good way to go about it. Try it out and see how much cleaner your code becomes.
When you've got one factory method that takes a string like "View" and returns an Action, and you call that, you have isolated your conditionality. That's great. And you can't properly appreciate the power 'til you've tried it - so give it a shot!
Better way to handle many if conditions?
This code could benefit greatly from the Replace Conditional with Polymorphism Refactoring.
In general, you never need any conditionals at all in an object-oriented language, because object-oriented languages have runtime polymorphic message dispatch, which is more powerful than any conditional. You can always replace conditionals with polymorphism; Smalltalk is the existence proof of that, it doesn't even have conditionals in the language, they are implemented in the library using message dispatch, kind of like this:
class TrueClass
def if_then_else(then_part, else_part)
then_part.()
end
end
class FalseClass
def if_then_else(then_part, else_part)
else_part.()
end
end
(2 < 3).if_then_else(-> { puts 'True' }, -> { puts 'False' })
# True
Ruby does have conditionals, but you don't need them.
So, what does runtime polymorphic message dispatch do, exactly? Well, it basically executes different code based on the type. For example, if you say
foo.bar
different bar
method will be ran based on the type of foo
.
And in your case, the value you are using to base on your decision which code to execute on, is literally called type
, so, you are essentially just re-implementing a basic feature of Ruby: executing different code based on the type is just message dispatch, which Ruby does on its own anyway.
So, in your case, you would have 4 different Feed
classes and 2 different Comment
classes.
Now, in your case, it is a little bit more complicated because the outcome does not depend solely on the feed type, but on the comment type as well. Ruby doesn't have multiple dispatch, so, we will either need to introduce new classes for every combination of feed type and comment type, or live with some conditionals in the final code.
So, let's start slowly improving your code. First off, I believe you mean elsif
instead of else if
in your code:
if feed.type == 1
if nested_comment
str = "test"
elsif comment
str = "test1"
else
str = "test2"
end
elsif feed.type == 2
if nested_comment
str = "test3"
elsif comment
str = "test4"
else
str = "test5"
end
elsif feed.type == 3
if nested_comment
str = "test6"
elsif comment
str = "test7"
else
str = "test8"
end
elsif feed.type == 4
if nested_comment
str = "test9"
elsif comment
str = "test10"
else
str = "test11"
end
end
Secondly, we can make use of the fact that conditionals in Ruby are expressions, not statements (in fact, everything in Ruby is an expression, there are no statements), and thus evaluate to a value:
str = if feed.type == 1
if nested_comment
"test"
elsif comment
"test1"
else
"test2"
end
elsif feed.type == 2
if nested_comment
"test3"
elsif comment
"test4"
else
"test5"
end
elsif feed.type == 3
if nested_comment
"test6"
elsif comment
"test7"
else
"test8"
end
elsif feed.type == 4
if nested_comment
"test9"
elsif comment
"test10"
else
"test11"
end
end
Now, we replace those if
s with case
expressions:
str = case feed.type
when 1
case
when nested_comment
"test"
when comment
"test1"
else
"test2"
end
when 2
case
when nested_comment
"test3"
when comment
"test4"
else
"test5"
end
when 3
case
when nested_comment
"test6"
when comment
"test7"
else
"test8"
end
when 4
case
when nested_comment
"test9"
when comment
"test10"
else
"test11"
end
end
Now, let's reformat a little, to easier see what's going on:
str = case feed.type
when 1
case
when nested_comment then "test"
when comment then "test1"
else "test2"
end
when 2
case
when nested_comment then "test3"
when comment then "test4"
else "test5"
end
when 3
case
when nested_comment then "test6"
when comment then "test7"
else "test8"
end
when 4
case
when nested_comment then "test9"
when comment then "test10"
else "test11"
end
end
It's time for our refactoring:
class Feed1
def magic_string
case
when nested_comment then "test"
when comment then "test1"
else "test2"
end
end
end
class Feed2
def magic_string
case
when nested_comment then "test3"
when comment then "test4"
else "test5"
end
end
end
class Feed3
def magic_string
case
when nested_comment then "test6"
when comment then "test7"
else "test8"
end
end
end
class Feed4
def magic_string
case
when nested_comment then "test9"
when comment then "test10"
else "test11"
end
end
end
str = feed.magic_string
We can further reduce some duplication by introducing a method which encapsulates the comment checking (or, like I said, we can introduce comment classes).
class Feed
def comment_string(nested_comment_string, comment_string, other_string)
case
when nested_comment then nested_comment_string
when comment then comment_string
else other_string
end
end
end
class Feed1 < Feed
def magic_string
comment_string("test", "test1", "test2")
end
end
class Feed2 < Feed
def magic_string
comment_string("test3", "test4", "test5")
end
end
class Feed3 < Feed
def magic_string
comment_string("test6", "test7", "test8")
end
end
class Feed4 < Feed
def magic_string
comment_string("test9", "test10", "test11")
end
end
str = feed.magic_string
Correct way to TDD methods that calls other methods
I'm presuming since you mention TDD the code in question does not actually exist. If it does then you aren't doing true TDD but TAD (Test-After Development), which naturally leads to questions such as this. In TDD we start with the test. It appears that you are building some type of menu or command system, so I'll use that as an example.
describe GameMenu do
it "Allows you to navigate to character creation" do
# Assuming character creation would require capturing additional
# information it violates SRP (Single Responsibility Principle)
# and belongs in a separate class so we'll mock it out.
character_creation = mock("character creation")
character_creation.should_receive(:execute)
# Using constructor injection to tell the code about the mock
menu = GameMenu.new(character_creation)
menu.execute("c")
end
end
This test would lead to some code similar to the following (remember, just enough code to make the test pass, no more)
class GameMenu
def initialize(character_creation_command)
@character_creation_command = character_creation_command
end
def execute(command)
@character_creation_command.execute
end
end
Now we'll add the next test.
it "Allows you to display character inventory" do
inventory_command = mock("inventory")
inventory_command.should_receive(:execute)
menu = GameMenu.new(nil, inventory_command)
menu.execute("i")
end
Running this test will lead us to an implementation such as:
class GameMenu
def initialize(character_creation_command, inventory_command)
@inventory_command = inventory_command
end
def execute(command)
if command == "i"
@inventory_command.execute
else
@character_creation_command.execute
end
end
end
This implementation leads us to a question about our code. What should our code do when an invalid command is entered? Once we decide the answer to that question we could implement another test.
it "Raises an error when an invalid command is entered" do
menu = GameMenu.new(nil, nil)
lambda { menu.execute("invalid command") }.should raise_error(ArgumentError)
end
That drives out a quick change to the execute
method
def execute(command)
unless ["c", "i"].include? command
raise ArgumentError("Invalid command '#{command}'")
end
if command == "i"
@inventory_command.execute
else
@character_creation_command.execute
end
end
Now that we have passing tests we can use the Extract Method refactoring to extract the validation of the command into an Intent Revealing Method.
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
if command == "i"
@inventory_command.execute
else
@character_creation_command.execute
end
end
def invalid?(command)
!["c", "i"].include? command
end
Now we finally got to the point we can address your question. Since the invalid?
method was driven out by refactoring existing code under test then there is no need to write a unit test for it, it's already covered and does not stand on it's own. Since the inventory and character commands are not tested by our existing test, they will need to be test driven independently.
Note that our code could be better still so, while the tests are passing, lets clean it up a bit more. The conditional statements are an indicator that we are violating the OCP (Open-Closed Principle) we can use the Replace Conditional With Polymorphism refactoring to remove the conditional logic.
# Refactored to comply to the OCP.
class GameMenu
def initialize(character_creation_command, inventory_command)
@commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
@commands[command].execute
end
def invalid?(command)
!@commands.has_key? command
end
end
Now we've refactored the class such that an additional command simply requires us to add an additional entry to the commands hash rather than changing our conditional logic as well as the invalid?
method.
All the tests should still pass and we have almost completed our work. Once we test drive the individual commands you can go back to the initialize method and add some defaults for the commands like so:
def initialize(character_creation_command = CharacterCreation.new,
inventory_command = Inventory.new)
@commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
The final test is:
describe GameMenu do
it "Allows you to navigate to character creation" do
character_creation = mock("character creation")
character_creation.should_receive(:execute)
menu = GameMenu.new(character_creation)
menu.execute("c")
end
it "Allows you to display character inventory" do
inventory_command = mock("inventory")
inventory_command.should_receive(:execute)
menu = GameMenu.new(nil, inventory_command)
menu.execute("i")
end
it "Raises an error when an invalid command is entered" do
menu = GameMenu.new(nil, nil)
lambda { menu.execute("invalid command") }.should raise_error(ArgumentError)
end
end
And the final GameMenu
looks like:
class GameMenu
def initialize(character_creation_command = CharacterCreation.new,
inventory_command = Inventory.new)
@commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
@commands[command].execute
end
def invalid?(command)
!@commands.has_key? command
end
end
Hope that helps!
Brandon
Short way to write a long case statement
I don't understand some of the details of the question, but here's a general approach you might take. I've assumed that :info
and the values of :email
in the hash below are names of methods. (I understand that assumption is incorrect.) The following may have errors, considering that I have no means of testing it.
DATA = [[/osha/, 'Creating OSHA Regional email..', :osha_reg],
[/pend/, 'Creating 6 day hold pending email..', :pend],
[/60/, 'Creating 60 day hold account deletion email..', :sixty_day],
[/generic/, 'Creating generic email..', :generic],
[/resolve/, 'Creating resolution ticket..', :resolve],
[/esc/, 'Creating escalation ticket..', :assign],
[/pii/, 'Creating request to remove personal info..', :remove_pii],
[/vip/, 'Creating VIP user email..', :vip_user],
[/inop/, 'Creating INOP user email..', :in_op_user]]
def gather_intel
type = OPTIONS[:type]
regex, msg, email = DATA.find { |r,*_| type =~ r }
if regex
FORMAT.send :info, msg
EMAILS.send email
elsif type =~ /dev/
message = type.to_s.include?('dev=unlock') ? 'unlock' : 'password reset'
FORMAT.info("Creating dev account #{message} email")
EMAILS.dev_account(type)
else
raise ERROR
end
end
If-less programming (basically without conditionals)
There are some resources on the Anti-IF Campaign site, such as this article.
I believe it's a matter of degree. Conditionals aren't always bad, but they can be (and frequently are) abused.
Additional thoughts (one day later)
Refactoring: Improving the Design of Existing Code is a good reference on this subject (and many others). It covers Replace Conditional with Polymorphism. There's also a new one, Replace Conditional with Visitor, on the web site.
I value simplicity and single responsibility over removing all if
statements. These three goals often coincide. Static analysis tools that support the cyclomatic complexity metric can quickly point out code with nested or serial conditionals. The if
statements may remain post-refactoring, but could be broken out into smaller methods and/or multiple classes.
Update: Michael Feathers wrote an article on Unconditional Programming.
This is a popular topic: Phil Haack on Death to the IF statement!
Replacing if else statement with pattern
This is a classic Replace Condition dispatcher with Command in the Refactoring to Patterns book.
Basically you make a Command
object for each of the blocks of code in your old if/else group and then make a Map of those commands where the keys are your condition Strings
interface Handler{
void handle( myObject o);
}
Map<String, Handler> commandMap = new HashMap<>();
//feel free to factor these out to their own class or
//if using Java 8 use the new Lambda syntax
commandMap.put("conditionOne", new Handler(){
void handle(MyObject o){
//get desired parameters from MyObject and do stuff
}
});
...
Then instead of your if/else code it is instead:
commandMap.get(someCondition).handle(this);
Now if you need to later add new commands, you just add to the hash.
If you want to handle a default case, you can use the Null Object
pattern to handle the case where a condition isn't in the Map.
Handler defaultHandler = ...
if(commandMap.containsKey(someCondition)){
commandMap.get(someCondition).handle(this);
}else{
defaultHandler.handle(this);
}
Why is the 'if' statement considered evil?
The if
statement is rarely considered as "evil" as goto
or mutable global variables -- and even the latter are actually not universally and absolutely evil. I would suggest taking the claim as a bit hyperbolic.
It also largely depends on your programming language and environment. In languages which support pattern matching, you will have great tools for replacing if
at your disposal. But if you're programming a low-level microcontroller in C, replacing if
s with function pointers will be a step in the wrong direction. So, I will mostly consider replacing if
s in OOP programming, because in functional languages, if
is not idiomatic anyway, while in purely procedural languages you don't have many other options to begin with.
Nevertheless, conditional clauses sometimes result in code which is harder to manage. This does not only include the if
statement, but even more commonly the switch
statement, which usually includes more branches than a corresponding if
would.
There are cases where it's perfectly reasonable to use an if
When you are writing utility methods, extensions or specific library functions, it's likely that you won't be able to avoid if
s (and you shouldn't). There isn't a better way to code this little function, nor make it more self-documented than it is:
// this is a good "if" use-case
int Min(int a, int b)
{
if (a < b)
return a;
else
return b;
}
// or, if you prefer the ternary operator
int Min(int a, int b)
{
return (a < b) ? a : b;
}
Branching over a "type code" is a code smell
On the other hand, if you encounter code which tests for some sort of a type code, or tests if a variable is of a certain type, then this is most likely a good candidate for refactoring, namely replacing the conditional with polymorphism.
The reason for this is that by allowing your callers to branch on a certain type code, you are creating a possibility to end up with numerous checks scattered all over your code, making extensions and maintenance much more complex. Polymorphism on the other hand allows you to bring this branching decision as closer to the root of your program as possible.
Consider:
// this is called branching on a "type code",
// and screams for refactoring
void RunVehicle(Vehicle vehicle)
{
// how the hell do I even test this?
if (vehicle.Type == CAR)
Drive(vehicle);
else if (vehicle.Type == PLANE)
Fly(vehicle);
else
Sail(vehicle);
}
By placing common but type-specific (i.e. class-specific) functionality into separate classes and exposing it through a virtual method (or an interface), you allow the internal parts of your program to delegate this decision to someone higher in the call hierarchy (potentially at a single place in code), allowing much easier testing (mocking), extensibility and maintenance:
// adding a new vehicle is gonna be a piece of cake
interface IVehicle
{
void Run();
}
// your method now doesn't care about which vehicle
// it got as a parameter
void RunVehicle(IVehicle vehicle)
{
vehicle.Run();
}
And you can now easily test if your RunVehicle
method works as it should:
// you can now create test (mock) implementations
// since you're passing it as an interface
var mock = new Mock<IVehicle>();
// run the client method
something.RunVehicle(mock.Object);
// check if Run() was invoked
mock.Verify(m => m.Run(), Times.Once());
Patterns which only differ in their if
conditions can be reused
Regarding the argument about replacing if
with a "predicate" in your question, Haines probably wanted to mention that sometimes similar patterns exist over your code, which differ only in their conditional expressions. Conditional expressions do emerge in conjunction with if
s, but the whole idea is to extract a repeating pattern into a separate method, leaving the expression as a parameter. This is what LINQ already does, usually resulting in cleaner code compared to an alternative foreach
:
Consider these two very similar methods:
// average male age
public double AverageMaleAge(List<Person> people)
{
double sum = 0.0;
int count = 0;
foreach (var person in people)
{
if (person.Gender == Gender.Male)
{
sum += person.Age;
count++;
}
}
return sum / count; // not checking for zero div. for simplicity
}
// average female age
public double AverageFemaleAge(List<Person> people)
{
double sum = 0.0;
int count = 0;
foreach (var person in people)
{
if (person.Gender == Gender.Female) // <-- only the expression
{ // is different
sum += person.Age;
count++;
}
}
return sum / count;
}
This indicates that you can extract the condition into a predicate, leaving you with a single method for these two cases (and many other future cases):
// average age for all people matched by the predicate
public double AverageAge(List<Person> people, Predicate<Person> match)
{
double sum = 0.0;
int count = 0;
foreach (var person in people)
{
if (match(person)) // <-- the decision to match
{ // is now delegated to callers
sum += person.Age;
count++;
}
}
return sum / count;
}
var males = AverageAge(people, p => p.Gender == Gender.Male);
var females = AverageAge(people, p => p.Gender == Gender.Female);
And since LINQ already has a bunch of handy extension methods like this, you actually don't even need to write your own methods:
// replace everything we've written above with these two lines
var males = list.Where(p => p.Gender == Gender.Male).Average(p => p.Age);
var females = list.Where(p => p.Gender == Gender.Female).Average(p => p.Age);
In this last LINQ version the if
statement has "disappeared" completely, although:
- to be honest the problem wasn't in the
if
by itself, but in the entire code pattern (simply because it was duplicated), and - the
if
still actually exists, but it's written inside the LINQWhere
extension method, which has been tested and closed for modification. Having less of your own code is always a good thing: less things to test, less things to go wrong, and the code is simpler to follow, analyze and maintain.
Huge runs of nested if
/else
statements
When you see a function spanning 1000 lines and having dozens of nested if
blocks, there is an enormous chance it can be rewritten to
- use a better data structure and organize the input data in a more appropriate manner (e.g. a hashtable, which will map one input value to another in a single call),
- use a formula, a loop, or sometimes just an existing function which performs the same logic in 10 lines or less (e.g. this notorious example comes to my mind, but the general idea applies to other cases),
- use guard clauses to prevent nesting (guard clauses give more confidence into the state of variables throughout the function, because they get rid of exceptional cases as soon as possible),
- at least replace with a
switch
statement where appropriate.
Refactor when you feel it's a code smell, but don't over-engineer
Having said all this, you should not spend sleepless nights over having a couple of conditionals now and there. While these answers can provide some general rules of thumb, the best way to be able to detect constructs which need refactoring is through experience. Over time, some patterns emerge that result in modifying the same clauses over and over again.
Related Topics
Undefined Method '>' for Nil:Nilclass <Nomethoderror>
Adding a Staging Environment to the Workflow
Is 'Yield Self' the Same as Instance_Eval
Mongo - Ruby Connection Problem
How to Install Ruby on Rails 3 on Osx
Rubygems + Cygwin: Posix Path Not Found by Ruby.Exe
Ruby Greed Koan - How to Improve My If/Then Soup
How to Manage Multiple Gemsets and Ruby Versions with Rvm
How to Take Screenshots of Web Pages Using Ruby and a Unix Server
Preferred Way to Private Messages Modeling in Rails 3
How to Get Parent Node in Capybara
Rails 4 Before_Action, Pass Parameters to Invoked Method
Is It Idiomatic Ruby to Add an Assert( ) Method to Ruby's Kernel Class
How to Create a Ruby Date Object from a String
Concatenating String with Number in Ruby
How to Convert an Array of Strings into a Comma-Separated String
Convert String to Specific Datetime Format
Calling Method in Parent Class from Subclass Methods in Ruby