Welcome to Part 2 of the ever growing, popular web series, How to Write Code that Doesn't Suck! Today I want to go over the Open/Closed Principle of OOP. By discussing this principle, we're also going to cover Liskov Substitution and Interface Segregation (the L and I of the SOLID principle).
In simplest terms, the Open/Closed Principle means code is open for extending but closed for modification. I know, it sounds techy, but all it means is once code is released to your client, it should have been written in a way we can extend the behavior without modifying the original source with a heavy hand. Why wouldn't we just modify the original source to match our new requirements? Because the original code has gone through the software lifecycle. It probably had research, design and code reviews, unit testing, marketing, etc... associated with it. By you changing that original source, anything touching it might need revisited, best case retested. So if we can write code properly, the idea is we can extend behavior without being invasive to the existing production code.
Well then, how do we do that? You have a choice. Either use abstract base classes or interfaces. What's the difference? You should choose an abstract base class when there is definite behavior the base class can provide subclasses. If there is no consistent behavior, then use interfaces to create a code contract. So how does this help us? Let's peek at some example code.
The easiest example I can think of is logging. We have so many log destinations these days. We can write to a physical file, a database table, table storage in Microsoft Azure, other cloud storage, the Windows desktop event viewer, etc...
Let's pretend we have an app in production that writes to a physical file that is circular. Circular file means once the tail reaches a certain length of bytes in the file, the file wraps back around to the beginning. It is beneficial for tracing, but for situations of auditing to maintain a large history of actions, it stinks. Well one of our clients is mad because an employee changed a record they shouldn't have and nobody knows what the old value was because the log overwrote itself removing the audit entry showing the change. They're screaming at the CEO this is unacceptable and we must address it in the next release or they're moving to a competing product. They're demanding we create a database table to store auditing rather than the physical file. How can we write code allowing us to make this change without modifying the code surrounding this logging?
To start, let's look at bad code. More than likely, we've all experienced the following:
public class Widget
{
private readonly FileLogger _log;
public int Value {get; private set;}
public Widget(int value)
{
_log = new FileLogger();
Value = value;
}
public void ChangeTheWidgetValue(int newValue)
{
_log.WriteEntry(string.Format("Changing the Widget Value from {0} to {1}", Value, newValue));
Value = newValue;
}
}
This works. It tracks the old and new values in the log. It probably met the requirements established. I can't argue that. However, while functionally correct, there is a major problem with this code. This issue is called coupling. We have coupled our FileLogger class to our Widget class. The Widget class could care less where the log is written, it's just telling the log to track the change. To meet our client's demands, I now have to change the Widget class to not create me this logger but one that writes to the database. What if I have 100 classes performing complex work and audit that work using the FileLogger? I have to change those as well. That's a minimum of 100 changes, and all should be retested. This is a major headache.
But wait...What if this horrible code didn't exist. What if we went back in time, thought things through and wrote decoupled code making the product codebase maintainable and extensible? What would the Widget class look like then?
To start, we need an interface called ILogger. I'm using an interface because there is nothing we can share between a database logger and a file logger through an abstract base class other than method names.
public interface ILogger
{
void WriteEntry(string entry);
}
Now, we need our FileLogger class, which will implement the ILogger interface:
public class FileLogger : ILogger
{
public void WriteEntry(string entry)
{
...
}
}
Now I can inject this logger dependency into the Widget class by making the Widget ask for an ILogger in its constructor:
public class Widget
{
private readonly ILogger _log;
public int Value {get; private set;}
public Widget(ILogger log, int value)
{
_log = log;
Value = value;
}
public void ChangeTheWidgetValue(int newValue)
{
_log.WriteEntry(string.Format("Changing the Widget Value from {0} to {1}", Value, newValue));
Value = newValue;
}
}
See what's happening here? In the first example which is very badly designed code, we're coupling the Widget class with a FileLogger. Now to change to the Database Logger, I have to break that coupling everywhere it exists. With the second set of code, which is decoupled, I just have to do the following:
Create the DatabaseLogger and implement the ILogger interface:
public class DatabaseLogger : ILogger
{
public void WriteEntry(string entry)
{
...
}
}
Next, wherever I'm creating the Widget, I just pass a DatabaseLogger object instead of a FileLogger. Then I just rinse and repeat wherever we need to audit to the database. The Widget class never has to change because it is not coupled to a specific class. Rather, it is coupled to a code contract by use of an interface (Liskov Substitution). As long as I pass something implementing the code contract through the ILogger interface, I only need to change where the Widget is instantiated. This now allows your QA department to use the app as normal and just ensure audit entries are written to the database. Even better is maybe there are clients that like the circular audit file because it saves storage space. We can add a config option to use File or Database logging, and based-on that value, inject the appropriate logger (Interface Segregation). If you follow these principles, you will surprise yourself with your own code, especially if you have to revisit it in the future.
In the next post, I'm going to cover Dependency Injection and the D in the SOLID principles, Dependency Inversion. It may take a few days to write, both are large subjects to cover, but if you understand them, your code will make you happy!
Since I write this stuff on my own time, I usually blaze through it. If you want more detail or if you find any technical errors, feel free to post a comment. If you want to correct my grammar or point-out typos, go walk the plank, I'm a software engineer not a literary mastermind. After years of programming, sometimes you lose grasp of writing with proper English.
Wednesday, April 15, 2015
Tuesday, April 14, 2015
How to Write Code that Doesn't Suck...Part 1 - OOP and Single Responsibility
I was enrolled in college for my BS in Computer Science from the mid-90s to 2003. My father had health issues which led to money issues, so I was forced to put myself through college. After 8 years of working weird jobs and finishing my degree with part-time and full-time schooling from Auburn University in Auburn, AL (WAR EAGLE!) and Wright State University in Dayton, OH, eventually I achieved my BS in Computer Science.
What does this have to do with Object Oriented Programming? Nothing. But, I did receive OOP instruction from two universities and I think both taught it strange.
I was taught an object is a thing. I was taught inheritance is something like a Shape class, then if I want a circle, I should create a Circle subclass that inherits the qualities of the Shape base class. While this provided a decent enough understanding of OOP, enough where I could pass tests and write coding projects, once I hit the real world, it wasn't enough.
Objects are more than things, 'thing' isn't granular enough. Luckily for me, early in my career, I read about the SOLID principle (SOLID on Wikipedia) and I've been living by it ever since.
What really caught my attention, is the idea objects should represent a single responsibility rather than being, simply put, a thing. In OOP, an object does not have to be something tangible. It doesn't have to be a shape, it could be a database reader. It could create objects, it could be a jersey on a player in a video game, configuration manager, etc...Sure it's a thing, but if you focus on objects encapsulating responsibility rather than tangibility, I guarantee your code will be more clean.
As a novice software engineer, the hole I was always fell in was by thinking an object was tangible, I never encapsulated the inner responsibilities of the thing. Let's take the player jersey in the video game as an example. I would code a class called PlayerJersey. PlayerJersey would have a string number property and a string last name property. It would have a color property, is it mesh, does it have trim, etc...Before I knew it, I was managing all that data in one class because all those properties are on a PlayerJersey. The product was an object that was not easily maintainable and it would hide behaviors and properties that I could have reused in other classes.
Now, let's think of the PlayerJersey as a responsibility and let's think of those properties as other responsibilities. We should have a Name class, that contains the string name, but maybe it contains other info like the font and it manages all the properties of the Name. We should have a Number class that is similar. Maybe there's a JerseyFabric class managing the fabric responsibilities...so on and so forth. If we encapsulate those inner behaviors into their own single responsibilities, the PlayerJersey class now becomes the manager of those objects. All of the logic is now encapsulated into smaller classes and the PlayerJersey class becomes cleaner and more manageable. The product is a number of re-usable classes managing a small subset of responsibility that when joined into another object creates another responsibility. For example, if we have a Name class, we could reuse that on a stats screen, maybe a scoreboard style ticker in the game, etc...Also, we now know if there is a problem with how we handle names, then we can go to the Name class rather than updating the PlayerJersey class or any other classes which incorrectly encapsulated the Name responsibility.
I know this was a quick blog entry. It's purpose is to wet your feet on how I think when I lead projects. In Part 2 we're going to discuss the Open/Closed principle of SOLID and how not following it can make a good app go bad.
Since I write this stuff on my own time, I usually blaze through it. If you want more detail or if you find any technical errors, feel free to post a comment. If you want to correct my grammar or point-out typos, go walk the plank, I'm a software engineer not a literary mastermind. After years of programming, sometimes you lose grasp of writing with proper English.
What does this have to do with Object Oriented Programming? Nothing. But, I did receive OOP instruction from two universities and I think both taught it strange.
I was taught an object is a thing. I was taught inheritance is something like a Shape class, then if I want a circle, I should create a Circle subclass that inherits the qualities of the Shape base class. While this provided a decent enough understanding of OOP, enough where I could pass tests and write coding projects, once I hit the real world, it wasn't enough.
Objects are more than things, 'thing' isn't granular enough. Luckily for me, early in my career, I read about the SOLID principle (SOLID on Wikipedia) and I've been living by it ever since.
What really caught my attention, is the idea objects should represent a single responsibility rather than being, simply put, a thing. In OOP, an object does not have to be something tangible. It doesn't have to be a shape, it could be a database reader. It could create objects, it could be a jersey on a player in a video game, configuration manager, etc...Sure it's a thing, but if you focus on objects encapsulating responsibility rather than tangibility, I guarantee your code will be more clean.
As a novice software engineer, the hole I was always fell in was by thinking an object was tangible, I never encapsulated the inner responsibilities of the thing. Let's take the player jersey in the video game as an example. I would code a class called PlayerJersey. PlayerJersey would have a string number property and a string last name property. It would have a color property, is it mesh, does it have trim, etc...Before I knew it, I was managing all that data in one class because all those properties are on a PlayerJersey. The product was an object that was not easily maintainable and it would hide behaviors and properties that I could have reused in other classes.
Now, let's think of the PlayerJersey as a responsibility and let's think of those properties as other responsibilities. We should have a Name class, that contains the string name, but maybe it contains other info like the font and it manages all the properties of the Name. We should have a Number class that is similar. Maybe there's a JerseyFabric class managing the fabric responsibilities...so on and so forth. If we encapsulate those inner behaviors into their own single responsibilities, the PlayerJersey class now becomes the manager of those objects. All of the logic is now encapsulated into smaller classes and the PlayerJersey class becomes cleaner and more manageable. The product is a number of re-usable classes managing a small subset of responsibility that when joined into another object creates another responsibility. For example, if we have a Name class, we could reuse that on a stats screen, maybe a scoreboard style ticker in the game, etc...Also, we now know if there is a problem with how we handle names, then we can go to the Name class rather than updating the PlayerJersey class or any other classes which incorrectly encapsulated the Name responsibility.
I know this was a quick blog entry. It's purpose is to wet your feet on how I think when I lead projects. In Part 2 we're going to discuss the Open/Closed principle of SOLID and how not following it can make a good app go bad.
Since I write this stuff on my own time, I usually blaze through it. If you want more detail or if you find any technical errors, feel free to post a comment. If you want to correct my grammar or point-out typos, go walk the plank, I'm a software engineer not a literary mastermind. After years of programming, sometimes you lose grasp of writing with proper English.
Subscribe to:
Posts (Atom)