Don't agree to develop what you don't understand.

Don't agree to develop what you don't understand.

Since the beginning of 2018, I have been in the position of lead/manager/senior developer in the team — call it what you want, but the essence is that I am fully responsible for one of the modules and for all the developers working on it. This position opens up a new perspective on the development process for me, as I am involved in a larger number of projects and participate more actively in decision-making. Recently, due to these two circumstances, I unexpectedly realized how much understanding affects the code and the application.

The thought I want to express boils down to the fact that the quality of the code (and the final product) is closely linked to how aware the people who are designing and writing the code are of what exactly they are doing.

You might be thinking right now: 'Thanks, Captain Obvious. Of course, it’s good to understand what you’re writing. Otherwise, you might as well hire a group of monkeys to hammer randomly on the keys and call it a day.' And you are absolutely right. Therefore, I take it as given: you understand that having a general idea of what you're doing is necessary. This can be called a zero level of understanding, and we won't analyze that in detail. We will go into detail about what exactly needs to be understood and how it affects the decisions you make every day. If I had known these things in advance, it would have saved me a lot of wasted time and questionable code.

Although you won't see a single line of code below, I still believe that everything said here is of great importance for writing quality, expressive code.

First level of understanding: Why isn't it working?

Developers usually reach this level at the very early stages of their careers, sometimes even without any help from those around them — at least according to my observations. Imagine you received a bug report: some function in the application isn't working; it needs to be fixed. How will you proceed?

The standard procedure looks like this:

  1. Find the piece of code that is causing the problem (how to do this is a separate topic; I cover it in my book about legacy code).
  2. Make changes to that piece of code.
  3. Ensure that the bug is fixed and no regression errors have occurred.

Now let's focus on the second point — making changes to the code. There are two approaches to this process. The first: delve into what exactly is happening in the current code, identify the error, and fix it. The second: proceed blindly — for instance, add +1 in a conditional statement or loop, see if this function works in the needed scenario, then try something else, and so on indefinitely.

The correct approach is the first one. As Steve McConnell explains in his book Code Complete (which I highly recommend), every time we change something in the code, we should be able to confidently predict how it will affect the application. I quote from memory, but if the bug fix doesn't work as you expected, it should raise serious concerns; you should question your entire plan of action.

To summarize, in order to perform a solid bug fix that won’t degrade code quality, one needs to understand both the entire structure of the code and the source of the specific problem.

The second level of understanding: Why does it work?

This level is grasped much less intuitively than the previous one. As a novice developer, I learned it from my boss and later explained the essence of it to newcomers on multiple occasions.

This time, let's imagine that you've received two bug reports at once: the first concerns scenario A, and the second relates to scenario B. Something is wrong in both scenarios. Accordingly, you tackle the first bug first. Guided by the principles we've established for the first level of understanding, you thoroughly investigate the code relevant to the issue, figure out why it causes the application to behave this way in scenario A, and make reasonable adjustments that yield the result you expected. Everything is going great.

Then you move on to scenario B. You repeat the scenario in an attempt to provoke the error, but — surprise! — everything works as it should now. To confirm your hypothesis, you undo the changes made while working on bug A, and bug B reappears. Your bug fix resolved both problems. Lucky!

You didn't anticipate this at all. You came up with a way to fix the bug in Scenario A and have no idea why it worked for Scenario B. At this point, the temptation is strong to conclude that both tasks have been successfully completed. It makes sense: the goal was to eliminate the errors, wasn’t it? But the work isn’t finished yet; you still need to understand why your actions fixed the bug in Scenario B. Why? Because it might be operating on incorrect principles, and then you'll need to look for another solution. Here are a couple of examples of such cases:

  • since the solution wasn’t tailored specifically for Bug B considering all factors, you may inadvertently have broken Function C.
  • it’s possible that there’s a third bug lurking somewhere related to that same function, and your bug fix binds the correct operation of the system in Scenario B to it. Everything seems fine now, but one day this third bug will be noticed and fixed. Then an error will arise in Scenario B again, and hopefully, it won’t be only there.

All this brings chaos into the code and will someday come back to haunt you—most likely at the most inconvenient moment. You'll have to gather your willpower to force yourself to 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 related to this level, and it’s probably the one that would have given me the most advantages if I had come to this realization sooner.

To make it clearer, let's consider an example: you need to make your module compatible with Function X. You are not particularly familiar with Function X, but you were told that to be compatible with it, you need to use Framework F. Other modules that integrate with X work precisely with it.

Your code has never interacted with framework F since its very inception, so implementing it won't be straightforward. This will have serious implications for some components of the module. Nevertheless, you dive headfirst into development: spending weeks writing code, testing, rolling out pilot versions, receiving feedback, fixing regression errors, encountering unforeseen complications, running behind originally agreed deadlines, writing more code, testing, receiving feedback, and correcting regression errors—all for the purpose of integrating framework F.

And at some point, you suddenly realize—or maybe you hear from someone—that framework F may not provide you with compatibility with function X after all. Perhaps all that time and effort has been completely misallocated.

Something similar happened once while working on a project for which I was responsible. Why did this occur? Because I did not fully understand the essence of function X and how it related to framework F. What should I have done? I should have asked the person assigning the development task to clearly explain how the planned actions would lead to the desired outcome, instead of just repeating what had been done for other modules or taking for granted that this was necessary for function X to work.

The experience from this project taught me to refuse to start the development process until we have a clear understanding of why we are being asked to perform certain actions. To refuse outright. When you receive a task, the first impulse is to jump right into it to avoid wasting time. However, the policy of 'freezing the project until we understand all the details' can save a significant amount of wasted time.

Even if you are being pressured to start working when you don’t understand the rationale—resist. First, figure out the purpose of the assigned task and determine whether it is the right path to the goal. I had to learn all this the hard way—hopefully, my experience will make life easier for those reading this.

Fourth level of understanding: ???

Programming always offers something to learn, and I believe I've only scratched the surface of understanding. What other levels of comprehension have you discovered over the years working with code? What decisions have you made that positively impacted the quality of your code and applications? Which choices turned out to be mistakes and taught you valuable lessons? Share your experiences in the comments.

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers šŸ”„ Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster