Debugging with the Scientific Method - Stuart Halloway

Debugging with the Scientific Method - Stuart Halloway — ClojureTV

ClojureTV

AI-assisted summary of Debugging with the Scientific Method - Stuart Halloway — original source. Check the original for full context.

Source published:

Key ideas

  • Software debugging accounts for an estimated 50% or more of development costs, yet it is often approached through reactive tool usage rather than systematic hypothesis testing. (0:00)
  • Unlike academic science, software debugging is purely deductive and insulated from sociopolitical biases because the system itself provides the existing grand theory. (13:59)
  • Effective troubleshooting relies on proportional reduction to bisect the search space and strictly changing only one variable per experiment. (25:02)
  • Failing to document hypotheses and observations forces engineers to hold complex state spaces in memory, turning debugging into an unnecessarily difficult meta-problem. (33:59)
  • Clojure's immutable data structures and code-as-data model make it uniquely well-suited for automating scientific debugging via state-shrinking algorithms. (44:57)

Detailed notes

## Background Published on YouTube by ClojureTV on November 17, 2015, this video captures a presentation by Stuart Halloway, founder and President of Cognitect, Clojure committer, and Datomic developer. According to the description, Halloway—co-author of 'Programming Clojure' and a regular speaker across industry events—explores the methodology and mindset of effective troubleshooting.

## TL;DR In this talk, Stuart Halloway argues that debugging software is fundamentally more scientific than professional science itself, advocating for hypothesis-driven inquiry over reactive, tool-directed tinkering. He breaks down the troubleshooting cycle into crisp problem statements, proportional search space reduction, and isolated single-variable experiments that prune away non-essential tooling. Drawing on real-world examples from Clojure and Datomic, Halloway highlights critical JVM realities—such as memory pressure masquerading as bizarre unrelated exceptions—and insists that documenting hypotheses and observations in writing is vital to avoiding cognitive overload. Finally, he urges the community to leverage Clojure's immutable, code-as-data architecture to automate diagnostic search algorithms like delta debugging and state shrinking.

## Atmosphere & style The presentation is witty, authoritative, and playfully cynical, delivered with the dry, self-deprecating humor of a battle-tested systems veteran. Halloway balances rigorous philosophy—drawing on Kuhn, Popper, and modus tollens—with pop-culture nods to 'House, M.D.', 'Ocean's Eleven', and plush Cthulhu toys, keeping the mood grounded and delightfully irreverent. Watching it feels like getting a candid masterclass from a pragmatic senior architect who demystifies the engineering ego, urging developers to replace chaotic trial-and-error with cool-headed discipline so they can finish work at five and enjoy a glass of wine.

## Bulletin - [00:00](https://www.youtube.com/watch?v=FihU5JxmnBg&t=0s) Software debugging accounts for an estimated 50% or more of development costs, yet it is often approached through reactive tool usage rather than systematic hypothesis testing. - [13:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=839s) Unlike academic science, software debugging is purely deductive and insulated from sociopolitical biases because the system itself provides the existing grand theory. - [25:02](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1502s) Effective troubleshooting relies on proportional reduction to bisect the search space and strictly changing only one variable per experiment. - [33:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2039s) Failing to document hypotheses and observations forces engineers to hold complex state spaces in memory, turning debugging into an unnecessarily difficult meta-problem. - [44:57](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2697s) Clojure's immutable data structures and code-as-data model make it uniquely well-suited for automating scientific debugging via state-shrinking algorithms.

## Key takeaways - [00:00](https://www.youtube.com/watch?v=FihU5JxmnBg&t=0s) Debugging drives over half of software development costs, making disciplined approaches far more valuable than ad-hoc tinkering. - [05:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=359s) Tool-directed development is an anti-pattern; developers must have a hypothesis and a plan before reaching for diagnostic tooling. - [11:47](https://www.youtube.com/watch?v=FihU5JxmnBg&t=707s) In debugging, falsification through modus tollens is an ideal outcome because eliminating a candidate hypothesis definitively narrows the search space. - [13:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=839s) Software debugging is more strictly scientific than academic science because it operates apolitically on concrete deductive behavior rather than social consensus. - [17:22](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1042s) An actual cause is the difference between the observed reality and the closest counterfactual world where the failure does not occur; a fix is merely the concluding experiment proving it. - [23:00](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1380s) Problem statements must explicitly define the steps taken, what was expected, and what actually occurred, completely eliminating vague pronouns like 'it didn't work'. - [25:02](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1502s) Hypotheses should target proportional reduction of the candidate failure space rather than jumping immediately to highly specific leaf-node guesses. - [28:56](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1736s) Reproduction cases should aggressively strip out non-essential tools and dependencies like test frameworks, editor plugins, and build tooling to isolate the core failure. - [31:05](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1865s) Unexplained system outputs and warnings cannot be dismissed as unrelated without first understanding them, as they frequently point to classpath collisions or underlying defects. - [33:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2039s) Not writing down hypotheses, justifications, and observations is an act of hubris that forces the mind to solve a difficult meta-tracking problem instead of the bug itself. - [35:51](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2151s) The observed failure—such as an exception—is merely where the system noticed an invalid state, not necessarily the location or nature of the defect. - [38:13](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2293s) Memory pressure and garbage collection on the JVM act as an 'anti-Lupus', triggering erratic thread scheduling, surfacing rare race conditions, and masquerading as unrelated errors. - [40:07](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2407s) Debugging documentation should function like a short, dense specification rather than an expansive tutorial so that it can be completely read when addressing unknown unknowns. - [41:34](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2494s) Diagnostic claims like 'it fails in production but works in dev' must be treated with skepticism and verified via minimal scripts, as engineering miscommunications often lead teams astray. - [44:57](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2697s) Debugging is fundamentally a search problem over program states that can be automated through difference-shrinking algorithms like those found in property-based testing. - [47:55](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2875s) Explaining a proposed test to an inanimate object before touching code forces deliberate reasoning and curbs reactive trial-and-error impulses.

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=FihU5JxmnBg&t=0s) Clarifications and Rationale for Debugging

Stuart Halloway begins by clarifying a couple of points from the introduction. He jokes that Alex Miller just drew a chain of causality suggesting Halloway is responsible for Strange Loop, so he is excited and eagerly waiting for his checks to arrive in the mail. He also playfully dismisses the implication that working at Cognitect offers endless chances to watch debugging in the product team, joking that Datomic has “really no bugs at all.” Turning serious, Halloway explains that in the Clojure community, driven by values instilled by Rich Hickey, every major talk should begin with a strong rationale.

His rationale for focusing on debugging has several parts:

1. **The Cost of Bugs:** Tracking down bugs is estimated to account for 50% or more of the total cost of software development. While developers in certain environments might manage to hand things off and run away, anyone who owns a system over time quickly realizes how real this cost is. 2. **Appearing Disproportionately Smart:** Debugging is a remarkably straightforward activity, yet people perceive it as exceedingly difficult. Halloway confesses he is constantly intimidated by the intelligence and specialized accomplishments of people in the audience. Being able to look bright without burning extra mental energy is a great trick: “this is not hard to do but people think it's hard to do and so you can look disproportionately smart by doing it.” 3. **Connecting Debugging to Clojure's Design Philosophy:** Understanding how Halloway and, by proxy, Rich Hickey think about debugging offers insight into the aesthetic, evolution, and processes behind Clojure itself. While people hold varied opinions on the onboarding experience, documentation, or exception handling, adopting a scientific approach inherently shapes the tools and techniques you choose. Halloway notes that exploring this deeper connection is a great topic for the evening's "office hours," which he jokes will take place at whichever local bars stay open the latest.

[03:37](https://www.youtube.com/watch?v=FihU5JxmnBg&t=217s) Why Stuart Halloway?

Halloway next addresses why he is qualified to speak on debugging, citing his deep operational history and practical motivations:

- **Clojure Committer and Screener:** Clojure does not follow the standard GitHub project workflow. Halloway reviews tickets, reads code, and has pushed every commit to Clojure since the 1.0 release. He has evaluated countless patches that entered the language—and far more that were rejected—learning to distinguish real bugs from perceived ones. - **Datomic Support Backstop:** In his role supporting Datomic, Halloway often acts as the place of last resort for the thorniest issues. Though support shifts rotate, he regularly serves as the backstop when hard problems arise, giving him extensive experience observing defects in Datomic itself as well as diagnosing tricky bugs within customer architectures. - **Gray Hair as an Appeal to Authority:** He playfully cites his graying hair as proof of experience, noting it was entirely dark when he began his career: “I earned this gray hair; I've been doing this for a long time and my hair was not gray at all when I first started debugging.” (He admits an alternate theory exists: it might just come from raising children.) - **Constructive Laziness:** Emphasizing programmer laziness, he explains that he hates working needlessly hard. His goal is to follow a systematic approach that allows him to close his laptop at 5:00 PM, enjoy dinner, have a glass of wine, watch whatever violent show HBO is airing, and go to bed.

[05:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=359s) Debugging in Clojure and the Trap of Tool-First Thinking

Turning to the Clojure ecosystem specifically, Halloway mentions a Reddit thread shared by his colleague Carin Meier. The post captured the widespread frustration of newcomers struggling with Clojure error messages. Beginners often complain about cryptic stack traces, confusion when dealing with dependencies or Leiningen plugins, or type-related errors that they feel would have been trivial to diagnose in Java. While Halloway's own experience with Clojure debugging has been positive rather than painful, he recognizes that many developers find it fraught and hopes to share principles to make the process smoother.

```mermaid flowchart TD accTitle: Tool-Directed vs. Scientific Debugging accDescr: Comparison of reactive tool-led tinkering versus hypothesis-driven debugging.

subgraph Reactive["Reactive Tinkering (Anti-Pattern)"] R1["Hit Bug / Cryptic Error"] --> R2["Reach for Tools Blindly"] R2 --> R3["Mutate Code Without a Plan"] R3 --> R4["Loss of Initial State & Context"] end

subgraph Scientific["Scientific Approach"] S1["Observe Issue"] --> S2["Formulate Hypothesis"] S2 --> S3["Select Tool to Test Hypothesis"] S3 --> S4["Know Direction Before Acting"] end ```

After asking how many people in the room had built tooling for Clojure—and acknowledging the impressive ecosystem of editors, debuggers, and visualizers—Halloway clarifies that his talk is deliberately not about tools:

- **Tools Overwhelm the Discourse:** A standard Google search for debugging reveals that well over half of all available resources are exclusively about tooling. Providing another tool tutorial would not deliver keynote-level value. - **Tools Do Not Provide Direction:** Developers must control their tools, not be driven by them. As Halloway quips, “there is no TDD out there that stands for Tool directed development; we wield the tools, they don't wield us.” - **The Need for a Plan:** Quoting Yogi Berra, Halloway reminds the audience: “if you don't know where you're going you might wind up someplace else.” When assisting customers in support, the first impulse is often wanting to travel back in time to the moment right before they started blindly changing things. While being a proactive self-starter is valuable, taking action without a deliberate plan typically obscures the original problem and leads developers astray.

[09:46](https://www.youtube.com/watch?v=FihU5JxmnBg&t=586s) The Scientific Method, Rainbow Graphics, and Etymology

Stuart Halloway admits that prior to preparing this talk, he had not read up on what people had to say specifically about the scientific method since earning his science degree in college. Naturally, he turned to Google to investigate why one should care about it. What he discovered was that according to the internet, the scientific method is represented entirely by "rainbows." He jokes that all the Edward Tufte information design purists must be pulling their hair out wondering what the meaningless color palettes are supposed to convey. Consequently, Halloway decided to strip all the colors out and build his own graphic using OmniGraffle—admitting self-deprecatingly that this also happened to represent the absolute limit of his diagramming skills.

```mermaid flowchart TD accTitle: Scientific Method for Debugging accDescr: The cycle of debugging through failure, hypothesis, experiment, observation, refinement, and theory. F["Failure<br/>(Omission of expected action)"] --> H["Hypothesis<br/>(Starting explanation)"] H --> E["Experiment<br/>(Test or trial)"] E --> O["Observation<br/>(Active acquisition of info)"] O -->|"Falsified (Modus Tollens)"| FH["Hypothesis Dead<br/>(Kill possibility)"] O -->|"Supported"| R["Refinement<br/>(Subtract unwanted elements)"] R --> H R -->|"Validated by predictions"| T["Theory<br/>(True given current info)"] ```

Leaning into Clojure/conj tradition, Halloway notes that he will define every term using the etymology dictionary. He advises future conference proposal submitters that including unfamiliar words and referencing etymology in their abstracts is clearly something highly valued in the Clojure community. He proceeds to lay out the scientific method as applied to debugging:

- **Failure**: A lack of success, or more precisely, an omission of an expected action (expecting one outcome and observing another). - **Hypothesis**: An explanation made without complete evidence, functioning as a starting point for further investigation. - **Experiment**: A test or a trial designed to probe that hypothesis. - **Observation**: The active acquisition of information from a primary source.

[11:47](https://www.youtube.com/watch?v=FihU5JxmnBg&t=707s) Falsification, Refinement, and the Danger of Emotional Attachment

When an experiment concludes, one of two positive outcomes occurs. The first is falsification—a deductive process governed by *modus tollens*. If a hypothesis implies that observation $O$ should not happen, but running the experiment reveals $O$, the hypothesis is decisively dead. While this may be disappointing to someone seeking research grant money in conventional science, Halloway stresses that in debugging, eliminating a candidate hypothesis is fantastic news because it narrows the problem space.

The second outcome is that the experiment supports the hypothesis, but the answer remains insufficiently granular to pinpoint the root cause. This requires refinement: the subtractive process of removing impurities or unwanted elements to shrink the conceptual size of the problem and hone in on the actual issue. Over time, repeated refinement yields a theory—a hypothesis that has been validated by predictions. Crucially, a theory is never absolute truth; it is simply true given all available information.

Halloway cautions against the dangerous cognitive trap of emotional attachment to pet theories: > “I have 99 pieces of evidence in favor of this is where the bug is and I have one little piece of evidence that Flatout contradicts it and I don't understand that piece of evidence and so well 99's pretty good I'm just going to keep going right if you have that one little piece of evidence that says you're dead you're dead right you can't and and we get attached to our theories that's the other thing you get emotional about it you're like you know what I really like that theory and look it had 99 good pieces of evidence in favor of it you can't do that”

[13:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=839s) Why Debugging Is More Scientific Than Science

Researching the topic led Halloway into the broader philosophical debate surrounding the scientific method. Drawing on his graduate school studies of Thomas Kuhn's *The Structure of Scientific Revolutions*, he cynically summarizes the book's premise: science is only slightly better than politics or religion because you wait for people with obsolete ideas to die of old age instead of actively murdering them. Kuhn demonstrated that science is fundamentally a messy social process driven by human biases, academic politics, and shifting moral debates (such as the controversy during Halloway's academic years over *The Bell Curve* and attempts to misuse science to rank human intelligence).

Remarkably, software debugging is completely insulated from this sociopolitical baggage. Halloway argues that if the scientific method is the gold standard of inquiry, debugging actually resembles true science far better than professional science does: > “So unfortunately the scientific method may not be all that great for science but it absolutely rocks for debugging.”

- **Deductive vs. Inductive**: Debugging does not require formulating an inductive, sweeping grand theory like gravity or biological evolution by turning a crank. In software, the grand theory already exists—it is the running software system itself—and debugging is purely deductive proof regarding concrete behavior. - **Apolitical Nature**: Unlike academia or politics, debugging does not trigger moral outrage, bureaucratic power struggles, or postmodern academic debates claiming reality is subjective. Everyone involved simply wants the bug found and resolved.

[17:22](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1042s) Causes, Actual Causes, and the Fix

Translating scientific terminology into concrete troubleshooting terms, Halloway substitutes the word "cause" for "hypothesis." Formally, a cause is an event preceding an effect without which that effect would not have occurred. However, plain causes are too broad to be actionable. For example, "the universe exists" is technically an antecedent cause of every bug, but knowing that does not help a developer write a patch.

To be effective, an engineer must isolate the *actual cause*: the difference between the actual world and the closest possible counterfactual world in which the failure does not occur. While philosophers dive deep into the formal semantics of "closeness," intuitively, if five environmental variables contribute to a failure and isolating one specific variable stops the problem while the other four do not, that single variable represents the actual cause.

| Term | Scientific Parallel | Practical Definition in Software | |:--- |:--- |:--- | | **Failure** | Anomaly | Omission of an expected action; receiving unexpected behavior | | **Cause** | Hypothesis | An event preceding an effect without which the effect does not happen | | **Actual Cause** | Refined Hypothesis | Difference between the actual world and the closest possible world where the bug does not occur | | **Fix** | Final Experiment | The concluding experiment validating that the actual cause has been neutralized |

This progression leads to Halloway's definition of a resolution: a fix is simply the final experiment in the debugging chain. The initial experiment is reproducing the failure, intermediate experiments test various hypotheses, and the fix is the terminal trial proving the defect is eliminated. Halloway emphasizes that if you have applied a code change but haven't run an experiment demonstrating that the issue is genuinely gone, you have not actually fixed it.

[19:07](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1147s) A Stack Overflow Case Study and the Power of the Scientific Method

Stuart Halloway emphasizes that even if an engineer is strongly suspicious about the root cause of an issue, the bug is not actually identified until it is fixed. To demonstrate how to apply the scientific method in a deliberately slapdash way, he brings up a real-world, bite-sized question sourced from Stack Overflow: “why is this partial not working?” In the snippet, a developer defines `partial-join` as `(partial (clojure.string/join ",")...)`, calls `(partial-join ["foo" "bar"])`, and is met with an intimidating runtime error: `ClassCastException: java.lang.String cannot be cast to clojure.lang.IFn`. Because the error includes an obscure generated numeric identifier, developers often assume it is impossible to understand.

```mermaid flowchart TD accTitle: REPL Bottom-up Evaluation accDescr: Step-by-step evaluation of the problematic Clojure form at the REPL revealing the type error. subgraph S1["Inner Form Evaluation"] A["(clojure.string/join \",\")"] -->|evaluates to| B["\",\" (String)"] end subgraph S2["Outer Form Substitution"] B --> C["(partial \",\")"] C --> D["Attempt to use String as IFn"] D -->|throws| E["ClassCastException"] end ```

Halloway runs through the common software engineering alternatives to debugging methodically: teams could build hyper-specific error messages that anticipate every conceivable mistake, provide extensive documentation, turn to syntax and paren highlighting, use interactive debuggers, rely on static typing, validate with schemas, or simply stare intently at the snippet until the solution surfaces. While every tool has a place—except merely staring at code—he argues that the scientific approach trumps them all. As Halloway insists:

> “The scientific method is better sauce than these other things because it's more general and because it doesn't require anything to exist in the world except your brain before we go in.”

Testing the hypothesis slapdash, Halloway notes there are only three plausible points of failure: `join` did not behave as expected, `partial` did not behave as expected, or `def` did not behave as expected. Adopting a simple heuristic for small code problems—evaluating forms bottom-up from the inside out at the REPL—he inspects the innermost expression: `(clojure.string/join ",")`. Evaluating it returns the string `","`. Since the separator was passed without a collection, `join` simply yielded the separator string. Substituting that back into `(partial ",")` reveals the bug immediately: Clojure is being asked to partially apply a string as if it were a function. By evaluating just one inner expression, the mystery unravels completely.

[23:00](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1380s) Rigor, Precise Problem Statements, and Eliminating Pronouns

Reflecting on the Stack Overflow example, Halloway explains that “weak science is stronger than strong tools.” The exercise involved a vague problem statement, poor hypotheses, experiments lacking explicit goals, and minimal domain knowledge. Yet it succeeded easily because the problem was trivial. To solve genuinely difficult real-world bugs, developers must execute every step of the scientific method with far greater rigor: formulating crisp problem statements, choosing efficient hypotheses, conducting reproducible experiments, gathering actionable observations, and faithfully writing things down.

The most critical upgrade begins with how an issue is reported. The absolute antithesis of an effective problem statement is the ubiquitous phrase: “it didn't work.” That phrase conceals all relevant technical reality behind vague pronouns. A rigorous problem statement requires three explicit components: 1. The exact steps taken. 2. What was expected to happen. 3. What actually happened.

Halloway jokes that this single slide alone is easily worth a hundred dollars, urging attendees to send him checks. Simply forcing oneself to verbalize these three details often sparks an instant realization of the root cause—much like realizing one stepped out of a rolling car because they forgot to put it in neutral. Clear communication dispels the fog long before reaching for heavy machinery.

[25:02](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1502s) Efficient Hypotheses and Proportional Reduction

After briefly troubleshooting a frozen Keynote slide deck during his presentation, Halloway moves to the formulation of hypotheses. He brings up the Malloy twins (played by Casey Affleck and Scott Caan) from the movie *Ocean’s Eleven* to illustrate poor deductive reasoning. In the film, while playing Twenty Questions, Affleck's character asks: "Am I alive? Yes. Am I a person? Yes. Evel Knievel! Damn it!" It mirrors how a five-year-old plays the game by immediately guessing a hyper-specific leaf node like "a tall oak tree" right after establishing the broad category.

```mermaid flowchart LR accTitle: Hypothesis Search Space Reduction accDescr: Progressively narrowing down the bug search space through proportional reduction. A["Total Possibility Space (100%)"] -->|Step 1: Carve space| B["Candidate Space (~50%)"] B -->|Step 2: Proportional cut| C["Candidate Space (~25%)"] C -->|Step 3: Rapid convergence| D["Root Cause Identified"] ```

A productive hypothesis, by contrast, seeks to bisect the search space. While often labeled "divide and conquer," computer science theoreticians frequently call this technique "decrease and conquer" because only one branch is pursued. Halloway dislikes that label because linear reductions (decreasing by one element each round) are painfully slow. Instead, the essential property is proportional reduction:

> “And the important characteristic here is proportional reduction: I want to take the space of where the problem is and I want to reduce it by ideally half, but as you know if you've done any algorithm analysis, right, if I can get rid of 10% of the possibilities on every step I'm going to have the answer really quickly.”

Formulating questions that cut the problem space in half demands domain knowledge. Beginners lacking domain experience must proceed with extreme caution on their initial cuts so they do not inadvertently eliminate the true cause from the universe of possibilities.

At the same time, engineers already possess natural intuitions about where bugs realistically live in an application stack: - Application-level business logic is overwhelmingly the most common culprit. - Underlying frameworks and language libraries account for a smaller fraction of genuine defects. - In all the years of triaging Clojure bugs, Clojure maintainers have had to pass very few issues down to the JVM or operating system, and they have never once had to escalate a ticket down to physics. - Because proportional reduction compounds so rapidly, an engineer does not have to be an infallible expert; eliminating even modest slices of the possibility space on each step converges on the bug remarkably fast.

[028:56] Designing Good Experiments and Pruning the Repro Case

An experiment must be small, reproducible, and strictly hypothesis-driven. Stuart Halloway cautions that simply trying things at random is not experimenting: real experiments require an idea of what is happening, accompanied by a test specifically designed to provide information that refines that idea. Crucially, developers must change only one variable at a time. If two things are changed at once, any resulting difference forces the investigator to step backward to untangle which change produced the impact, meaning no real information was gained.

```mermaid flowchart TD accTitle: Scientific Experiment Loop accDescr: Cycle of hypothesis formation, minimal isolated testing, observation, and refinement. subgraph HYP ["Hypothesis Stage"] H["Formulate Hypothesis"] P["Prune Non-Essential Tools<br/>(Cursive, Leiningen, Midje, etc.)"] end subgraph EXP ["Experimentation"] E["Change Exactly ONE Thing"] R["Reproduce Minimally"] end subgraph OUT ["Evaluation"] O["Observe All Outputs & Correlate"] F["Refine Mental Model"] end H --> P P --> E E --> R R --> O O --> F F -->|Next iteration| H ```

To illustrate what belongs in an experimental setup, Halloway poses a trick quiz to the audience: when creating a reproduction case for an application bug to hand off to a teammate, which of the following tools should not be included? The list includes `clojure.test`, Cursive, Prismatic Schema, Midje, Pipethrough, nREPL, Leiningen, Leiningen plugins, `core.typed`, and `test.generative`. The answer is that none of them belong in the repro case. Unless the hypothesis explicitly states that a component fails only when running under Cursive inside `clojure.test`, every single one of those dependencies should be stripped out. Removing unrelated tooling not only simplifies the problem space, but it also spares tool maintainers like Colin Fleming from receiving irrelevant bug reports.

- “Experiments are small and when you're making changes to a system you change one thing at a time right because if you change two things now you just have to go back to figure out what the impact of that was.” — Stuart Halloway - “It's incredibly important to remove things that do not contribute to your hypothesis statement and it's a freebie right I was saying earlier that you have to have this like mental model of the universe to allow you to narrow down the things that aren't it right this is a freebie right in bug these things are not it so start by taking them out make a really tiny thing that shows the problem.” — Stuart Halloway

---

[031:05] Observations, Tools, and the "Unrelated" Logback Warning

Making sound observations requires understanding every single output emitted by a system. If a system produces several familiar outputs alongside an unfamiliar one that appears unrelated, the investigator must screech to a halt: without understanding it, there is no way to know whether it connects to the failure. Halloway emphasizes that failures correlate strongly with recent edits: the bug is almost always in the last five lines of code written. Tools such as debuggers, structured logging, and metrics are vital because they turn opaque black boxes into white boxes, producing more observable outputs. While Google searches on debugging focus more than 50% of their results on these observational tools alone, tools represent only one small piece of the broader scientific discipline.

To demonstrate the risk of ignoring strange outputs, Halloway shares an incident from his own work the previous week. While working on a Clojure application, he encountered a cryptic failure in a subsystem, accompanied by a seemingly unrelated Logback configuration warning in the logs. Although tempted to dismiss it as a Java logging quirk, he decided to stop and clean up the Logback issue first. That investigation revealed that a build error had copied two different versions of the same library (`foo.bar` 2.1 and `foo.bar` 2.2) into the `lib` directory. Because the JVM had both versions on the classpath, it loaded an overlapping, non-identical set of classes from both JARs, creating subtle incompatibilities that triggered the cryptic subsystem errors. Addressing the seemingly unrelated output immediately solved the real failure.

- “If your system adds outputs right if your system has four or five outputs that you understand and then it has one that's unrelated to your current problem and you don't understand it breaks Screech right if you don't understand it how do you know if it's related to your problem or not so you need to understand the output from your system and you need to be suspicious of correlations.” — Stuart Halloway

---

[033:59] Write Things Down: The Mastermind Analogy and the Deadly Sin of Hubris

Halloway identifies the single most crucial recommendation of the entire presentation: write things down. Programmers should explicitly document the problem statement, every hypothesis, what the proposed experiment will prove, the justification for running that experiment before touching the system, and all resulting observations.

To demonstrate why, Halloway references the board game Mastermind. In Mastermind, colored pegs at the bottom represent a hidden code, pegs across the rows track past guesses, and small red and white pegs provide feedback on correctness. Debugging without writing things down is equivalent to playing Mastermind blindfolded or covering all previous guesses and trying to hold the entire history and state space in memory. Failing to document the process is not sloth; it is pure hubris. Trying to keep the entire state space of experiments in one's head forces the engineer to solve a meta-problem harder than the debugging task itself just to show off.

- “Write things down this is the single most important piece of advice I'm going to give in this talk write things down.” — Stuart Halloway - “It's amazingly arrogant not to write things down because basically what you're saying is I'm such a badass at solving problems that I'm going to solve the meta problem of keeping track of everything in my head just to show off when that's actually probably a harder problem than the debugging that you're doing and people do this all the time and it's staggering.” — Stuart Halloway

---

[035:51] Software Advice: Choosing Abstractions and Separating Failures from Defects

Turning to advice specific to software development, Halloway offers two core principles:

1. **Avoid C when possible:** Reviewing historical debugging literature reveals countless pitfalls tied to low-level memory management and pointers. Unless a specific domain strictly prevents using high-level environments like Java, Clojure, or Python, developers should not default to C in 2015, as it operates at the wrong level of abstraction. 2. **The failure is not the defect:** Programmers frequently confuse the manifest failure—such as an exception—with the underlying defect. An exception marks where the system noticed an invalid condition, not necessarily where the bug originated.

Halloway illustrates this with an incident encountered on a Datomic deployment. The system suffered from a large query, high CPU utilization, and an `IllegalStateException` thrown out of HornetQ. The naive conclusion would be to assume HornetQ was broken. However, HornetQ had been part of Datomic since version 0.0, and in Halloway's extensive experience with its development team, an issue traced below Datomic into HornetQ had only happened once—and even then, the root cause lay deeper down inside Netty's SSL handling. The Datomic team knew HornetQ was rock-solid, making an internal HornetQ defect an extremely poor hypothesis to pursue.

- “Boy you shouldn't reach for that as the default tool in 2015 in most domains.” — Stuart Halloway - “The failure is not the defect right the failure is not the defect and the way this comes up the most often in software is assuming that the exception has anything to do with the actual problem I mean it does have something to do with it that's how you know right but that it's in some way directly translatable into the problem.” — Stuart Halloway

[38:13](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2293s) The Anti-Lupus of the JVM: Garbage Collection

Stuart Halloway introduces a key philosopher for debugging: Gregory House from the television series *House, M.D.* On that show, a running catchphrase is that “it’s never lupus.” In Clojure and Java programming on the JVM, however, developers face the exact opposite phenomenon: an anti-Lupus. As Halloway argues, “the actual bug is always garbage collection just as just as in House it's never lupus.” It is the culprit that it constantly turns out to be, even though it never looks like garbage collection at the outset.

```mermaid flowchart TD accTitle: How Out Of Memory Cascades Into Obscure Bugs accDescr: Diagram showing how low memory conditions trigger erratic GC scheduling, expose rare race conditions, and surface as misleading exceptions. subgraph GC["Memory Pressure"] A["Low Memory Condition"] --> B["GC Thread Runs Constantly"] end subgraph Effects["System Disruption"] B --> C["Radical Thread Scheduling Shift"] C --> D["Race Conditions Exposed in 2,000 Seconds"] A --> E["Allocation Failures"] E --> F["Cascading Secondary Failures"] end subgraph Output["Symptom"] D --> G["Bizarre Unrelated Exceptions"] F --> G end ```

Halloway outlines several concrete reasons why memory problems are uniquely insidious:

- **Untested code paths**: Most applications are never deliberately designed or tested to induce `OutOfMemoryError` conditions, leaving the resulting recovery paths as uncharted territory. - **Ubiquity**: An out-of-memory error can strike anywhere; there is no designated critical section of code where you can guarantee memory will never run out. - **Chameleon exceptions**: When an allocation fails deep in the stack, cascading secondary failures report radically different, misleading exceptions to the caller. - **Distorted thread scheduling**: Approaching the memory ceiling causes the garbage collection thread to run continuously, radically disrupting standard thread scheduling and exposing rare concurrency race conditions: “all those race conditions that would normally take your system 2,000 years to expose by accident all of a sudden it takes them 2,000 seconds to expose by accident.” - **Cascading breakdowns**: Out-of-memory issues snowball across components, requiring developers to maintain constant suspicion of memory health in any garbage-collected runtime.

---

[40:07](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2407s) Specs as Debugging Docs and Re-evaluating the Clojure Bug

Halloway turns to documentation strategy, offering the blunt advice to read the entire manual. Because a bug begins as an unknown unknown, developers do not know where to look in advance. Consequently, ideal documentation for debugging differs fundamentally from tutorial documentation. Halloway summarizes the distinction: “good docs for debuggers are specs.” While learning materials can span a thousand pages and repeat themselves to ease comprehension, debugging documentation needs to be short, dense, and non-repetitive—ideally around 50 pages—so a developer can realistically read all of it.

Returning to the earlier string-joining bug with `partial`, Halloway illustrates how reading documentation immediately resolves misunderstandings. Looking up the docstring for `clojure.string/join` reveals it has two arities. The single-argument arity simply takes a collection and returns a string concatenation of its elements, rather than taking a separator and returning a partially applied function. A developer using a single step of experimentation or a single step of reading the documentation would have caught the problem instantly.

---

[41:34](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2494s) The Datomic Mystery: "Everybody Lies"

Halloway applies these principles to a real-world, complex Datomic bug that surfaced with high CPU utilization and an `IllegalStateException` on HornetQ. The investigation team was provided a crucial clue: the failure occurred on Cassandra in production, but never on H2 in local development. Halloway challenges the audience to scrutinize that claim scientifically, noting that “it happens on Cassandra in production but not on H2 in development” is an amorphous claim lacking a concrete subject.

To turn this vague notion into an actionable experiment, they eliminated application complexity by writing a tiny, 10-to-15-line standalone reproduction script (`test.Java`) using a Datomic peer that executed the offending query in a tight loop. When run against Cassandra with production-sized data, the failure reproduced immediately—and turned out to be an out-of-memory error because the query demanded more memory than the JVM possessed. Suspicious of why Cassandra would hog so much more memory than H2, they resisted theorizing ahead of the evidence and ran the exact same tiny script against H2 storage. The reproduction failed identically on H2.

This result demonstrates Dr. House’s second famous adage: “everybody lies.” Halloway clarifies that in software engineering, this is rarely malicious deception:

> “And by the way never attribute to malice what you can contribute to accidental miscommunication. This is not lying in the sense of covering up your drug use which is always what it is on House right this is this is lying as in saying a statement which turns out not to be true.”

He jokes about how hard it is to teach his own children that not every mistaken statement is a lie. Had the engineers sat around a conference table trading blame—Datomic pointing at Cassandra drivers and Cassandra devs pointing back—they would have wasted days debating a premise that was completely false. Writing a minimal reproducer mechanically eliminated application code, third-party libraries, and baseless assumptions.

---

[44:57](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2697s) Debugging as a Search Operation: Automating with Shrinking

Halloway presents his final recommendation: recognizing that debugging is fundamentally an algorithmic search operation over possible states. Since software engineers specialize in writing search algorithms, they should expand their tooling beyond passive inspectors and breakpoints. Instead, developers should write programs that automate the scientific method by generating hypotheses and running experiments programmatically. A basic example already in widespread use is `git bisect`, which cuts the revision history in half repeatedly to locate the commit that introduced a failure.

Halloway issues a direct call to action to the Clojure community to study Andreas Zeller’s book *Why Programs Fail*, highlighting chapters 5 through 7 and chapters 11 through 14. These chapters outline algorithms that model the complete state space of failing program executions against passing executions and isolate defects by shrinking the differences. Halloway points out that the Clojure community is uniquely qualified for this challenge:

> “Do we know how to do shrinking in the Clojure world? We're pretty good at that right we have um test.check and test.generative there are algorithms for this.”

While Zeller's book demonstrates these concepts using Python, Halloway challenges attendees to implement them in Clojure for upcoming conferences like ClojureWest or the next Clojure/conj. Capturing and comparing state spaces across program runs is notoriously hard in mutable, object-oriented codebases, but Clojure's emphasis on immutable data structures and explicit, minimal state makes it an ideal platform to automate scientific debugging.

[47:21](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2841s) Clojure, Code as Data, and the Core Debugging Checklist

Reflecting on automated debugging techniques, Stuart Halloway notes that if such automated approaches can be implemented for a Python program, they ought to be substantially easier to build in Clojure. Because Clojure treats code as data and passes everything around as data, developers in the Clojure ecosystem are uniquely well-positioned to implement and take advantage of these sophisticated diagnostic algorithms. As Halloway states: “And oh by the way, having a language that models code as data and passes everything around as data, we are uniquely in a good position to implement these kinds of algorithms.”

Looking back over the technical rules of thumb covered throughout the presentation, Halloway recaps the core software-specific tenets of debugging: - Work at a high level of abstraction. - Remember the fault is not the defect. - Remember that garbage collection (GC) did it. - Read the manual twice. - Don’t trust, because people lie. - Reproduce the problem. - Go out and automate things.

Recognizing that a six-bullet list might be too much for an audience to retain after an exhausting conference day, Halloway strips the discipline down to its most fundamental scientific essence.

---

[47:55](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2875s) Scientific Discipline: Well-Founded Choices, Plushy Cthulhu, and Writing It Down

To make the methodology as simple and actionable as possible, Halloway distills debugging into three foundational habits: knowing where you are going, making well-founded choices, and writing down your reasoning before acting. Invoking Yogi Berra, he points out that if you know which direction you are heading, you stand a much better chance of arriving there. The biggest trap for novice and veteran engineers alike is reacting frantically under pressure. When a system breaks and deadlines loom, developers instinctively panic and declare that they will just try a random tweak. Halloway urges engineers to immediately pause and challenge that impulse: “Stop and ask yourself, why should I try that?”

```mermaid flowchart TD accTitle: The Scientific Debugging Decision Flow accDescr: A flow illustrating the shift from haphazard trial-and-error to hypothesis-driven debugging. subgraph Reactive["The Pressure Trap"] A["Bug Occurs / System Fails"] --> B["'I'm just going to try this'"] end subgraph Scientific["The Scientific Approach"] B -.->|"Pause and Challenge"| C["Formulate a Hypothesis"] C --> D["Explain to Plushy Cthulhu"] D --> E["Justify Action in Writing"] E --> F["Focused, Directed Cruise to Solution"] end ```

To break the trial-and-error cycle, Halloway suggests explaining the problem to an inanimate object, such as a plushy Cthulhu, rather than immediately pulling in another person. Joking about a glitch in his own talk, he suggests telling the toy: “Plushy Cthulhu, I have this idea about what’s going wrong and why my Keynote presentation didn’t do what it was supposed to do during the middle of the talk; maybe you and I can sit down afterwards and I’m going to talk you through—I’m going to give you a hypothesis.” The cognitive effort of formulating that hypothesis and recording it in writing transforms troubleshooting entirely. As Halloway summarizes: “These those two steps—making good choices and writing your steps down—are going to turn a haphazard random walk around into a directed, focused cruise to an easy solution and an early night relaxing at the bar with your friends.”

- **Know where you are going:** Heading in a deliberate direction dramatically increases the likelihood of reaching a solution. - **Explain the hypothesis:** Talking through an assumption—even to a desk toy like a plushy Cthulhu—forces logical rigor. - **Justify actions in writing:** Writing down the rationale before modifying code stops random trial-and-error and ensures focused, reproducible progress.

## Notable quotes > [13:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=839s) the scientific method may not be all that great for science but it absolutely rocks for debugging. — Stuart Halloway

> [05:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=359s) there is no TDD out there that stands for Tool directed development; we wield the tools, they don't wield us. — Stuart Halloway

> [40:07](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2407s) good docs for debuggers are specs. — Stuart Halloway

## Mentioned Stuart Halloway, Alex Miller, Rich Hickey, Carin Meier, Colin Fleming, Andreas Zeller, Edward Tufte, Thomas Kuhn, Yogi Berra, Cognitect, Clojure, Datomic, Cassandra, H2, HornetQ, Netty, Leiningen, Cursive, Midje, Pipethrough, nREPL, Prismatic Schema, Logback, Keynote, OmniGraffle, The Structure of Scientific Revolutions, Why Programs Fail, House, M.D., Ocean's Eleven, Mastermind, Strange Loop, Clojure/conj, ClojureWest

## Video description About the speaker: Stuart Halloway (@stuarthalloway) is a founder and President of Cognitect (formerly Relevance). He is a Clojure committer, and a developer of the Datomic database. Stuart has spoken at a variety of industry events, including Strange Loop, Clojure/conj, EuroClojure, ClojureWest, SpeakerConf, QCon, GOTO, OSCON, RailsConf, RubyConf, JavaOne, and NFJS. Stuart has written a number of books and technical articles. Of these, he is most proud of Programming Clojure.

[Source](https://www.youtube.com/watch?v=FihU5JxmnBg)

中文简报

用科学方法进行调试 - Stuart Halloway 在本次演讲中,Stuart Halloway 提出软件调试从根本上比专业科学本身更具科学性,并主张采用假说驱动的探究,而非由工具主导的被动盲目尝试。他将排错周期拆解为清晰的问题表述、按比例缩小搜索空间,以及剔除非必要工具的单变量受控实验。结合 Clojure 和 Datomic 的实际案例,Halloway 强调了关键的 JVM 现实问题——例如内存压力会伪装成看似毫不相干的离奇异常——并坚持认为用书面形式记录假说和观察结果对避免认知过载至关重要。最后,他敦促社区利用 Clojure 的不可变性与“代码即数据”架构,实现增量调试和状态缩减等诊断搜索算法的自动化。 软件调试估计占开发成本的 50% 或更多,但人们往往通过被动使用工具来应对它,而非开展系统性的假说检验。 (0:00) 与学术界的科学不同,软件调试是纯粹的演绎推理,且不受社会政治偏见的影响,因为系统本身就提供了现成的宏观理论。 (13:59) 有效的排错依赖于按比例缩小以二分搜索空间,并在每次实验中严格仅改变一个变量。 (25:02) 不记录假说和观察结果会迫使工程师在大脑中维持复杂的的状态空间,从而使调试变成一个不必要的棘手元问题。 (33:59) Clojure 的不可变数据结构和“代码即数据”模型,使其非常适合通过状态缩减算法实现科学调试的自动化。 (44:57)

Detailed notes — zh

## Background 该视频由 ClojureTV 于 2015 年 11 月 17 日在 YouTube 平台发布。视频记录了 Cognitect 创始人兼总裁、Clojure 核心提交者及 Datomic 开发者 Stuart Halloway 的主题演讲,主要探讨如何将严谨的科学方法应用于软件调试流程中。 ## TL;DR Stuart Halloway 提出软件排错的成本高昂,开发者不应过度迷信具体的辅助工具,而应将调试视为一种基于演绎推理与状态搜索的科学探究循环。他结合科学哲学与哲学词源学,阐明了从故障出发,通过提出可按比例缩减搜索空间的假设、设计单变量极简实验及收集一手观察来进行证伪与精炼的完整闭环。演讲结合生产环境中的真实案例,揭示了“故障不等于缺陷”、“在 JVM 中往往是垃圾回收(GC)在作祟”以及“排错时不要盲信口头断言”等核心工程法则。最终,他呼吁开发者在动手前务必落实书面记录,甚至利用不可变数据结构将科学调试算法走向自动化,以此告别无序试错并大幅提升排查确定性。 ## Atmosphere & style 整场演讲充满典型 Clojure 社区推崇的哲学思辨与理性幽默,Stuart 举重若轻地在严肃的形式逻辑、科学史典故(如库恩的范式论)与美剧《豪斯医生》式的自嘲吐槽间自如切换。现场氛围轻松而专注,讲者不仅频频拿自己的白头发、极致懒惰和对早点收工喝红酒的渴望开玩笑,更以极具穿透力的反直觉工程金句敲打盲目自信的开发者。观众在阵阵会心笑声中,体会到一种将杂乱混沌的“玄学排错”重塑为清晰、从容且高度确定性的系统方法论力量。 ## Bulletin - [13:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=839s) 软件调试本质上是一场演绎推理与状态空间搜索,相较于归纳性的宏观科学,调试没有政治偏见和道德阻碍,更加贴合假说演绎的科学方法闭环。 - [05:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=359s) 工具本身并不提供排查方向,甚至弱科学思维都强于重型工具;未经规划就盲目使用调试器或修改代码往往会进一步破坏故障现场。 - [33:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2039s) 科学排错最关键的行动是将问题陈述、待证假说与实验依据落于书面,拒绝记录本质上是程序员试图用有限脑力记忆庞大状态空间的傲慢表现。 - [38:13](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2293s) 在 JVM 和 Clojure 生态中要谨防垃圾回收(GC)假象,且故障表现(Fault)绝不等于缺陷本身(Defect),任何未经最小隔离复现的口头断言都不可轻信。 ## Key takeaways - [00:00](https://www.youtube.com/watch?v=FihU5JxmnBg&t=0s) 软件维护成本中超过 50% 消耗在排错上,科学调试能让工程师免于心智耗竭,高效收工享受个人生活。 - [05:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=359s) 工具驱动开发(Tool Directed Development)是一个误区,工具只能提供更多可观测输出,唯有科学方法论才能指引排错方向。 - [09:46](https://www.youtube.com/watch?v=FihU5JxmnBg&t=586s) 科学调试循环由故障、假说、实验与观察构成,证伪是排除错误可能性的利好结果,而精炼则是收敛核心变量的减法过程。 - [11:46](https://www.youtube.com/watch?v=FihU5JxmnBg&t=706s) 切忌对自己的假说产生情感依附;即使有 99 条证据支持,只要存在一条确凿的反例证据,该假说就必须被彻底推翻。 - [17:22](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1042s) 根据可能世界语义学,排错旨在找出让故障不发生的“最近可能世界”中的实际原因,而系统修复本质上只是验证假说的最后一个实验。 - [21:44](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1304s) 在极简代码报错场景下,自底向上的 REPL 局部求值等微弱科学推演,其通用性与效果往往胜过依赖外部环境的重型工具。 - [23:42](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1422s) 严禁使用含糊的“它坏了”作为问题描述,一个合格的问题陈述必须清晰包含所采取的具体步骤、预期结果以及实际发生的结果。 - [25:02](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1502s) 构造假说应追求按比例缩减(Proportional Reduction)搜索空间,而非像盲目玩“二十个问题”那样在缺乏依据时胡乱猜测底层系统。 - [28:56](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1736s) 优质实验必须是严格的单变量控制,构建复现用例时应剔除所有未被假设直接指控的周边插件与测试框架。 - [31:05](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1865s) 重视系统产生的全部输出,绝不要随意忽视看似不相关的日志报错(例如类路径冲突引发的日志配置异常),它们常是连锁反应的线索。 - [33:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2039s) 把事情写下来是排错的第一法则,如同玩 Mastermind 游戏必须保留猜测记录,单凭心算追踪状态是效率极低的傲慢行为。 - [35:51](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2151s) 故障表现绝不等于系统缺陷,抛出异常的位置往往只是深层逻辑矛盾在最后暴露的表象,切勿武断归咎于基础设施。 - [38:13](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2293s) 在垃圾回收环境中,GC 停顿与濒临 OOM 经常打乱线程调度并暴露出罕见竞态条件,导致异常伪装成与内存完全无关的错误。 - [40:07](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2407s) 调试者应当精读简短且不重复的规范文档(Specification),而非动辄翻阅上千页充斥冗余上下文的入门教程。 - [41:34](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2494s) 践行豪斯医生“所有人都会撒谎”的法则,非恶意的沟通失误极为常见,任何跨环境必现/不现的断言都必须通过最小隔离脚本亲自验证。 - [44:57](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2697s) 调试本质上是对程序状态空间的搜索,应借鉴 delta 调试算法与基于属性的测试,借助 Clojure 数据不可变的天然优势推进排错自动化。 - [47:51](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2871s) 在按下键盘尝试修复前必须暂停盲动,运用毛绒玩偶进行推演,在书面上充分论证行动合理性后再执行实验。 ## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=FihU5JxmnBg&t=0s) 开场调侃与演讲动机(Rationale) Stuart Halloway 在掌声中登台,测试过两个麦克风后,先对主持人 Alex Miller 刚才的介绍做了幽默的澄清。他调侃说,Alex 刚才似乎梳理出了一条因果链,暗示自己要对 Strange Loop 大会的诞生负责,“我很高兴发现这一点,已经迫不及待想收到寄来的支票了”。针对 Alex 提到的在 Cognitect 产品团队能常常观察到调试过程,Stuart 打趣道:“实际上 Datomic 根本没有 Bug,所以我也不知道他到底在说什么。” 言归正传,面向 Clojure 社区的听众,Stuart 强调必须从“设计初衷与理由”(Rationale)切入,因为这是 Rich Hickey 根植在社区中的核心价值观。他为此准备了一套多层次的演讲动机。首先是“为什么要关注调试”: 1. **现实代价**:软件 Bug 无处不在,有估算认为,软件开发中超过 50% 的成本实际上都耗费在后续追踪 Bug 上。尽管有些人身处的环境可以写完代码就脱身跑路,但如果你长期负责并维护一个系统,就会深刻体会到这一点。 2. **看似高深实则直截了当**:调试是一项相当规范直接的活动,但外界普遍认为它极难。Stuart 坦言在座听众的才华和成就常令他感到自卑,大家在各种他不曾涉足的领域都是专家,而调试这种技能却能让你“无需耗费太多脑细胞,就能显得格外聪明”。 3. **理解 Clojure 的美学与演化**:理解 Stuart 以及 Rich Hickey 思考调试的方式,有助于理解 Clojure 语言设计、演进流程及其美学取向。社区对于入门体验、文档组织乃至异常处理往往有各种不同的意见,科学的调试方式会直接决定你在其他领域选择何种工具与实践。 ### [03:37](https://www.youtube.com/watch?v=FihU5JxmnBg&t=217s) 讲者的底气:长期维护、灰色头发与“懒惰” 关于为什么由他来谈论这个话题,Stuart 提出了几项硬核资质。作为 Clojure 的代码审查者(screener),Clojure 拥有不同于普通 GitHub 项目的严格流程,自 1.0 版本发布以来,几乎所有的 commit 都是由他推上去的,他阅读了合入 Clojure 的每一行代码,也见过更多因各种原因未被采纳的代码,审查过真正的 Bug 以及被误认为是 Bug 的情况。此外,在 Datomic 商业支持中,他通常是疑难杂症的“终极防线”(place of Last Resort),长期维护系统的经验让他见识了团队自身引入的 Bug、外部用户发现的 Bug 以及用户自身系统的各类疑难问题。 除了技术履历,Stuart 还给出了两个更个人化的理由: - **岁月沉淀的经验**:他指着自己的头发自嘲这是诉诸权威,“我的灰白头发是实打实熬出来的,刚开始做调试时头发一点都不白”。虽然另一种假说是这与养育孩子有关,但他确信大部分白发都与调试直接相关。 - **极致的懒惰**:“我真的很懒,不过我还不是世界上最懒的人,因为要做到极致懒惰你还得有竞争心”,Stuart 如是说。程序员天生追求省力的诀窍,他真正追求的是一套高效解决问题的方法体系,让他能在绝大多数日子里“在下午 5 点合上笔记本电脑,陪伴孩子,享用美味的晚餐并喝一杯红酒,看一眼 HBO 正在热播的暴力电视剧,然后安心上床睡觉”。 ### [05:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=359s) Clojure 社区的痛点:挫败的新人与工具迷思 谈及为何在 Clojure 语境下讨论调试,Stuart 提到了同事 Karen Meyer 分享的一个 Reddit 讨论帖。帖子的核心反映了大量新手在 Clojure 中的真实痛苦体验:面对晦涩古怪的报错信息(cryptic error messages)、在配置依赖或 Leiningen 插件时完全不知道从何处着手排查,抑或是遭遇在 Java 中能被编译器秒级捕获但在 Clojure 中却让人晕头转向的“类型错误”。许多人面对 Clojure 的调试感到充满挫败与不适,而 Stuart 自身的体验截然相反,因此他希望分享这套思路,让调试过程变得顺畅愉悦。 现场调查显示,许多听众都参与过 Clojure 生态中编辑、调试、数据可视化或执行流可视化等工具的开发。Stuart 明确表示自己绝非反对工具,但如果去 Google 搜索“debugging”,绝大多数结果(远超一半)全都是关于工具的。然而,工具本身并不提供方向: > “我们驾驭工具,而不是工具驾驭我们。”——Stuart Halloway ```mermaid flowchart TD accTitle: 工具与方法论的关系 accDescr: 展示了在缺乏清晰目标时工具容易让人迷失,唯有科学方法论才能有效驾驭调试工具。 subgraph S1["调试的思维盲区"] A["遭遇 Bug 现象"] --> B["在缺乏计划时急于尝试"] B --> C["被工具功能牵着走"] C --> D["迷失排查方向(陷入歧途)"] end subgraph S2["科学方法论导向"] E["遭遇 Bug 现象"] --> F["明确方向与目标假说"] F --> G["有针对性地操纵调试工具"] G --> H["验证假说并定位根因"] end ``` 软件工程中从来没有所谓“工具驱动开发”(Tool Directed Development,TDD)。Stuart 引用棒球名宿 Yogi Berra 的名言警示大家:“如果你不知道自己要去哪儿,你很可能会走到别的地方去(If you don't know where you're going, you might wind up someplace else)。”在商业技术支持中,当接手一个故障时,技术人员最强烈的愿望往往是“穿越时空,回到客户刚发现不对劲、还没开始胡乱操作的那一刻”。缺乏明确计划就盲目动手试错,往往会将现场破坏得更加混乱。由此,建立一套基于科学方法的系统性调试思维,远比单纯依赖工具更为根本。 ### [09:46](https://www.youtube.com/watch?v=FihU5JxmnBg&t=586s) 彩虹图与调试中的科学方法循环 Stuart Halloway 回忆起自己在大学拿了科学学位后,其实很少再去专门阅读别人关于“科学方法”的论述。为了准备这次演讲,他打开 Google 搜索为什么人们要关注科学方法,结果映入眼帘的几乎全是五彩斑斓的彩虹色流程图。他打趣说,像 Edward Tufte(著名信息图形专家)这样讲究数据可视化的人看到这些毫无意义的配色大概会抓狂。于是,受限于自己使用 OmniGraffle 软件的水平,Halloway 制作了一张剔除了所有无意义色彩、只保留黑白线框的科学方法调试图。按照 Clojure 社区推崇的传统,他在引入每个概念时都给出了词源学(Etymology)层面的解释,并幽默地给未来的会议提案提交者支招:只要在摘要里引用两三个生僻词并带上词源解释,提案被录取的概率就会大增。 调试的流程总是始于一个“故障”(Failure)。从词源上讲,故障就是“缺乏成功”,或者更贴切地说,是“预期行为的遗漏”——即你期望系统表现出某种行为,它却给出了截然不同的结果。面对故障,开发者会提出一个“假说”(Hypothesis),即在缺乏完整证据的情况下做出的解释,它作为进一步探究的起点。有了假说后,需要设计并执行一个“实验”(Experiment,即测试或试炼),并通过实验做出“观察”(Observation,即从第一手来源主动获取信息)。 ```mermaid flowchart TD accTitle: 科学方法在调试中的循环流程 accDescr: 展示从故障出发,提出假说、执行实验、收集观察,进而走向证伪或者精炼理论的循环结构。 subgraph Problem["问题起点"] F["故障 (Failure)<br/>预期行为的遗漏"] end subgraph Loop["核心探究循环"] H["假说 (Hypothesis)<br/>未完全证实的解释"] E["实验 (Experiment)<br/>测试或试炼"] O["观察 (Observation)<br/>主动获取的一手信息"] end subgraph Outcomes["实验结果"] FA["证伪 (Falsification)<br/>否定后件,假说作废"] R["精炼 (Refinement)<br/>剔除杂质,缩小范围"] T["理论 (Theory)<br/>被预测反复验证"] end F --> H H --> E E --> O O -->|"观测结果冲突"| FA FA -->|"杀死可能分支"| H O -->|"观测结果支持"| R R -->|"递进迭代"| H R -->|"充分收敛"| T ``` --- ### [11:46](https://www.youtube.com/watch?v=FihU5JxmnBg&t=706s) 证伪、理论与对假说的情感依附 在一个设计良好的实验中,可能出现两种同样令人欣慰的结果。第一种是观察结果将假说“证伪”(Falsification)。证伪是一个运用形式逻辑中否定后件律(Modus Tollens)的演绎过程:如果假说为真,那么某种现象 $O$ 就不应该出现;然而实验中却偏偏观察到了 $O$。如果逻辑推理足够严密,那么这个假说就被宣判死亡。Stuart Halloway 指出,在日常争取科研基金的学术界,假说被证伪可能令人沮丧,但在软件调试中这绝对是个大好消息,因为你成功排除了一种错误可能性。第二种结果是实验支持了该假说,但证据仍不足以将故障彻底隔离,此时就需要进行“精炼”(Refinement)。精炼在词源上是一个减法过程,旨在去除杂质或多余成分;尽管为了排除干扰项,陈述假说的语句可能会变长,但它所指向的概念核心正在不断收敛并聚焦于实际问题。 经过多次迭代后,假说最终会上升为“理论”(Theory)——即经过各种预测验证的假说。然而,理论并不意味着绝对真理,而仅代表“在目前所掌握的一切信息下它是成立的”。Halloway 警告称,很多工程师在调试时常犯一个严重错误,那就是对自己的假说产生了非理性的情感依赖: > “I have 99 pieces of evidence in favor of this is where the bug is and I have one little piece of evidence that flat out contradicts it and I don't understand that piece of evidence and so, well 99's pretty good I'm just going to keep going right? If you have that one little piece of evidence that says you're dead, you're dead.” > —— Stuart Halloway 即使有 99 条证据支持你的假说,只要有一条你无法解释的铁证与之直接矛盾,假说就必须被推翻;绝不能因为自己“喜欢这个理论”就选择对矛盾视而不见。 --- ### [13:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=839s) 库恩的范式与“调试比科学本身更像科学” 在深入探究科学哲学的历史后,Halloway 发现,现实中对“科学方法究竟能否代表实际科学运作”存在大量质疑。除了某些肤浅的争执外,学界有着非常严肃的反思。例如他在研究生阶段研读过的托马斯·库恩(Thomas Kuhn)的经典著作《科学革命的结构》(*The Structure of Scientific Revolutions*)。Halloway 调侃说,库恩的理论如果用稍带偏颇的话来总结,那就是“科学其实只比政治或宗教稍微强一点点——在科学界,你只需等着抱持旧观念的人自然老死,而不用像政治斗争那样主动去把他们干掉”。库恩勾勒出一幅并不十分美妙的图景:科学本质上是一种极度社会化的过程,必然受到人际网络与社会优先事项的制约,进而引发诸如道德层面的挑战(例如学术界曾利用“科学”名义出版《钟形曲线》(*The Bell Curve*),试图在智力上给人种分群的恶劣先例)。 然而,这一连串足以在学术界引发剧烈争吵的社会与道德问题,在软件调试领域却完全不存在。Halloway 得出了一个大胆而风趣的结论: > “It turns out that debugging, if the scientific method is the measure of how science is done, debugging is actually more like science than science.” > —— Stuart Halloway 常规科学极其复杂,你不可能指望对着一个固定的科学方法公式“摇动曲柄”,就能自动产出万有引力定律或进化论;但调试完全不同: - **演绎而非归纳**:科学往往需要从有限现象归纳出宏大世界观;而在调试中,软件系统本身就是既定的“宏大理论系统”,工程师是在这个受限世界中通过演绎推理去证明某些极其具体的实体行为。 - **不存在政治与道德包袱**:调试在绝大多数情况下没有任何政治阻力,出现 Bug 时用户希望修复,开发者也希望解决,目标高度一致。 - **免受虚无主义困扰**:不会有学术左派跳出来声称“客观现实根本不存在,因此你无法证明这里有一个 Bug”。科学方法用在宏观科学上或许差强人意,但用于调试却无往不利。 --- ### [17:22](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1042s) 原因、实际原因与“修复不过是最后一个实验” 为了让概念更加贴合排错与调试,Halloway 进一步将“假说”细化为“原因”(Cause)。在哲学和逻辑层面上,“原因”被定义为“先于结果发生的一个事件,若没有该事件,该结果就不会发生”。这个定义看似完美,但用于实际工程却远远不够。例如,“宇宙的存在”也是某个软件 Bug 出现的前提——如果没有宇宙,这个 Bug 绝不会发生;但把宇宙归为原因对于修代码没有任何指导意义,因为开发者无法改变宇宙。 因此,调试的关键是要从广义的“原因”收敛到“实际原因”(Actual Cause)。借用可能世界语义学的概念,实际原因被定义为:“实际世界与结果不发生的‘最近可能世界’之间的差异”。撇开严密的哲学数理推导,在直觉层面,如果在引发故障的五个要素聚合中,改变其中某一个要素就能使故障消失,而保留另外四个则无法消除故障,那么被改变的这一项就更接近实际原因。整个排查过程就是在不断做减法,逼近这个核心变量。最后,Halloway 重新定义了“修复”(Fix): > “In this context, a fix is just the last experiment. The first experiment is reproducing the bug, and then there are a bunch of experiments that are about hypotheses, and a fix is the last experiment.” > —— Stuart Halloway 这一论断揭示了一个极其核心的事实:复现 Bug 是整个流程的第一个实验,中间验证各种推测是实验,而最终所谓的“修复”本质上只是验证系统行为恢复正常的最后一个实验。如果你部署了修改,却没有通过一次严密的实验证明 Bug 确实被消除,你就根本谈不上完成了修复。 ### [19:07](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1147s) 假说的验证与一个极简的 Stack Overflow 案例 Stuart Halloway 指出,在真正修复问题之前,你可能强烈怀疑自己找到了 Bug,但这都算不上真正确认了它:“You may be strongly suspicious that you've identified the bug, but you haven't actually identified the bug until you fixed it.” 随后,他决定先以一种极其草率、随性的方式,将科学方法应用到一个他在 Stack Overflow 上找来的微型调试问题上——这个代码极其简短,台下听众甚至能在脑海中直接心算推演。 问题源于提问者的一行代码: ```clojure (def partial-join (partial clojure.string/join ",")) (partial-join "foobar") ``` 执行后程序抛出了一条看似可怕的报错信息:`ClassCastException: Cannot cast java.lang.String to clojure.lang.IFn`,并且还夹杂着一串冗长的内部编号。初学者看到这种堆栈信息,往往会觉得可怕、不知所云,以为根本不可能搞清楚到底出了什么问题。 ### [20:02](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1202s) 工具 vs. 科学方法:通用思维与具体技术的较量 面对这样的错误,开发者拥有一整套应对方案与改进路径。例如可以去改进错误信息,让编译器直接给出贴心的指导,比如“我发现你试图用 `s/join` 创建 partial……”;社区甚至可以试图穷举开发者可能掉进去的各种坑,但即便不做这么极端的穷举,努力去改善报错信息依然非常有价值。此外,更好的文档、使用交互式调试器(Debugger)、乃至编辑器的语法高亮与括号匹配(Parenthesis highlighting),都可以提示问题所在;更不用说静态类型检查(Static typing)或 Schema 验证了——在静态类型系统中,这种传参错误甚至在编译阶段就会被拦截下来。甚至因为代码太短,程序员单靠“干瞪眼”(Staring at it)可能也能猜出问题。 然而 Stuart 强调,在这些工具之外,真正占据根基地位的是思维方法。科学方法比上述任何具体工具都更为通用,且它所要求的先决条件最低:“Science is more general-purpose and requires less on hand to do than any of these other things.” 学界可能会称之为“假说演绎法”(Hypothetico-deductivism),但核心就是科学方法。相比其他强依赖外部环境的辅助手段,科学方法是更好的“秘方”(better sauce),因为它不需要世界为你预备任何现成的基础设施,只需要带着脑子即可介入排查。 ### [21:44](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1304s) REPL 中的自底向上验证与微弱科学的胜出 Stuart 随后用非常随意的逻辑拆解该问题。既然整个代码表达式只有三个要素,他的假说便极其模糊而直白:要么是 `join` 没有按预期工作,要么是 `partial` 出了问题,要么是 `def` 不对劲。 对于这种微小问题,Stuart 提出了一个经验法则(Heuristic):在 REPL 中采用自底向上(Bottom-up)的方式进行验证,即优先测试最底层、最内层的求值形式(Form)。 ```mermaid flowchart TD accTitle: REPL自底向上验证流程 accDescr: 展示从最内层代码求值到暴露类型转换异常的推导过程 subgraph Step1["步骤 1: 评估最内层 Form"] A["(clojure.string/join ',')"] --> B["求值结果: ',' (单字符串)"] end subgraph Step2["步骤 2: 代入外层函数求值"] B --> C["(partial ','...)"] C --> D["错误根源: 将 String 当作函数 (IFn) 调用"] end subgraph Step3["结论"] D --> E["抛出 ClassCastException"] end ``` 在 REPL 中求值最底层的 `(clojure.string/join ",")` 时,直接返回了 `","`。这显然不是开发者预期的行为,因为如果只是要一个逗号字符串,直接传逗号就行了。紧接着将其代入外层:`(def partial-join (partial ","))`,这相当于把字符串 `","` 当成了需要调用的函数传给了 `partial`,自然在后续执行时触发了无法将 String 转型为 `clojure.lang.IFn` 的异常。 仅仅在 REPL 里执行了一步最底层的求值,问题就水落石出。尽管在此过程中问题陈述极其模糊、假说粗糙至极(充其量只是“这里痛还是那里痛”)、实验没有明确的指标,且假设排查者缺乏深厚的领域知识,但科学方法依然瞬间奏效了。Stuart 总结道,即便在条件极其恶劣的场景下,“弱科学也比强工具管用”(Weak science is stronger than strong tools)。 ### [23:42](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1422s) 构建严谨的问题陈述:告别“程序坏了” 在微型问题上验证了原理后,Stuart 指出微型问题其实掩盖了调试的真实难度——对于极简代码,不管用什么手段都能搞定。真正要应对复杂棘手的大型现实问题,就必须把科学方法的每一步做到极致:清晰的问题陈述、高效的假说、精良的实验、有效的观测以及书面记录。 关于问题陈述,最恶劣的反例莫过于一句干瘪的“它坏了”(It didn't work)。这类表述用代词把所有关键上下文全部遮蔽了。一个合格的问题陈述必须清晰包含三个核心要素: 1. 你采取了哪些具体步骤(Steps taken); 2. 你预期的结果是什么(What you expected); 3. 实际发生了什么(What actually happened)。 Stuart 调侃道:“This slide right there, that's worth a hundred bucks. Send me your checks.” 他解释说,把模糊的抱怨具体化为这三点并不困难,而且很多时候,仅仅是将“它坏了”转变为精准的语言叙述(例如“我走出车门,车开始滑行……哦,原来是我没挂空挡”),大脑就会在梳理过程的同时直接顿悟出 Bug 究竟在哪。这比一句“它坏了”高出了整整一个数量级。 ### [25:02](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1502s) 高效假说:按比例缩减与“二十个问题” 在切换幻灯片时现场 Keynote 出现小故障,Stuart 幽默地将其作为“现场调试”的即兴演出。恢复后,他展示了电影《十一罗汉》(Ocean's Eleven)中凯西·阿弗莱克(Casey Affleck)饰演的莫洛伊双胞胎兄弟(Malloy twins)玩“二十个问题”(Twenty Questions)的滑稽桥段:一人刚问了“我是活人吗?”(是)和“我是人类吗?”(是),另一个人下一句立刻盲猜“我是不是特技演员埃维尔·克尼维尔(Evel Knievel)?”,被当场痛骂。Stuart 笑称这就像他五岁儿子玩猜谜时,上来第一句就问“我是一棵高大的橡树吗?”一样荒唐。 这种盲目乱猜恰好是构造假说时最忌讳的做法。构造假说最理想的目标,是每一次提问或测试都能精准地将可能性空间“一刀切成两半”: - **算法命名的辨析**:虽然大多数人将其称为“分治法”(Divide and conquer),但算法专家可能会纠正这应该叫“减治法”(Decrease and conquer),因为分治通常要求遍历两侧分支。然而 Stuart 认为“减治”这个词不够好,因为它可能暗示按常数递减(每次只排除 1 项的线性时间)。 - **按比例缩减(Proportional Reduction)**:核心特征必须是按比例缩小搜索空间。即便做不到完美的五五开(50%),只要每一步能稳定剔除 10% 的可能性,就能在极短的步数内锁定目标,这与每次排除一半在复杂度上仅差一个常数因子。 要提出能将世界切掉一半的有效假说,离不开**领域知识**(Domain knowledge)。缺乏领域知识时,最初的假说必须极其谨慎,否则很可能在第一步就把真正的原因排除在宇宙之外。 但绝大多数开发者在面对具体技术栈时,本能地知道哪里最容易出问题。Stuart 抛出设问:当应用崩溃时,有多少人会去怀疑是“物理定律失效了”?在软件栈的排查中,极少需要深入到底层物理世界:“We've never had a Clojure bug report that we had to forward on to physics.” 实际上,开源项目收到的 Bug 报告中,极少有需要归咎于 JVM 的,操作系统层面的都屈指可数,绝大部分都在应用层本身。构建起对可能性空间的层次认知,就能快速运用比例缩减排除庞大的无关领域。 ### [28:56](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1736s) 什么是优质的实验与可复现用例(Repro Case) Stuart Halloway 指出,一个优秀的实验必须具备三个核心特征:可复现(以复现 Bug 为起点)、由假设驱动(而非无目的的“碰运气试错”),以及足够微小。当对系统进行修改以验证推测时,每次只能改变一个变量;如果同时改动两处,发生变化时你根本无法辨别到底是哪项修改产生了效果,也就没有真正获取到任何有效信息。 ```mermaid flowchart TD accTitle: 科学实验排错的构成要素 accDescr: 展示优质实验的核心要素以及剥离无关依赖以构建极简复现用例的过程 subgraph Experiment["优质实验的标准"] A["提出假设"] --> B["单变量实验:一次只改动一处"] B --> C["获得确定性增量信息"] end subgraph Repro["极简复现用例 (Repro Case)"] D["应用中的真实 Bug"] --> E["剥离环境依赖<br>(cursive, clojure.test, lein 等)"] E --> F["极其微小的复现用例"] end ``` 为了加深理解,Halloway 抛出了一个随堂小测验:如果在自己的应用中发现了一个 Bug,打算转交给队友协作排查,以下哪些东西**不应该**出现在复现用例中?选项包括 `clojure.test`、Cursive、Prismatic Schema、Midje、Puppy (Pipkin)、nREPL、Leiningen 及其插件、core.typed、test.generative。答案是一道陷阱题:除非你的假设直接断定 Bug 出在这些工具本身(比如“当在 Cursive 内部使用 `clojure.test` 时,购物车系统失效”),否则**以上任何一项都不应该包含在内**。不仅同事会感谢你,Cursive 的作者 Colin 也会松一口气,因为他绝不想收到与 IDE 毫无关联的业务代码报错。提炼出极其小巧、剔除所有非核心依赖的最小复现代码,是科学排错的免费利器。 - 优质实验的铁律:以复现 Bug 为始、由明确假设驱动、范围极小、严格保持单变量变更。 - 只有当故障假设直接指向某一周边工具时,该工具才有资格留在复现代码中。 - 剥离无关依赖不仅能减轻心智负担,也是缩小搜索空间的最快途径。 --- ### [31:05](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1865s) 审视全部输出:Logback 日志配置引发的离奇血案 在观察系统现象时,开发者必须理解系统产生的**全部输出**。如果系统给出的四五个输出你都能看懂,偏偏有一个看似不相关的输出你不明白,那么排查就很容易脱轨——若你根本不懂它,又如何断定它与眼前的问题无关?此外,排查时要对“相关性”保持极度怀疑:Bug 往往就潜藏在刚刚写下的最后五行代码里。包括调试器、日志、监控指标在内的工具,本质上都是在提供更多输出,将“黑盒”转变为“白盒”。 Halloway 分享了自己前一周亲身经历的真实案例:他在调试一个 Clojure 应用时,某个子系统报出了非常晦涩难懂的错误,而在日志里同时出现了一条完全无关的报错,提示 Logback(Java 常用日志组件之一)的配置文件加载异常。Halloway 起初下意识觉得这绝不可能与问题相关,但他决定先暂停手头的业务逻辑排查,花两分钟把 Logback 的异样查清楚,确保日志输出是干净可信的。 结果证明,问题恰恰出在 Logback 这里。在构建阶段,某个库因构建配置疏漏被重复复制进了 `lib` 目录两次,分别带有 2.1 和 2.2 两个不同版本。JVM 在类路径上遇到同名且部分重叠的类包时,会各自加载一部分互不兼容的类定义,从而在底层引发了极度诡异的连锁故障。Halloway 总结道:“如果不是优先清理了这个刺眼的现象,哪怕使用科学方法进行二分排查,从表面症状推导到真实原因也得耗费海量时间。” - 必须弄清全部输出:不要轻易忽略任何看似“无关”的异常日志或警告。 - 工具的真正价值在于把黑盒变为白盒,暴露更多可观察状态。 - JVM 类路径(Classpath)中若混入同一依赖的两个冲突版本,可能导致随机拼装的类加载,造成完全无关子系统的崩溃。 --- ### [33:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2039s) 科学排错的第一法则:动笔写下来(Write Things Down) 在演讲中,Halloway 强调了整场分享最重要的一条建议:“把事情写下来,这是我在这场演讲中给出的最重要的一条建议(Write things down. This is the single most important piece of advice I'm going to give in this talk.)。”写下清晰的问题陈述、写下每一个假设、写下实验能证明什么、写下在运行实验前为什么该实验有意义,最后写下所观察到的真实结果。 ```mermaid flowchart LR accTitle: 科学排错中的书面记录闭环 accDescr: 展示记录问题、假设、验证理由及实验观察的闭环链路 P["写下问题陈述<br>(Problem)"] --> H["写下待证假设<br>(Hypothesis)"] H --> E["写下实验意图与理由<br>(Experiment & Why)"] E --> O["写下观察结果<br>(Observations)"] O -->|修正与迭代| H ``` 为了直观展现不记录的危害,Halloway 借用了经典桌面猜密码游戏《Mastermind》(珠玑妙算)作类比。游戏底部是防守方隐藏的颜色密码,上面一排排则是攻击方历史上的每一次猜测,旁边红白相间的小图钉是对猜测提供的结构化反馈。如果不写下来,就等同于蒙住以往所有的猜测和反馈记录,指望全凭大脑记忆去追踪庞大的历史状态空间以及当前的一步。 Halloway 犀利地指出,不写记录绝非七宗罪里的“懒惰”,而是“傲慢”(Hubris)。程序员自负地以为自己是解决问题的大师,单凭心算就能在脑海中管理所有排错状态的元问题(Meta-problem),以此来炫耀智力;然而在脑中维系全局状态往往比 Debug 本身更加困难。 - 必须书面记录的内容:问题陈述、全部假设列表、实验目的及其合理性论证、实验后的实际观察数据。 - 拒绝记录是极其傲慢的表现,是在给本就复杂的排查无端增加“脑力记账”负担。 - 像玩 Mastermind 一样保留所有历史尝试与反馈,才能建立严谨的二分逼近。 --- ### [35:51](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2151s) 软件专属法则:放弃底层 C 陷阱与“故障非缺陷” 从普适的科学方法转向软件工程领域,Halloway 提出了两项专门针对软件开发的规劝。第一条是针对语言选择的直言劝告:在 2015 年的大多数业务领域中,“不要使用 C 语言(Don't use C)”。计算机调试历史充斥着底层内存与未定义行为带来的巨大陷阱,除非所在的专业领域绝对无法运行 Java、Clojure 或 Python,否则完全没必要把自己绑在不必要的抽象层级上去直面徒增的 Bug。 第二条软件原则是:“故障不等于缺陷(The failure is not the defect)。”在软件中最常见的误区,就是认定抛出异常的地方就是真正的病灶所在。异常确实是 Bug 引发的一种表象,但它与真正的逻辑缺陷绝不是直接等价的。 作为论据,Halloway 分享了 Datomic 运行中的一个经典故障:一个涉及大查询的系统出现了极高的 CPU 占用率,紧接着在 HornetQ 消息队列中抛出了 `IllegalStateException`。多数人看一眼报错就会立刻下定论:“`IllegalStateException`?那一定是 HornetQ 坏了!”但 Halloway 表示事实完全不是这样,HornetQ 是一套极其稳定出色的软件,Datomic 从 0.0 版本就开始集成它。在团队与 HornetQ 深度交道的历史中,唯一一次真正定位到底层代码问题,最后发现问题甚至不在 HornetQ 自身,而是其下层的 Netty 在特定 SSL 场景下的极端罕见 Bug。 > “故障不等于缺陷,而且这种情况在软件中最常出现的形式,就是臆断异常与实际问题有着直接对应的因果关系。” —— Stuart Halloway - 在绝大多数常规应用领域中,不要轻易下沉到 C 语言去承担不必要的底层陷阱。 - 严禁将“抛出异常的表象”简单等同于“系统中的缺陷本身”。 - 遇到高 CPU 伴随深层依赖(如 HornetQ)的异常时,切忌盲目推断是第三方核心基础设施存在故障。 ### [38:13](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2293s) 豪斯医生的第一法则:永远是垃圾回收(GC) Stuart Halloway 引入了美剧《豪斯医生》(House, M.D.)中的核心哲学来指导排错。在剧里有一句名言:“从来都不是红斑狼疮(It's never lupus)”。但在 JVM 和 Clojure 编程的世界里,情况恰恰相反,存在一种“反向狼疮”定律:那个表面上看起来完全不像、但实际上永远都是真凶的,正是垃圾回收(Garbage Collection, GC)。正如 Stuart 所断言的:“在 Clojure 和 Java 中,永远都是垃圾回收(in Clojure and in Java it's always garbage collection)。” 之所以 GC 总是罪魁祸首,主要有以下几个深层机制: - **未经验证的代码路径**:绝大多数应用程序根本没有设计去故意触发内存溢出(Out of Memory, OOM),因此相关的异常处理路径极少被执行和测试,一旦触发就处于完全未知的代码领地。 - **无处不在的发生点**:OOM 可以爆发在任何一行代码上,没有任何一段代码能被标记为“绝对安全、不会耗尽内存的临界区”。 - **异常伪装与级联反应**:内存分配失败发生在一个模块,引发的连锁反应可能导致最终抛出的异常信息与内存毫无关联,展现为完全不同的报错形态。 - **线程调度突变与竞态条件显现**:当系统濒临 OOM 时,GC 线程会疯狂持续运行,彻底打乱常规的线程调度顺序。那些在正常运行下可能需要 2000 年才会被撞出来的微妙竞态条件,在 GC 的搅动下短短 2000 秒就会瞬间暴露。Stuart 调侃道:“算你走运,感谢垃圾回收帮我们找出了这些竞态条件。” 因此,在垃圾回收环境中遭遇疑难杂症时,必须始终对内存保持极高的怀疑。 --- ### [40:07](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2407s) 调试者的文档法则:只读严谨的规范(Specification) 软件排错的另一条关键建议是“阅读那该死的手册(Read the entire fracking manual)”。Bug 从本质上讲最初都是“未知的未知”(unknown unknown);正因为你不知道问题出在哪里,你根本无从得知自己该翻阅手册的哪一页。这就对文档的性质提出了苛刻的要求:最适合调试者的文档,必须是简短、高度精炼且不自我重复的规范(Specification)。 教程类的文档为了便于新手入门,往往长达上千页,充斥着上下文阐述和重复说明;但用于排错的文档,理想状态下应当只有 50 页左右且毫无废话。回到之前探讨的 Clojure `join` 函数与 `partial` 的案例:如果不做实验,而是直接查阅 `clojure.string/join` 的文档字符串(docstring),就会一眼看到它的第一种 arity(单参数重载)直接接收一个集合并返回其拼接后的字符串。阅读严谨的规范文档,只需一步就能直接发现代码错传了参数。 --- ### [41:34](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2494s) 拆解生产疑云与豪斯第二法则:“所有人都在撒谎” Stuart 带领大家回到前文提到的真实案例:一个庞大的 Datomic 查询引发高 CPU 占用,抛出 `IllegalStateException`。此时运维给出了一个关键线索:“该问题在生产环境的 Cassandra 上必现,但在开发环境的 H2 上却不会出现。” 面对这句话,运用科学方法的程序员必须抱持怀疑态度。从语法和逻辑上看,“它在 Cassandra 上发生而 H2 没有”是一个连明确主语都没有的句子,充斥着模糊性。为了验证这一断言,团队排除了所有干扰项,构建了一个极致简化的独立测试:编写一个仅 10 到 15 行的 `test.java` 文件,以相同的生产数据启动 Datomic Peer,在死循环中重复执行出问题的查询。通过这种方式,团队机械化地切断了整个复杂应用层、所有第三方依赖和工具链的干扰。 ```mermaid flowchart TD accTitle: 最小化复现验证流程 accDescr: 对比 Cassandra 与 H2 环境在极简代码下的行为,戳穿未经证实的线索 subgraph Baseline ["原始模糊断言"] A["口口相传:Cassandra 必现,H2 正常"] end subgraph Isolation ["极简隔离测试 (test.java)"] B["剥离应用代码与第三方依赖"] --> C["针对生产级数据执行循环查询"] end subgraph Verification ["双环境验证"] C --> D["测试 Cassandra 存储"] C --> E["测试 H2 开发存储"] D --> F["结果:OOM (GC 假象),内存爆满崩溃"] E --> G["结果:同样 OOM 崩溃!"] end Baseline --> Isolation F & G --> H["结论:断言破灭,两端行为完全一致"] ``` 在极简代码下运行 Cassandra 测试后,发现系统抛出异常的根本原因正是内存耗尽崩溃——“结果证明它就是红斑狼疮(GC/内存用尽)”。接下来,由于怀疑 Cassandra 驱动存在严重内存泄漏并不合常理,团队进一步克制住了“脱离证据做假设”的冲动,将同一套极简测试跑在本地 H2 数据库上。结果出人意料:**在 H2 上问题同样被复现了。** 这印证了豪斯医生的另一条名言:“所有人都在撒谎(Everybody lies)”。Stuart 特别澄清,这里的“撒谎”绝非指道德恶意或故意掩盖错误,而是指沟通中出现的非恶意失误(Accidental miscommunication):有人未经精确验证就传达了失实信息。如果不先通过科学手段构建最小复现,双方团队很可能会坐在会议室两端互相甩锅,让排错彻底陷入死局。 - 永远不要把意外的沟通失误归咎于恶意(Never attribute to malice what you can attribute to accidental miscommunication)。 - 任何未经独立严格证实的“线索”,都不能作为构筑排错假说的基石。 --- ### [44:57](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2697s) 调试的本质是搜索:迈向自动化科学方法 Stuart 提出了一个对软件工程师至关重要的认知跃迁:**调试在本质上是一个搜索操作(Debugging is fundamentally a search operation)**。程序员每天都在编写各种搜索算法,但面对排错时,思维却常常局限在打断点、暂停执行或肉眼看日志上。既然它是搜索问题,就应当编写程序来实现前述的科学方法——让程序自动生成假设、自动执行实验。 `git bisect` 就是这种思想的雏形:开发者提供一个测试脚本,Git 在版本历史(状态空间)中进行二分搜索,折半排除未知领域,自动找出破坏测试的边界提交。 Stuart 呼吁 Clojure 社区将这种思想推向深处,并向参会者发出了阅读号召:去阅读 Andreas Zeller 所著的经典名著《程序为什么会失败》(*Why Programs Fail: A Guide to Systematic Debugging*),特别是第 5 至 7 章以及第 11 至 14 章。这些章节系统阐述了自动化调试算法: - 将出错程序的完整状态空间与无故障运行的相似状态空间进行建模比对。 - 采用差分算法对两个状态空间之间的差异进行不断缩小(Shrinking),定位极小关键变量。 Stuart 指出,Clojure 社区在缩小差异方面有着天然的工具与算法优势(例如 `test.check` 和 `test.generative` 基于属性的测试)。书中虽然大多采用 Python 代码演示,但 Clojure 具备不可变数据结构和极低状态开销的优势,比起在传统充满“泥潭(ball of mud)”状态的语言中捕获整个应用的状态空间,Clojure 才是实现自动化科学调试的理想平台。 ### [47:21](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2841s) 软件调试的核心准则与 Clojure 的独特优势 在探讨自动化调试算法的实现时,Stuart Halloway 指出,既然类似的技术能在 Python 程序中实现,那么在 Clojure 中理应容易得多。得益于 Clojure 将代码建模为数据(code as data)并将一切作为数据传递的特性,该社区在实现这类算法方面处于极其有利的地位。紧接着,他梳理了针对软件调试的一系列关键要点:在高抽象层级上工作、牢记“表现出的故障不等于根本缺陷(the fault is not the defect)”、时刻留心可能是垃圾回收(GC)在作祟、仔细把文档阅读两遍、不要轻信口头说法(因为人总会说谎)、坚决重现问题,并积极去实现各种流程的自动化。 - **高抽象层级工作**:避免立刻陷入底层的微观泥潭,从全局和高维视角审视系统。 - **区分故障与缺陷**:外部表现出来的异常行为(fault)与代码中真正的根因漏洞(defect)往往相隔甚远。 - **提防垃圾回收**:当遇到不可思议的延迟或停顿,优先怀疑并检查 GC 的行为。 - **精读文档与保持怀疑**:手册必须反复研读两遍;不要盲目相信他人的口头描述,必须依靠数据和代码本身。 - **重现与自动化**:坚持构建可重现测试用例,并将繁琐的排查步骤沉淀为自动化程序。 ### [47:51](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2871s) 科学调试法:从盲目乱试到胸有成竹的排查 考虑到漫长的一天结束后大家很难记住多达六条的具体建议,Stuart 将科学方法的精神进一步凝练为两个最基础的原则:明确目标,并做出有据可依的选择。他引用了 Yogi Berra 的名言,提醒大家只有明确前进的方向,才有更大的几率达成目标。开发者不论是新手还是资深老手,最常犯的错误就是在高压环境下病急乱投医——当某些东西无法正常工作时,大家往往脱口而出“我要试一下这个”。此时最关键的动作是停下来,问问自己为什么要去尝试那个方案。开发者甚至不需要找同事,哪怕是拿一只毛绒克苏鲁(plushy Cthulhu)做“小黄鸭调试”,对着它讲清楚自己的假设:“毛绒克苏鲁,我有个关于到底哪里出了问题的想法……我来给你推导一下我的假说。” ```mermaid flowchart TD accTitle: 科学调试决策流程 accDescr: 描述遇到 bug 时停止盲目乱试、确立假设、书面论证后再行动的流程 subgraph Problem["排错情境"] A["系统出现异常 / 陷入高压状态"] end subgraph ScientificAction["科学应对策略"] B["强行暂停:阻止盲目尝试"] C["明确目标并构建假设<br/>(向玩偶/同事阐述)"] D["采取任何行动前<br/>书面记录并论证理由"] end subgraph Outcome["最终结果"] E["从无序试错转变为<br/>有方向的巡航排查"] F["快速定位方案并轻松收工"] end A --> B B --> C C --> D D --> E E --> F ``` Stuart 强调,一旦决定采取任何行动,必须先用文字将其背后的合理性记录下来。这种将假说与理由书面化的习惯,能够彻底扭转原本在黑暗中盲目乱撞的局面,将其转化为一场高效、目标明确的巡航,最终轻松找到解决方案。正如 Stuart Halloway 所总结的:“一旦你决定要采取任何行动,首先在书面上证明它的合理性;做出明智的选择并写下你的步骤,这两步将把在沼泽里的盲目随机乱撞,转变成直奔简单解决方案的专注航行,让你能早早收工,晚上和朋友在酒吧轻松放松。” - **克苏鲁调试法(Plushy Cthulhu Debugging)**:通过向没有生命的玩偶完整复述问题和推导假设,强迫大脑梳理逻辑盲点。 - **行动前书面论证**:在敲下任何修复代码或改动系统之前,先写下“为什么我认为做这个改动能验证当前假说”。 - **终极收益**:告别盲目的猜测与耗竭式加班,换来高效的确定性以及下班后惬意的休闲时光。 ## Notable quotes > [05:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=359s) 我们驾驭工具,而不是工具驾驭我们。 — Stuart Halloway > [13:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=839s) It turns out that debugging, if the scientific method is the measure of how science is done, debugging is actually more like science than science. — Stuart Halloway > [17:22](https://www.youtube.com/watch?v=FihU5JxmnBg&t=1042s) In this context, a fix is just the last experiment. The first experiment is reproducing the bug, and then there are a bunch of experiments that are about hypotheses, and a fix is the last experiment. — Stuart Halloway > [33:59](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2039s) 把事情写下来,这是我在这场演讲中给出的最重要的一条建议(Write things down. This is the single most important piece of advice I'm going to give in this talk.) — Stuart Halloway > [35:51](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2151s) 故障不等于缺陷,而且这种情况在软件中最常出现的形式,就是臆断异常与实际问题有着直接对应的因果关系。 — Stuart Halloway > [47:51](https://www.youtube.com/watch?v=FihU5JxmnBg&t=2871s) 一旦你决定要采取任何行动,首先在书面上证明它的合理性;做出明智的选择并写下你的步骤,这两步将把在沼泽里的盲目随机乱撞,转变成直奔简单解决方案的专注航行。 — Stuart Halloway ## Mentioned Stuart Halloway, Alex Miller, Rich Hickey, Karen Meyer, Thomas Kuhn, Andreas Zeller, Yogi Berra, Edward Tufte, Cognitect, Clojure, Datomic, JVM, Java, Python, C, Logback, HornetQ, Netty, Cassandra, H2, Cursive, Leiningen, git bisect, test.check, test.generative, clojure.test, The Structure of Scientific Revolutions, Why Programs Fail: A Guide to Systematic Debugging, Ocean's Eleven, House, M.D., Mastermind ## Video description About the speaker: Stuart Halloway (@stuarthalloway) is a founder and President of Cognitect (formerly Relevance). He is a Clojure committer, and a developer of the Datomic database. Stuart has spoken at a variety of industry events, including Strange Loop, Clojure/conj, EuroClojure, ClojureWest, SpeakerConf, QCon, GOTO, OSCON, RailsConf, RubyConf, JavaOne, and NFJS. Stuart has written a number of books and technical articles. Of these, he is most proud of Programming Clojure. [Source](https://www.youtube.com/watch?v=FihU5JxmnBg)

Source quotes

[5:59] Stuart Halloway: 我们驾驭工具,而不是工具驾驭我们。 [13:59] Stuart Halloway: It turns out that debugging, if the scientific method is the measure of how science is done, debugging is actually more like science than science. [17:22] Stuart Halloway: In this context, a fix is just the last experiment. The first experiment is reproducing the bug, and then there are a bunch of experiments that are about hypotheses, and a fix is the last experiment. [33:59] Stuart Halloway: 把事情写下来,这是我在这场演讲中给出的最重要的一条建议(Write things down. This is the single most important piece of advice I'm going to give in this talk.) [35:51] Stuart Halloway: 故障不等于缺陷,而且这种情况在软件中最常出现的形式,就是臆断异常与实际问题有着直接对应的因果关系。 [47:51] Stuart Halloway: 一旦你决定要采取任何行动,首先在书面上证明它的合理性;做出明智的选择并写下你的步骤,这两步将把在沼泽里的盲目随机乱撞,转变成直奔简单解决方案的专注航行。

Source quotes

[13:59] Stuart Halloway: the scientific method may not be all that great for science but it absolutely rocks for debugging. [5:59] Stuart Halloway: there is no TDD out there that stands for Tool directed development; we wield the tools, they don't wield us. [40:07] Stuart Halloway: good docs for debuggers are specs.

Original transcript

[0:00] [Applause] [0:02] all right so testing two different mics [0:03] here we all good all right so um I guess [0:08] I should make uh a couple of [0:09] clarifications on the introduction uh [0:12] before we begin I'm pretty sure that [0:15] Alex just drew a chain of causality [0:17] whereby I can say that I'm responsible [0:18] for strange Loop so I'm pretty I'm [0:22] pretty excited to have discovered that [0:23] and and uh can't wait to see my checks [0:25] in the mail [0:27] um uh the second thing that he implied a [0:30] little bit was that um there's a lot of [0:33] opportunity uh to watch debugging uh [0:35] happening on the product team uh if you [0:37] work at cognitech and actually you know [0:39] datomic is really no bugs at all so I [0:42] don't uh I don't know what he's talking [0:44] about there uh either uh but seriously [0:47] um you know uh in a closure audience I [0:50] really want to begin this talk uh with a [0:53] rationale right this is something that [0:56] uh Rich has instilled uh as a value in [0:58] this community and so [1:00] uh I put a lot of effort in having a [1:02] good rationale or trying to and I'm [1:04] going to have a actually a multi-part [1:05] rationale for this talk the first one is [1:08] debugging why should we care about [1:09] debugging and I think there are uh [1:12] there's an obvious reason because bugs [1:15] right we have bugs that is a um you know [1:17] there are some estimates that that have [1:19] it that 50% or more of the cost of [1:22] software development is actually [1:23] tracking down the bugs later um some of [1:26] us you know work in environments where [1:27] we get to run away and somebody else has [1:28] to do that so depending on what you do [1:30] you might not always feel that but if [1:31] you own something for a while uh you [1:33] come to realize that that's pretty [1:34] obvious the second thing is that [1:38] debugging is is really a quite [1:40] straightforward activity and so you know [1:43] Alex made some sort of used some sort of [1:45] positive adjective to describe my [1:47] amazing awesomeness at this or whatever [1:49] it was that he said and the fact is this [1:52] is not hard to do but people think it's [1:53] hard to do and so you can look [1:55] disproportionately smart uh by doing it [1:59] and I am continually uh intimidated by [2:02] the intellect and the achievements of [2:04] the people in this room uh every time I [2:06] talk to somebody you know they have some [2:07] new area of knowledge that I didn't even [2:09] know existed about which they are expert [2:11] and so to be able to have something [2:13] where without the expenditure of neurons [2:16] you can appear to be very bright uh is H [2:19] is a valuable thing to have and the [2:22] final thing and this is quite subtle and [2:23] I won't call this out as much in the [2:25] talk I mean there's really probably I [2:27] probably have about 8 hours worth of [2:28] material uh worked up right now but [2:31] there's a there's a a follow-on strand [2:32] which might lead to an interesting [2:34] conversation which is that understanding [2:36] the way that I and and by proxy Rich uh [2:40] think about debugging might help you [2:42] understand uh some of the aesthetic that [2:44] goes into the design of closure and the [2:46] process and the evolution of closure um [2:49] it has come to my attention uh that that [2:51] sometimes uh people out in the world [2:54] have more than one opinion about how to [2:56] do things in the closure Community more [2:58] than one opinion about how to do the [2:59] getting started experience more than one [3:01] opinion about how to do documentation uh [3:03] more than one opinion about uh how to [3:06] handle exceptions uh we have lots of [3:08] different ways of doing these things uh [3:10] and I'm not going to lay out a here's [3:12] one way to do it right it's fantastic [3:14] that there are multiple different ways [3:15] but I think that the scientific uh [3:17] approach to debugging uh when you when [3:20] you adopt it it influences what kinds of [3:22] tools and practices you're going to [3:23] reach for in other areas and so that's a [3:26] very worthwhile of followon conversation [3:29] tonight um those who continue on into [3:31] office hours office hours will be held [3:33] at you know whatever local bars you know [3:35] stay open the [3:37] latest um the second thing as a [3:40] rationale for why to have me talk about [3:42] this uh there's a couple of reasons um [3:44] I'm a closure screener I am also um I [3:47] mean closure has a a process which is [3:49] not identical to every other GitHub [3:51] project out there as you may have [3:52] noticed um and so uh I in fact push all [3:56] of the commits and have for uh many [3:58] releases now um I read every line of [4:01] code that goes I've read every line of [4:02] Cl uh code that went into closure since [4:04] the 10 release um so I've seen a lot of [4:06] things uh I've seen many more things uh [4:09] that didn't go into closure for various [4:10] reasons and I've seen uh things that [4:12] were bugs and things that were thought [4:14] to be bugs uh and not bugs uh I also uh [4:18] typically am the uh the place of Last [4:21] Resort in datomic support um that [4:24] doesn't mean that you're on fire if you [4:25] call and you actually I answer answer [4:27] the phone right we're just rotating [4:28] around but uh but I'm often the back [4:30] stop on the hard problems and so that [4:32] has given me an opportunity to to own a [4:34] system for a long period of time and to [4:37] observe uh bugs that we've had and uh [4:40] bugs that other people have found that [4:41] we had and bugs that people have had in [4:42] their own systems that we've ended up [4:44] helping them out with because of the [4:46] support [4:46] relationship um also I'm going to make a [4:48] little bit of appeal to Authority here [4:51] um I earned this gray hair uh I've been [4:53] doing this for a long time and and my [4:56] hair was not gray at all when I first [4:57] started debugging so [5:00] I I'm pretty sure that uh that most of [5:02] this uh you know is directly related [5:04] somehow um the alternate theory is that [5:06] it has something to do with children um [5:10] and finally I am really lazy um I'm not [5:13] the most terribly lazy person in the [5:15] world because you'd have to be [5:16] competitive right to be that lazy um but [5:21] I [5:25] really I really don't want to work hard [5:28] and so I want have an approach to [5:30] solving problems um that allows me on [5:33] most days uh to close up my laptop at [5:35] 5:00 and spend time with my children and [5:38] have a tasty dinner and a glass of wine [5:40] whatever um uh watch whatever HBO's most [5:43] violent show you know currently is on TV [5:46] and go to bed so I'm lazy and uh you can [5:49] benefit from you know programmers we're [5:51] all about laziness right we're all about [5:52] finding tricks that will let us do [5:54] things uh in easier ways and so I think [5:57] these all contribute um then the [5:59] question is okay we've talked about why [6:01] debugging and why maybe I should talk [6:02] about it why should we talk about [6:04] debugging in a closure context well [6:06] Karen Meyer one of my co-workers uh [6:08] pointed me at this thread on Reddit I [6:10] believe and the text of this doesn't [6:12] really matter um but but you can sort of [6:14] read it as you know blahy blahy blahy [6:17] blah blah error messages blahy blahy [6:18] blahy blahy blah and it's basically the [6:21] sort of beginner experience of closure [6:23] right A lot of people when they are new [6:24] to closure U particularly struggle with [6:27] debugging their Pro uh problems and [6:29] figuring out what's wrong when something [6:30] doesn't work and so there's this long [6:32] thread on Reddit where people uh talk [6:34] about the problem but this was actually [6:35] the uh the post that launches it and it [6:38] g it sort of captures uh you know some [6:40] of the tone and and a lot of times [6:42] people do things like well I had a [6:44] problem and I got a cryptic error [6:45] message or I was interacting with a [6:48] dependency or a lining and Plugin or [6:50] something and I didn't understand even [6:51] where to start looking for my problem or [6:54] uh I had a air quotes type error in my [6:57] system that would have been trivially [6:58] found if I was working in Java and I I [7:01] now feel flued and so uh there are a lot [7:04] of people whose experience of debugging [7:06] closure is uh is fraught or unpleasant [7:10] and my experience has not been like that [7:12] um and I'm not saying that there's you [7:14] know there wrong and I'm right uh but [7:16] I'm saying that that you know I might [7:18] have some ideas that could make it [7:19] Pleasant for others and so that's kind [7:22] of an objective here [7:24] um now before I go further how many [7:27] people in here show of hands have help [7:29] helped to develop Tooling in the closure [7:31] ecosystem right worked on any kind of [7:34] tool so a lot of interesting tools uh in [7:38] the closure EOS system there are a lot [7:39] of interesting tools about editing about [7:41] debugging about visualization of data [7:44] about visualization of flow people have [7:45] built a lot of different things and this [7:49] talk is not about that and uh I want to [7:52] make it clear up front that this talk is [7:54] not anti- that right I am a huge [7:57] proponent of using tools uh that being [8:00] said if you do a Google search for [8:03] debugging and I did this um you know the [8:06] vast majority of the hits are about [8:08] tools right overwhelming well more than [8:11] half of the Google hits all the [8:12] resources out there are about tools and [8:14] so uh I don't think you would be getting [8:16] your sort of end of the day keynote [8:18] value ad if I told you how to use tools [8:20] because there's a lot of good advice out [8:22] there and the thing is about [8:25] tools and on a [8:28] second that's interesting [8:30] um the thing with tools is uh we're not [8:33] directed by tools right there is no tdd [8:36] out there that stands for Tool directed [8:39] development um um we wield the tools [8:42] they don't wield us and so when I think [8:44] about debugging I want to have a set of [8:46] ideas in my head um that give me [8:49] direction uh or as that great [8:51] philosopher of computer science Yogi [8:53] Bara said um if you don't know where [8:56] you're going you might wind up somewhere [8:58] place else um this is one of the biggest [9:01] problems uh when people start debugging [9:03] a system right when from my from my [9:04] experience in a support perspective you [9:06] know what you discover the first thing [9:07] that happens when you start trying to [9:08] help somebody with a bug the first thing [9:10] you find yourselves wanting to do is [9:12] like I really want to go back in time to [9:15] when you first thought there was [9:16] something wrong before you started doing [9:17] anything I want to go back to that point [9:20] where you started acting before you had [9:22] a plan before you knew where you wanted [9:24] to go um you know it's so often the case [9:27] that and and I'm an advocate of being a [9:29] selfstarter right so by all means try [9:32] things but have a plan and then finally [9:35] why would we talk about the scientific [9:37] method uh you know I had considered for [9:40] a while that the approach that I used to [9:41] debugging was a scientific method but I [9:43] had not you know read the literature on [9:46] the scientific method you know past [9:48] having had a science degree in college [9:51] uh and I had not read what people had to [9:53] say specifically about the scientific [9:55] method so of course I turned to Google [9:58] to find out why I care about the [9:59] scientific method and what I discovered [10:01] is that the scientific method apparently [10:03] is because [10:05] rainbows [10:07] which I have no idea why I'm pretty sure [10:10] that all the um sort of convey you know [10:13] the Edward tufty people out there are [10:14] just you know pulling their hair out [10:15] right what do these colors uh even mean [10:18] and so rather than use one of the [10:20] pre-built graphics with the meaningless [10:22] uh colors I've made my own uh scientific [10:25] method for debugging graphic it's not [10:27] that different from any of these uh [10:29] except it's got all the colors [10:31] removed so because it's also sort of the [10:33] limit of my ability to manipulate [10:35] omnigraffle so what is debugging all [10:37] about well debugging typically starts [10:38] with a [10:39] failure so a failure and by the way in [10:43] in K's tradition uh all the terms I'm [10:45] introducing I'm going to give you [10:46] something from the atmology dictionary [10:48] on them um uh advice to future you know [10:52] uh proposal submitters if you use two or [10:54] three words that are slightly unfamiliar [10:56] and make reference to etymology in your [10:57] abstract [10:59] um clearly clearly that's something that [11:02] that is valued uh in this space so what [11:04] is a failure a failure is a lack of [11:06] success or better it's an omission of [11:08] expected action right I expected [11:10] something and I got something else now [11:13] as a result of this [11:14] failure you form a hypothesis and a [11:17] hypothesis is an explanation that's made [11:19] without complete evidence right without [11:21] necessarily enough evidence to answer [11:22] the question actually maybe you don't [11:23] even know whether you have enough [11:24] evidence to answer the question maybe [11:26] you do but you just haven't sort of [11:27] worked through the process enough and it [11:29] acts as a starting point for further [11:31] investigation then given that hypothesis [11:34] you perform an experiment which is a [11:36] test or a trial and after that [11:39] experiment you make some observations [11:41] where an observation is active [11:43] acquisition of information from a [11:44] primary source now um in a good [11:47] experiment one of two sort of equally [11:50] good things can happen you can discover [11:52] that uh the observation falsifies your [11:55] hypothesis uh so falsification is a [11:57] deductive process um using modus tlins [12:00] you say I have a hypothesis it implies [12:02] there's something I should not see [12:03] there's some observation o that I [12:04] shouldn't see I run the experiment and I [12:06] see o well if I've if my if my thinking [12:10] has been rigorous and the hypothesis [12:11] says if this is true then not o should [12:14] happen and I see o my hypothesis is now [12:16] dead and that's great right I know it's [12:19] disappointing if you're trying to get [12:20] grant money in in the kind of regular [12:22] everyday science but in debugging this [12:24] is good news right we've now uh killed a [12:26] possibility and that's good the other [12:28] possibility [12:29] is that the hypothesis um is supported [12:32] by the experiment but maybe you still [12:34] don't have an answer that's sufficient [12:36] to let you say I've isolated it at which [12:37] point you need to do refinement and [12:39] refinement is removing impurities or [12:41] unwanted elements right it's subtractive [12:44] so I have some sort of story here now my [12:47] story may have to get bigger to remove [12:48] elements so I'm not actually saying the [12:50] number of words in your hypothesis is is [12:52] going to go up or down um but the [12:54] conceptual size of it is going to be [12:56] focusing in on whatever the actual [12:58] problem is and then you know eventually [13:01] and I'm not going to promise this [13:02] happens after two or five iterations uh [13:04] but eventually you have a hypothesis and [13:07] the hypothesis is something that offers [13:10] I'm sorry a theory is a hypothesis that [13:13] offers has uh been validated by [13:16] predictions now this does not mean a [13:18] theory is guaranteed to be true it does [13:21] mean though that it is true given all of [13:25] the information we have so far right so [13:28] and this is another place where people [13:29] go wrong and I see this all the time [13:31] right you know I have 99 pieces of [13:33] evidence in favor of this is where the [13:35] bug is and I have one little piece of [13:37] evidence that Flatout contradicts it and [13:39] I don't understand that piece of [13:40] evidence and so well 99's pretty good [13:42] I'm just going to keep going right if [13:44] you have that one little piece of [13:45] evidence that says you're dead you're [13:47] dead right you can't and and we get [13:48] attached to our theories that's the [13:49] other thing you get emotional about it [13:50] you're like you know what I really like [13:51] that theory and look it had 999 good [13:53] pieces of evidence in favor of it you [13:55] can't do that now [13:59] it turns out and sort of I I get dragged [14:02] into the sort of history of this you [14:03] know researching the you know what other [14:05] people have said about this for this [14:06] talk it turns out that there are all [14:10] sorts of objections to the notion that [14:13] the scientific method is how science [14:15] actually gets done um and um some of the [14:19] objections [14:21] are [14:23] superficial um or or idiotic perhaps [14:29] um but there are there are more serious [14:32] concerns as well so one of the things [14:34] that I studied in graduate school is uh [14:36] Thomas Coon's book the structure of [14:38] Scientific Revolution and sort of [14:41] there's all kinds of py ways I could [14:42] unfairly summarize this book but it [14:45] essentially says that um that science is [14:48] a little bit better than politics or [14:49] religion uh in that you wait for people [14:52] whose ideas are going to die out to die [14:54] instead of actively going and killing [14:55] them right but it basically it basic [14:59] basically paints a pretty unpleasant [15:00] picture that science is an extremely [15:02] social process right that and how could [15:05] it not be right it's performed by people [15:07] uh and then once you have this notion [15:09] that science is a social process uh it's [15:12] opened up to what are the social [15:13] priorities behind science and you get [15:16] all these moral challenges to science so [15:19] these hot button things you know they [15:20] change from decade to decade one of the [15:21] hot ones when I was in Academia was the [15:24] bell curve right this notion that we [15:26] were going to use uh sence in air quotes [15:30] to sort of split the human race up into [15:32] different groups and say you know these [15:34] groups are are more intelligent and [15:35] these groups are less intelligent um [15:38] that's not the case um certainly not [15:41] justified by the evidence but these [15:43] challenges are real and here's the funny [15:45] thing right here's the good news right [15:47] there's all kinds of stuff that I just [15:48] said that we could have very heated [15:50] debates about and get angry about and in [15:52] the context of debugging we don't have [15:53] to worry about anything any of that [15:55] stuff at all it turns out that debugging [15:57] if the scientific meth is the measure of [15:59] how science is done debugging is [16:02] actually more like science than [16:05] science right science is hard it's [16:08] difficult to imagine how you could take [16:09] that little process I just had and turn [16:10] a crank on the side of it and come out [16:12] with the theory of gravity or the theory [16:14] of evolution right or any of the other [16:15] most important theories uh that people [16:17] have ever thk but that's not what we're [16:20] doing right that is not what we're doing [16:22] we're doing something that is far more [16:24] constrained right and debugging is [16:26] deductive not inductive right we're not [16:28] trying to to come up with a grand Theory [16:29] of Everything right we have a grand [16:31] Theory of Everything it's called the [16:32] Software System we're running we're [16:34] trying to use that to prove some very [16:35] concrete thing right this thing happen [16:37] also there's not a big political problem [16:39] right there's not usually I mean in my [16:42] experience debugging in and of itself [16:45] has not been politically fraught right [16:47] there are people who are experiencing [16:48] bugs and they want to see them fixed and [16:50] as the developers we want to see them [16:52] fixed so we're not having these kinds of [16:54] arguments and there's not generally a [16:56] lot of moral outrage right and there's [16:58] not any kind of like academic left [17:00] saying you know there is no real reality [17:02] so you can't actually you know uh you [17:05] can't actually prove that there was a [17:07] bug here right there's there's none of [17:09] that so I mean seriously this is really [17:11] good news so so unfortunately the [17:14] scientific method may not be all that [17:16] great for science but it absolutely [17:18] rocks for debugging now having said that [17:22] we can drill in a little bit and we can [17:23] start to use some more specific terms [17:26] that are more about troubleshooting and [17:28] debugging so I'm going to replace the [17:29] word hypothesis with cause right we're [17:33] looking for a cause and the definition [17:35] of a cause is an event preceding an [17:38] effect without which that effect would [17:39] not have [17:41] occurred that sounds awesome right if I [17:43] knew the cause of the problem then you [17:46] know I could eliminate the effect from [17:47] happening so I caused strange Loop right [17:50] as we said earlier so you know so the [17:53] problem with causes is that they're not [17:55] sufficient right the cause the universe [17:57] exists right if the Universe didn't [17:59] exist this wouldn't happen that's a [18:00] cause but it's not a very interesting [18:02] cause and it's not one that when we're [18:03] debugging software we're not in a much [18:04] of a position to do anything about so [18:07] you want to get from a cause to an [18:09] actual cause and an actual cause is the [18:13] difference between the actual world and [18:15] the closest possible World in which the [18:16] effect does not [18:18] occur and we I mean we could go down the [18:21] Rat Hole of getting rigorous about [18:22] closeness and I'm not going to do that [18:23] in this talk uh but we can be I'll just [18:25] be intuitive about closeness right if [18:28] you have you know this aeration of five [18:30] things that all contribute and with four [18:34] of them it doesn't happen and one of [18:35] them it does then that one is closer to [18:37] being the actual cause and so that's [18:38] what the pairing down uh process does we [18:41] want to start with an idea about a cause [18:43] and then we want to narrow down to an [18:45] actual cause and then we have this great [18:47] word fix in this context a fix is just [18:51] the last [18:52] experiment right the first experiment is [18:55] reproducing the bug and then there are a [18:56] bunch of experiments that are about [18:58] hypotheses and a fix is the last [19:00] experiment this captures a really [19:02] important idea which is if you haven't [19:04] fixed it and run an experiment that [19:06] shows it's fixed you haven't really [19:07] identified the bug yet you may be [19:09] strongly suspicious that you've [19:10] identified the bug but you haven't [19:12] actually identified the bug until you [19:13] fixed [19:14] it so I'm going to take this scientific [19:18] method and I'm going to apply it [19:22] um in a really slap Dash way to a [19:25] problem and I'm going to start I'm going [19:27] to start with a problem that I grabbed [19:28] off of stack Overflow this is a very [19:29] tiny debugging problem you can probably [19:31] do it in your heads so I'll show it to [19:33] you this is a question on stack Overflow [19:35] why is this partial not working so I [19:37] Define partial join as the partial of [19:39] closure. string jooin with comma then I [19:42] call partial join on Fubar and I get [19:45] back the scary error message Class cast [19:48] exception you cannot cast java. lang. [19:51] string to closure. lang. IUN and there's [19:53] a scary number in it so there's no way [19:55] we can possibly figure out what the [19:56] problem is with this terrible error [19:57] message [20:02] so what should we do well we have a lot [20:05] of choices here we could make error [20:07] messages better right we could have an [20:09] error message that says oh I see you [20:10] were trying to make a partial with sjin [20:13] and you know that has this exact [20:14] scenario we could anticipate every [20:16] possible scenario anybody could ever get [20:17] into and not to be unfair to error [20:19] messages we could go a long way towards [20:21] having better error messages without [20:22] being ridiculously over the top about it [20:24] error messages can be made better sounds [20:25] like a good plan to me we could have [20:27] better Docs right maybe there's an [20:29] answer to this problem in the [20:30] documentation um we could use a debugger [20:33] uh in fact in this case it might be even [20:35] that syntax highlighting and Pen [20:36] highlighting would have tipped you off [20:38] as to where the problem was and I didn't [20:39] give you that uh maybe static typing [20:41] would have helped right in fact static [20:43] typing would have helped right you could [20:44] imagine a scenario where static typing [20:45] would have solved this problem um using [20:47] some sort of schema and schema [20:49] validation would have helped and in fact [20:51] this one's so easy that you might have [20:52] stared at it and just known what the [20:53] problem was so all of those things are [20:56] useful and all of those things should be [20:58] part of your toolkit except for maybe [21:00] staring at it right that one's actually [21:01] really quite weak and we'll come back to [21:03] that but the important thing to realize [21:05] here is that science is more general [21:10] purpose and requires Less on hand to do [21:13] than any of these other [21:15] things I'm G to say that again because [21:17] it's so important right science is more [21:19] General the scientific method here not [21:21] science let's say the scientific method [21:23] or we can even be what is it that the [21:24] the academics say um hypothetical [21:27] deductivism [21:29] right let's just stick with scientific [21:30] method the scientific [21:32] method is uh is better sauce than these [21:37] other things because it's more General [21:38] and because it doesn't require anything [21:40] to exist in the world except your brain [21:42] before we go in so let's try that we're [21:44] going to go down this road and I'm going [21:45] to do this as I said in a really slap [21:46] Dash way my hypothesis is going to be [21:50] well look there's only three things here [21:52] why is this partial not working well [21:53] join doesn't do what I expected so it's [21:56] a very fuzzy hypothesis or partial [21:59] doesn't do what I expected or deaf [22:02] doesn't do what I expected right that's [22:04] it has to be in one of those three [22:06] things right those are the only three [22:07] things there it's a very small problem [22:09] to to check and so the experimental [22:13] approach to this um I I will propose a [22:15] heuristic for small problems like this [22:17] which is you should do a bottomup check [22:19] from the reppel right pick the form [22:21] that's at the bottom or that's on the [22:22] inside and check that and in this case [22:25] we're done right I check the very bottom [22:27] form here the bottom form was closure. [22:29] string /join comma which returns [22:33] comma that seems like almost certainly [22:36] wrong because if it was just going to [22:38] return comma I could have just passed in [22:39] comma to begin with and then when I look [22:41] at the next one partial join if we if we [22:44] substitute in the result of that right [22:46] in the next in the original form we're [22:48] now partialling over comma which is [22:50] using comma as a function so by applying [22:53] this now I got lucky this time I might [22:54] have had to do three little things at [22:56] the reppel to figure this out but I got [22:58] lucky and I only had to do [23:00] one so weak science is stronger than [23:03] strong [23:04] Tools in this case I had a poor problem [23:06] statement I really never got Clarity on [23:07] my problem statement I had really poor [23:10] hypothesis each one of my hypothesis was [23:12] that was ouch that was ouch or that was [23:14] ouch right those are not very specific [23:16] uh my experiments didn't even really [23:17] have stated goals right I just sort of [23:19] took this heuristic approach um and I [23:22] didn't have much domain knowledge right [23:25] or hypothesize that that a beginning [23:27] closure program are doing this didn't [23:28] have have much domain knowledge to [23:30] figure it out so of course at this point [23:32] I've stacked the deck uh in favor of the [23:34] scientific method by picking a trivial [23:37] problem trivial problem doesn't matter [23:38] what you do right any one of the [23:40] approaches would work so it hardly [23:41] matters so now we need to talk about [23:42] hard problems and what it would be like [23:44] uh to do the scientific method well so [23:46] what do we need we need to do each of [23:48] those steps way better than we just did [23:50] we need clear problem statements we need [23:51] efficient hypothesis we need good [23:53] experiments useful observations and we [23:55] need to write things down I'll start [23:57] with problem statements right the [23:59] antithesis of a good problem statement [24:00] is it didn't [24:02] work right it didn't work is hiding [24:04] everything behind pronouns that's no [24:06] good right what you want is the steps [24:08] you took what you expected and what [24:10] actually [24:12] happened this slide right there that's [24:14] worth a 100 bucks send me your [24:18] checks all right this is not a hard [24:20] thing to do this is not harder than and [24:22] in fact sometimes saying this actually [24:24] causes you to realize what the problem [24:25] was going from the exercise of [24:27] verbalizing it didn't work from the [24:29] exercise of verbalizing you know I [24:31] stepped away the car and it started to [24:32] all right I didn't put it in neutral [24:35] whatever so so problem statements you [24:39] know there's a lot as I said I have [24:41] eight hours of material there's a lot we [24:42] could say about problem statements this [24:43] is all you're going to get right now but [24:45] this is an order of magnitude better [24:46] than it didn't work so start with this [24:49] the next thing you want to do is to and [24:53] I am [24:55] gonna just give me one second [25:02] why I don't know why it's not [25:05] working I'm going to debug [25:11] this have a live performance [25:14] now [25:16] H so my hypothesis [25:26] is actually it's keynote right which is [25:28] just just [25:29] uh terribly uh terribly painful so I [25:35] want oh [25:37] well fine problem I'm not going to tell [25:46] you how many people remember this [25:49] guy yep I mean it's um it's Casey [25:51] Affleck but um it's the Malloy twins [25:55] from Oceans 11 and there's this great [25:57] scene where where they're playing 20 [25:59] questions and he they're playing 20 [26:02] questions and he's like am I alive yes [26:05] am I a person yes evil coneval damn [26:10] it and and it's a joke on an idea that [26:13] we all know it's like the opposite of [26:14] what you want to do right it's so it's [26:16] what my 5-year-old does when we're [26:18] playing 20 questions he's like okay [26:19] we're going to play 20 questions and he [26:20] looks at me and goes are am I a tall oak [26:22] tree it's like no you don't [26:26] want we all know [26:29] we all know what we need to do here when [26:31] we're forming a hypothesis right you [26:32] want to form a [26:33] hypothesis that ideally carves the world [26:36] in half and so it turns out that the [26:40] naming around this is contested right [26:43] most of the time you hear people say [26:44] this is divide and conquer but if you [26:46] ask a an algorithms person they would [26:48] say that's actually not it because in [26:50] divide and conquer you go down both [26:52] branches and so they want to call it [26:54] decrease and Conquer which I think kind [26:55] of sucks because because it doesn't I [26:58] mean decrease and Conquer could be a [26:59] linear time thing right you could [27:01] decrease by one every time and the [27:03] important uh characteristic here is [27:05] proportional reduction right I want to [27:07] take the space of where the problem is [27:10] and I want to reduce it by ideally half [27:13] but as you know if you've done any [27:13] algorithm analysis right if I can get [27:15] rid of 10% of the possibilities on every [27:17] step I'm going to have the answer really [27:19] quickly right that's only going to [27:20] differ from getting rid of half the [27:21] possibilities by a constant Factor the [27:23] number of steps that I have to do so the [27:26] question is then how do you take the of [27:28] I don't know what went wrong and turn it [27:31] into you know something that cuts the [27:33] world in half well this is going to [27:35] require domain knowledge right there's [27:36] no two ways about it um having said that [27:39] um it does uh imply that you should be [27:41] super cautious on your initial step if [27:43] you don't have much domain knowledge [27:45] right if you don't have much domain [27:46] knowledge it's possible that your [27:48] initial step leaves out of the entire [27:49] universe of possibilities what the [27:50] actual problem was and so having said [27:53] that everybody knows where to look for [27:55] their bugs right if I showed you your [27:57] application stack something just stopped [27:58] working right how many of you going are [28:01] going to [28:04] guess how often right it just doesn't [28:08] right how often is it physics it's it's [28:13] pretty rare right physics is not usually [28:14] I mean sometimes it is and that's [28:15] exciting [28:19] but and I have to say that on every open [28:22] source you know project I've ever worked [28:24] on you know you get bug reports and some [28:26] of them are bugs and you fix them and [28:28] some of them are not bugs and some of [28:29] them are bugs in something underneath [28:31] you right we've never had a closure bug [28:33] report that we had to forward onto [28:34] physics right there haven't been any of [28:37] those and in fact very few even have to [28:39] do with you know jvm much less OS [28:41] operation and you could make other [28:42] Stacks like this there's a there's a [28:44] whole conversation we can have about [28:46] sort of developing a notion of what the [28:48] possible space is and you know where you [28:51] can go in it but the my point is you [28:52] don't have to be that good at it anyway [28:54] right as long as you can get rid of some [28:56] proportion of the possible causes with [28:58] your experiment you're going to quickly [28:59] uh find the [29:01] answer now what is a good experiment a [29:04] good experiment is reproducible right [29:06] you start by reproducing the bug it's [29:08] driven by a [29:09] hypothesis right people say they're [29:11] experimenting when they're just trying [29:12] right that is not hypothesis driven [29:15] you have an idea this is the case and [29:18] then you have an experiment that [29:19] provides more information to help you [29:21] refine that idea also experiments are [29:24] small and when you're uh when you're [29:26] making changes to a system you change [29:28] one thing at a time right because if you [29:31] change two things now you just have to [29:32] go back to figure out what the impact of [29:34] that was uh when when something changes [29:36] right you don't you haven't actually [29:37] gained information so I'm going to give [29:38] you a quiz on [29:39] this if you have uh a bug uh let's say [29:44] you want to report a bug in your own app [29:47] right and you you you know you're you're [29:49] handing it off to another member of your [29:51] team to help you look at it which of the [29:53] following things should not be in your [29:56] Repro case [29:59] closure test cursive Prismatic schema [30:02] mij pkin inreal linan linan plugins core [30:05] type test generative right unless you're [30:07] actually unless you think the bug is in [30:09] one of these things with one of these it [30:11] really stands out as boy you really [30:13] wouldn't want to have that in a Repro [30:15] case and the answer is it's a trick [30:17] question you don't want any of these in [30:19] your Repro case unless your theory is [30:22] that you know the shopping cart on my [30:24] system doesn't work when I'm developing [30:26] inside cursive and using closure. test [30:29] then your Repro case shouldn't have [30:30] anything to say about cursive enclosure. [30:33] test and Colin and other people will [30:35] thank you for that cuz he doesn't he [30:37] doesn't want to get bug reports about [30:38] cursive that about that kind of thing [30:39] either so it's incredibly important to [30:43] remove things that do not contribute to [30:45] your hypothesis statement and it's a [30:47] freebie right I was saying earlier that [30:49] you have to have this like mental model [30:51] of the universe to allow you to narrow [30:53] down the things that aren't it right [30:56] this is a freebie right in bug these [30:58] things are not it so start by taking [31:01] them out make a really tiny thing that [31:03] shows the [31:05] problem now when you're making [31:07] observations what's that all about well [31:09] one thing you need to do is you need to [31:10] understand all the [31:11] outputs right if your system adds [31:13] outputs right if your system has four or [31:15] five outputs U that you understand and [31:18] then it has one that's unrelated to your [31:19] current problem and you don't understand [31:20] it breaks Screech right if you don't [31:24] understand it how do you know if it's [31:25] related to your problem or not so you [31:27] need to understand the output from your [31:28] system and you need to be suspicious of [31:30] correlations where is the code it's in [31:32] the last the bug it's in the last five [31:34] lines of code you [31:35] wrote right quite often and so if [31:38] anything correlates with the pro with a [31:41] failure appearing then you want to [31:42] suspect that and in order to make [31:44] observations you need good tools you [31:47] need debuggers you need logging you need [31:48] metrics all the kinds of things that let [31:50] you they basically all those things give [31:51] you more [31:53] outputs right they turn things that are [31:55] black box into things that are white box [31:57] and it's amazing that we have this [31:58] inversion that when you do a Google [32:00] search for debugging like more than 50% [32:02] of it is about just this one sub bullet [32:03] of one sub point it's important right [32:06] you need to have tools and in fact there [32:08] there are more things that tools can do [32:09] than just sits and we'll talk about [32:10] those later so I'll give you another [32:12] example and while I was writing this [32:13] talk I had several things that happened [32:14] to me uh in the course of the week so [32:16] this is one that happened to me last [32:17] week I was working on a closure app and [32:21] I got this error message in the log that [32:24] was completely unrelated to my problem [32:27] right my had nothing so log back is the [32:29] configuration file for log back which is [32:31] one of the Java logging things which [32:34] we're all so happy that Java did such a [32:36] good job with [32:38] and so I get this message and I'm having [32:40] this cryptic problem in a subsystem that [32:42] I'm working on and I know this can't be [32:45] it because it just has to do with [32:47] logging except it actually this was it [32:50] right so it turns out that uh after you [32:53] know two minutes looking at the problem [32:54] I said you know what before I think [32:55] about this problem I'm going to go run [32:56] down what this log back thing is about [32:58] because I want this to be clean well log [33:00] back occurs multiple times on the class [33:02] path because some library because of a [33:05] build problem had been copied into a lib [33:07] directory twice at two different version [33:10] points so I had food. bar version 2.1 [33:13] and food. bar version 2.2 now what the [33:16] jvm helpfully does in that scenario [33:18] without you know further configuration [33:20] is if those things have uh overlapping [33:23] but not entirely uh uh unioned uh set of [33:28] named things in them you can get some of [33:29] them from one and some from the other [33:30] and they're not compatible with each [33:31] other and it can lead to all manner of [33:33] errors that are absolutely cryptic which [33:35] is exactly what happened but by tracking [33:37] this down I was able to say you know [33:39] what I saved myself all that time and um [33:42] it would have been even using the [33:44] scientific method and trying to sort of [33:45] bisect towards the problem um it would [33:47] have taken a while from the symptom to [33:49] come up with a better cause to [33:51] investigate than the one that was [33:52] standing in front of my face [33:59] write things down this is the single [34:01] most important piece of advice I'm going [34:02] to give in this talk write things down [34:05] write the problem statement down don't [34:07] say it write it down write your H write [34:09] every hypothesis down write what the [34:12] experiment would show write why the [34:14] experiment makes sense write a [34:16] justification for the experiment before [34:18] you run it and write down your [34:19] observations and this is something that [34:21] we all know sort of intuitively um uh [34:25] consider the game Mastermind so in the [34:28] game Mastermind the the colored pegs [34:30] down at the bottom represent a code that [34:33] the player is trying to guess and the [34:35] colored pegs across the top represent [34:37] past guesses and then the red little red [34:39] and white pegs uh represent uh [34:41] information feedback that you've gotten [34:43] on your past guesses so if you view this [34:45] as a scientific method right you have a [34:47] series of hypotheses about what the [34:49] colors are and you're getting feedback [34:51] from an experiment which is the human [34:52] player is scoring your guesses we don't [34:55] have to talk about exact scoring if you [34:56] can't remember how Mass Master mind [34:58] works that doesn't matter the thing [34:59] that's dominant here is that doing your [35:01] experiments with write without writing [35:03] things down is like playing Mastermind [35:05] like [35:07] this right you're saying that you're [35:09] going to keep in your head the entire [35:11] State space of all the previous things [35:13] you've tried and even the one try you're [35:15] currently on you're going to hold in [35:16] your head so I'm you can barely see one [35:18] of those through my uh color there and [35:20] the thing is that um not writing it down [35:25] right you might think that of the seven [35:26] deadly sins that sloth but it's actually [35:29] not it's [35:30] hubris it's amazingly arrogant not to [35:33] write things down because basically what [35:35] you're saying is I'm such a badass at [35:37] solving problems that I'm going to solve [35:38] The Meta problem of keeping track of [35:40] everything in my head just to show off [35:42] when that's actually probably a harder [35:44] problem than the debugging that you're [35:45] doing and people do this all the time [35:46] and [35:51] staggering okay everything I've said so [35:53] far uh probably could more or less apply [35:56] to the use as a scientific method in a l [35:57] lot of different domains and because we [35:59] are here at a software closure [36:01] conference I'm going to give you some [36:02] software specific advice um the first [36:05] piece of software specific advice is [36:07] something I was reminded of as I was [36:08] reading through the literature on [36:10] debugging and what programmers have said [36:12] about debugging in the past um and I [36:14] usually don't say mean things like this [36:15] and it's going to come off a meeme and I [36:17] can't help it don't you [36:19] see right I I mean the history of [36:22] debugging is just riddled with oh my God [36:25] this thing has so many pitfalls and [36:26] traps in it and and it's not that c is [36:28] bad I mean we could have a separate [36:30] conversation about it's that this is not [36:31] a level of abstraction you need to be [36:33] working at right this I mean and [36:36] sometimes it is right and if you have a [36:38] domain that's like it would be [36:40] impossible to do this in Java or closure [36:43] or what python or whatever then you can [36:45] use C but boy you shouldn't reach for [36:46] that as the default tool in 2015 in most [36:50] domains second piece of software [36:52] specific [36:53] advice the failure is not the defect [36:59] right the failure is not the defect and [37:01] the way this comes up the most often in [37:03] software is assuming that the exception [37:06] has anything to do with the actual [37:08] problem I mean it does have something to [37:09] do with it that's how you know right but [37:11] that it's in some way directly [37:12] translatable into the problem so I'll [37:14] give you an example we had a datomic [37:15] system that had a large query High CPU [37:19] utilization and an illegal State [37:21] exception in Hornet Q so your first [37:24] guess is you know wow illegal State [37:27] exception hornetq must be broken well [37:29] let me just tell you Hornet Q is not [37:30] broken um hornetq is a pretty awesome [37:33] piece of software um I've had fantastic [37:35] interactions datomic has had Hornet q in [37:37] it from the the 0.0 days I've had [37:40] fantastic interactions with the Hornet Q [37:42] team and the only time that working with [37:44] those people that we ever got to the [37:46] point where we tracked a bug down to the [37:48] point where it was underneath the atomic [37:50] and in Hornet Q it actually was [37:51] underneath Hornet q and in [37:53] netti right which is pretty unusual by [37:55] the way you don't find those uh those [37:57] kind I mean this is a you know a [37:58] showstopping nety plus SSL doesn't work [38:00] in this scenario uh bug so pretty darn [38:03] unusual so this was not this bug and [38:06] I'll just tell you I'm sort of giving [38:07] you a little bit of unfair advanc [38:09] information the answer was not it's [38:10] Hornet q and uh there's another [38:13] important philosopher of debugging who [38:15] can really help us out here um in a [38:18] couple of steps um and that is [38:20] house [38:25] so house says it's never lupus well in [38:30] closure programming on the jvm we have a [38:33] kind of anti- Lupus right we have the [38:35] thing that it always is but it doesn't [38:37] look like it at first and the thing that [38:39] it always is is garbage [38:41] collection right the actual bug is [38:44] always garbage collection just as just [38:46] as in house it's never lupus um [38:48] inclosure and in Java it's always [38:49] garbage collection and there's several [38:51] reasons for that one is most [38:53] applications are not designed to [38:54] deliberately induce um out of memory [38:56] errors but because they're not designed [38:58] to deliberately induce those those code [39:00] paths are not checked very much so [39:02] you're in kind of un Uncharted Territory [39:04] the second one is out of memory can [39:06] happen anywhere so there's no line of [39:07] code where you can oh look there's the [39:09] critical section where memory doesn't [39:11] possibly run out right you can't do that [39:13] um also out of memory can appear as [39:15] almost any other exception because once [39:17] something fails to allocate over here [39:19] you can get a cascading series of [39:20] reactions where the actual exception [39:22] that's reported back is radically [39:24] different finally um well not even [39:27] finally subf finally um when you get [39:29] close to out of memory you get uh a [39:32] radical change in thread scheduling as [39:35] the GC threat is running all the time [39:37] and all of a sudden things are happening [39:38] in orders that they never happen in [39:40] everyday life and so all those race [39:42] conditions that would normally take your [39:44] system 2,000 years to expose by accident [39:46] all of a sudden it takes them 2,000 [39:48] seconds to expose by accident um so [39:50] lucky you thanks garbage collection for [39:51] helping us find those uh race conditions [39:54] um and finally out of memory related [39:56] problems tend to Cascade [39:58] so uh always be suspicious of memory um [40:03] uh when you're having problems uh in a [40:05] garbage collected environment now [40:07] another piece of software specific [40:09] advice is to read the entire fracking [40:13] manual and so the thing is that a bug [40:17] almost by definition starts out as an [40:19] unknown unknown right if you knew more [40:21] about it you'd already be well on the [40:22] way to fixing it you don't know what's [40:23] wrong so you don't know which part of [40:25] the manual you need to read and and this [40:27] means that if you want a set of docs [40:29] that are good for [40:30] debuggers that set of docs uh ideally is [40:34] short in specification like now this [40:36] goes against other objectives right [40:38] short docs uh may be very difficult to [40:40] consume they may not be very narrative [40:42] they may not be very tutorial um but [40:44] they are a good place to say you know if [40:46] I have to read the whole thing I would [40:47] like for it to be 50 pages long and not [40:49] repeat itself um if I want to learn in a [40:51] more you know tutorial kind of [40:52] environment I'd like for it to be a [40:54] thousand pages long and repeat itself in [40:55] various ways and anticipate problems [40:57] that people have every day so good docs [41:00] for debuggers are [41:02] specs and we'll go back to the partial [41:05] problem again so if instead of doing [41:08] experiments we had chosen to read the [41:09] docs here we could have pulled up the [41:10] doc string for join and we would have [41:12] seen that join has two arodes and the [41:15] first arity um takes a collection and [41:17] then it returns a string of all the [41:19] elements in that [41:20] collection oops so one step plus [41:24] experiment would have identified this [41:25] problem one step plus reading the docs [41:28] would have identified this problem right [41:30] would have been the first thing you [41:31] found when you went to look for it so [41:34] let's take some of these ideas back to [41:37] this uh more tricky debugging problem [41:39] that we encountered and that is this [41:41] large datomic query High CPU utilization [41:44] illegal Hornet State uh illegal State [41:47] exception and now I'm going to give you [41:48] one more really piece of interesting [41:49] information it happens on Cassandra in [41:53] production but not on H2 in [41:56] development so what should we do right [41:58] now H if we're going to apply the method [42:03] what we need to do right now is look [42:04] very suspiciously at that last sentence [42:07] is that last sentence an observation a [42:09] hypothesis what is it it's actually not [42:12] really anything right it doesn't have a [42:14] subject right it's a sentence without a [42:16] subject in it so there's some sort of [42:18] amorphous it that happens on Cassandra [42:21] and not on H2 so let's try to Define [42:25] that it a little bit um that certainly [42:27] sounds suspicious so let's make the [42:30] smallest possible thing that could show [42:32] us that so we'll create a test [42:34] environment with the same data that [42:35] production had and then we will run a [42:38] little file that we're going to write [42:39] which is going to be 10 or 15 lines long [42:41] test. Java with a datomic pier and [42:43] that's just going to come up and it's [42:44] going to perform the problem query in a [42:46] loop and if Cassandra or datomic or [42:50] Cassandra plus datomic is trivially [42:51] broken in some way we're going to see [42:53] that there um and then we can run the [42:55] same test again against Dev and not see [42:58] that there and now we have eliminated an [42:59] entire universe of complexity right all [43:03] of the application code that was in play [43:04] all of the tools all the third party [43:06] dependencies all that other stuff has [43:08] been mechanically eliminated well so we [43:10] did this and what did we [43:12] learn the problem happens on Cassandra [43:15] that's this is our statement happens on [43:16] Cassandra but not on H2 so we create the [43:18] trivial Repro and it happens with [43:20] Cassandra and guess what it's lupus so [43:22] it turns out that this query was just [43:25] using more memory than the jvm Hat had [43:27] and had no chance of winning and so then [43:29] it's like well that's pretty suspicious [43:31] because it's not at all obvious why [43:32] Cassandra would be such a memory hog and [43:35] use a ton more memory than H2 that seems [43:37] really you know candra has a good [43:38] reputation why would their driver you [43:40] know be messed up like that well you [43:42] know stop hypothesizing ahead of the [43:43] evidence let's go test uh H2 the dev [43:46] mode storage and guess what the trivial [43:48] Repro happens there as [43:50] well and that leads me to the other [43:53] important observation that we get from [43:55] house which is that everybody lies right [43:58] it turns out that it was not the case [44:00] that the Cassandra and the H2 Behavior [44:02] were different it turns out that there [44:04] was a miscommunication about what had [44:05] been tried and so and by the way never [44:08] attribute to malice what you can [44:09] contribute to Accidental [44:10] miscommunication this is not lying in [44:12] the sense of covering up your drug use [44:13] which is always what it is on house [44:15] right this [44:16] is this is this is lying as in saying a [44:20] statement which turns out not to be true [44:22] I'm having a lot of trouble teaching my [44:23] kids not to always call that lying right [44:24] any statement which turns out not to be [44:26] true later or turns out to slightly [44:27] mistaken they're like Daddy you lied [44:29] like actually we tend to use that word [44:31] to imply malice so this is the non- [44:32] malice kind of lying this is the uh you [44:34] know go back and and and double check [44:36] statements and again going through the [44:38] exercise of reproducing it I mean [44:40] imagine how much fun we would have had [44:42] If instead of doing that we had sat [44:43] across a conference table and the [44:44] datomic team had pointed at the [44:46] Cassandra devs and the Cassandra devs [44:47] had pointed at the atomic devs and said [44:49] you know you guys have messed up [44:50] somewhere because this works in other [44:52] contexts right we didn't even have a [44:54] problem statement you know until we did [44:55] that now the final piece of advice I'll [44:57] give you as software developers is that [45:00] debugging is fundamentally a search [45:04] operation we should ponder that a little [45:06] bit because this is something we [45:07] actually know how to do right we [45:09] actually know how to write algorithms [45:10] that do it so instead of limiting our [45:12] thinking about debugging to the ability [45:15] to see things or stop things um why [45:17] don't we write programs that do this job [45:19] that I just described right implement [45:21] the scientific method and code make [45:23] something that automatically generates a [45:24] hypothesis uh and then automatically [45:26] runs an experiment and some of you may [45:29] have done that in a small way using get [45:32] bisect right get bisect is actually [45:35] partial automation of the scientific [45:36] method right you write a little program [45:38] that's going to test for something and [45:40] then get will bounce back and forth [45:42] cutting off cutting the world in half [45:43] every time until it finds the boundary [45:45] at which your test changes right that's [45:48] automating the experiment part as and [45:51] and we have you know we have the set of [45:52] possible World States already given to [45:54] us from git well this idea [45:57] is well suited to being taken further [45:59] and so I'm going to give you um your [46:01] first maybe closure con call to action [46:03] this year which is go and read this book [46:06] why programs fail uh by Andreas Celler [46:09] and in particular there's a lot of ideas [46:10] in this book that have now become [46:11] standard practice so there are chapters [46:13] you can skip but in particular read [46:15] chapters 5-7 and chapters 11- 14 which [46:19] actually lay out algorithms for doing [46:21] automatic debugging of programs and so [46:23] what they're doing is modeling the [46:24] entire State space of a broken program [46:27] and the entire State space of similar [46:29] programs that don't exhibit the failure [46:31] and then uh shrinking the difference in [46:34] those State spaces do we know how to do [46:35] shrinking in the closure World we're [46:37] pretty good at that right we have um uh [46:40] test. Che and test. generative there are [46:42] algorithms for this so I would love to [46:44] see uh maybe at closure West or maybe at [46:46] closure CL next year somebody giving a [46:48] talk about taking the ideas in this book [46:50] and realizing them in closure um most of [46:53] the ideas in the book are demonstrated [46:54] with python code and I don't think and [46:55] python is a great language but we should [46:57] not let them have all the fun right this [46:59] is a problem which is well suited for [47:01] closure because as you might imagine one [47:03] of the places where this gets [47:04] complicated is I can just glibly say the [47:06] entire State space of a succeeding [47:08] application and the entire State space [47:10] of a failing application but how are you [47:11] actually going to capture that well [47:13] that's a hard problem but it's easier in [47:15] a language that doesn't have very much [47:16] state right so to the extent that it's [47:19] possible at all right in a ball of mud [47:21] Python program it ought to be a lot [47:23] easier to do in a closure program and [47:24] know by the way having a language that [47:26] had models code as data and passes [47:29] everything around as data we are [47:30] uniquely in a good position to implement [47:32] these kinds of [47:33] algorithms so that's the that's the uh [47:36] software specifics work at a high level [47:38] of extraction remember the fault is not [47:40] the defect remember that GC did it read [47:43] the manual twice don't trust because [47:46] people lie reproduce and let's go out [47:49] and automate some things and you know [47:51] I've been told that Six Bullets is too [47:52] many to Remember at the end of a long [47:53] day so I'll make that even simpler and [47:55] say let's back up and talk about about [47:57] science a little bit it's really easy [47:59] know where you're going right remember [48:01] Yogi Bara if you have if you're heading [48:03] into a particular direction you have a [48:04] much better chance of getting there and [48:05] then make well-founded choices this is [48:08] where uh developers both beginner and [48:11] expert uh make the most frequent [48:13] mistakes they're under pressure and [48:15] something's not working and you say and [48:17] we've all done it and you say you know [48:18] what I'm going to try this stop and ask [48:20] yourself why should I try that stop and [48:23] ask plushy cthulu you don't even have to [48:24] talk to another person you can say [48:26] plushy cthulu I have this idea about [48:29] what's going wrong and why my keynote [48:31] presentation didn't do what it was [48:32] supposed to do during the middle of the [48:33] talk maybe you and I can sit down [48:35] afterwards and I'm gonna talk you [48:35] through I'm going to give you a [48:36] hypothesis the effort of doing that and [48:39] writing it down that's the final thing [48:41] once you've decided you're going to take [48:42] any kind of action um justify that in [48:44] writing first right these those two [48:48] steps making good choices and writing [48:51] your steps down are going to turn a [48:54] halfhazard random walk around a [48:57] into a directed focused cruise to an [49:00] easy solution and an early night [49:02] relaxing at the bar with your friends [49:04] thank you very much