How to write maintainable code?
I've been writing code for over 10 years now, and although I've been doing more management lately, at my peak I was able to write 500+ lines of well-performing code a day. Here are the principles that helped me with this:
- Don't over-generalize. If you can't create a universal solution with a little blood, then it doesn't matter, solve a specific current problem and move on. A generalization, even a good one, remains unused in 70% of cases.
- Don't optimize your code in advance. The idea of making code more complex in order to speed it up is almost always wrong. An exception is possible only when this particular section of the code "slows down" so that it is already noticeable at the product or business level. Of course, there is no need to "pessimize" the code, of the two versions, which are the same in complexity and code size, choose the faster one. This has an important consequence: you cannot duplicate data and you cannot cache the results of calculations where performance does not demand it at all. More than half of the structural bugs arise due to the fact that the cache and real data have "parted", and it is usually hellishly difficult to debug this, because at the moment of the actual "driving" no bug is still visible, it will appear later when you set a breakpoint- It’s too late to go through the steps.
- Name and group everything that happens correctly. Code that is free of algorithmic or technological complexity should read like text written in English. It's good when the code in which the ninja is sneaking somewhere looks something like ninja.sneak (...), and not pDst2.trySetCoord (...) and ten more lines after that, none of which can be forgotten ... If a function changes something in the state of the object, it cannot be called isSomething - if you do this, the next code with its participation is doomed to an interesting debug. If a function is hard to compute, it cannot be called getSomething - someone will probably start calling it in a loop and wonder why things are slowing down. The class that stores the state of the document can be called DocumentState or Document, but not SDManager. By the way, about the Manager. If the only name you can choose for a class or method is very vague, this is a sure sign that you are doing something wrong. The BaseObject and World classes or the databaseOps and initService functions will quickly lead to all sorts of problems and bugs that violate this and the previous point.
- Don't mix algorithms and other technologically complex pieces of code with business logic. The expressiveness of modern programming languages is quite enough so that, say, the graphics engine of a computer game does not know anything about ninjas and helicopters, the database functions in a CRM system do not know the words "account" and "client", etc. etc. Business logic is characterized by constant change, fuzziness and confusion. As soon as entities from different levels of abstraction begin to be mentioned in adjacent lines of code, all this immediately begins to penetrate into the technologically complex code, and everything explodes.
- Don't use any advanced features of any language. In C ++, for example, you should not use template magic, operator redefinition, multiple inheritance, etc. etc. Exotic programming languages (Haskell, Lisp dialects, cunning declarative languages running on top of the JVM) should generally be used only as a hobby, as a source of inspiration. Not directly in the work you are paid for. This point of view is often controversial. Unfortunately, it will not be possible to substantiate it in detail in the format of the answer on the Experts. Therefore, I will simply refer to my almost 20 years of experience in industrial programming. In all areas and organizations in which I managed to work, whether in Yandex, in game development, or in science, the idea of using "the beautiful flight of free thought, inaccessible to ordinary minds" as a working tool turned out to be destructive. Often for the entire project, but always, without exception, for the author of the idea.
- It is worth throwing all OOP out of your head. The only useful thing that came to imperative languages from this ideology is the private modifiers. Class hierarchies are evil, you need to forbid yourself to inherit implementations. Interfaces can be inherited, and there are not too many levels. Aggregation is almost always better than inheritance. Most of the classic "design patterns" are either outdated or supported at the language level.
- Use as many asserts, logs and other methods to catch unplanned system state as early as possible. Very often, at the moment when the incorrect behavior of the system becomes noticeable to the user, it is already difficult to debug it. If you were able to catch the system at the very moment when its internal state first becomes inconsistent or it begins to behave differently from what you intended, most often it becomes trivial to figure out why.
- Every extra line of code is evil. Wherever possible, you should not use someone else's code that you have not read and understood