11 Mighty Software Engineering Principles to Improve Apps


Having well-designed software is important.

It can increase engagement and retention and reduce user frustration. However, developing a good software application isn’t as straightforward. Apps are made up of many design elements that must work together to create a successful and user-friendly experience. So how do you design an application that meets the needs and expectations of your users?

Designing applications requires good software engineering practices (and principles). If you’re new to software engineering or looking for ways to improve your software engineering skills, then you’re in the right place.

Read on to learn 11 powerful software engineering principles to help you build better apps.

#1 YAGNI

YAGNI is an acronym for “You Ain’t Gonna Need It” in software development. It is a principle that suggests that developers should not add unnecessary features or functionality to a system. The idea is that unneeded features can add complexity and cost to a system without providing any real benefit.

YAGNI is often contrasted with the “kitchen sink” approach, where everything that could possibly be included is added, whether or not it is needed. Instead, the YAGNI principle suggests that a simpler system is often better and that developers should resist the temptation to add unneeded features.

So, why is YAGNI important?

Unneeded features can clutter a system and make it harder to use and maintain. They can also add unnecessary costs in terms of development time and money. Most importantly, it’s hard to get the feature right when designing it now, but to be used in the future. The following diagram shows the cost associated with the feature that is not needed right now.

Following the YAGNI principle, you can keep your systems lean and focused on their core functionality. Most importantly, you will eliminate the cost of repair/adjustment of the feature for actual use.

So how do you know when to add functionality and hold off? It can be tough to tell, but a good rule of thumb is to only add functionality when you are confident you will use it. If you’re unsure whether you’ll need something, it’s probably best to wait.

Of course, there are always exceptions to the rule, but following the YAGNI principle will help you keep your code simpler and more maintainable.

#2 KISS

KISS is an acronym for “keep it simple, stupid.” The phrase reminds us that sometimes the best solution is the simplest one. This principle says software should be “as simple as possible, but not simpler.”

Of course, this principle is easily misinterpreted. Too many programmers mistake simple for “easy” or “finished.” It’s not.

If a software project is simple, it does exactly what it needs to do, no more, no less.

Adhering to the KISS principle can help developers create easier software to use, understand, and maintain. It can also help ensure that software runs more smoothly and efficiently. In addition, by keeping things simple, developers can avoid adding unnecessary features and complexity that can eventually lead to problems.

You can apply this principle to many different areas of life, from business to personal relationships.

For example, you should keep the design simple when designing a website. This will make it easier for people to use your website.

In business, the KISS principle often reminds us that customers don’t want complexity. Instead, they just want a product or service that solves their problem in the simplest way possible.

#3 SOLID principles

The SOLID principles of object-oriented programming emphasize the need to create and maintain a software architecture that is easy to change over time. First introduced by Robert C. Martin (Uncle Bob), the principles have become essential to many software developers’ daily routines.

SOLID is a mnemonic acronym that stands for five principles that describe how to create good object-oriented software. The principles are:

  1. Single Responsibility Principle – This is the idea that a class should have one and only one reason to change. Each class needs to have a single responsibility or goal for its existence. For example, if you have a class that handles getting a user’s name, the class should not be responsible for sending an email within the application.
  2. Open-Closed Principle – The Open-Closed Principle (OCP) is a principle of software design. It states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. That means that such an entity can allow its behavior to be extended without modifying its source code.
  3. Liskov Substitution Principle – Or LSP for short is used in object-oriented programming because it helps programmers write code and design objects to be easily reused in other programs. The LSP is all about substitutability, replacing any object of one type (the base type) with an object of another type (the derived type), and having the program still work. Read this article for more details on how the LSP can help you in the development process.
  4. Interface Segregation Principle – The Interface Segregation Principle (ISP) is a model for building loosely coupled modules in object-oriented design. The principle states that a class should be forced to depend on methods it does not use. To implement the ISP, classes should be segregated into too many classes that are loosely coupled to one another. You can read this article about Interface Segregation Principle to see more explanations with code examples.
  5. Dependency Inversion Principle – Dependency inversion is a technique that allows you to separate a class’s dependencies (code that it uses) from the class itself. This means you can make changes to the dependencies without having to change the class. For example, you have a class called Worker that depends on a class called Task. If the Task changes, you may have to change the Worker to accommodate the change. But if you first create an interface called ITask that Task implements and Worker has a dependency on ITask, you don’t have to change Worker if Task changes.

#4 DRY – Don’t repeat yourself

The Don’t Repeat Yourself (DRY) software engineering principle states, “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system”. DRY is all about avoiding redundancy and duplication in your code. Duplicate code increases the complexity of your codebase.

DRY helps you avoid these problems by ensuring that every piece of knowledge is represented only once in your code. It is often associated with the concept of “code reuse”, which is the practice of reusing code that has already been written. That means if one part of the business logic is already implemented in the system, you should try to reuse it instead of copy-pasting it in your part of the codebase and tweaking the code.

To reuse the code, you can:

Code reuse can be a powerful tool for software developers, but it’s important to remember that not all code is meant to be reused. In some cases, duplicating code may be the best solution.

For example, if you have two similar methods in two different parts of the system, maybe it is ok to leave them both. The reason for that is that, even though they are similar at this time, they might change in the future for different reasons. And the last thing you want to add yet another boolean parameter to determine method behavior.

All in all, DRY is an important principle to follow in software engineering, and it can help you keep your codebase clean and maintainable.

#5 Avoid premature optimization

In software development, it is important to avoid premature optimization.

Premature optimization is optimizing a piece of code or an algorithm before it is known whether or not that optimization is necessary. This can often lead to wasted effort and make code less maintainable than it would otherwise be.

There are many reasons why you should avoid premature optimization. For one, it can lead to code that is hard to read and maintain. In addition, premature optimization can make code more susceptible to bugs and make it more difficult to extend or modify.

So how can you avoid premature optimization?

The best way is to ensure your code is correct and working as intended. The decision for code optimization should come as feedback from:

  • Testing – you identify during testing (performance testing) that a feature doesn’t satisfy the performance targets.
  • Customer – once a customer starts to use a feature, he will tell you if the feature is slow under heavy data load.

In a way, code optimization should come from the monitoring part of the DevOps process.

devops

Once you have verified that, you can then start profiling your code to see where the bottlenecks are. Only after you have found the bottlenecks should you start optimization. By following this process, you can avoid wasted effort and ensure that your optimization efforts are effective.

#6 Law of Demeter

The Law of Demeter is a principle that dictates how an object should interact with other objects. The basic idea is that an object should only interact with the objects it needs to function properly. This principle helps to keep code clean and maintainable.

The Law of Demeter is also sometimes called the Principle of Least Knowledge. This is because it helps to limit the amount of knowledge an object has about other objects. By only allowing an object to interact with the objects it needs to, we can prevent it from becoming needlessly complex.

For example, observe the following message chain:

customer.LastInvoice.Order.OrderId

If you need to update the OrderId in the future, you would have to return to this line and update it. Most IDE-s can do that automatically for you, but you still need to ensure the code works properly after the refactoring.

While the Law of Demeter is a good rule to follow, it is not always possible or practical to adhere to it strictly. For example, in some cases, it may be necessary for an object to interact with other objects that it would not normally need to. This is usually due to the nature of the problem being solved. In some cases, it may be necessary for an object to interact with all objects passed down the call chain to perform as expected.

#7 Big design upfront

The big design upfront (BDUF) principle is the idea that a product or system should be designed all at once before any work is started on implementation. This approach is often used in traditional waterfall development processes.

The main advantage of BDUF is that it can help ensure that all aspects of a product or system are considered before work begins. This is important because the design reflects how you will build the application and how it will look before any code is written. This enables a team to discover and catch any complications and mistakes at the early stage of the project to reduce the costs and risks involved in the software development process.

However, BDUF can also be disadvantageous, leading to inflexibility and a lack of agility. This can make it difficult to respond to changes during the development process. As a result, the BDUF principle is not always used in modern software development.

What you use in modern software development is Architectural Decision Record (ADR).

An Architectural Decision Record (ADR) is a documentation technique that captures your important design decisions. The ADR defines the problem being addressed, the context in which the decision was made, the decision itself, the consequences of the decision, and the status of the decision.

#8 Single source of truth

A single source of truth is one unifying record system containing authoritative data about an item or entity. It is a system that includes all the data relevant to a given item. Without a single source of truth, data can get duplicated and lost. And you may end up with multiple versions of the truth.

A single source of truth is not just a matter of convenience but critical for any software product that aspires to be reliable, consistent, and trustworthy. Without a single source of truth, there is no way to keep information in sync, and everybody’s version of reality is different.

This problem has become more prevalent with the advent of microservices. For example, let’s say you have three services, one for ordering, one for shipping, and the third for inventory. Now, what happens when you change the item’s name? You need to propagate the name change information to other services.

In addition, different services often have their way of storing and managing data. As a result, it can be difficult to get a clear picture of what is the most accurate data within the application.

#9 Principle of least astonishment

The Principle of least astonishment is a theory that states that the system you are designing should not surprise the user. Instead, create your system so its behavior is consistent with the user’s prior knowledge. If the user is not surprised, they can feel a sense of control over the system.

On the other hand, surprises can leave them feeling frustrated or out of control.

If you have to press a button more than once to perform an action, you should be surprised (astonished). If you press the button only once, but it performs two activities, that’s also a violation of the principle of least astonishment.

The principle of least astonishment dictates that when multiple options are available, the one that is least likely to cause astonishment should be chosen. In other words, when given a choice, people will usually prefer the most familiar or expected option.

This principle is often applied in the design of user interfaces and other products. For example, when designing a website, it is important to consider how users expect it to look and function. If the design deviates too far from user expectations, it will be confusing and difficult to use.

The principle of least astonishment is also sometimes known as the principle of least surprise or least effort.

#10 Pareto principle

The Pareto principle, also known as the 80/20 rule, states that 80% of the outcome results from 20% of the input. For example, 80% of a company’s sales come from 20% of its customers, and 80% of its profits are generated by 20% of its products or services.

This principle is commonly used in engineering to identify the most critical factors in a system. In the workplace, it is used to evaluate the most common reasons for a mistake or disaster and determine the most effective solutions. One example from computer programming: 20% of the system has 80% bugs in a complex system. This is often the area in code that is very complex and hard to test with unit tests.

You can apply this principle in many different ways in software development. For example, you can use it to prioritize your work by identifying the most important tasks that will impact your project.

Going back to the above example, let’s say 20% of the legacy system has caused 80% of the bugs in the last year. Following the Pareto principle, it would make sense to introduce the characterization tests to that part of the system. That means you should prioritize writing integration or UI tests for that part. That might take some time, but it will be worth it. You will have fewer bugs in the long run.

The Pareto principle is a powerful tool to help you get the most out of your development efforts. If you’re not already using it, I recommend trying it.

#11 Command-query separation

Command–query separation (CQS) is a design principle that recommends separating methods that change the state of an object from those that only return information about the object. The separation keeps information and side effects separated and makes it easier to reason about code.

It also helps make code more robust, for example, in handling unexpected input cases.

Bertrand Meyer first proposed CQS in his 1988 book Object-Oriented Software Construction. Since then, it has been widely adopted in many software development frameworks and is considered a good practice that simplifies methods.

Many software developers often confuse CQS with CQRS, Command Query Responsibility Segregation.

Command-query responsibility separation (CQRS) is an architectural pattern separating data modification actions from data retrieval actions. This separation provides a clean separation of concerns, allowing each side to evolve independently.

Typically, the data modification side is implemented as a command-processing service optimized for performance. In contrast, the data retrieval side is implemented as a query-processing service optimized for reads.

CQRS is a variation of the traditional CRUD model. CQRS allows different read and write frameworks to be used on the same data store. For example, in .NET applications, you can use the Entity Framework for command operation when creating and updating entities. On the other hand, you can use Dapper ORM for read operations.

The difference between CQS (command-query separation) and CQRS (command query responsibility segregation) is that CQS is applied on the method level, while CQRS is applied on the architectural level.

Conclusion

In conclusion, software engineering principles are essential for any software development project. These principles help to ensure that the software is of high quality, is delivered on time, and is within budget.

If you want to become a better software engineer, make sure to have them by your side while making major engineering decisions.

Recent Posts