The majority of software development consists of writing new functions in numerous iterations, testing them and finally identifying and fixing errors or inconsistencies. When troubleshooting, it is fatal if there are no clues that point to the cause, so that the affected places in the code cannot be narrowed down exactly.
Advertisement
The more time has passed between the development and the appearance of the error, the trickier the troubleshooting becomes. It makes the search even more difficult when many new functions have been added during this time.
Frequent test iterations alleviate the elapsed time problem. They help limit the affected area and improve the quality of new features. A more efficient strategy is needed for the existing code base in order to receive indications of occurring errors.
symptom and cause
The strategy is to understand the problem as a combination of symptom and cause and to reduce the distance between them. The symptom is the visible result that deviates from the desired result. The cause is the place that initiates this deviation. All function lines between the two points give the distance. A stack trace is useful to represent them.
The following example of a Divide-By-Zero Exception illustrates the concept. It demonstrates how to use a stack trace and reveals the hidden challenges.
public class Stack
{
public void Execute()
{
int x = 10;
int y = 0;
Console.WriteLine(x / y);
}
}
The code shows two variables, the second of which takes the number 0. The subsequent division leads to a divide-by-zero exception. The example shows the difference between symptom and cause: The stack trace of the error message contains one line and indicates the division:
Advertisement
Unhandled exception. System.DivideByZeroException:
Attempted to divide by zero.
at ExceptionFirst.Stack.Execute() in … /Stack.cs:line 7
This is the symptom: something is not behaving as it should. The cause of the problem is one line above: the second variable with the value 0. Changing this point fixes the error. The distance between the symptom and the cause is one line of code, so it is one.
More complexity, longer distance
The following code extracts the division into its own method:
public class Stack
{
public void Execute()
{
int x = 10;
int y = 0;
Console.WriteLine(Divide(x, y));
}
private int Divide(int x, int y)
{
return x / y;
}
}
It is a refactoring without any functional changes: the program keeps throwing the Divide-By-Zero Exception. However, the stack trace has become one line longer:
System.DivideByZeroException: Attempted to divide by zero.
at Stack.Divide(Int32 x, Int32 y) in … /Stack.cs:line 12
at Stack.Execute() in … /Stack.cs:line 7
In the first place is again the line with the division: the symptom. In the second place is the line of code calling the separate function. The line of code doesn’t show the cause, but the correct method. One line above is the variable with the value 0: the cause of the error. The distance between the symptom and the cause is two lines of code. The stack trace is still meaningful and shows all methods that are helpful in finding the source of the error.
The following code extracts the setting of the variables into their own methods. This simulates taking values from an unknown source such as user input, API return values, or the contents of a database.
public class Stack
{
public void Execute()
{
int x = ReadX();
int y = ReadY();
Console.WriteLine(Divide(x, y));
}
private int Divide(int x, int y)
{
return x / y;
}
private int ReadX()
{
return 10;
}
private int ReadY()
{
return 0;
}
}
Again, there is no functional change and the behavior remains the same. The Divide-By-Zero exception still occurs. Although the execution has changed, the stack trace remains the same as before. Why is this and what are the consequences for troubleshooting?
System.DivideByZeroException: Attempted to divide by zero.
at Stack.Divide(Int32 x, Int32 y) in … /Stack.cs:line 12
at Stack.Execute() in … /Stack.cs:line 7
Missing method
The stack trace still shows the symptom on the first line and the division call on the second line. The problem here is that the ReadY method, which returns 0, doesn’t appear in the stack trace. As a result, the stack trace no longer contains all information about the application process, which makes troubleshooting more difficult. At this point, the stack trace alone is not enough, a detailed analysis of the source code is required. In detail, the dynamic behavior of the system must be reproduced by logging and debugging.
Why does the call to the ReadY method not show up in the stack trace? When using a framework, the displayed stack traces are extremely long, even if hardly a line was executed in your own code. This is deceptive and can lead to the false assumption that the stracttrace contains the entire history of a call.
However, a stack trace does not log the lines or methods called, but only shows the currently open methods. These are the methods that have been called and have not yet completed execution. Because methods in turn call other methods, a stack results. Completed methods are removed from the stack and it continues with the top method on the stack.
So the ReadY method is missing from the stack trace because its execution is complete. It’s near the bottom of the stack trace and the cause is easy to pinpoint. There is a distance of three lines of code. The example shows on a small scale why it is often difficult to find the cause of a problem. At the same time, it clearly demonstrates the stack trace behavior. With this knowledge, one can develop a strategy to use stack traces more effectively.
Go to home page
#Debugging #Guard #Clauses #preparing #code #errors