There are as many clean code definitions as there are programmers. Often, while interviewing potential candidates for a vacancy, we hear that good code is one that is easy to read. We agree, but as our personal experience suggests, this is just the tip of the iceberg.
The first sign that informs us about the bad code quality is an increase in the development time for new functionality and an increase in the regression scale at the slightest change in the system. This is a consequence of the fact that technical debt accumulates, the components in the system are very closely related, and there are no autotests. The reasons for this may be different:
- External — such as pressure from the business and its desire to get the new functionality faster.
- Internal — poorly established development processes and interaction within the team, lack of control and development standards, or a banal lack of competence.
You should not put the work on the quality of code and software architecture on the back burner. During a sprint, it is important to be able to separate tasks that have direct business value (tasks with new functionality) and technical tasks that have only an indirect effect on the business. The proportion of the split depends on the current state of the project, the time frame, and the number of free hands.
In this article:
What is Clean Code?
Clean code meaning has a much broader concept than a correctly designed system with easy-reading code. It should have other qualities as well:
- The code is easy to modify. With the right software design and architecture, extending your code can be done without much time and expense. The entities of the code should not be closely related to each other; the code should be somewhat abstract and self-sufficient. Each entity that we operate during the development should be responsible only for its own part of the functionality.
- The code must be stable, predictable, secure, and reliable. No matter how simple the code is to read, it should be covered by tests. Good code and tests are always there. Moreover, it is not only the number of tests that is important; it is also their quality. With this code, there are no problems when running and debugging; it does not cause changes in the environment.
- Protected code. When writing any code, you must not forget about the overall security of the product. We recommend that you get to know the basic principles of security and stick to them. If we're talking about web projects, we recommend OWASP.
- A good code is code that isn't there. This does not mean that all the code should be written on one line, and you should definitely be proud of subtle methods. This means that you should avoid duplicate code and some of the common things you should leave at the abstraction level. In theory, simplifying your code should lead to fewer defects.
- Reading the code itself is also important. Each developer has their own writing style, and the level of reading depends on our experience. We all want to write simple, beautiful, and concise code.
To get away from the subjective assessment of the quality of your code, in 1961, the term "code smells" or "code with a smell" was introduced. This is a group of rules and guidelines that clearly define whether it's time to refactor code. Odor leads to code decay, and software developers should always strive to eliminate it. The need for refactoring directly follows from the presence of a code smell, and by preventing the cause, we can avoid the consequence. You can find more information on the key signs that it's time for you to refactor in Martin Fowler's book Refactoring: Improving the Design of Existing Code.
By the way...
Apache Airflow Overview: Full Guide
Make your work simpler, automating most of everyday tasks. We're introducing a full overview of Apache Airflow, including Python tips.
Let's seeClean Code Principles
KISS Principle
KISS stands for Keep It Simple. This principle is worth using for solving various life problems, and it helps a lot in writing code.
For example, you are going to develop a mobile game. Maybe, you will not create the next Attack Hole or Bridge Race. So try to keep the project small and simple. And if it's already simple, make it even simpler. You can create additional features when you make a stable working base.
During your execution flow, try to keep the code simple because a complex one quickly becomes time-consuming. The more time you spend on programming, the more bugs and errors you will get. And in the end, this will lead to difficulties if you want to change something in the project later.
DRY Principle
DRY means "don't repeat yourself" and it should be taken literally. If your code is repetitive and repeats the same actions, you are breaking this rule. Do multiple functions do the same thing? — Refactoring into one function. Do multiple variables contain the same data? — Refactoring into one variable.
DRY is opposed by WET: ''write everything twice.'' The main question for determining rule breakers is this: how many places need to be changed to fix the program? If your answer is "more than one," then you were acting contrary to the DRY principle.
Imagine that you are developing a sports application with three pages: exercises, reps, and workouts. Each of these pages has its own class, and each class has its own fetchSport=() function. So why are there three places in the code that behave the same way? Remove this feature, and don't go against the DRY principle.
Open/Closed Principle
This principle of a good programmer means that already implemented logic functions should remain so, and there is no point in rewriting them. At the same time, new requirements or elements can be added to existing classes and methods, and these same classes and methods can be extended rather than changed. So they are "open" to extensions but "closed" to modifications.
If someone makes major changes to the code during a major release, all changes need to be merged. This takes a lot of time and can lead to bugs. Not to mention that those who rewrote the code will make their own mistakes. Following the open/closed principle protects your software from such problems because simple extensions cannot break any existing code.
Composition Instead Of Inheritance Principle
This means that the behavior of programs must be prescribed, and not taken from outside. Why? Because as it grows, the inheritance tree becomes more and more confusing, and each of its "branches" gets its own set of behaviors. And trying to move a behavior pattern from one "branch" to another can be difficult.
Behavior written from scratch is easier to handle and maintain. In this way, it is also possible to produce an infinite number of behaviors. And from each combination, you can get your own class.
One example would be enemies in a video game. At the first level, they can only hit. At the next level, hit and kick, or hit and spit, but don't kick. Or spit poison and kick, but not with a fist. Now imagine that this set of skills grows for each level. Try drawing an inheritance tree, and you'll soon see that building from scratch is much more convenient.
Principle Of Single Responsibility
This principle of a good programmer says that each class should only care about providing one bit.
If you stick to the KISS principle or your project does not have a lot of features, then most classes simply and error-free does one type of work at a time. But as code requirements grow and deadlines approach, most classes start doing multiple things and break this principle.
To stick to the fifth rule, ask yourself where and when each feature changes. If the answer is "in more than one place and for more than one reason," you are violating this principle.
Principle of Tasks Separation
This principle is similar to the previous one, but it should be considered at a higher level of abstraction. The program consists of several parts that are, at best unrelated to each other.
A well-known example is MVVM-Pattern (Model, View, and ViewModel), where the application is divided into three non-overlapping parts. The data model contains the raw data and executes some algorithms. The view model contains aggregated data that should be displayed but does not know how to display it. A View displays this data in the best possible way. In addition, only it has the ability to interact with the user and respond to it directly.
So the ViewModel and the Model don't know which button or area the user clicked on. The View handles human actions and delivers data to the ViewModel, which runs its own algorithms without interacting with the user. This makes it possible to create modular codes and develop each part in parallel.
YAGNI Principle
The principle of a good programmer, "you aren't gonna need it," wants you not to write features that may not be needed in the future. Because chances are you won't need them at all. But complicate the code.
You can think of it as strict adherence to the principles of DRY and KISS. Mostly it is violated by inexperienced developers. They write very abstract code and end up with something bloated and unusable.
By the way...
Do you know how to conduct incident response planning?
In this article, we will tell you what would happen with your system if you're not responding to incidents properly and how to plan this activity correctly
Let's seeReasons to Write Clean Code
There are many reasons to write clean code, but here are five of the most important ones.
1. Easier to read and understand
Clean code is easy to read and understand, which makes it easier for other people to work with your code. This is especially important when working on a team or when you need to come back to your own code after a long period of time.
2. Fewer errors and bugs
Code that is well-organized and easy to understand is also less prone to errors and bugs. This means that you'll spend less time debugging and more time adding new features.
3. Higher productivity
When you write clean code, you'll be able to work faster and more efficiently. This is because you'll be able to focus on the task at hand rather than getting bogged down in a mess of confusing and poorly-written code.
4. Easier to maintain
Code that is easy to read and understand is also easier to maintain over time. This means that you'll be able to make changes and updates to your code more quickly and with fewer issues.
5. Improved collaboration
When your code is clean and well-organized, it's easier for other people to work with you on a project. This can improve collaboration and lead to better results overall.
Should you write clean code?
It’s definitely worth doing it! But it is not always worth paying too much attention to cleanliness.
Do not forget about the feasibility and lifetime of your code. For example, if you are faced with the task of developing a concept - PoC (Proof of Concept), and you prove that the selected technology stack fulfills the task, your code will become irrelevant in a week or two. You shouldn't waste your energy on improving existing functionality.
There is an opinion that there is no need to monitor the quality of the code or part of the system that will soon be replaced. And this is not true for several reasons. High-quality workmanship will make the transition or integration with new parts easier, smoother, and faster. It will surely make life easier in cases where several versions of the code have to be supported at the same time. The number of regression errors with clean code will be several times less. Also, do not forget that nothing is more permanent than a temporary solution. Perhaps the tasks for improving this part of the code will lie in the backlog for several months.
What can help improve your code?
Most programmers dream of writing code quickly and as beautifully as possible so that everything works perfectly right away. But not everyone succeeds in making the code not only workable but also understandable. How do you succeed in writing clean code? There are two ways — self-organization and teamwork.
Self-organization
Let's look at several possible ways to improve individual code quality. These guidelines are suitable for any developer level.
Instruments
Pay attention to the tools used, especially the main one - the development environment. A convenient tool is half the battle. The IDE (Integrated Development Environment) not only complements the code on the fly but also suggests smelling places to tidy up. By extending the IDE with the necessary plugins, you get a Swiss army knife. You do most of the work in this tool — make it as convenient and flexible as possible.
Don't miss out on the opportunity to purchase your favorite product. Fighting licenses will save you some time, and official support will never be superfluous.
Participation in open-source projects
In teamwork, the ability to work with someone else's code is important. Understanding, reading, and adhering to a style is not always easy. Working with someone else's code, it is often possible to learn about new approaches to solving non-trivial problems. Read and study the code!
Strict framework
Start developing a small project that solves a specific problem. Design the architecture yourself and implement it. In doing so, you can set yourself technical limits, for example, development using only OOP, the cyclomatic complexity of methods not more than 10, compliance with all development recommendations in a given programming language, conscious use of design patterns, etc.
Get into the habit of working within limits. After all, there will not always be someone nearby who can monitor the quality of your work.
Development of abstract thinking
Read and use programming patterns. They are not tied to a specific language and help to solve problems more efficiently. You will have the same understanding of problem-solving design as other developers. You will have a better understanding of how third-party tools and libraries work.
Solve programming puzzles. This is a great way to improve your programming skills and learn the intricacies of your chosen language.
Decision analysis
Do not rush to solve problems head-on. Ask senior developers questions, and ask yourself questions too. It is always important to understand the causal relationship of certain decisions. By understanding the problem well, you can effectively solve it.
A good developer is not an artisan who writes code but an engineer who combines applied research, planning, and design in his work.
Reading the documentation
Being able to read the documentation is just as important as reading the code. The next step is to learn how to write documentation.
Practice!
Any experience is better than no experience.
Thank you for Subscription!
Teamwork
Most of the tasks are solved in a team. It is very important to share responsibility for quality among its participants. The larger the team, the more difficult it is to keep the product in good shape. Let's take a look at several approaches to retaining code in the conditions mentioned above.
1. Code review
This is a simple principle to understand and follow. At least two people, including the author of the code, review the code.
There are a few things to keep in mind when reviewing your code:
One is to check if the code violates the rules of the coding convention. This is a process that can and should be automated using static analyzers in CI (Continuous Integration).
Others are maintainable code and error handling that cannot be checked automatically.
Last but not least, the code should be checked for completeness. Does this code snippet contain the entire scope of the function as intended?
2. Continuous integration
The essence of continuous integration is that it allows you to get a lot of feedback on the current state of your code quickly.
Continuous integration works when you follow two simple rules:
Product assembly is fast. Avoid slow builds. Continuous Integration improves the quality of your code by providing quick feedback. If the tests fail, the build will fail, and you will be instantly notified.
You add static analyzers to your build script that check coding conventions, improve code quality, and check security.
3. Coding Conventions
It is important to have a list of coding conventions. But before you start making a list, everyone on the team must understand the significance of this agreement. Do not expect such an agreement to be adopted the first time, there will be a lot of discussions.
Make a list of coding conventions in which you define how variables should be declared, naming conventions, and so on. The number of rules you can add to this list is unlimited and may vary. Just do what works for you and your team. Feel free to add new rules to the agreement list if the team is happy with it. The same goes for removing agreements from the list.
Once you've got your list of coding conventions, it's imperative to stick to them. The preferred way is to check coding conventions with static analyzers and continuous integration since it doesn't require any manual steps.
Good code can speed up long-term software development. It is reusable, and developers don't need to waste time fixing bugs. It also makes it easier for new people to join the project.
4. Tests
The fewer errors in the code, the higher quality. Rigorous testing filters out critical errors and ensures that the code works as intended.
Having a clear testing strategy is important when it comes to improving code quality. At a minimum, your code should be modular. It's even better if you want to use other methods, such as integration or regression testing.
The largest number of tests in a working software project should be unit tests. They are cheap and fast. There are many different tools that can help you create unit tests and code coverage reports. Running a test suite and generating a code coverage report can be done automatically through continuous integration. It is even possible to make a build fail if the code coverage does not meet the required percentage.
5. Error analysis
Having bugs in your code is probably inevitable. Therefore, analyzing and handling these errors is very important. If you want to improve your skills, it's important to learn from your mistakes.
When an error occurs, analyze it with a few questions:
Is this a low or high-priority bug? If it is a high priority, it should be corrected immediately. If the bug is minor and allows the product to complete the task without major problems, the bug may be fixed in future iterations.
Did something go wrong?
Why haven't we checked this (right)?
Where else does this happen?
And most importantly, how can we prevent this from happening in the future?
Of course, there are tools to help you track errors. You can choose the tracker available on the market that suits your needs.
6. Collecting metrics
There are several metrics you can use to quantify the quality of your code. SonarQube easily copes with this task. It will easily help you collect all the important metrics you need:
Potential errors
The number of defects and their severity is important indicators of overall quality. Finding errors can and should be automated, but only partially. The code review remains valid to identify deeper errors in the code's logic itself.
Repetitions of code sections
Each piece of knowledge should have a single, consistent and authoritative representation within the system—- the DRY principle (Don't repeat yourself).
Complexity metrics
Complexity is often measured using the cyclomatic complexity metric. This is an indicator of the number of linearly independent paths in the program code. There is a correlation between the number of cyclomatic complexity and the defect rate. In theory, simplifying your code should lead to fewer defects.
Availability of required comments
Just a few well-spaced lines of comments, a comment to a module, class, or method, will be enough to make the code much clearer.
- Test coverage
Used when testing software. It shows the percentage of the program's source code that was executed during testing. Set the standard below which the percentage of your tests does not fall.
Do you know?
Fiddler Web Debugger: how to become a pro
Become an expert in using Fiddler with our tips based on real experience. Only essntial information from Geniusee specialists
Show meConclusion
Errors in code are akin to a carbon footprint. It is absolutely impossible to avoid, and the extra exhaust by itself will not kill either humanity or the surrounding nature. Nevertheless, it seems like a natural necessity today to reduce the negative effect of your stay on the planet. In much the same way, clean coding is the responsibility of every developer. Regardless of which path you choose, you should strive to write code that works and is understandable.
It's good if we manage not to turn purity into a fetish, given the lifespan of our code and the feasibility of further improvements. The main thing to remember is people: users who can be let down by a sudden failure of even a small part of the system we have developed, and the engineers who have to support this system.