Software Engineering is about Simplicity
In my early career, microservices and other complex things are always popped into my mind. Solving future problems that don’t even exist at that time. Thought it was worth it for every case. But I was wrong.
In engineering, we offer a solution. The solution must be effective yet reliable. This applies to everything. We want to solve a problem, not make it more complicated. A complex thing should be avoided if it is not necessary.
For example, we know in-memory caching is good to improve performance and speed. But if the service we create won’t have that much traffic yet or only for the utility to help finish a smaller task, why do we even need it. It just gives complexity and more development time but the result is not really used.
There is a software design principle for simplicity in software engineering. It is called KISS, YAGNI, DRY, and SOLID.
KISS (Keep It Simple, Stupid)
This is a simple principle that helps me a lot when I write a code, review others' code, or even design a new system or feature. Make it simple bro. Even if it looks stupid, but it is solved the problem, then it is good. Complexity is prone to creating more bugs. It also potentially makes your colleagues hard to understand. Remember we work with other people. Make them easy to understand what you are writing and it will help to reduce misunderstanding and increase development time (because the learning curve to understand the code is smaller).
YAGNI (You Ain’t Gonna Need It)
This is the most common problem I found when I mentored my interns. They somehow always think too far and solve a problem that they don’t even have at the current time. Like they add data a data definition, fields in the database, or a function to solve a non-existing problem. It is good to think about maintainability but that doesn’t mean creating the solution too early. You ain't gonna need it right now. The key is to make your code or design changeable/maintainable for possible change in the future. Because again, it just adds more development time and complexity that we don’t need at the time.
DRY (Don’t Repeat Yourself)
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
- The Pragmatic Programmer
Make sure there is no repetition in your code. Why? it makes it hard to manage. And add more unnecessary complexities to your code. The common way to overcome this is to reuse the code by creating it into a function.
func createUser(requester model.User, name string) (model.User, error) {
// some code in funA
//... user, err := db.User.Find(requester.ID)
if err != nil {
return err
}
if user == nil {
return ErrPermissionDenied
} for _, access := range user.access {
if access == CREATE {
return nil
}
}
return ErrPermissionDenied// another code
}func updateUser(requester model.User, name string) (model.User, error) {
// some code in funA
//... user, err := db.User.Find(requester.ID)
if err != nil {
return err
}
if user == nil {
return ErrPermissionDenied
} for _, access := range user.access {
if access == CREATE {
return nil
}
}
return ErrPermissionDenied// another code
}
Clean it up:
func hasAccess(user model.User) error {
user, err := db.User.Find(requester.ID)
if err != nil {
return err
}
if user == nil {
return ErrPermissionDenied
}for _, access := range user.access {
if access == CREATE {
return nil
}
}
return ErrPermissionDenied
}func createUser(requester model.User, name string) (model.User, error) {
// some code in funA
//... err = hasAccess(requester)
if err != nil {
return ErrPermissionDenied
}// another code
}func updateUser(requester model.User, name string) (model.User, error) {
// some code in funA
//... err = hasAccess(requester)
if err != nil {
return ErrPermissionDenied
}// another code
}
If the `hasAccess()` function needs to be modified, we only need to modify the function and it applies to wherever it is used in the code. Imagine you need to change the whole code and your code like 10000 lines of code in total. RIP. Waste of time and prone to create more bugs.
Another approach is using an interface or Object-Oriented Programming in general.
SOLID
This is an acronym for five Software Design Principles. It stands for:
- S — Single-responsibility Principle
- O — Open-closed Principle
- L — Liskov Substitution Principle
- I — Interface Segregation Principle
- D — Dependency Inversion Principle
Those are a deep dive concepts of Software Design Principles, I suggest you to read more about them here.
Conclusion and Disclaimer
This is an important disclaimer, complexity is not the thing that we need to get rid of it at all costs. If it is worth the effort, then we are good to go. It is all about the trade-off, when we face a bigger problem, we sometimes need more decisions that have big risks to solve the problem. For example, if the apps or services that we create start to have huge traffic and the customer potentially could make the service run slower in terms of response time then we should make a change to save the business. In this case, we can create an in-memory cache to increase the response time. Or use a tool like Elasticsearch to reduce the read operation to databases which also will help to increase the response time (read more about the CQRS pattern here). Or of course, the ultimate one, implement Microservice. It’ll cost more development time, add complexities and probably cost more engineers too. But if it is worth the effort, if it makes our apps run smoother and helps to grow our customers, then why not. It is all worth it.
Reference
- The Pragmatic Programmer: Your Journey to Mastery — David Thomas and Andrew Hunt