Software developers spend much of their time figuring out how code behaves, not just writing new features or fixing bugs. This chapter frames code investigation as a broader skill set than debugging alone, emphasizing that understanding software behavior is often the real work behind solving problems, extending systems, and learning new technologies. It introduces the idea that investigation techniques are essential for being productive, especially when code is complex, poorly structured, or part of a larger ecosystem of libraries and frameworks.
The chapter explains that debugging is only one tool in a larger toolbox. While a debugger is useful for stepping through code and observing values, it is not always enough, especially when the relevant code is hard to locate, the application is multithreaded, or using a debugger changes the behavior of the program. In those situations, other techniques such as profiling, logging, and postmortem analysis become important. The text also illustrates how different investigation methods fit different goals, such as identifying unexpected results, tracing where code is executing, and understanding interactions with external systems.
It also shows how investigation helps with common real-world problems: unexpected outputs, learning frameworks, performance bottlenecks, and crashes. For output problems, a debugger or profiler may help determine what part of the app is responsible; for dependency-heavy or unfamiliar technologies, deeper inspection of framework code can clarify behavior; for slowness, profiling reveals where time is spent; and for crashes, heap dumps and thread dumps provide clues after the fact. Overall, the chapter’s message is that effective troubleshooting depends on choosing the right technique for the situation, combining tools when needed, and developing a disciplined way to understand software systems quickly and accurately.
Code investigation is not only about finding problems in software. Today, apps are complex. We often use investigation techniques to understand an app’s behavior or simply to learn new technologies.
Investigating code doesn’t require much physical effort, but debugging sometimes makes you feel like Lara Croft or Indiana Jones. Many developers enjoy the unique sensation of solving the puzzle of a software issue.
Code investigation techniques. Depending on the case, a developer can choose from one or more of these techniques to understand how a certain capability works.
Using a debugger, you can pause the execution before a particular instruction and then observe how the app’s logic changes the data by manually running the instructions step by step.
Identifying code in execution with a profiler. If you don’t know where to start debugging, the profiler can help you to identify the code that is running and give you an idea of where you can use the debugger.
A Heisenberg execution. In a multithreaded app, when a debugger interferes with the app’s execution, it might change how the app behaves. This change doesn’t allow you to correctly investigate the initial app behavior that you wanted to research.
A wrong output can be your app sending erroneous requests to another system component. You may be asked to investigate such a behavior and find its root cause.
You can replace the system component your app calls with a stub. You control the stub to quickly determine where your app sends the request from. You can also use the stub to test your solution after you correct the issue.
Investigating slowness problems with a profiler. The profiler shows you the time spent on each instruction during code execution. This profiler feature is excellent for identifying the root causes of performance problems.
A heap dump is like a map of the heap memory. If you learn how to read it, it gives you invaluable clues about how the app internally processes data. A heap dump helps you investigate memory problems or performance issues. In this example, you can easily find which object fills most of the app’s memory and that the Product and String instances are related.
A thread dump provides details about the threads that were running when the dump was taken. It includes thread states and the stack traces, which tell you what the threads were executing or what blocked them. These details are valuable for investigating why an app is stuck or is having performance problems.
Summary
- You can use various investigation techniques to analyze software behavior.
- Depending on your situation, one investigation technique may work better than another. You need to know how to choose the correct approach to make your investigation more efficient.
- For some scenarios, using a combination of techniques helps you to identify a problem faster. Learning how each analyzing technique works gives you an excellent advantage in dealing with complex problems.
- In many cases, developers use investigation techniques to learn new things rather than to solve problems. When learning complex frameworks such as Spring Security or Hibernate, simply reading books or the documentation isn’t enough. An excellent way to accelerate your learning is to debug examples that use a technology you want to better understand.
- A situation is easier to investigate if you can reproduce it in an environment where you can study it. Reproducing a problem not only helps you find its root cause more easily, but it also helps you to confirm that a solution works when it is applied.