Unit 04.03: Dead ends and how to surface them
Graphs accumulate nodes that nothing reaches and nodes that go nowhere. Both look like live behaviour in the source file.
Two set operations over the edge list
A node with no outgoing edge is a dead end unless it is deliberately terminal. A node nothing routes to is unreachable. Both fall out of comparing the edge list against the node list.
The code runs both checks on a small graph.
NODES = {"start", "load", "process", "review", "publish", "archive"}
EDGES = [("start", "load"), ("load", "process"), ("process", "review"),
("review", "publish")]
outgoing = {src for src, _ in EDGES}
incoming = {dst for _, dst in EDGES}
dead_ends = sorted(n for n in NODES if n not in outgoing and n != "publish")
unreachable = sorted(n for n in NODES if n not in incoming and n != "start")
print("nodes with no outgoing edge:", dead_ends)
print("nodes nothing routes to :", unreachable)
print("""
`archive` is both: nothing reaches it and it goes nowhere. It was probably
added for a case that was later handled elsewhere, and it will sit in the file
looking like live behaviour until someone runs this check.
`publish` has no outgoing edge on purpose -- it is terminal. The check has to
know which terminals are intended, which is an argument for declaring them.
""")
archive is both unreachable and a dead end - almost certainly added for a case that was later handled elsewhere. It will sit in the file looking like behaviour until someone runs this check, and anyone reading the code will assume runs sometimes reach it.
publish has no outgoing edge on purpose. That is why the check needs to know which terminals are intended, which is a good argument for declaring them explicitly rather than inferring them.
The mistake this prevents
The mistake is trusting the rendered diagram to reveal this. A visualiser draws what the edge list says, so an unreachable node appears as a perfectly ordinary box. The check has to be a check, run in CI, not a picture someone looks at.
Takeaway
Compute unreachable and dead-end nodes from the edge list and run it in CI. Declare intended terminals so the check can tell a designed endpoint from an abandoned one.
