debugging

Why is it important to forbid self-debugging by hand?

When you debug a program, you yourself, without realizing it, think that for one debugging session, fix all the problems that have arisen in the framework of this task. But our shortsightedness does not want to believe that in fact there is not one problem, but several. And for one debug session it will not be possible to solve all these problems.

Therefore, you will need to run this code several times in debug mode, spending hours debugging over the same piece of code. And it’s only you alone spent so much time on this part of the program. Each team member who is “lucky” to work with this code will have to live the same story that you lived.

I’m not talking about the fact that people in teams change, teams change and so on. The man-hours go by the same thing. Stop doing it. I’m serious. Take responsibility for other people on yourself. Help them not to experience the same part of your life.

Problem: It is difficult to debug a composite code

Possible algorithm for solving the problem:

  1. Split into separate parts
  2. We throw out debugging, we simply forbid using the Debug mode
  3. We analyze the individual parts (inventing for them invalid situations, boundary cases)
  4. We write tests for each individual part of the whole algorithm
  5. In the tests, sometimes you have to learn the intermediate data, but …
    Debugging is not available for us, so we stick Trace into those parts where there is a suspicion of incorrect execution of the algorithm
  6. On trace it is necessary to understand the cause of the problem
  7. If it is not clear, then most often either it is worth writing another test, or doing a trace one stage earlier

The solution is automated testing using logging. You write tests for all (or almost all) situations that lead to problems. Plus, if there is a need for debugging, then you trace until you figure out the problem.

Thus, you get rid of other people from debugging this code, because if there is a problem, then the person will look at your logs and understand what the reason is. Only important parts of the algorithm should be logged.

Let’s see an example. You know that individually, all implementations of interfaces work (because tests written to prove this). But with the interaction of all together there is an incorrect behavior. What you need? It is necessary to log responses from the “correct” interfaces:

void Register(User user)
{
	// на этом участке не нужно ничего логировать,
        // т.к. всё будет понятно из исключения
	var isExists = _userRepository.IsUserExists(user);
	if (isExists)
	{
		throw new InvalidOperationException($"User already exists: {user}");
	}

	// а вот это уже можно и залогировать, 
        // т.к. от этого зависят дальнейшие детали алгоритма
	var roleInOtherService = _removeService.GetRole(user);
	_log.Trace($"Remote role: {roleInOtherService}")

	switch (roleInOtherService)
	{
		case "...":
			break;
		...
	}

	// тут нам не критично, если пользователю не добавили 
        // определённые привелегии в каком-то удалённом сервисе,
        // но мы бы хотели знать об этом
	foreach (var privilege in Privileges)
	{
		var isSuccess = _privilegeRemoteService.Grant(user, privilege);
		if (isSuccess)
		{
			_log.Trace($"Add privilege: {privilege} to User: {user}");
		}
		else
		{
			_log.Trace($"Privilege: {privilege} not added to User: {user}");
		}
	}

	...
}

In this example, you can see how we trace individual parts of the algorithm so that we can fix one or another stage of its execution. Having looked at the logs, it becomes clear what the reason is. In real life, the whole algorithm would be worth breaking into separate methods, but the essence of this does not change.

All this at times speeds up the writing of the code. You do not need to run on a cycle with F10 in order to understand what cycle iteration the problem has occurred. Simply log the state of the objects you need under certain conditions at a certain iteration of the cycle.

In the end, you leave important logs and delete side logs, for example, where you learned the state of the object at a certain iteration of the loop. Such logs will not help your colleagues. you, most likely, have already understood the problem of the incorrect algorithm and corrected it. If not, then run the development in your branch until you find the cause and fix the problem. But the logs that record important results from the execution of other algorithms will be needed by all. Therefore, they should be left.

What if we do not need this garbage? In this case, trace only within the debugging framework, and then clean it up.

Pros Minuses
It is possible to trace the execution of the algorithm without debugging I have to write the trace code
In production, logs will sometimes collect the required data In production, logs will sometimes collect unnecessary data
Debugging time is shortened, because you often run the code and immediately see the results of its execution (all in the palm of your hand) It is necessary to clean up after itself.This sometimes leads to the re-writing of the trace code
Colleagues can track the implementation of the log algorithm Colleagues have to look not only at the algorithm itself, but also on the trace of its execution
Logging is the only way to debug nondeterministic scenarios (for example, multithreading or network) In production, performance decreases due to I / O and CPU operations

Finally I would like to say this: think with your own head. You do not need to jump from the extreme to the extreme. Sometimes debugging manually with your hands really quickly can solve the problem here and now. But as soon as you notice that something is fast, here and now it does not work, it’s time to switch to another mode.