Software Design Principles
Aneshka Goyal
AWS Certified Solutions Architect | Software Development Engineer III at Egencia, An American Express Global Business Travel Company
Software Architecture
Software architecture at high level has patterns that define the overall shape and structure of software applications.A level down, architecture that is specifically related to the purpose of the software application.Coming another step down it involves the modules and their interconnections, this is the field that concerns us the most as this is the field of application of design principles and patterns.
Software design when new looks perfect. The design of many software applications begins as a vital image in the minds of its designers. At this stage it is clean, elegant, and compelling. But with time changing requirements can introduce unplanned and new dependencies that can degrade the design and with it the maintainability of the software.We, as software engineers, know full well that requirements change. Indeed, most of us realise that the requirements document is the most volatile document in the project. If our designs are failing due to the constant rain of changing requirements, it is our designs that are at fault.Thus the software design should be such that it can cater to the changing requirements and the changes introduced to fulfil the requirements should also abide by the good design principles.
Four Primary Symptoms of Rotting Design
Rigidity - Rigidity is the tendency for software to be difficult to change, even in simple ways. Every change causes a cascade of subsequent changes in dependent modules
Fragility - Closely related to rigidity is fragility. Fragility is the tendency of the software to break in many places every time it is changed.
Immobility - Immobility is the inability to reuse software from other projects or from parts of the same project. This happens because of cascade of dependencies associated with the desirable part of the code that makes separation of desirable from undesirable code a lot of effort and time.
Viscosity - Viscosity comes in two forms: viscosity of the design, and viscosity of the environment.When the design preserving methods are harder to employ than the hacks, then the viscosity of the design is high.Viscosity of environment comes about when the development environment is slow and inefficient, hence to reduce burden on environment the design goes for a toss.
Thus to guide the software design we have principles that help to manage dependencies between modules and keep them under check to prevent the rotting.A design rots because of a common thread that the modules have undesirable dependencies and are coupled.Some of these principles will be discussed below.
- SOLID
SOLID is an acronym formed by the names of 5 design principles centred around better code design, maintainability, and extendability. The principles were first introduced by Robert Martin (more familiar in the developer circles as Uncle Bob) in his 2000 paper Design Principles and Design Patterns. These principles are Object Oriented design principles.
Single Responsibility Principle - This principle says that a code component should just have one responsibility which means it should just have a single reason to change.For example this is a clear violation of single responsibility as this class has text edit as well as text printing responsibilities while we could have segregated them easily to make the class look simple. Spring framework also demonstrates the abidance of single responsibility by having different layers each having its own responsibility like controller has light weight methods and service layer handles all the business logic and repository layer has the responsibility to deal with data bases.
class Text { String text; String getText() { ... } void setText(String s) { ... } /*methods that change the text*/ void allLettersToUpperCase() { ... } void findSubTextAndDelete(String s) { ... } /*method for formatting output*/ void printText() { ... } }
Open/Close Principle - Modules should be open for extension but closed for modification.We want to be able to change what the modules do(extend behaviour), without changing the source code of the modules(closed for modifications). All of these techniques are based upon abstraction. Indeed, abstraction is the key to the OCP. The below example abides to OCP as a new shape addition requires no code changes for already existing code.
Liskov Substitution Principle - Derived classes must be usable through the base class interface without the need for the user to know the difference.This simply means that if one class is derived from another we should be able to easily substitute the derived class in place of the base class without hampering any functionality and hence without the user noticing the change and if we are not possible to do the substitution its a failure to abide by this principle and hence the inheritance relationship we used is not the correct one.
Interface Segregation Principle – Many client specific interfaces are better than one general purpose interface. If there is a service that is accessed by different types of clients each requiring to call different methods then rather than loading the service with all the methods the clients need, we can have specific interfaces for each client which our service can implement. This reduces rigidity and allows for better maintainability .
Dependency Inversion Principle – Depend upon Abstractions. Do not depend upon concretions.If the OCP(open/close principle) states the goal of OO architecture, the DIP states the primary mechanism. Dependency Inversion is the strategy of depending upon interfaces or abstract functions and classes, rather than upon concrete functions and classes.
High level modules depend upon lower level modules, which depend upon yet lower level modules, etc.. A little thought should expose this dependency structure as intrinsically weak. The high level modules deal with the high level policies of the application. These policies generally care little about the details that implement them. Why then, must these high level modules directly depend upon those implementation modules? An object oriented architecture shows a very different dependency structure, one in which the majority of dependencies point towards abstractions. Moreover, the modules that contain detailed implementation are no longer depended upon, rather they depend themselves upon abstractions. Thus the dependency upon them has been inverted.
Apart from the SOLID principles discussed we have some others which help guide the design process and lead to better development.
- DRY – It stands for Do not Repeat Yourself, this principle states that “Every piece of knowledge(code) must have a single, unambiguous, authoritative representation within a system”. This helps us to write scalable, maintainable and reusable code.When the DRY principle is applied successfully, a modification of any single element of a system does not require a change in other logically unrelated elements. Additionally, elements that are logically related all change predictably and uniformly, and are thus kept in sync.We can use Abstractions for example to keep common code at one place.
- KISS – It is an acronym for keep it simple, stupid, is a design principle noted by the U.S. Navy in 1960. The KISS principle states that most systems work best if they are kept simple rather than made complicated; therefore, simplicity should be a key goal in design, and unnecessary complexity should be avoided.
- YAGNI – It is as acronym for You Arn’t Gonna Need It. This principle states that always implement things when you actually need them never implements things before you need them.Never just foresee that we might need them.
- Principle of Least Knowledge – Also known as Law of Demeter, it is a is a design principle which provides guidelines for designing a system with minimal dependencies. It is typically summarized as “Only talk to your immediate friends.”The rules promoted by this principle are:
- Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.
- Each unit should only talk to its friends; don’t talk to strangers.
- Only talk to your immediate friends.
And now let’s change a bit the rules above and apply them to object-oriented programming. Imagine that we have a class which implements a given method. This method should only call the following objects:
- The object that owns this method.
- Objects passed as arguments to the method.
- Objects that are dependencies of the owner instance (are held in instance variables).
- Any object which is created locally in the method.
- Global objects that can be accessed by the owner instance within the method.
Thus we have seen some of the most common design principles that can help develop maintainable and extendible software solutions which don’t rot with time.
Sources of Knowledge
- https://web.archive.org/web/20150906155800/https://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf
- https://www.ericfeminella.com/blog/2008/02/02/principle-of-least-knowledge/
- https://dzone.com/articles/software-design-principles-dry-and-kiss