Advice Has a Dependency Graph: Why Good Practices Fail When Their Preconditions Are Ignored

Kai Nguyen

Hatched by Kai Nguyen

Sep 11, 2026

10 min read

94%

0

What if most bad advice is not false, but badly ordered?

“Keep it simple.” “Write clean code.” “Use the right abstraction.” “Move fast.” Each statement can be useful. Each can also produce a disaster when applied before the conditions that make it useful are in place.

This is the hidden structure of technical judgment: practices do not exist as isolated rules. They form a dependency graph. Some decisions must come before others. Some recommendations only make sense after a problem has been defined. Some are destinations disguised as methods. And some are merely habits that survive because nobody has asked what they are for.

Topological sorting offers an unexpectedly powerful model for thinking about all of this. In a dependency graph, a task can be performed only after its prerequisites are satisfied. The same is true of engineering advice. Before choosing an abstraction, you may need to understand the domain. Before optimizing, you need evidence of a bottleneck. Before standardizing a practice, you need to know which variations are genuinely harmful.

The central lesson is simple:

Good advice is not just a statement about what to do. It is a statement about what to do, under which conditions, and in what order.

The hidden graph inside every recommendation

Consider a small software project. A team is deciding whether to introduce a complex architectural pattern. One person says, “We should separate everything cleanly from the beginning.” Another says, “That is overengineering. Keep it simple.” Both may be right, but neither recommendation is complete.

The real decision depends on several prior questions:

  • What problem is the architecture meant to solve?
  • How stable are the requirements?
  • How expensive would future change be?
  • How many developers will work in the system?
  • What evidence suggests that coupling is already causing damage?

These questions are not administrative details. They are precedents in the decision graph. If the team has not identified the problem, then debating the pattern is premature. If the system is changing rapidly, some flexibility may be valuable. If the system is tiny and temporary, elaborate boundaries may impose more cost than they remove.

A source node in a dependency graph has no incoming edges. It can be considered first. In practical reasoning, source nodes are the observations and goals that should anchor a decision: the user problem, the business constraint, the measured failure, the available resources, and the risks that matter.

A sink node has no outgoing edges. It is where the reasoning eventually arrives: a chosen practice, a design, a technical constraint, or a decision to do nothing. Many arguments begin at the sink. People announce the practice before establishing the conditions that justify it.

That reversal creates a peculiar kind of dogmatism. The recommendation appears first, and the reasoning is reconstructed afterward.

“Use dependency injection.”

“Never use global state.”

“Always write tests first.”

“Keep functions small.”

The language of universal rules hides the missing edges. It conceals what the practice depends on, what it is intended to accomplish, and what tradeoffs it introduces.

Means become dangerous when they impersonate ends

A recurring failure in technical culture is the confusion of means and ends. A means is a technique used to produce a result. An end is the result itself. Once the technique becomes socially prestigious, people begin to treat it as morally correct independent of its purpose.

“Clean code” is a useful example. Cleanliness is not a property that exists apart from a reader, a future change, or a failure mode. Code can be elegant yet difficult to modify because it is organized around the wrong boundary. It can be repetitive yet easy to understand. It can contain a clever abstraction that reduces duplication while making the actual behavior harder to locate.

The phrase “code smell” can similarly shift attention from consequences to ceremony. A smell is not a verdict. It is a signal that deserves investigation. Duplicate code may indicate a missing abstraction, or it may indicate two concepts that should remain separate. A long function may reflect poor structure, or it may tell a coherent story that would become harder to follow if scattered across ten tiny functions.

The mistake is not caring about quality. The mistake is defining quality through a proxy and then forgetting the outcome the proxy was meant to represent.

This can be modeled as a broken dependency graph:

  1. The actual goal is maintainability, reliability, speed of change, or reduced operational risk.
  2. A practice is discovered that often supports that goal.
  3. The practice becomes easier to teach than the goal.
  4. The practice is promoted without its conditions.
  5. Compliance replaces judgment.

At the end of this process, teams can become very good at satisfying visible rules while becoming worse at solving the original problem.

A useful diagnostic question is: What would improve if we followed this advice? If the answer is vague, moralized, or circular, the recommendation has probably detached from its purpose. Ask the question again until the expected benefit can be observed or at least described concretely.

For example, instead of saying “we should avoid duplication,” say: “We believe these two implementations will change for the same reason, and maintaining them separately will create inconsistent behavior.” That sentence exposes the dependency. The proposed abstraction now has something to prove.

Context is not a footnote. It is an input

Advice often fails because context is treated as decoration rather than as data. Yet context changes the graph itself. A recommendation that is sensible in a large organization may be wasteful for a small team. A practice that reduces risk in a regulated environment may slow experimentation in a prototype. A design that protects a stable interface may obstruct a domain that is still being discovered.

Imagine two teams building similar features. Team A maintains a financial system used by millions of people, with strict audit requirements and several independent groups contributing code. Team B is testing a new product idea with three people and uncertain requirements. The same advice about abstraction, review, documentation, or testing cannot have the same priority for both teams, because their constraints create different dependency relationships.

For Team A, traceability may be a prerequisite for deployment. For Team B, learning whether anyone wants the product may come first. In one setting, a decision record is part of responsible engineering. In the other, it may be an elaborate artifact produced before the central uncertainty has been resolved.

This does not mean that “anything goes.” Contextual reasoning is not a retreat into relativism. It is a demand for more precise claims. Instead of asking whether a practice is good in the abstract, ask:

  • What problem does it address?
  • Which conditions make that problem likely?
  • What costs does the practice impose?
  • What evidence would show that the practice is working?
  • What would make us stop using it?

These questions turn advice into a conditional statement. Conditional statements are harder to repeat, but far more useful.

Programming knowledge also contains a large amount of tacit knowledge. Experienced engineers often recognize a dangerous coupling, a premature abstraction, or a misleading metric before they can explain exactly why. Their judgment includes pattern recognition built from consequences, not just rules memorized from books.

The challenge is that tacit knowledge can be transmitted badly. A novice may receive the conclusion without the situations that gave rise to it. “Do not abstract too early” becomes a slogan, even though the deeper lesson is about preserving learning while the shape of the problem is uncertain. “Keep things simple” becomes permission to ignore future costs, even though simplicity may mean reducing accidental complexity rather than refusing all structure.

The cure is not to eliminate rules. It is to attach rules to the experiences and failure modes that make them intelligible.

Topological order as a method for engineering judgment

Topological sorting provides more than an analogy. It suggests a practical method for making decisions in the correct order.

Start by listing the decision you are tempted to make. Then work backward from it. What must be true for this decision to be sensible? What facts, goals, and constraints support those conditions? Continue until you reach observations that can be checked rather than preferences that merely feel authoritative.

Suppose a team wants to rewrite a service because “the code is messy.” A dependency aware analysis might look like this:

  1. The team identifies the concrete pain: deployments are slow, defects cluster in one module, or changes repeatedly break unrelated behavior.
  2. The team gathers evidence: deployment data, incident reports, examples of costly changes, and developer time spent navigating the system.
  3. The team distinguishes local defects from structural problems.
  4. The team compares possible interventions: refactoring a boundary, improving tests, changing ownership, replacing a component, or rewriting the service.
  5. The team chooses the smallest intervention that addresses the observed constraint.
  6. The team defines a measure that would indicate whether the intervention helped.

The rewrite, if it remains justified, appears near the end of the ordering. It is a sink, not a source.

This method also clarifies why confident advice is often suspicious. Confidence tends to increase when people jump directly to the sink and omit the path that led there. “The answer is to rewrite it” sounds decisive because it suppresses uncertainty about the diagnosis. But a decision made before its prerequisites is not bold. It is merely underdetermined.

A related danger is the absence of a useful gradient. If every outcome is interpreted as evidence that a favored practice was correct, the practice cannot be learned from. A team may praise a process because a project succeeded, without asking whether the same result would have occurred with less ceremony. Or it may defend a framework after a failure by inventing reasons the failure was unrelated.

Learning requires a gradient: signals that help distinguish better decisions from worse ones. That can come from measurements, counterfactuals, small experiments, or honest retrospectives. Without a gradient, popularity becomes confused with effectiveness. Repetition becomes confused with proof.

The practical implication is important: do not only ask whether a practice produced a good result. Ask what the result teaches you about the practice.

A field guide for unlearning universal rules

The next time you encounter a strong recommendation, treat it as a node whose incoming edges have been hidden. Reconstruct them before accepting or rejecting it.

Ask first what the recommendation is trying to protect. Is it reducing defects, preserving optionality, lowering cognitive load, increasing speed, or satisfying an external obligation? Different goals can require opposite tactics.

Then ask what kind of uncertainty is present. Are you uncertain about the requirements, the technology, the scale, the failure modes, or the people who will maintain the system? A practice that helps with one uncertainty may worsen another. Heavy upfront design can reduce implementation uncertainty while increasing the cost of discovering that the product itself is unwanted.

Next, separate reversible from irreversible decisions. A temporary implementation choice may not deserve the same analysis as a public interface, a database migration, or an organizational commitment. This gives you a way to allocate rigor without turning every decision into a ceremony.

Finally, make the feedback loop explicit. Decide what evidence would confirm the choice, what evidence would weaken it, and when you will revisit the decision. Advice becomes more trustworthy when it makes itself falsifiable.

The mature engineer does not ask, “What is the right practice?” The mature engineer asks, “What must be true before this practice becomes right?”

Key Takeaways

  • Treat advice as a dependency graph. Identify the goal, constraints, and evidence that must come before the recommended practice.
  • Separate means from ends. Replace “we should follow this rule” with “we expect this practice to produce this specific benefit.”
  • Make context explicit. Team size, system maturity, uncertainty, risk, and reversibility can change the correct order of decisions.
  • Create a learning gradient. Use measurements, experiments, and retrospectives to distinguish effective practices from merely popular ones.
  • Prefer conditional rules to universal slogans. “Use this when these conditions hold” is harder to memorize, but much safer to apply.

The deepest shift is to stop thinking of engineering judgment as a collection of rules. It is better understood as an ordering problem. First discover what matters. Then identify the constraints. Then locate the uncertainty. Only after those nodes are in place should a practice be selected.

Bad advice often sounds attractive because it offers a shortcut through the graph. It removes the discomfort of diagnosis and replaces judgment with obedience. Good advice does the opposite. It restores the missing edges, shows what depends on what, and leaves room for reality to change the answer.

The goal is not to find one method that rules every situation. The goal is to become capable of discovering the order in which this situation should be understood. That is where technical maturity begins: not in knowing more rules, but in knowing which questions must be answered before any rule deserves to be followed.

Sources

← Back to Library

Hatch New Ideas with Glasp AI 🐣

Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)

Start Hatching 🐣