📜 ⬆️ ⬇️

Practice good code.

image Over the years of presence at Habré, I read quite a few articles on how the ideal code should look. And less article about how to specifically achieve this ideal. It is also worth noting that a very large part of all these materials was a translation of Western sources, which is probably the result of a more mature IT industry "abroad", with all the attendant issues and problems.

Unfortunately, in many cases, the authors either climb into unattainable high-layered architectures, which is required at best for 1% of projects, or are limited to common phrases like “the code should be clear” or “use OOP and patterns”, without going down to detailed explanations, in which, for example, the “clarity” of the code is measured.

In this article I want to try to systematize the criteria for assessing the quality of the code, and those writing practices that I managed to use in real projects almost regardless of their size and specificity, the programming language used, and other factors.

1. Simplicity

Here, everything has already been invented before us - there is a wonderful KISS principle, as well as the aphorism of Albert Einstein “Everything should be stated as simply as possible, but not simpler”, which we should all be guided by.
')
Simple code is better than complex in all respects - easier to understand, easier to change and test, requires less comments and documentation, contains fewer errors (at least statistically, if we accept the average number of errors in relation to the number of language and logic constructions in the code per some constant value for each specific programmer).

Also, paraphrasing the position of TRIZ about the ideal system, we can say that the ideal code - the one that does not exist, and the task that it must solve - is successfully solved. Obviously, this ideal (like any other) is unattainable in practice, but it is able to direct the mind of the developer in the right direction.

In practice, all these beautiful phrases mean one thing - do not use tools, the real (and not hypothetical) use of which does not exceed the damage from the complication and entanglement of the code. Do not build the application around the whole framework for one narrow task, which it can help solve. Do not use complex patterns where you can get by with a simple class. Do not use the class for a couple of functions that do not even work with the properties of the native object in your code (otherwise use). Do not wrap a 3-line code into a function that is not used again in the application (and will not be used in the near future, and not after 150 years).

Of course, all this does not mean that you need to write spaghetti code in the imperative style of 5000 lines. Once such a desire arises, you need to re-read and realize the second part of the quotation above, that the code should be " as simple as possible, but not simpler ."

Do not be afraid of simple designs, if they allow you to solve the problem. Also, do not hide behind reflections on how your code can theoretically be used when developing a project or changing the wishes of a customer in a year or two. The point is not even that your code will be “in any case thrown out and rewritten from scratch,” as many advocates of simple and fast development say. It may not be. But the problem is that with a high degree of probability you simply cannot guess the right direction. You will design the hammer so that they can (after a small modification with a file) tighten the screws, and it turns out that you need to fasten the automatic nailing and laser guidance during the swing. And your architecture, designed for development in a certain direction, will only lose compared to “just a hammer”. Believe me, I know what I'm talking about, because For 5 years, I have been supporting the project in runet, which grew and became quite successful not at all on where the maximum efforts were invested and for what it was created, but at the expense of a small branch of the "side" functionality.

2. Conceptuality

This criterion largely overlaps with the “simplicity” of the code, but still I decided to put it in a separate section. The essence of the approach is to use concepts, and ideally, generally accepted concepts that are already widely used in other solutions.

Let me explain in a simple analogy, what I mean:
Imagine that you have a task - to memorize the results of multiplying all numbers from 2 to 9 by each other. You can simply write to the line: 2x2 = 4, 2x3 = 6, ..., 3x7 = 21, ..., etc., and then proceed to memorize this text.
Formally, it can be used, and it can even be learned. But there is a much more practical option - to use the concept of "table" - i.e. data structures defining lists of X and Y values, with the ability to quickly determine their points of intersection and the value of these points by a predetermined formula (in this case Z = X * Y). As a result, we get the multiplication table known to everyone since childhood, which, in addition to the banal saving of the number of characters in the text, has several other advantages:

Please note that we don’t have to make any additional efforts for the table to “work” in this way, and also don’t need to apply the “instructions for using tables” to our document - this is “free of charge” and this is the main strength of using concepts in programming. Decisions in which concepts are correctly used, we often call elegant .

An example of a brilliant concept is “redirection” in UNIX-based systems , which allows you to create different programs without spending a single line of code on how they will work with reading / writing to files or other streams, or interacting with other programs, such as a text filter. grep. All you need is to provide a standard text input / output interface.
Thus, even a few dozen of the most popular teams give rise to thousands of options for their use. Reminds something? That's right - this is a solution to the problem with the help of "a code that does not exist."

A simpler practical example - once I needed to rotate ads from a third-party ad network from two different accounts on the same ad space from a site, with a condition of approximately equal distribution of profits between them (with a tolerance of 1-2%). A cumbersome solution that could be applied "in the forehead" was to record ad impressions for each account in the database, taking into account the cost of these impressions (which could also differ), and based on this data, each time you open the page deciding whose ad to show to ensure minimal imbalance.

A little later, it turned out that, according to the law of large numbers, a banal probability check on the principle of "heads-and-tails" when choosing an account completely solves the problem within the framework of specified conditions. Moreover, it does not make any problems in such a way to distribute impressions between 3 or more accounts. Of course, this is not a concept invented by man, but a basic mathematical principle, but from the point of view of code, only one thing is important - it works.

3. Uniqueness of functionality (Don't repeat yourself)

The principle “Don't repeat yourself” (abbr. DRY) tells us that each functional unit of the system, be it a logical block of code, a function or a whole class, must be represented in the code only once.

Repeating blocks of code are usually made into a function. Functions use plug-in libraries and namespaces. When it comes to classes, a whole zoo of techniques is introduced to reduce code duplication, ranging from banal inheritance to complex systems from “factories producing factories” mixed with “front adapters”, which often require more code and mental effort. programmer than allow to save.

The desire to design the code from the very beginning so as to avoid any repetitions is obviously destructive, since for 80% of situations, you will not know about code duplication until you write it.

Real advice for practical use in any project - solve the problem of duplicating code immediately after its detection (but not earlier!), And make it the easiest and most obvious way available to you in the current situation.

There are several very similar blocks of code of 5-10 lines that differ only in a couple of conditions and variables, but you are not sure yet how best to wrap them in a function? Use cycles.

Not sure which class / object to put the code of the new method in? Simply create a function and put it in the link library until the time when you have more complete information about the system being developed.

Use class inheritance, but you have to completely override the 50-line method in the heir for the sake of changing one logical construct? First of all, think about splitting a large method into several more isolated ones, or about using instances of other classes with all their methods as values ​​of the properties of the original object.

Simple and proven solutions will solve 95% of all problems with code duplication, and for the remaining 5%, you need to think 10 times about the appropriateness of mechanically applying complex “textbook” constructions compared to meaningful redesigning a “bad” code segment (with an emphasis on reducing complexity and increase readability).

You can always replace a simple solution with a higher level, and it will be much easier to do this than to apply complex refactoring to the raw code at a later stage, when duplicates are already scattered throughout the system.

4. Readability


Of course, in itself, a good design code does not guarantee the quality of the program and the ease of its perception. But it is precisely such attention to detail that distinguishes a real professional from a simple artisan working in the “maybe fit” style.

What can I say in conclusion

The article turned out to be somewhat more extensive than planned, although I consciously avoided discussing many aspects, such as connectivity and connectivity , design patterns, and other concepts about which entire books have already been written, and you can write a lot of new ones.

My goal was to set out in detail the very basics of “correct” (from my point of view) programming, many of which are worthy of the pen of Captain Obvious, and nevertheless regularly ignored by most developers, especially in the web segment. Although adherence to only these simple principles, without deepening into the wilds of perfectionism, can make their code better at times, in all respects.

If I missed something or got it wrong somewhere, constructive criticism in the comments is highly appreciated.

Source: https://habr.com/ru/post/206868/


All Articles