RailsConf 2020 CE

Debugging: Techniques for Uncertain Times

Debugging: Techniques for Uncertain Times

by Chelsea Troy

The video "Debugging: Techniques for Uncertain Times" by Chelsea Troy delves into effective debugging strategies amidst uncertainty, both in coding and life. Troy shares her unique journey into software engineering and emphasizes that while programming often focuses on building features, debugging requires a different mindset, especially when dealing with unfamiliar code.

Key points discussed include:
- Transition from Certainty to Uncertainty: Developers typically excel at feature creation when they understand the code, but debugging often occurs under lack of understanding, making it more challenging.
- Skill Transfer from Life Experiences: Troy explains how her experiences of adapting to various life challenges taught her valuable skills that aid in debugging, such as slowing down and acknowledging ignorance.
- Debugging Mindset: It's crucial to shift from a progress-oriented mindset to an investigative approach when debugging. Recognizing that one's understanding may be flawed is essential.
- Common Debugging Strategy Flaws: The common strategy of testing ideas in sequence only works when the developer has a solid grasp of the code behavior. Troy advises against this method when one is unsure, as it often leads to confusion and frustration.
- Assumption Checking: A systematic approach to identifying and verifying assumptions about the code is vital. Troy suggests techniques like binary search strategies and maintaining notes to keep track of assumptions and findings, leading to a clearer path to the bug's source.
- Empirical Evidence vs. Assumptions: Differentiating between personal perspectives and empirical realities is necessary for accurate debugging. This understanding helps in recognizing faulty assumptions.
- Long-Term Learning through Bugs: Each bug is an opportunity to learn and improve one's debugging skills, which can be applied across various programming languages and situations.

Examples from Troy's life illustrate her points, demonstrating the importance of building resilience and adaptability. She concludes that by investing time in developing these skills, individuals can enhance their comfort with uncertainty, enabling more effective debugging and personal growth.

Ultimately, the talk emphasizes that debugging, much like life adjustments, requires patience, curiosity, and a willingness to confront one’s limitations.

00:00:09.639 This talk is called "Debugging Techniques for Uncertain Times" by Chelsea Troy.
00:00:15.660 You can reach me at ChelseaTroy.com, or you can contact me on Twitter at @HeyChelseaTroy.
00:00:30.360 Before I became a software engineer, I had various experiences. I coached rowing at a high school in Miami, blogged for a startup whose business model turned out to be illegal, performed stand-up comedy at bars, danced with fire on haunted riverboats, edited a magazine focusing on dubious psychology, and conducted open-source investigations for international crime rings.
00:00:42.850 All these experiences were exciting in retrospect, but during that time, it was a difficult journey of self-discovery. I was constantly adapting at frequent intervals to keep myself afloat. I entered software engineering for job security rather than a passion for programming. However, many of the coping mechanisms learned from those various adaptations transitioned into my programming career.
00:01:07.590 It turns out that the skills we develop to cope with rapid and substantial life changes also help us become calmer and more effective debuggers. Debugging, in my opinion, does not get the attention it deserves from the programming community. We often view it as an amorphous skill for which we rarely teach and do not have definitive practices or pedagogies.
00:01:35.020 Instead, we teach people how to write features, how to build something new in software we understand. When we have certainty about what the code is doing, it makes it easier to develop. I suspect you've watched a video or two about programming. If I didn't know better, I'd say you're watching one right now. However, while this talk doesn’t specifically deal with code examples, I assume you've seen demos where speakers share code on their screens or demonstrate how to perform tasks in a codebase during a video.
00:02:05.350 Here’s the dirty secret: when we give demos on stage or upload them to YouTube, that’s rarely the first time we’ve written that code. Usually, we’ve created a similar feature in production before, modified it for our talk or video, and practiced repeatedly to minimize errors and mistakes, so we present the code as seamlessly as possible.
00:02:36.830 However, when programming on the job, it’s common to spend the majority of our time writing something that is quite different from prior experiences. If we had done something exactly the same before, our clients would ideally use the off-the-shelf solution we initially wrote, not pay exorbitant rates for custom work. Thus, we frequently find ourselves outside our comfort zone with code. Debugging feels challenging partly because we take skills learned in feature building, in a context of certainty, and attempt to apply them in situations where we face uncertainty.
00:03:03.300 The first thing we need to debug effectively is to acknowledge our lack of understanding about the behavior of our code. This may seem like an obvious detail, but we often misinterpret it, creating stress that makes problem-solving more difficult because we have predominantly seen models where the programmer knows what is happening. We feel as though we should know what is going on, and that creates a sense of urgency to figure it out, but speed is often the enemy when dealing with insidious bugs.
00:03:41.530 I struggled with similar feelings throughout my diverse career. I often felt inadequate and unprepared for adulthood because I didn't know how to do my taxes, find my next job, or manage family expectations. I experienced insecurity and guilt which made me want to run away from my issues rather than confront them. However, repeatedly facing failure over time led me to understand that not knowing things is normal. I've learned to acknowledge my insecurities without letting them dictate my actions.
00:04:04.630 When feelings of inadequacy urged me to speed up, I realized that I needed to slow down and examine why I wasn't achieving desired results. I had to transition from progress mode into investigation mode. This is the second principle needed for effective debugging: switching our focus from making progress to investigating the problem.
00:04:38.100 The most common debugging strategy I encounter resembles this: we try our best idea first, if it fails, we move to our second-best idea, and so on. I refer to this as the standard strategy. If we understand the behavior of our code, this approach can be effective, but problems arise when we do not understand the underlying behavior and continue applying this method as if we do.
00:05:02.080 By acting as though we understand the code when we don’t, we hinder ourselves. In fact, the less we grasp the actual behavior of our code, the weaker the correlation becomes between our assumptions about what may be causing a bug and the actual cause. This leads to circling around ineffective ideas simply because we are unsure of what’s happening, resulting in impediments to actual progress.
00:05:36.100 Once we have established that we do not understand the behavior of our code, we should shift focus from trying to fix the problem to asking questions that help us uncover the real issue. By 'problem,' I mean specific invalid assumptions we might hold about the code. Let me illustrate this with examples. We might employ a binary search strategy in which we assume the code follows a straightforward, linear flow from beginning to end. We then test segments of code that might contribute to the bug.
00:06:04.000 In this context, 'test' doesn’t necessarily refer to an automated test, although that’s one tool we can use. Rather, testing here involves quickly gathering feedback about whether our assumptions match the actual state of the system at that point. It’s important to recognize that insidious bugs often stem from inaccurate assumptions, as the insidious nature of bugs arises from incorrect beliefs that lead us to look in the wrong places.
00:06:40.560 It’s challenging to detect when our assumptions about a system are incorrect because it’s difficult to recognize when we are making assumptions at all. We frequently take certain things for granted, believing them to be true without verification. For instance, we might just assume a variable must exist at a certain point in the code without ever checking.”
00:07:16.950 This is where fast feedback becomes crucial. We can create a list of our assumptions and utilize our available tools to test those assumptions. We have various automated methods to run numerous feedback loops at once, allowing us to check many paths through our code simultaneously. However, it's essential to remember that tight feedback loops, including manual run-throughs, are also effective tools.
00:07:49.230 As developers, we typically start doing this almost as soon as we begin writing code, and we continue when we want to verify specific parts. Using breakpoints, we can pause code at certain lines and inspect variables currently in scope. Furthermore, we can run methods in scope from the command line to see how the system responds. Print statements can be supplementary when breakpoints are insufficient, especially with multi-threaded or asynchronous code.
00:08:17.420 Logging is particularly useful for deployed code or scenarios where we cannot access standard output; in such cases, we might require a more robust logging framework. By manipulating variable values deliberately, we can test how our program should react, helping us clarify our understanding of the affecting code.
00:08:49.340 In the process of debugging, it’s vital to identify which elements are truths and which are merely our perspectives. I can’t express how many things I was convinced to be true in my early independent life that I later discovered were inaccurate. Learning to distinguish between personal views and empirical evidence, as well as reassessing my perspectives, has been essential for my growth.
00:09:23.790 Now, let's engage in an exercise where we document the assumptions we make while debugging, as indicated by a rounded box in our debugging flowcharts. For each step we're checking, we maintain a list of our assumptions, indicating things we’re not checking, and a separate list for things we are checking. In this context, the 'Given' section aims to specifically outline our assumptions, while the 'Checking' section details items we are verifying.
00:09:54.620 Though this exercise may seem tedious, it becomes incredibly valuable once we have verified all possible paths within the code and find that everything checks out, yet the bug persists. At this point, we should revisit our given assumptions one by one. I recommend keeping notes, as many bugs tend to hide in our underlying assumptions. This practice can illuminate which of our assumptions are prone to being incorrect.
00:10:29.200 When we check each assumption, we can apply a binary search approach to systematically reduce the problem space, ideally isolating the insidious bug in a limited number of steps. What we create with notes like this is a pattern based on what we believe, which we can compare with a shared reality.
00:10:58.090 I should also mention that binary search methods might not apply in cases where the code path does not follow a single-threaded, linear flow. In such instances, we may need to trace the complete code path from beginning to end ourselves. Nonetheless, the core idea remains the same: we explicitly list our assumptions and checks to scrutinize our code from a logical standpoint and uncover the underlying causes of defects.
00:11:24.490 By consistently practicing to identify our underlying assumptions, we can enhance our debugging expertise. The more we perform this exercise, the more adept we become at uncovering our flawed assumptions, particularly those that caused previous bugs. This growing intuition forms the foundation for effective debugging, expanding beyond the initial context of our experience.
00:11:56.570 To effectively debug, we must also possess an awareness of how our current practices serve broader, long-term objectives. I have a history of creating stress for myself. For instance, on a visit to Disney World, my mom recalls changing my diaper on a bench while I people-watched in anxiety, fearful that I might roll off.
00:12:29.920 This tendency to stress persisted through high school when I believed that college acceptance decisions would ultimately define my fate. Over time, I learned to view moments—be it tests, sports competitions, or job interviews—not as make-or-break situations but rather as opportunities for growth and learning.
00:12:59.840 Now, when I walk into a job interview, my objectives are to connect with others and gain new insights, regardless of whether or not I secure the position. Thus, I always walk away with greater understanding, achieving success in that sense.
00:13:33.860 Similarly, each insidious bug we encounter offers a valuable lesson. Even if what we learn is unpleasant or frustrating, we can leverage that knowledge to avoid potential issues in the future. Our insights can crystallize into wisdom that we can carry forward, whether into different codebases, workplaces, or even personal life.
00:14:16.220 As we navigate this journey, we cultivate our abilities to communicate with code and maneuver through uncertainty in our lives. The practice of recognizing what we don’t understand, slowing down, differentiating between our views and shared realities, and persistently seeking ways to progress remains a worthwhile investment.
00:15:06.060 Thank you.