Always be able to find out where you live.
Damian Conway, co-designer of Perl 6
A good program code is determined by at least three signs: unambiguity, efficiency, and maintainability.
Unambiguity is primarily a coding style. Uniqueness is determined by what names of variables and functions the programmer chooses, how it formats the code, how it processes errors and forms the structure of the code.
Effective code is a code consisting of efficient algorithms. Effective does not mean fragile, complex or difficult to follow. The effectiveness of the code is achieved by using the strengths of the language and at the same time avoiding its weaknesses.
')
Accompanied by the fact that the code is written primarily for those who will accompany him. Maintainability - ease of use of written code, minimizing the possibility of errors when it changes.
Code uniqueness
All programmers are lazy, sometimes in a good way, sometimes in the ordinary. And few people give each line of code enough time to put it in order in accordance with the criteria of a good code. Especially in the cases of "the sooner I put this check in the function, the sooner I will go home." Hence the variables, called one symbol, and several expressions in one line, and many other tricks, which only the programming language used is capable of.
Meanwhile, all existing code improvement techniques (and this article too) are aimed at making life easier for the programmers themselves. For example, in order for your code to be understandable to other programmers (and to you yourself N months after writing it), you need to follow the coding style. Of the suggested styles for the language used, it is better to choose one that recommends formatting code in the style of K & R (by the initials Kernighan and Ritchie, authors of the book on the C language), because it differs from the rest by better readability and makes the code more flexible.
Do not hide your incompetence by turning off warnings. Language features that allow you to silence error or warning messages are the weak side of the language. This practice can hide from you potential errors in your algorithm, which will make your code very fragile. When refactoring, such an algorithm will almost certainly be broken.
For function names, variables, and constants, choose such names so that their purpose is obvious without the accompanying comment (which does not eliminate the need to write comments). Of abbreviations and abbreviations, it is allowed to use only generally accepted and well-known: min, max, avg, sum, len, ctrl, src, msg and others. If you cannot pick up an abbreviated form of the variable name, leave it as it is.
Use named constants, avoid bare numbers and string literals in code. Even if your algorithm understands the purpose of each number used in the code in its pure form, the adjustment of the algorithm can break your code when you need to change one number to another. When using named constants, it will be enough to change the number in one place without touching the code of the algorithm itself. I would not recommend making exceptions even for well-known values, such as 3.14, 2.7, 9.8, 42.
Code Efficiency
Many programmers (not necessarily working in their specialty) remain at the level of students for a very long time, who brag to each other that they have reduced the function of calculating factorial to a single expression. Continuing to use a bitwise shift instead of divide and multiply is not a good idea either. These are the very cases of premature optimization that have been written and spoken about so much.
Before optimizing any part of the algorithm, you need to be sure that it needs to be optimized. It is possible to change the postfix increment for the prefix and win a couple of nanoseconds of CPU time in dozens of places for a long time and persistently, but it is better to optimize database queries and get performance gains visible even to the naked eye. In order to find the weak link of the algorithm, you need to profile and measure the execution time of the code at each stage of the algorithm.
Before you reinvent the wheel, you need to make sure that this has not been done to you. And only if there are no bicycles, or they are not suitable for you for any reason (except for the reason “it is not written by me”), you can start implementation. Using third-party libraries allows you to focus on your own algorithm. The more popular a library is, the more efficient the code it uses and the less likely it is to detect errors in it. If the library interface is bulky or too unified, write a wrapper interface for this library so that your code is in the same style.
If the algorithm often uses the results of long calculations, it makes sense to consider the possibility of caching - saving these results to prevent repeated calculations in case the result of these calculations does not change.
Code maintainability
When making changes to the code, there is always a chance to break it, so of all the alternative variants of the algorithm, always use the most flexible one. For example, instead of several else-if or switch, it is much more convenient to use a key search in a hash table. In such cases, to add another value to the list of alternatives, it is enough to add another value to the hash, without touching the search algorithm itself.
If you are writing a library of functions, first design its interface and write code that will use functions from your library that has not yet been written. Convenient to use? If not, this is a reason to revise the interface, and since the functionality itself has not yet been written, it will not have to be refactored.
Many in their practice constantly deal with inherited code. And sometimes, when it is necessary to use a function written by someone for a long time, and if the interface of this function is designed sloppyly, only its name pops up in memory, since the set of its parameters is not remembered. In such cases, you have to look for a place in the code where this function is already used, and copy its call from there to your code. If you don’t devote enough time and effort to designing an interface, the same can happen to your own functions when it comes time for someone else to accompany your code.
Regardless of the complexity of the code, comments must be present necessarily. At a minimum - in the header of each function / method: what it does, what parameters it takes, what it returns. Without comments, you can leave only the most obvious parts of the algorithm, it makes no sense to comment on each expression. The easiest way to determine if an algorithm requires detailed comments is to estimate how many seconds it takes to understand it. If more than 5, comments are required.