Wednesday, January 16, 2008

Refactoring Vs. Extending

Today I want to talk about refactoring versus extending and why you should almost always extend code instead of refactoring it. Like most of my posts, this post is a direct result of my personal experience as a Junior Developer.

Real World Example:  Recently, I was pulled into my boss' office and it was explained to me why a developer shouldn't refactor working code. The cause of my boss' ire was some poor testing on my part. I had been given the task of adding some functionality to an aspx page and while in there, I thought it would be a good idea to clean up the code and make it more consistent with logic in other places. In doing so, I remembered to test all the functionality of the page except for one stinkin' button. This button happened to be a button that is not pressed very often, so it was almost an entire month before someone finally clicked it and realized that my code was broken. It was then that my boss found my heavy refactoring and talked to me about it.

The basic gist of our talk was that a developer should always extend code instead of refactoring it. After mulling this idea over for some while, I realize the truthiness of the concept, but allow me to explain.

See, as a good Junior Developer, you cringe at bad code. All good developers worth their salt do. You may see a piece of code from a year or six months ago and actually be embarrassed that someone at your organization wrote such terrible code (imagine your surprise when you check the history of the code in source control and see that it was you that wrote it!). The urge to fix such terrible code is very strong and is pretty much in line with our desire as developers (and Junior Developers!) to push out only good, clean code.

But don't do it, young jedi! You must resist the temptation! For you see, for every line of code that you change and/or add, you increase the chances of introducing errors into existing code that already works. See those words in italics? They're important. So important, in fact, that they italicized themselves. Like, srsly. They came straight from the keyboard italicized. In fact, for the few seconds I was typing that phrase, the actual keys on my keyboard became italicized. Not the stickers on the keys, no, the actual physical keys bent their shape into an italicized form. It was very bizarre.

But seriously, it's true. Statistically speaking, for every line of existing, working code that you modify, you multiply the chances of introducing errors that will break that working code. Code that's already been tested and that another programmer has already been paid to write.  I'm not talking about compiler errors, either, oh no. I'm talking about anomalies that may not throw any compile- or run-time errors, but insidiously change the behavior of your application or website in subtle ways that may not be directly obvious. You could introduce anomalies into the code that may not even affect the area in which you're working or that may not be seen for several months, like in my earlier real-life example. I don't care how bad-ass you think you are, you are not perfect.

So how do you work around this maxim of not refactoring working code? By doing what any good Junior Developer worth his salt would do, by extending it! Remember extending from your computer science classes? Maybe they didn't call it that. Maybe they called it inheritance, or method overloading, or polymorphism, or any other name of very abstract ideas that you may or may not actually use in the real world. Well, now's your chance to put that expensive education to use! Sort of!

For instance, let's say that your boss comes to you and tells you to change the functionality of some user control used throughout the site. As a Junior Developer eager to please and hopefully show your value, you pull up the ascx page and it's code-beside and begin rewriting the offending methods. STOP RIGHT THERE! See what you're doing? You're changing working code, which as stated, is a big no-no! Instead of rewriting those methods and increasing the chances that you might introduce errors or anomalies, you should instead overload the offending methods and add whatever custom logic you need in order to gain the desired behavior. Many times, the original method isn't incorrect, other than that the business rules or user requirements have changed, so overloading really is the way to go. With overloading, you maintain the functionality of the original method while gaining whatever functionality that you write.

Real World Example:  This very idea paid off for me in spades yesterday at work.  I had to add a parameter to a search method in our data layer.  This parameter would be searched against in the database, so I not only needed to add a parameter to this method, I also needed to add logic to the method to search against that parameter.  Instead of simply adding the parameter and logic to the existing method, I just overloaded the method, copied the code from the original, and added the specific logic that I needed.  Several hours later, my boss changed the specifications for the search page on the website that eventually calls this search method in the data layer, and he changed it such that the new parameter was no longer needed.  Groaning and gnashing of teeth, right?  WRONG BITCH!  Because I was smart enough to overload the original method, I now only needed the extra logic I'd added in the overloaded method.  I had already thoroughly tested this extra logic, so to easily meet my boss' specifications, all I had to do was copy the logic from the overloaded method to the original and change the method call in the search page to call the original.  Utterly quick and painless, and since I was smart enough to overload, no on was the wiser!

Of course, overloading methods isn't the only way to extend current functionality without breaking anything. Let's say that a certain class exists that has all the functionality you need except for a few key ingredients. This could be a built-in class like Control or a custom class in one of your projects. To add this functionality, your first temptation again will be to add the properties and methods that you need to the class and move on. However, this would be just as incorrect as before. While adding properties and methods to a class isn't nearly as egregious an error as refactoring a method, it's still incorrect. Yes, adding properties and methods to a class will give you many similar benefits as overloading a method, but by adding properties and methods to a class that don't really belong to it, you're violating principles of OOP by giving it properties and behaviors that don't really belong to it. Instead, you should create a new class that inherits from the class that you want to extend. This of course gives you all the functionality of the original class and gives you the opportunity to not only add functionality, but also override any properties or methods in the base class that may be poorly implemented or implemented in a way that is inconsistent with the needs of your class.

While the obvious advantage here is that you are not breaking any working code, there are several non-obvious advantages as well. Firstly, you are adding code that is only called whenever your code calls it. So if you overload a method or extend a class, the code you add to call that class or method is the only code in the system at that moment that calls it. That means that you have now added code in a strictly controlled environment and in a strictly controlled manner. Secondly, that gives you huge peace of mind, knowing that even if you write a boneheaded logic error into your new code, it won't be adversely affecting existing functionality. Lastly, this gives you a huge advantage in testing, because you can also strictly control parameter values, break points, and any other way of testing the code. Of course, the burden will then be on you the Junior Developer to effectively test the new method or class.

Another non-obvious advantage of extending existing code is that even though current code may be ugly or less than optimal, the code you add will be as clean and optimal as you make it. Again, this lays the burden on your shoulders to write clean, easily-read code that doesn't do anything incredibly stupid, but if you're practicing the art of self-education mentioned in my previous posts, then you should be able to look at that old, ugly code and claim victory over it's stupidocity.

Having said all that, extending over refactoring isn't an absolute, of course.  However, there are really only two times that you should refactor working code. Firstly is when you're told to by your boss. If your boss comes into your office and tells you specifically to refactor something, then you probably should. Hopefully, your boss will have a very good reason for refactoring. Maybe he found a new language feature that more easily or efficiently accomplishes the code's business objectives, or maybe he just plain doesn't like what's there.  Regardless, make sure you cover with your boss exactly what he wants changed and exactly in what ways.  Make sure you talk it out with him as best you can so that you both know what you're getting into.

The second time you should refactor code is when it's actually broken. However, this post addresses why you shouldn't refactor working code, so that's not really relevant to this post.

I'll end this post by acknowledging that much of what I've said here is quire elementary.  Many developers might read this post and think, "Well no shit."  However, I've blogged about it because, for one thing, no one ever explained it to me.  I can't even fathom how much heartache I might have been saved had I read a blog post like this during my first six months as a professional developer.

Moreover, elementary though these practices may be, I still see developers, myself included, making these mistakes all the time.  In every job I've ever had, I've noticed that the people who succeed are not the ones with the fancy methods and tactics or the overly expensive equipment.  It's the people who master the basics and learn to integrate those basics into more complex processes that succeed.