Building Exceptional Code: 6 Not So Simple Steps To Make Code Simple
Get on the path to write simple and easy to understand code
Hello to all friends, colleagues, and all the weekend developers!
I had heard this zen story some years back and it has stayed with me for quite a while.
George had a lot of problems in this life. He found a monk 60km away who could solve his problems. He drove down to the monastery. The monk listened to him. He talked about his problems until evening. The monk gave him some advice. George complained it did not solve all the problems. The monk asked, since it was dark, how he was going to go back. Thinking it was a trick question, he answered — “I have a car which has headlights.”
The monk asked, “Your headlights cannot shine 60 km from 60km away.”
George said, “I only need to see a few feet. I will see few feet ahead when I get there.”
The monk said, “My answers are the headlights. You will see — and you will see further once you reach there.”
The reason it has stayed with me is as developers, we want to write the best solution that can outlast all possible scenarios. But a lot of time there are unclear requirements and zero visibility in the future.
This story helps put things in perspective as you cannot foresee all the possible requirements.
So what we can do is make the code modular and loosely coupled. What that does is, any future change that comes in becomes easier to change or remove.
So how do we write a modular and loosely coupled code?
Step 1: Understand the Principles
Before diving into the practical aspects, let's first understand the principles behind writing modular, reusable, testable, and loosely coupled code.
The reason you need to learn the principles is you will see elements of principles sprinkled over all the steps.
These principles form the foundation of good software development practices:
Single Responsibility Principle (SRP)
Each module or class should have a single responsibility. It should do one thing and do it well. This helps in making your code more maintainable and easier to test.
Don't Repeat Yourself (DRY)
Avoid duplicating code. Instead, encapsulate common functionality into reusable modules or functions. This reduces code redundancy and improves maintainability.
I have my own variation around this.
Don’t Repeat Others
It is important to use other people’s code, and libraries so that you are not re-inventing the wheel and adding unnecessary reusable code to the codebase.
Dependency Inversion Principle (DIP)
Depend on abstractions, not on concrete implementations. This promotes loose coupling between modules, allowing for easier testing and flexibility in making changes.
Separation of Concerns (SoC)
Separate different concerns or responsibilities into distinct modules or classes. This improves code organization and makes it easier to understand and maintain.
Step 2: Modularize Your Code
To write modular code, break your code into smaller, focused modules or classes.
Each module should have a clearly defined responsibility and a well-defined interface.
This way, it becomes easier to reason about the behavior of each module and test them independently.
Think of your code as a collection of Lego blocks, where each block serves a specific purpose.
By breaking your code into smaller modules, you can reuse these blocks across different parts of your application, improving code maintainability and reducing bugs.
Step 3: Favor Composition over Inheritance
When designing your code, favor composition over inheritance.
Inheritance creates tight coupling between classes, making it harder to change or extend functionality without affecting other parts of the codebase.
Instead, use composition, where one class encapsulates an instance of another class.
This allows for more flexible and loosely coupled designs.
It also enables you to switch out different implementations at runtime, facilitating easier testing and extensibility.
Step 4: Write Testable Code
Writing testable code is crucial for ensuring software quality and reducing bugs.
By designing your code with testability in mind, you make it easier to write automated tests that verify the correctness of your code.
To write testable code, follow these guidelines:
Isolate Dependencies:
Use dependency injection to provide dependencies to your modules or classes. This allows you to replace real dependencies with test doubles or mocks during testing.
Mock External Dependencies
When testing a module, mock external dependencies to focus solely on the behavior of the module being tested. This improves test reliability and isolates failures.
Keep Methods and Functions Small
Aim for small, focused methods and functions. This makes it easier to reason about their behavior and write targeted unit tests.
Use Test-Driven Development (TDD)
Write tests before implementing the code. This helps in designing modular and testable code from the start and ensures that your code meets the desired behavior.
Step 5: Apply Design Patterns
Design patterns are reusable solutions to common problems in software development.
Familiarize yourself with commonly used design patterns such as the Factory, Singleton, Observer, and Dependency Injection patterns.
These patterns can help you design modular, reusable, and loosely coupled code.
However, don't fall into the trap of overusing design patterns. Apply them judiciously where they add value to your codebase and make it more maintainable.
Step 6: Continuous Refactoring
Refactoring is an essential practice for improving the quality of your code.
As you gain more experience and insights into your codebase, regularly revisit and refactor your code to make it more modular, reusable, and testable.
By continuously improving your code, you enhance its maintainability and reduce technical debt.
Remember, refactoring is not a one-time task but an ongoing process that should be integrated into your development workflow.
Conclusion
You cannot do all these steps in a month or two. This is the basis of continuous learning that makes you a better developer over the years.
Having these principles and steps front-of-mind helps you identify code smells and write simpler code.
Note: It is easy to write complex code. Anybody can do that. The challenge is always to make the code simpler.
Weekend Reads
Why Pagination in Spring Is not The Most Efficient Solution?: Learn about different pagination techniques and also why not to use default pagination
We Programmers: Ethics and programming.
14 Tech Pros Share Their Best Tips For Troubleshooting Code Malfunctions: Learn troubleshooting technique when your code breaks down which happens more often than you might expect