diffox
Blog

Blast Radius: was ein Pull Request außerhalb seines Diffs berührt

Blast Radius ist der Code, den eine Änderung außerhalb des Diffs erreicht. An einem echten Pull Request zeigen wir, wie Sie ihn ohne falsche Pfeile im Code finden.

TeilenXLinkedIn

Zusammenfassung. Der Blast Radius (Wirkungsbereich) ist der Code, den eine Änderung außerhalb des Diffs erreicht: die Dateien, die den geänderten Code aufrufen oder importieren. Dieser Beitrag zeigt, wie Sie den Blast Radius eines Pull Requests ohne Modell allein aus dem Code ermitteln. Außerdem erklärt er, warum ein Abgleich über Namen falsche Pfeile erzeugt und wie Sie das Ergebnis auf GitHub als Diagramm darstellen. Wie sich die Tools verhalten, haben wir am 14. Oktober 2026 in ihrer Dokumentation nachgelesen. Das Beispiel ist ein gemergter Pull Request der Bibliothek requests.

  • Der Diff zeigt nicht, wer den Code aufruft. Er zeigt die geänderten Zeilen, aber nicht die Dateien, die sie nutzen. In unserem Beispiel ändern sich zwei Dateien, und 9 Dateien greifen von außen auf sie zu.
  • Ein Abgleich über Namen erzeugt falsche Pfeile. Vier Arten haben wir in echter Ausgabe gefunden: eine zweite Definition mit demselben Namen, einen Kommentar, der den Namen nur erwähnt, eine zweite Kopie des Quellbaums und einen Import, der der falschen Datei zugeordnet wird.
  • Ein Pfeil nur dann, wenn der Import auf die geänderte Datei zeigt. Wenige sichere Pfeile sind besser als viele, die der Leser einzeln prüfen muss.
  • Kein Diagramm, wenn nicht alles gelesen wurde. Ein halbes Diagramm sieht aus wie „diesen Code ruft niemand auf“.

Was ist der Blast Radius?

4Arten falscher Pfeile, in echter Ausgabe gefunden
9Dateien, die im Beispiel von außen zugreifen
8Kästen pro Diagramm, höchstens

Der Begriff kommt aus dem Infrastruktur-Engineering und beschreibt, wie weit sich ein Ausfall ausbreitet. Laut dem AWS-Leitfaden zur zellbasierten Architektur verkleinert es den Blast Radius, ein System in voneinander isolierte Zellen aufzuteilen. Das hilft nicht nur beim laufenden Traffic, sondern auch, wenn ein neues Feature oder ein Bugfix ausgerollt wird (Quelle).

Im Code-Review ist die Frage enger gefasst: Wer ruft den geänderten Code außerhalb dieses Pull Requests auf, und wer importiert ihn? Ändert sich der Rückgabetyp einer Funktion, ist jede Stelle betroffen, die sie nutzt. Wer die Änderung prüft, muss also auch diese Dateien im Blick haben. Viele suchen das Thema auch unter „Change Impact Analysis“ (Auswirkungsanalyse von Änderungen).

In diesem Beitrag beschränken wir den Blast Radius auf seinen innersten Ring: die Dateien, die die geänderte Datei direkt aufrufen oder importieren. Für ein solches Diagramm mit ausschließlich echten Pfeilen braucht es vier Regeln.

4 Regeln für echte Pfeile. Ein Blast-Radius-Diagramm ohne Modell, nur aus dem Code. 1. Import zur Datei auflösen. Der Import entscheidet, nicht der Name. 2. Nur den Code lesen. Ein Name im Kommentar ist kein Pfeil. 3. Nur eindeutige Namen. Zwei Definitionen: Wer ist gemeint? 4. Nur vollständig zeichnen. Ein halbes Diagramm sagt „niemand ruft es auf“.
Vier Regeln, mit denen Sie den Blast Radius eines Pull Requests ohne Modell allein aus dem Code zeichnen.

Was der Diff nicht zeigt

Wenn Sie einen Pull Request öffnen, zeigt Ihnen GitHub die geänderten Dateien und Zeilen. Dateien, die sich nicht geändert haben, aber den geänderten Code nutzen, stehen nicht auf dieser Seite; an sie muss der Reviewer selbst denken.

Ein echtes Beispiel: Pull Request 7441 der Bibliothek requests ändert zwei Dateien, _types.py und models.py, und nur diese beiden stehen im Diff. Beim Durchsuchen des Baums am Head-Commit des Pull Requests fanden wir 9 weitere Dateien, die auf sie zugreifen. sessions.py und adapters.py etwa nutzen die Klasse PreparedRequest aus models.py direkt. Ändert sich diese Klasse, kann sich auch das Verhalten dieser beiden Dateien ändern.

Wie wir auf diese Zahl kamen und warum wir manche Pfeile bewusst weglassen, erklärt der Rest des Beitrags.

Drei gängige Wege, Aufrufer zu finden

  • Textsuche. git grep -w name findet jede Zeile, in der der Name als ganzes Wort vorkommt (Dokumentation). Das ist schnell und braucht kein Setup. Allerdings trifft es auch Kommentare, Strings und andere Funktionen mit demselben Namen; welche Treffer echte Aufrufe sind, müssen Sie selbst herausfinden.
  • Sprachserver. „Find All References“ im Editor schickt die Anfrage textDocument/references des Language Server Protocol, und der Sprachserver liefert alle Referenzen auf das Symbol unter dem Cursor im ganzen Projekt (Spezifikation). Das Ergebnis ist exakt, gilt aber immer nur für ein Symbol, und das Projekt muss im Editor eingerichtet sein. GitHubs Code-Navigation im Browser funktioniert anders: Sie nutzt tree-sitter und durchsucht laut Dokumentation alle Definitionen und Referenzen eines Repositorys nach Einträgen mit dem angegebenen Namen (Dokumentation). Sie gleicht also über den Namen ab.
  • Aufrufgraph-Tools. Tools wie pyan für Python und madge für JavaScript erstellen den Graphen des ganzen Repositorys. pyan nennt seine eigene Analyse „ziemlich oberflächlich“ (Quelle); madge zeichnet keine Funktionsaufrufe, sondern Modulabhängigkeiten (Quelle). Beide betrachten das ganze Repository statt eines einzelnen Pull Requests, und ihr Ergebnis passt in keinen Review-Kommentar.
Drei Wege, drei Irrtümer. Textsuche: git grep -w. Schwäche: Gleicher Name. Auch Kommentare und Namensvettern. Sprachserver: Find All References. Schwäche: Ein Symbol. Exakt, aber ein Name pro Anfrage. Aufrufgraph: pyan, madge. Schwäche: Ganzes Repo. Sieht das Repo, nicht den Pull Request. Import auflösen: geänderte Dateien. Zeichnet: Echte Pfeile. Wenige Pfeile, nichts nachzuprüfen. Die vierte Karte zeigt unseren Weg.
Drei verbreitete Wege, Aufrufer zu finden, und der vierte, den dieser Beitrag vorschlägt. Quelle: die Dokumentation der Tools, 14. Oktober 2026.

Für einen Pull Request braucht es etwas dazwischen: ein Diagramm, so exakt wie der Sprachserver und so schnell wie die Textsuche, das nur die Umgebung der geänderten Dateien betrachtet. Der Weg dorthin: Folgen Sie nicht dem Namen, sondern der Import-Zeile.

Vier Arten falscher Pfeile aus echter Ausgabe

Beim Bau des Schritts in diffox, der den Blast Radius eines Pull Requests zeichnet, haben wir die Ausgabe der ersten Versionen von Hand durchgesehen und vier Arten falscher Pfeile gefunden. Für jede gibt es jetzt eine Regel:

  1. Ein Name, an mehreren Stellen definiert. Drei verschiedene Tools definierten je eine Hilfsfunktion mit demselben Namen, und die Aufrufer der einen erschienen als Aufrufer der anderen. Regel: Kein Pfeil für einen Namen, der nicht genau einmal im ganzen Baum definiert ist.
  2. Eine zweite Kopie des Baums. Ein Git-Worktree im Repository war eine Kopie des ganzen Baums; zwei Drittel dessen, was der erste Lauf fand, kamen von dort. Regel: Ordner, deren Name mit einem Punkt beginnt, werden übersprungen.
  3. Eine Zeile, die den Namen nur erwähnt. Eine Kommentarzeile nannte den Namen der Funktion als Beispiel. Regel: Nur der Code selbst zählt; nach dem Entfernen von Kommentaren und Strings muss ein Aufruf oder ein Import übrig bleiben.
  4. Ein Import, der der falschen Datei zugeordnet wird. Die Zeile from werkzeug.exceptions import NotFound wurde als Aufrufer der repository-eigenen Datei exceptions.py gezeichnet, nur weil beide Dateien gleich hießen. Regel: Ein Pfeil nur dann, wenn die Import-Zeile des Aufrufers genau auf die geänderte Datei zeigt. Ein relativer Import lässt sich über den Pfad berechnen; alle anderen werden anhand der Dateien im Baum genau einer Datei zugeordnet.

Die vierte Regel hat eine Kehrseite: Ein Aufrufer, dessen Import sich nicht verfolgen lässt, kommt nicht ins Diagramm. In Go und Ruby bindet ein Import keinen Namen, deshalb bekommen Dateien in diesen Sprachen keine Pfeile. Auch eine Methode, die über ein Objekt aufgerufen wird (obj.x()), lässt sich keiner Datei zuordnen. Ein fehlender Pfeil kostet den Leser nichts. Ein falscher Pfeil dagegen kostet ihn das Vertrauen in den Rest des Diagramms.

So bleibt das Diagramm lesbar

In einem echten Repository wächst die Zahl der Aufrufer schnell. Zwei Dinge helfen.

  • Lassen Sie Tests weg. In unserem eigenen Repository kamen 16 der 19 Pfeile, die die erste Version zeichnete, aus Testdateien. „Ihre Tests rufen diese Funktion auf“ muss niemand sehen, der einen Pull Request öffnet. Ob eine Datei ein Test ist, erkennen Sie an ihrem Pfad, nicht an ihrem Inhalt: der Ordner tests/, test_*.py, *.test.ts und Ähnliches. Wie viel das ausmacht, hängt vom Repository ab: Im Beispiel requests hätten die Tests nur eine Datei hinzugefügt.
  • Begrenzen Sie die Zahl der Kästen und nennen Sie den Rest. Ein Diagramm mit vierzig Kästen liest niemand, und ein Diagramm, das nicht sagt, dass es gekürzt ist, wirkt vollständig. Wir zeichnen höchstens 8 Kästen und höchstens 6 Aufrufer pro geänderter Datei und zählen den Rest. Im Beispiel requests wurden 6 von 9 Dateien gezeichnet; die übrigen 3 nennt ein Satz unter dem Diagramm.

Schnellere Suche bei großen Änderungen

Der einfachste Weg, Aufrufer zu finden: für jeden Namen, den die geänderten Dateien definieren, jede Zeile des Baums nach \bNAME\b durchsuchen. Eine große Änderung definiert Hunderte Namen; in einem Baum mit Hunderten Dateien sind das Millionen Regex-Suchen. Bei einem großen Pull Request in unserem eigenen Repository führte dieser Schritt 19,1 Millionen Suchen aus, und auf dem Server wurde das Diagramm nicht innerhalb seines Zeitbudgets von 20 Sekunden fertig. Zwei Änderungen verkürzen die Laufzeit:

  1. Filtern Sie zuerst die Kandidaten. Eine Datei, die den Namen der geänderten Datei nie als Wort nennt, kann sie nicht importieren, denn in jeder Import-Zeile wie from service.app import x oder require("./app") steht das letzte Stück des Pfads als eigenes Wort. In unserem alten Tool dauerte das Indizieren des ganzen Baums bei 299 Dateien 51 Sekunden; dieser Filter senkte das auf 38 Dateien und 3,2 Sekunden, ohne einen einzigen Pfeil zu verlieren.
  2. Suchen Sie pro Zeile, nicht pro Name. Laut Python-Dokumentation ist \b die Grenze zwischen einem Wortzeichen und einem Nicht-Wortzeichen (Dokumentation). Besteht ein Name nur aus Wortzeichen, trifft \bNAME\b also genau dann, wenn eines der Wörter der Zeile dieser Name ist. Ein einziges findall pro Zeile beantwortet die Frage deshalb für alle Namen auf einmal; übrig bleibt eine Schnittmenge. Namen mit anderen Zeichen wie $foo oder valid? bekommen weiterhin ein eigenes Muster.
import re

WORD = re.compile(r"\w+")

def hits(lines, names):
    """Die Namen jeder Zeile: ein findall pro Zeile statt eines Regex pro Name."""
    wanted = set(names)
    for number, line in enumerate(lines, 1):
        found = wanted.intersection(WORD.findall(line))
        if found:
            yield number, found

Wie viel das bringt, hängt von der Zahl der gesuchten Namen ab. Wir haben beide Wege auf denselben Bäumen und derselben Maschine gemessen, und beide fanden jedes Mal genau dieselben Ergebnisse:

Gesamtzeit von Scan und Index · Median aus drei Läufen · Apple-M4-Laptop Django, 1 Datei (127 Namen · pro Name): 0,94 s; Django, 1 Datei (pro Zeile): 0,79 s; Django, 5 Dateien (577 Namen · pro Name): 5,30 s; Django, 5 Dateien (pro Zeile): 1,37 s; Unser Pull Request (570 Namen · pro Name): 12,70 s; Unser Pull Request (pro Zeile): 1,54 s.
Drei Änderungen, jede mit beiden Suchwegen: Gesamtzeit von Scan und Index, Median aus drei Läufen. Auf einem Laptop, 14. Oktober 2026; der Server ist langsamer.

Ändert sich in Django eine einzige große Datei (127 Namen), ist der Unterschied zwischen beiden Wegen klein. Verteilt sich die Änderung auf die fünf größten Dateien (577 Namen), ist die Suche pro Zeile 3,9-mal so schnell, in unserem eigenen Pull Request (570 Namen) 8,2-mal. Diese Änderung beschleunigt also nicht jedes Review; wenn die Zahl der Namen wächst, bringt sie das Diagramm wieder ins Zeitbudget. Den Engpass haben wir nicht erraten, sondern mit einem Profiler gefunden: Der größte Teil der Zeit steckte in einer einzigen Schleife.

Wann gar kein Diagramm gezeichnet wird

Stößt ein Scan an eine Zeit- oder Größengrenze und bleibt mitten im Baum stehen, ist das Ergebnis keine Stichprobe der Aufrufer. Eine Datei, die nicht gelesen wurde, hat im Diagramm keine Aufrufer, und das sieht aus wie „niemand ruft das auf“. Deshalb zeichnen wir in diesen Fällen gar kein Diagramm:

  • Die Zeit ist um. Scan und Index müssen innerhalb eines Zeitbudgets fertig werden; bei uns sind es 20 Sekunden.
  • Die geänderte Datei ließ sich nicht lesen. Sie war zu groß, ein Symlink oder keine reguläre Datei.
  • Eine Grenze wurde überschritten. Die Zahl der gelesenen Dateien, die Gesamtgröße oder die Zahl der gesuchten Namen lag über dem Schwellenwert.
  • Es gibt keine Aufrufer. Ein Kasten ohne Pfeil behauptet eine Isolation, die die Daten nicht belegen.

Dem Leser nichts zu zeigen ist besser, als ihm etwas Falsches zu zeigen. Warum das Diagramm fehlt, gehört aber ins Log: Nur dort sieht man, ob es ein Fehler war oder ein erwarteter Abbruch.

Das Diagramm mit Mermaid auf GitHub darstellen

Am einfachsten zeigen Sie das Diagramm mit Mermaid. Laut GitHub-Dokumentation werden Diagramme in Issues, Discussions, Pull Requests, Wikis und Markdown-Dateien gerendert; dazu schreiben Sie die Mermaid-Syntax in einen Codeblock mit dem Sprachbezeichner mermaid (Dokumentation).

Das Diagramm für das Beispiel requests, so wie es im Pull-Request-Kommentar steht:

```mermaid
flowchart LR
  n0["_types.py"]:::changed
  n1("utils.py")
  n2("adapters.py")
  n3("cookies.py")
  n4("sessions.py")
  n5["models.py"]:::changed
  n6("__init__.py")
  n7("exceptions.py")
  n1 -.->|"SupportsItems"| n0
  n2 -.->|"is_prepared"| n0
  n3 -.->|"is_prepared"| n0
  n4 -.->|"is_prepared"| n0
  n6 -.->|"Request, PreparedRequest +1"| n5
  n7 -.->|"Request, PreparedRequest +1"| n5
  n4 -->|"Request, PreparedRequest +1"| n5
  n1 -.->|"Request, PreparedRequest +1"| n5
  n2 -->|"PreparedRequest, Response"| n5
  classDef changed stroke:#3a6b8b,stroke-width:2px
```
BEISPIEL. Vier Teile, ein Diagramm. Pull-Request-Kommentar: flowchart LR /   n0["_types.py"]:::changed /   n5["models.py"]:::changed /   n4("sessions.py") /   n4 -->|"PreparedRequest"| n5 /   n4 -.->|"is_prepared"| n0 /   classDef changed stroke-width:2px. :::changed: Geänderte Datei, eigener Rahmen. -->: Durchgezogen: Aufruf gefunden. -.->: Gestrichelt: nur Import. |"Name"|: Genutzte Namen über dem Pfeil.
Die vier Teile desselben Diagramms: der Rahmen der geänderten Datei, der durchgezogene Pfeil, der gestrichelte Pfeil und die Namen über dem Pfeil. Beispiel: requests 7441.

Drei kleine Entscheidungen machen das Diagramm lesbar. Geänderte Dateien bekommen einen eigenen Rahmen. Am Pfeil stehen die verwendeten Namen; ab dem dritten werden sie zu einer Zahl wie „+1“ zusammengefasst. Importiert eine Datei die geänderte Datei nur und wurde kein direkter Aufruf gefunden, ist ihr Pfeil gestrichelt. Und ein Satz unter dem Diagramm sagt, was fehlt.

Der Blast Radius in diffox

Jedes Mal, wenn diffox einen Pull Request prüft, führt es diesen Schritt am Head-Commit erneut aus. Findet es Aufrufer außerhalb des Diffs, fügt es dem zusammenfassenden Kommentar ein Mermaid-Diagramm mit der Überschrift „Was diese Änderung berührt“ hinzu. Es gelten die Regeln aus diesem Beitrag: kein Modell, nur Pfeile, deren Import auf die geänderte Datei zeigt, keine Tests, höchstens 8 Kästen, ein Zeitbudget von 20 Sekunden und kein Diagramm, wenn der Scan unvollständig ist. Unterstützt werden Python, JavaScript/TypeScript und Java. Wenn Sie das Diagramm nicht möchten, schalten Sie die „Änderungsübersicht“ im Tab Einstellungen der Konsole ab.

Das Diagramm zeigt den Blast Radius; ob dabei etwas kaputtgeht, sagen die Feststellungen. Der Blast Radius Agent, einer von 17 spezialisierten Agenten, sucht kaputte Aufrufer, veränderte API-Oberflächen und Signatur- oder Rückgabetyp-Änderungen, die andere Dateien kaputt machen (wie er arbeitet, erklärt ein eigener Beitrag).

Häufige Fragen

Was ist Blast Radius in der Softwareentwicklung?

Der Blast Radius beschreibt, wie weit sich die Wirkung eines Fehlers oder einer Änderung ausbreitet. In der Infrastruktur sagt er, wie viele Nutzer ein Ausfall erreicht, im Code-Review, welche Dateien außerhalb des Pull Requests den geänderten Code aufrufen oder importieren.

Sind Blast Radius und Change Impact Analysis dasselbe?

Sie sind eng verwandt. Change Impact Analysis (Auswirkungsanalyse von Änderungen) ist der allgemeine Name für die Aufgabe, vorab herauszufinden, was eine Änderung sonst noch beeinflusst. Der Blast Radius eines Pull Requests ist der innerste Ring dieser Analyse: die Dateien, die die geänderte Datei direkt nutzen.

Wie finde ich heraus, wer eine Funktion aufruft?

Nutzen Sie „Find All References“ in Ihrem Editor; der Sprachserver löst alle Referenzen des Symbols auf. Für einen schnellen Blick reicht git grep -w funktionsname, aber Treffer in Kommentaren und in anderen Definitionen mit demselben Namen müssen Sie von Hand aussortieren. Für alle geänderten Dateien eines Pull Requests braucht es dagegen einen Scan, der die Imports auf die geänderte Datei auflöst.

Funktioniert ein Mermaid-Diagramm in einem Pull-Request-Kommentar auf GitHub?

Ja. GitHub stellt Mermaid-Diagramme in Issues, Discussions, Pull Requests, Wikis und Markdown-Dateien dar. Sie müssen die Syntax nur in einen Codeblock mit dem Sprachbezeichner mermaid schreiben.

Warum fehlen Tests im Blast-Radius-Diagramm?

Tests gehören zu den Dateien, die geänderten Code am häufigsten aufrufen, und überladen das Diagramm; in unserem eigenen Repository kamen die meisten Pfeile der ersten Version aus Tests. Für die Person, die den Pull Request öffnet, ist die eigentliche Frage, was im Produktionscode betroffen ist.

Was bedeuten die gestrichelten Pfeile im Diagramm?

Ein gestrichelter Pfeil zeigt, dass die aufrufende Datei etwas aus der geänderten Datei importiert, aber kein direkter Aufruf gefunden wurde. Ein durchgezogener Pfeil bedeutet, dass mindestens ein Aufruf gefunden wurde.

In welchen Sprachen zeichnet diffox den Blast Radius?

In Python, JavaScript/TypeScript und Java. In diesen Sprachen bindet ein Import einen Namen, und es lässt sich herausfinden, zu welcher Datei er führt. In Go und Ruby bindet ein Import keinen Namen, deshalb zeichnet diffox dort keine Pfeile; statt zu raten, lässt es den Pfeil weg.