Preface

This article is about how to remove old and outdated code, with some examples in C#. It is written for programmers of intermediate or above skill and it helps if you understand dependency injection.

Your new code is already old

The moment your hands are lifted from your keyboard and your unit tests are passing - then your code is old and outdated. Chances are someone else, somewhere in the world, has coded something similar that is better than what you did.

This is nothing to be frustrated about, for it is the nature of human invention and creativity. We take other’s ideas and improve upon them, one step at a time. We are always striving to improve our surroundings and ourselves. It is my opinion that we should not work against our nature, and try to write code that stands the test of time forever and ever. Rather, we should make it easy to remove code and replace it with new.

One can argue that all processes and ideas become outdated, and that we should easily replace everywhere where improvements can be made, and that this mentality could be an important asset to both society and ourselves, but I digress. Let us talk about code and how to make it easier for you to remove old code from a practical standpoint.

The Monolith

If you have worked as a programmer for a while, chances are you have met The Monolith. It stands in your way. Immovable, insurmountable, arcane and mysterious. It is both praised and hated as the core of the operation. There are those who worship it, and those who lives in fear of it.

It cannot be moved, for it weighs many tons. It's made from an alien material, from a time before time, before you or your parents were born. It’s interconnected to all you do. It watches you as you sleep. And it’s code is terribly, terribly outdated.

You wish everyday that you could just bury it and replace it with a simple application that is faster, more robust, documented, more fun, more understandable and easier to use for the new developers you employ. It is possible, but it is hard, because The Monolith was made to stand the test of time - forever. And you now live the consequences of that decision.

Done is done. The developers making it probably did the best they could. But how do we learn from their mistakes and avoid writing another Monolith in the future?

The art of writing outdated code - C# example

Consider the following code which attempts to demonstrates code that is easy to replace. It is not meant as a perfect solution, or the perfect example, but only to show the mindset.

public interface IEmailSender { bool SendEmail ( string body , string recipientEmail , string senderEmail ); } public class OldEmailSender : IEmailSender { public bool SendEmail ( string body , string recipientEmail , string senderEmail ) { //do things slowly //do smtp connections //verify //serialize //send email return true ; } } public class NoReplyEmailManager : IEmailManager { private readonly IEmailSender _emailSender ; private readonly EmailFormatter _emailFormatter ; private string senderEmail => "noreply-emailmanager@domain.com" ; public NoReplyEmailManager ( IEmailSender emailSender , EmailFormatter emailFormatter ) { _emailSender = emailSender ; _emailFormatter = emailFormatter ; } public bool SendEmail ( string body , string recipientEmail ) { body = _emailFormatter . FormatBody ( body ); return _emailSender . SendEmail ( body , recipientEmail , senderEmail ); } }

The IEmailSender has one outdated implementation: the OldEmailSender, and it uses some strange serialization principle, maybe it makes a few unecessesary loops (not shown in the above code). It’s bad and you want to replace it.

So you write NewEmailSender. Because you are smart, and wrote code that is easy to replace, you just update your dependency injection and it's done. At all places where OldEmailSender was instantiated, it is now replaced with NewEmailSender.

public class NewEmailSender : IEmailSender { public bool SendEmail ( string body , string recipientEmail , string senderEmail ) { //do things faster return true ; } }

What if we had originally simply put the code of the OldEmailSender in the NoReplyEmailManager? Then you would have to rewrite the NoReplyEmailManager and possibly introduce bugs, since you need to take the EmailFormatter into consideration. What does the EmailFormatter do? You have no idea! Now you are forced to look at what the EmailFormatter does and bend your new code around it.

What if there are many types of EmailManager that needed updates to speed them up? Perhaps these EmailManagers handles dangerous email, emails for finances, errors, logs? And all of these had possibly complex code and behavior, much more advanced than the EmailFormatter. You would have to go into each of these EmailManagers and mess around with the code, instead of simply writing a new instance of IEmailSender and bootstrap it to all of your EmailManagers with dependency injection.

public class NoReplyEmailManager : IEmailManager { //[...] public bool SendEmail ( string body , string recipientEmail ) { body = _emailFormatter . FormatBody ( body ); //do smtp connections //verify //serialize //send email } } public class LogEmailManager : IEmailManager { //[...] public bool SendEmail ( string body , string recipientEmail ) { body = _logFormatter . FormatBody ( body ); _logDatabase . Save ( body ) //do smtp connections //verify //serialize //send email } } public class ErrorEmailManager : IEmailManager //[...]

What if you had The Monolith where a single EmailManager was made of hundreds of lines of complex cases and if/else clauses, deeply integrated within each other where any change would affect other things in unexpected ways? Where would you even begin to look to change how your emails were sent? How many hours of debugging and searching would you need to do to guarantee that nothing strange happened? What if it was simply impossible to change the code within your deadline time limit?

public class EmailManager : IEmailManager { public bool SendEmail ( string body , string recipientEmail ) { if ( recipientEmail . Contains ( "log" )) { //do smtp connections //verify //serialize //send email } else if ( recipientEmail . Contains ( "errors" )) { //do smtp connections //verify //serialize //send email } else if ( recipientEmail . Contains ( "danger" )) { //do smtp connections //verify //serialize //send email } // etc } }

What if you had a behavior within The Monolith where it was dependent on emails being sent in a faulty way to continue it's operation, and your new improved code fundamentally broke it? What if The Monolith required emails to be sent in a slow way because it made some asynchronous operation which needed to be completed before the email processing was done?

Does it sound insane? These are all real-world examples I have experienced with The Monolith, and you will too if you work long enough. The list goes on, simply because we didn't bother to think about how to make code easy to replace. The developers making The Monolith were not able to easily replace code, and just heaped EmailManagers upon eachother in an if-clause. The codebase increased, and at some point the original developers left. The new coders became fearful of The Monolith's strange ways, and became thus reluctant to change it or replace it.

A wider perspective

The example of using dependency injection was one way of making it easier to replace code. It's not the only one, it is not the perfect one, and it made only to demonstrate the point.

The most important thing is that we start to think about what it implies, as a community of developers. We will all touch each other's code. We will all be affected by other people's choices. How easy or difficult is it to remove our code? How high is the chance of it being replaced? Because it will be replaced, whether we like it or not. We are not perfect. We cannot make the best code, that will remain forever and ever. That is simply not how the nature of human invention and creativity functions.