Hi! I'm back again, with another off-the-top-shelf software principles to recite before bed - literally.
Throughout the software engineering lifecycle, practices have come and gone. Those that have stuck and those that have withstood the test of time have been the building blocks of what we have today. They've pushed teams and developers alike to better code bases and practices. Here we discuss a few of the most notable ones.
In the last article, we discussed the endless stream of libraries and our need to know them all.
Are you going to use it really? Another dead end? The libraries you are actually going to use are those that you stumble upon while looking for a specific need and problem to solve, not those that you blindly go online and scroll.
Not a state of matter. Coined by Robert C. Martin, SOLID is a software engineering acronym, broken down to:
S - Single-responsibility Principle
O - Open-closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle
The goal of this is to make whatever that project you, as the engineer, is working on, be maintainable and extendable. Elaborate this.
Single-responsibility principle
A class or module should have one and only one function.
For instance, we have a class that renders a GUI application, say a PyQt program. Lots of things are happening within this. Our program, in this case, streams our favorite music streams from source ABC, a video player of sorts. What about creating just one class for this? Seems simple enough, right?
Well no. Beyond that corner of 'the program worked! ' lies a wet mop willing and ready to slap you in the face.
As with any other program, we expect downtimes, a stream not found, and additional functionality. You get this gist. So break it down. Our single class cannot have all this all at once, make modules to work on error logging, make modules specific for the video player, a class-specific to listing our feed, and another to show history. If it can be small, make it smaller. Just do not overdo it and clear out the whole point of it.
Open-closed Principle
Objects or entities should be open for extension but closed for modification.
A function, class, or module can be extended but not modified by an external entity. All notification services show a notification, but do all of them show an error notification? Break it off. Utilize polymorphism.
All my error service needs to do is pass ABC to XYZ. The rest should be none of its concern. Pass it relevant info and let the rest be handled.
Liskov Substitution Principle
Derived classes must be substitutable for their base classes.
Objects in a program should be replaceable with instances of their subtypes without changing the correctness of the program.
Let's simplify this: what we mean is if S is a T subtype, then Type T objects can be replaced with Type S objects.
Bear with me; A school application.
We have a class
Staff
that houses all the staff of an institution. As well, we have two subclasses - Instructor
and Support staff
, both of whom are still staff members.As usual, we have our assumptions as to what properties and methods belong to what object. Following the same principle, we state the below:
Using the example; we break the programming principle by stating, as a property that
class Instructor
, must not only be an instructor, but also be an instructor of a specific institution. Pause a little and reason this out. We are modifying the parent class from within the child. This is similar to saying a dog species class, say
Grayhound
, modifies a parent class , Dog
, to have color red. We just break everything else that depends on that single class Dog
. We have, essentially, stated that all dogs are red, even though it is just this one species.Interface Segregation Principle
Make fine grained interfaces that are client-specific.
We have a class,
Library
that helps manage our books. Creating an instance of this class is the same as interfacing with it. Take, as another example, an e-commerce platform. We lean towards microservices at this point. We have a database that houses our stall, an admin panel for the store owner, and the user navigation section, where our buyers get to see and hopefully purchase our products. As microservices, at the top level of this becomes:
Why should the buyer interface with the admin panel when they are not using it in the first place? Why would all that code be with them at that specific instance?
Dependency Inversion Principle
Depend upon abstractions, [not] concretions
A higher class should always depend upon the abstraction of the class rather than the detail.
A good example of this is the implementation of abstract classes in Django models, the Abstractuser model.
In our application models, we would, upon migration, have models from classes inherited from it but have no table within the database to represent the abstract model itself because it's, well, abstract.
You may note how:
A high-level module in any program is one that depends on others. We specify, I repeat, abstraction, an interface upon which we build.
Oh, duplicate code, where have you been? You have a piece of code from A that is exactly familiar to the one in C. You have a
div
, (a little web development for a while), across multiple pages. Repeating the same CSS styles and methods across those multiple pages. Why not make it a component in itself? Then tag it and write one CSS file and so forth and change data based on props?Often engineers find themselves getting lost in algorithms and data flow other than the value the application will bring to the table. We focus more on 'features' as opposed to what the user will actually want our program to do.
Follow the software principles and remember to install libraries you actually need. No bloatware! Even on your current device. Do you need that app, or is it there for that one time you thought about it?
Also published on https://thegreencodes.com/software-engineering-principles-to-live-by.