Refactoring the Refactored Switch Statement
Semantic Competence and Disquotational Knowledge
In her book, Relevance and Linguistic Meaning: The Semantics and Pragmatics of Discourse Markers, Diane Blakemore speaks, among other things, of incoherent interpretation and contextual assumptions. While pondering these phrases, I was reminded of Chris Brandsma’s recent article on Refactoring a switch statement.
You see, it’s not that Chris Brandsma is wrong, per se… it’s that I believe that his advice in this matter applies, at best, to a very small number of people who better already understand what they are doing. For the large majority of developers who still misuse the word “refactoring”, the article merely builds on their disquotational knowledge.
Disquotational knowledge describes the concept of knowing something without understanding it. There is wealth of this knowledge in our industry. For me, it’s more important to firmly establish the 90% case and allow for discovery of the 10% case in time. To that end…
Sean Timm’s rules of proper switch statement etiquette:
- If your code has multiple switch statements working with a common subset of case instances, the switch must go away.
- If you find yourself about to write a switch statement:
- Stop, and mentally confirm that:
- You’re not already doing another switch on a subset of these same case instances.
- You don’t plan on doing another switch on a subset of these same case instances.
- You can’t for the life of you figure out why anyone else would ever do another switch on a subset of these same case instances.
- You are willing to get rid of this switch statement when you discover that you actually do need to switch on a subset of these same case instances.
- If any of the above are not true, the switch must go away.
- Stop, and mentally confirm that:
- If you can’t articulate precisely why a switch must stay, the switch must go away.
- If you’re not working in a language that supports polymorphic behavior, ignore all of this advice, and do something like Chris suggested.
A Brief Analysis of the Problem
Let’s take a quick look at the original problem:
1: public string ReturnSomething(string name, string value)
2: {
3: switch (name)
4: {
5: case "Chris":
6: return _valueProcessor.Chris(value);
7: case "David":
8: return _valueProcessor.David(value);
9:
10: ...
11: }
12: }
Can anyone tell me what will return when you pass “Chris” and another value into this function? Of course not… we’ve lost all semantic meaning in this code. We could fix this with something like the following:
1: public string ReturnSomething(string name, string value)
2: {
3: switch (name)
4: {
5: case "Chris":
6: return _valueProcessor.PrependLastNameOfChrisToValue(value);
7: case "David":
8: return _valueProcessor.PrependLastNameOfDavidToValue(value);
9:
10: ...
11: }
12: }
Ok, that’s a bit awkward, but we’ll run with it. Since we wrote this code, we’ve discovered that we need to add some new functionality, so we stick to the code:
1: public string ReturnSomethingElse(string name, string value)
2: {
3: switch (name)
4: {
5: case "Chris":
6: return _valueProcessor.LookupNoteToChris(value);
7: case "David":
8: return _valueProcessor.LookupNoteToDavid(value);
9:
10: ...
11: }
12: }
Pretty soon, your interface is starting to look like this:
1: public interface IValueProcessor
2: {
3: string PrependLastNameOfChrisToValue(string data);
4: string LookupNoteToChris(string noteKey);
5: byte TheHandThatFeedsChris();
6: bool AskChrisIfItIsReallyStillAGoodIdeaThatYouAreGoingDownThisPath();
7: void AllKnowledgeOfWhatYouThoughtWasReallyAGoodIdeaThatYouGotFromChris();
8:
9: string PrependLastNameOfDavidToValue(string data);
10: string LookupNoteToDavid(string noteKey);
11: byte TheHandThatFeedsDavid();
12: bool AskDavidIfItIsReallyStillAGoodIdeaThatYouAreGoingDownThisPath();
13: ...
14: }
Dealing with Code Tumors
When you’re facing uncontrollable growth like this, it’s time to get in and do some surgery. It should have been dealt with back when it was easier to operate on because now it has spread throughout your code.
First, we introduce a test because we want to remove this malignant growth without interfering with the rest of the code’s ability to function:
1: [TestFixture]
2: public class SwitchRefactorDemoTest
3: {
4: [Test]
5: public void DoSomethingWithChrisTest()
6: {
7: var switchDemo = new SwitchDemo(new ValueProcessor());
8: var result = switchDemo.ReturnSomething("Chris", "Hey");
9: Assert.That(result, Is.EqualTo("BrandsmaHey"));
10: }
11:
12: ...
13: }
ValueProcessor is being used directly in this test because we’re attempting to get rid of it (oh, I didn’t mention that?). Among other things, ValueProcessor is clearly violating the Single Responsibility Principle. This class is doing way too much! For the purposes of this discussion, I won’t get into details on why this is important, but feel free to add this to your disquotational knowledgebase.
For this example, we’ll be looking at the refactoring known as Replace Conditional with Polymorphism. You may also want to spend some time investigating Replace Conditional with Visitor as a solid combination of both these techniques can be required at times.
We’re refactoring, so we’ll go step by step and avoid breaking functionality. We should be able to stop at any time and get back to work on that new defect we just got assigned without worrying about the state of the code.
First, we’ll work with the Chris method of ValueProcessor which currently looks like:
1: public class ValueProcessor : IValueProcessor
2: {
3: public string Chris(string data)
4: {
5: return "Brandsma" + data;
6: }
7:
8: ...
9: }
We do an Extract Method on the logic, and things now look like:
1: public string Chris(string data)
2: {
3: return PrependLastNameTo(data);
4: }
5:
6: private string PrependLastNameTo(string data)
7: {
8: return "Brandsma" + data;
9: }
We run the tests, and everything still passes. This will be our new method name, but we have a lot more work to do before the conditional is gone. For now, we’ll just Move Method and delegate our call to the new ChrisProcessor class that we create:
1: public string Chris(string data)
2: {
3: var processor = new ChrisProcessor();
4: return processor.PrependLastNameTo(data);
5: }
We run the tests, and everything still passes. Now we go ahead and do this for each of the other conditionals. We run the tests after each step, and everything continues to pass. At last, all of the logic has been delegated to these new classes, and the code in the functions looks the same other than which processor we’re instantiating.
We change ChrisProcessor to derive from ValueProcessor, add the PrependLastNameTo() method to the IValueProcessor interface and add a default implementation to ValueProcessor that throws an exception:
1: public class ValueProcessor : IValueProcessor
2: {
3: public virtual string PrependLastNameTo(string data)
4: {
5: throw new System.NotImplementedException();
6: }
7: ...
8: }
This method is currently not called, and as we modify our conditional to call it, we shouldn’t get the exception because each subclass will override the method. ChrisProcessor is now ready to be fully responsible for its behavior, but we have an interesting situation with this code.
An IValueProcessor implementation is currently being passed in to the SwitchDemo class. This interface was being used by the switch statement to call into the correct conditional logic, but that can now happen automatically. Most likely, we aren’t going to need to pass the IValueProcessor in at all (since the whole SwitchDemo class will likely disappear).
For now, we’ll introduce a factory method that instantiates the correct ValueProcessor subclass (yes, I appreciate the irony of introducing a new switch method, but this is temporary and may disappear depending on the external callers… if not, use Chris’ dictionary for this
):
1: private class ValueProcessorFactory
2: {
3: public static IValueProcessor GetValueProcessor(
4: string processorKey,
5: IValueProcessor valueProcessor)
6: {
7: switch(processorKey)
8: {
9: case "Chris":
10: return new ChrisProcessor();
11: default:
12: return valueProcessor;
13: }
14: }
15: }
Note that we’re doing these one at a time, so we start with just replacing the ChrisProcessor and add the others as we go:
1: public string ReturnSomething(string name, string value)
2: {
3: IValueProcessor processor = ValueProcessorFactory.GetValueProcessor(
4: name, _valueProcessor);
5: switch (name)
6: {
7: case "Chris":
8: return processor.PrependLastNameTo(value);
9: case "David":
10: return _valueProcessor.David(value);
11: ....
12: }
13: }
Run the tests. Everything passes. Do the same thing with each of the other conditionals, and you finally end up with removing the conditional statement altogether:
1: public string ReturnSomething(string name, string value)
2: {
3: IValueProcessor processor = ValueProcessorFactory.GetValueProcessor(
4: name, _valueProcessor);
5: return processor.PrependLastNameTo(value);
6: }
At this point, it’s likely that you don’t even need the ReturnSomething method, so you Inline Method and move the factory instantiation to wherever it makes sense. Now you go and do the same to all your other switch statements that you need to push into these subclasses. Lather, rinse, repeat.
Afterthoughts
When you’re disciplined about these things, you’ll find yourself realizing you could have taken a different path and gotten to the same place (or perhaps a better place) a lot sooner. While working through this example, I realized that rather than delegating the PrependLastNameTo method to the new ChrisProcessor subclass when I was getting started, I should have created a ChrisProcessor class that took IValueProcessor in its constructor. This would have allowed me to call into the old Chris() function in ValueProcessor while working to modify the conditional logic. In real code where things are more complicated than this, I probably would have needed access to the IValueProcessor beyond its use in the ReturnSomething method, so keeping it around for delegation purposes makes a lot more sense. Perhaps next time… – Sean Timm






I’m feeling a slight need to defend myself here.
The main point of “Refactorring a switch statement was that a switch statement can be replaced with a Dictionary. Even if the switch is done correctly (with polymorphism), like you did here, can still contain a rather nasty switch statement in the guts.
For example: If you are writing a tag processor (you are taking in a file with custom, distinct, tags in that that indicated a need to perform an action), the list of possible tags can get quite large. One commenter on my post was talking about having 1500 case statements in a single switch because of this.
Everything was kept simple in that post to highlight one point: a Dictionary is an effective alternative. Even all the suggestions that people put on the blog ignored one fact: they had done everything but remove the switch. I wanted it GONE.
You moved the switch to another class, which had merely migrated an ugly piece of code to a new home — which is what you are supposed to do with code that is becoming unmanageable, but I wanted to make it less ugly from the start. No polymorphic trick will remove a switch statement (but it will make it easier to remove).
Part of this could just be me. I don’t hate the switch statement, I use them from time to time, but I think they should be used much more rarely than they are.
But, effective refactoring is a multi-step process. Thank you for showing one of the other steps.
It’s good to bring out the apologist in you, Chris.
I completely agree with your desire to eliminate the switch statements, and it’s rare that I run across one that wouldn’t be better off in a different form.
My main concern called out in this article is that the majority of switch statements I’ve run into in production code involve multiple switches with multiple responsibilities and invariably would be better off in polymorphic form.
In these cases, your dictionary refactor is an effective addition to the steps called out in this article (in the case where creation of the appropriate subclass needs handled by a switch), but the main problem in the code isn’t the existence of a switch but the use of it.