The importance of good programming principles for developing maintainable software

Pradipta Davi Valendra
7 min readMay 18, 2023

--

Source

In the fast-paced world of software development, creating functional code is only the first step. A true mark of software quality lies in the maintainability of code that can be effortlessly understood and changed by other developers because quality software can withstand the test of time.
Most of the time said software is developed by developers following proper programming principles — a set of guiding practices and methodologies that shape the way we code. In this article, I will articulate how important and helpful these programming principles are in ensuring quality software with two popular principles-OOP and SOLID, both in theory and my own applications.

Source

Object Oriented Programming

The first principle we will discuss is “Object Oriented Programming”, a hallmark programming paradigm which is supported by many languages, including but not all; Ada, ActionScript, C++, Common Lisp, C#, Dart, Eiffel, Fortran 2003, Haxe, Java, JavaScript, Kotlin, logo, MATLAB, Objective-C, Object Pascal, Perl, PHP, Python, R, Raku, Ruby, Scala, SIMSCRIPT, Simula, Smalltalk, Swift, Vala and Visual Basic.NET.

Object-Oriented Programming (OOP) is a programming paradigm that organizes code into reusable and modular structures called objects. At its core, OOP is based on the concept of “objects,” which are instances of classes. A class is a blueprint or template that defines the properties (attributes) and behaviours (methods) of objects.

There are four main principles in Object-Oriented Programming:

1. Encapsulation:

Encapsulation is the practice of bundling data (attributes) and related behaviours (methods) together within a class. It allows objects to have control over their internal state and selectively expose data and functionality to the outside world. Encapsulation provides data hiding, which helps in maintaining the integrity of the object and allows for easier code maintenance and modification.

An example application of encapsulation from my PPL course project “kembara”.

In the example code above, our Place object has attributes and methods in a single class and file. This allows developers to easily access methods and attributes relevant to “places” by simply accessing them straight from an instance of the Place class.

An example of an instance of Place being utilized.

In the above code, we easily query an instance of Place that we want by simply searching our database by the attribute ‘id’ that’s stored in the Place class. We can also manipulate attributes in our place instance by calling a method built into the Place object. All of this is straightforward and doesn’t hassle developers by forcing them to understand what the correct way to manipulate the attributes of Place.

2. Inheritance:

Inheritance enables the creation of new classes (derived classes or subclasses) based on existing classes (base classes or superclasses). The derived classes inherit the properties and behaviours of their base classes, allowing code reuse and promoting a hierarchical structure. Inheritance facilitates the creation of specialized classes that inherit and extend the functionality of their parent classes, promoting modularity and reducing code duplication.

3. Polymorphism:

Polymorphism allows objects of different classes to be treated as objects of a common superclass. It enables a single interface to be used to represent different types of objects, providing flexibility and extensibility. Polymorphism allows methods to be overridden in subclasses, allowing specific implementations while still adhering to a common interface.

The example below showcases both application of Inheritance and Polymorphism.

The base ‘View’ class from Django.
An example application of inheritance & polymorphism from my PPL course project “kembara”.

The above code is an example of inheritance where the class “ProfilePicture” inherits from the base Django “View” class, what this means is the ‘ProfilePicture’ class gets all the same methods and attributes that the parent class ‘view’ have, with the option of overriding said methods and modifying them.
This application of inheritance allows us to also apply polymorphism by being able to simply override the ‘get’ method of view with our implementation yet still have other behaviour from the rest of the ‘view’ class, which allows us to send an HTTP GET request directly without specifying that we require the method header ‘GET’ in our request because we’re still using URLconf specified in the ‘view’ class.

A test making an http get request to call our method specified above.
The test passing.

4. Abstraction:

Abstraction focuses on the essential characteristics and behaviours of an object while hiding unnecessary details. It allows programmers to create abstract classes or interfaces that define a common structure and behaviour for a group of related objects. Abstraction provides a higher level of generalization and allows for the creation of modular and loosely coupled code.

An example application of abstraction from my PPL course project “kembara”.

Above is an example of a class filled with abstract methods — Methods which doesn’t have implementations when directly called, rather awaiting another class to inherit the abstract class to provide the implementation. This allows for objects to have different behaviours with the same methods, helping with generalization.

A concrete class providing actual implementations for methods from the above abstract class.
Source

Solid Principle

The second principle we’ll discuss will be the SOLID principle(s), a set of design principles that help in creating software systems that are easy to understand, maintain, and extend. The term “SOLID” is an acronym formed from the first letter of each principle:

Single Responsibility Principle (SRP):

The SRP states that a class should have only one reason to change. It means that a class should have a single responsibility or purpose, and it should encapsulate that responsibility. By keeping classes focused and cohesive, the SRP helps to reduce the impact of changes, enhances code readability, and promotes easier maintenance and testing.

An example of a class that has two responsibilities.

In the above example, our Profile class has two main HTTP methods, the GET route provides information on a user’s profile, meanwhile, the POST route provides a way to change the user’s profile picture.
While functionally there’s nothing wrong with this, the two methods means the Profile class has two responsibilities and thus violates our principle, and so it might be more logical to separate the second method into its own class, as a side effect this will make our code more readable and maintainable for other developers.

The slimmed down Profile class
The separated change picture method into its own class

Open/Closed Principle (OCP):

The OCP states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. It encourages designing systems in a way that allows adding new functionality by extending existing code, rather than modifying it. This principle promotes code reuse, and scalability, and minimizes the risk of introducing bugs in existing code.

Liskov Substitution Principle (LSP):

The LSP states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. In other words, any derived class should be able to be used as a substitute for its base class. Adhering to the LSP ensures that the behaviour of the program remains consistent when substituting objects and helps to maintain the integrity of the inheritance hierarchy.

An example of both OCP and LSP would be the abstraction principle from the previously discussed object-oriented programming, as abstraction, Open/Closed, and LSP principles are like peas in a pod.

Interface Segregation Principle (ISP):

The ISP states that clients should not be forced to depend on interfaces they do not use. It promotes creating specific interfaces that are tailored to the requirements of clients, rather than having large, monolithic interfaces. By adhering to the ISP, the codebase becomes more modular, clients only depend on what they need, and changes to one interface have minimal impact on others.
An example of this can be referred back to the Single Responsibility example, where if a developer required the details of a user’s profile, they would only need to depend on the slimmed-down Profile class, and not the previously bloated class with the change profile picture method which we currently do not need.

Dependency Inversion Principle (DIP):

The DIP states that high-level modules should not depend on low-level modules. Both should depend on abstractions. It emphasizes decoupling modules by introducing abstractions (interfaces or abstract classes) that define contracts between them. This principle promotes loose coupling, facilitates easier testing, and allows for the interchangeability of implementations.
An example of this can again be shown in the abstraction principle example. Where instances of different implementations can be referred to the same type by the superclass.

Conclusion

In conclusion, adhering to the SOLID principles and embracing the concepts of Object-Oriented Programming can significantly impact the quality and longevity of software systems.

These principles promote code reusability, flexibility, and scalability, allowing software systems to adapt to changing requirements and evolving technologies. By adopting these principles, developers can build software that not only meets current needs but also lays a solid foundation for future enhancements and ensures the long-term success of their applications.

References

SOLID: The First 5 Principles of Object Oriented Design

What is Object-Oriented Programming (OOP)?

Abstraction in Java

Exploring Inheritance in Python

Polymorphism In Programming

--

--