It’s More what You’d Call “Guidelines” than Actual Rules
Unlike methodologies – which are entire, well-defined processes – and patterns – which are concrete, readily identifiable on sight, and reproducible on demand – principles are simply concepts that software engineers can use to guide how we write code, as opposed to what code we write. Just look at the definition of the word principle:
Principle: n, def: a fundamental truth or proposition that serves as the foundation for a system of belief or behavior or for a chain of reasoning.
This is an introductory post to a series on software design principles. As with design patterns, design principles don’t apply to every situation. For example, the Interface Segregation Principle clearly wouldn’t apply to a static method since static methods can’t belong to an interface. Likewise, the Liskov Substitution Principle wouldn’t apply to statics since statics can’t be subclassed. However, the Single Responsibility Principle would certainly apply, and can be used to control the scope and complexity of the method. Also, the Open/Closed Principle would apply (and said application is one of the many, many, many reasons why statics are bad, mm’kay? I’m looking at you, MongoDB C# Driver).
Software design principles typically come in various flavors of concerns regarding object-oriented design. The Single Responsibility Principle and the Open/Closed Principle can certainly be applied to procedural programming languages, but other members of the SOLID principle group wouldn’t make a lot of sense. As such, software design principles are usually aimed at helping developers make the most of object-oriented design/programming by giving us a set of guidelines for how to write high-quality, efficient code that’s both easy to understand and easy to maintain.
This is all very abstract, and it’s meant to be. After all, principles have no real concrete component: they’re purely conceptual in nature. So perhaps the easiest way to see their value is to imagine unprincipled code. For those of us that have been writing code for quite some time, it’s not hard to remember back to some serious digital atrocities.
Just imagine an 8,000 line long class that could double as a thesis paper. Dozens of unrelated methods placed side-by-side, while related methods are spaced far apart by the unbound “organic” growth of the class. Maybe the original intent was to keep related methods together, but then the author pivoted and decided to alphabetize the methods, and eventually just gave up and started implementing new methods at the closest available spot to wherever the cursor happened to be. Overloaded constructors that reproduce code, rather than calling each other or a shared initialization method. Logic control flow so deeply nested that the innermost lines start beyond the IDE’s end-of-line margin, resulting in having to scroll both vertically AND horizontally to see all of the code. Self-obfuscating, single-character variable names that quickly eat up the entire alphabet until the author wraps back around and starts using the dreaded aa. Instance methods that call static methods and pass this for context so that the static method can, in turn, call another method on the current instance.
Code written with any one of the previous flaws would be terrible enough. But we’ve all seen code that’s written with all of them. It’s difficult to read. It’s difficult to debug. It’s difficult to test. It’s difficult to modify without breaking something. Code written this way has a massive total cost of ownership, and its author will be cursed by all future generations of engineers as “that jerk” who left behind a wake of brittle, illegible, unmaintainable sewage code for them to wade through.
There was a time when doing this was considered a form of job security. An employer is much less likely to replace me if they know my code is both integral to the system but also completely unintelligible to anyone but me. But in today’s world of highly distributed development teams, continuous delivery, and quickly-pivoting Agile workshops, the era of unmaintainable code as job security is over.
We may move on to some other project, and come back later, long after we’ve forgotten our original intent with a particular module. Our code might be a proof of concept that’s taken up by a different implementation team. Or we might be that implementation team, and when we’re done the code moves on to a maintenance team. Or it might be part of an open-source solution whose success depends on the ability of the community to readily contribute bug fixes and new features. Maybe we were hired as a consulting team or as contractors, temporarily assigned to a company to meet a short deadline, only to move somewhere else in a few months.
Whatever the reason, in the modern development world there’s a considerably high chance that the code we write will soon end up being maintained by different developers. And regardless of whether that happens quickly, slowly, or even never, writing high-quality, easily maintainable code is not only in the best interest of the next group to look at it, but also in our own interest. Because if we ever do come back to our own code after any period of time, it’s best if it still makes some semblance of sense. We shouldn’t have to go digging through source control (developers who submit “Bug fix” as a commit message should be docked pay) in order to understand what we wrote, and why we wrote it.
The easiest way to achieve this goal is by learning and applying software design principles. There’s a host to choose from, and some of them can take a mini-career in and of themselves to truly master. But even the act of trying to apply principles will lead to writing better quality code in the long run.
The rest of this series will be focused on introducing principles, and walking through their application. Check back for more in-depth articles on the following principles:
- SOLID – a set of 5 object-oriented design principles that have recently gained in popularity
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
- General Responsibility Assignment Software Principles (GRASP)
- Don’t Repeat Yourself (DRY)
- Separation of Concerns (SoC)
- Abstraction Principle