
Since the beginning of 2018, I have held the position of lead / chief / lead developer in a team - call it what you will, but the point is that I am entirely responsible for one of the modules and for all the developers who work on it. This position gives me a new look at the development process, as I am involved in more projects and more actively participate in decision making. Recently, due to these two circumstances, I suddenly realized how much the measure of understanding affects the code and the application.
The idea that I want to express is that the quality of the code (and the final product) is closely related to the extent to which people who are engaged in designing and writing code are aware of exactly what they are doing.
')
You might be thinking now: “Thank you, cap. Of course, it would be nice to understand what you write. Otherwise, with the same success you can hire a group of monkeys, so that they threshed on arbitrary keys, and calm down on this. " And you are absolutely right. Accordingly, I take for granted: you realize that it is necessary to have a general idea of ​​what you are doing. This can be called a zero level of understanding, and we will not analyze it in detail. We will analyze in detail what needs to be understood and how it affects decisions that you make every day. If I knew these things in advance, it would save me a lot of wasted time and questionable code.
Although below you will not see a single line of code, I still think that everything said here is of great importance for writing high-quality, expressive code.
The first level of understanding: Why is it not working?
Developers usually come to this level at the earliest stages of their careers, sometimes even without any help from others — at least according to my observations. Imagine that you got a bug report: some function in the application does not work, it needs to be fixed. How will you act?
The standard layout looks like this:
- Find the code snippet that causes the problem (how this is done is a separate topic, I will reveal it in my book about obsolete code)
- Make changes to this fragment
- Make sure that the bug is fixed and no regressive errors have occurred.
Now we will focus on the second paragraph - making changes to the code. There are two approaches to this process. The first is to understand what exactly is happening in the current code, identify the error and correct it. The second: to advance by touch - add, say, a +1 to a conditional statement or a cycle, to see if this function has worked in the right script, then try something else and so on to infinity.
The first approach is correct. As Steve McConnell explains in his Code Complete book (by the way, I highly recommend it), every time we change something in the code, we should be able to predict with certainty how this will affect the application. I quote from memory, but if the bugfix doesn’t work the way you expected, you should be alerted by this, you have to question your entire plan of action.
In summary, in order to perform a good bugfix that does not degrade the quality of the code, you need to understand the whole code structure and the source of the specific problem.
The second level of understanding: Why does it work?
This level is comprehended much less intuitively than the previous one. I, being still a novice developer, learned it thanks to my boss, and later I myself repeatedly explained the essence of the matter to beginners.
This time, let's imagine that you received two bug reports at once: the first one is about scenario A, the second is about scenario B. In both scenarios, something is not right. Accordingly, you are taken first for the first bug. Guided by the principles that we derived for the first level of understanding, you get into the code that is relevant to the problem, find out why it causes the application to behave exactly like this in scenario A, and make reasonable adjustments that give exactly the result you were expecting. Everything is going great.
Then you go to scenario B. You repeat the script in an attempt to provoke a mistake, but - surprise! - now everything works as it should. To confirm your guess, you undo the changes made while working on error A, and bug B comes back again. Your bugfix solved both problems. Lucky!
You did not count on it at all. You came up with a way to correct the error in scenario A and you have no idea why it worked for scenario B. At this stage, there is a great temptation to decide that both tasks are successfully completed. This is quite logical: the point was to eliminate the errors, didn't it? But the work is not yet finished: you still have to figure out why your actions corrected the error in scenario B. Why? Then, that he, perhaps, works on the wrong principles, and then you will need to look for another way out. Here are a couple of examples of such cases:
- since the solution was not chosen precisely for error B, taking into account all factors, you may have unknowingly broke the function of C.
- it is possible that somewhere there is also a third bug, associated with the same function, and your bugfix ties up the correct operation of the system in script B on it. Now everything looks good, but one day this third bug will be noticed and fixed. Then in script B an error will occur again, and well, if only there.
All this introduces randomness in the code, and someday will fall on your head - most likely, at the most inappropriate moment. We'll have to gather our will into a fist to make ourselves spend time understanding why everything seems to work, but it's worth it.
The third level of understanding: Why does it work?
My recent insight is connected with this level, and, probably, it would give me the most advantages if I came to this idea earlier.
To make it clearer, let's take an example: your module needs to be made compatible with the X function. You are not particularly familiar with the X function, but you have been told that for compatibility it is necessary to use the F framework. Other modules that integrate with X work exactly with him.
Since the first day of your life, your code has never come into contact with the F framework, so it will not be so easy to implement it. This will have serious consequences for some components of the module. Nevertheless, you are heading into development: for weeks you have been writing code, testing, rolling out pilot versions, getting feedback, correcting regression errors, discovering unforeseen complications, do not keep within the initially agreed time, write some more code, test, get feedback connection, correct regression errors - all this in order to implement the framework F.
And at some point you suddenly realize - or, maybe, hear from someone - that maybe the F framework will not give you compatibility with the X function at all. Maybe all this time and strength was not applied at all. to that.
Something similar happened once during the work on a project for which I was responsible. Why did this happen? Because I didn’t understand well what the essence of function X was and how it relates to the framework F. How should I do? Ask the person who sets the development task to clearly explain how the intended action plan leads to the desired outcome, instead of simply repeating what was done for other modules, or believing the word that is needed for the X function.
The experience of this project has taught me to refuse to begin the development process, until we have a clear understanding of why we are asked to perform certain actions. Refuse direct text. When you get a task, the first impulse is to take it immediately, so as not to waste time. But the policy of “freezing the project until we enter into all particulars” can reduce wasted time by entire orders.
Even if they are trying to put pressure on you, force you to start work, although you don’t understand how it is justified - resist. First, figure out the purpose for which such a task is set for you, and decide whether this is the right path to the goal. I had to learn all this in bitter experience - I hope those who read it will make my example easier for life.
The fourth level of understanding: ???
There is always something to learn in programming, and I suppose I just touched the very top layers of the topic of understanding. What other levels of understanding have you discovered over the years with the code? What made decisions that well affected the quality of the code and application? What decisions were wrong and taught you a valuable lesson? Share your experiences in the comments.