Zum Inhalt springen

Technik

Warum wir eine Summe nie aus den gerenderten Zeilen bilden

Von Jahangir Alam · 16. September 2026 · 6 Min. Lesezeit

Wenn Ihre Oberfläche eine Position ausblenden kann, dürfen Ihre Summen nicht aus den Zeilen abgeleitet werden, die sie zeigt. Aufgeschrieben klingt das offensichtlich. Während man die Komponente schreibt, ist es das nicht – und es ist die Wurzel einer ganzen Familie von Fehlern, bei denen ein System genau die Zahl verrät, die es verbergen sollte.

Dieser Beitrag handelt von zwei Funktionen, die zusammenhanglos wirken, bis sie aufeinandertreffen, von der Invariante, die sie ehrlich hält, und von der Stelle, an der wir es trotzdem falsch gemacht haben.

Der Aufbau

Wir bauen eine B2B-Angebots-App für Shopify. Zwei Fähigkeiten greifen ineinander, ohne dass eine von beiden das vorhergesehen hätte:

  1. Preissichtbarkeit je Position. Ein Händler kann eine Angebotsposition als Preis ausgeblendet markieren – der Käufer sieht den Artikel mit „Inklusive“ statt einer Zahl. Fracht, die übernommen wird, ein Muster, eine Leistung, die in einer anderen Position steckt.
  2. Teilannahme. Ein Käufer kann manche Positionen eines Angebots annehmen, andere ablehnen und den Rest offenlassen.

Beide ändern, welche Zeilen ein Käufer sieht. Keine darf ändern, was der Käufer schuldet.

Der Fehler, den Sie schreiben werden

Die naheliegende Umsetzung eines Summenblocks ist ein Reduce über die Zeilen, die Sie rendern:

const subtotal = visibleLines.reduce((acc, l) => acc + l.lineTotal, 0)

Blenden Sie nun eine Position über 500 $ aus. Die Zwischensumme fällt still um 500 $, und der Käufer sieht eine Summe, die nicht zu dem Angebot passt, das er bekommen hat. Also beheben Sie es auf die naheliegende Weise – alle Zeilen summieren, einige rendern:

const subtotal = allLines.reduce((acc, l) => acc + l.lineTotal, 0)  // immer noch falsch

Jetzt stimmt die Summe und die Position ist verborgen. Und der Käufer kann den verborgenen Preis mit einer einzigen Subtraktion zurückgewinnen, denn die Lücke zwischen den sichtbaren Zeilen und der gedruckten Summe ist die verborgene Zahl. Sie haben ein Rätsel gebaut, keine Vertraulichkeit.

Die Regel

Summen für den Käufer stammen aus der gespeicherten Angebotssumme. Nie aus den gerenderten Zeilen.

Die maßgebliche Zahl wird geschrieben, wenn das Angebot gesendet wird. Jede Oberfläche – die Storefront, die Kundenkonto-Erweiterung, das gehostete Portal, vier PDF-Vorlagen – liest diesen gespeicherten Wert. Keine leitet ihn neu ab.

Das ist notwendig, aber nicht hinreichend, denn für sich genommen verschiebt es das Leck nur. Enthält die gespeicherte Summe den Preis einer verborgenen Position, funktioniert die Subtraktion weiterhin. Die Regel braucht also eine zweite:

Eine preislich ausgeblendete Position muss null sein, durchgesetzt vor dem Senden.

Was ohnehin die ehrliche Version der Funktion ist. „Preis ausgeblendet“ kann nur heißen das kostet Sie nichts extra. Es kann nicht heißen wir sagen Ihnen nicht, was das kostet, denn eine Position, die eine sichtbare Summe bewegt, ist nicht verborgen – sie ist verschleiert, und Käufer können rechnen. Wir weisen das Angebot beim Senden zurück, statt etwas zu rendern, wofür wir uns entschuldigen müssten.

Zusammen ergeben beide Bedingungen eine Eigenschaft, die man als Invariante festhalten sollte:

Die sichtbaren Zeilen ergeben immer die gedruckte Summe. Nicht ungefähr. Genau.

Dann kommt die Teilannahme und bricht es trotzdem

Hier ist, was wir falsch gemacht haben, und es ist die interessantere Hälfte.

Nimmt ein Käufer einen Teil eines Angebots an, soll die Auftragsbestätigung die angenommenen Positionen zeigen. Wir haben die Positionstabelle eingegrenzt, eine Summe der Annahme ergänzt, die Tests geschrieben, ausgeliefert.

Das Dokument kam mit einer Tabelle über 1.400 $ angenommener Positionen heraus – unter einer Überschrift, die BESTÄTIGTE SUMME 1.825,00 $ las.

Der Summenblock war aktualisiert. Der Kopfbereich – eine eigene Komponente, darüber gerendert, gebaut zu einer Zeit, als jede Annahme alles oder nichts war – nicht. Er las weiterhin die Gesamtsumme des Angebots, aus seiner eigenen Sicht korrekt, und widersprach der Tabelle acht Zentimeter darunter.

Die Invariante hielt an der Stelle, an die wir dachten, und brach an der, an die wir nicht dachten. Ein zweiter Leser derselben zugrunde liegenden Tatsache, Monate früher geschrieben, wurde still veraltet.

Die Behebung, die verallgemeinert

Nicht „den Kopfbereich auch aktualisieren“. Eine Funktion, durch die jeder Konsument der Schlagzeile hindurch muss:

export function documentHeadlineTotal(quote: QuoteContext): {
  label: "Accepted total" | "Confirmed total"
  amount: string
} {
  return quote.acceptedTotals
    ? { label: "Accepted total",  amount: quote.acceptedTotals.subtotal }
    : { label: "Confirmed total", amount: quote.version.totalEstimate }
}

Beschriftung und Betrag werden gemeinsam gewählt und gemeinsam zurückgegeben. Sie können nicht den angenommenen Betrag nehmen und die Beschriftung des ganzen Angebots behalten, weil beide als ein Wert ankommen. Die Fehlerklasse wird wegentworfen statt geflickt.

Wenn Sie eines mitnehmen: Wenn zwei Dinge übereinstimmen müssen, geben Sie sie aus derselben Funktion zurück. Zwei Aufrufstellen, die dieselbe Quelle lesen, driften in dem Moment auseinander, in dem eine davon geändert wird.

Was wir einem anderen Team sagen würden

  • Finden Sie jeden Konsumenten einer abgeleiteten Zahl, bevor Sie ändern, was sie bedeutet. Wir haben die Summenkomponente durchsucht und den Dokumentkopf nicht. Eine Inventur „wer rendert eine Summe?“ hätte es in fünf Minuten gefunden.
  • Erzwingen Sie an der Grenze, nicht beim Rendern. Die Null-Preis-Bedingung wird beim Senden geprüft. Zum Renderzeitpunkt bleibt nur lügen oder abstürzen.
  • Testen Sie die Arithmetik, nicht die Anwesenheit. „Die Summe der Annahme wird angezeigt“ besteht fröhlich, während der Kopf ihr widerspricht. Prüfen Sie, dass die sichtbaren Zeilen die gedruckte Zahl ergeben.
  • Rastern Sie Ihre PDFs in Tests. Die Textextraktion sagte uns, die richtigen Zeichenketten seien da. Das waren sie – an der falschen Stelle, in Kombination das Falsche sagend. Nur ein Bild zeigte es.

Wir haben das Endverhalten gegen ein echtes Angebot geprüft: vier Positionen mit 1400 + 350 + 75 + 0 (die letzte preislich ausgeblendet), eine gespeicherte Summe von 1825, ein Käufer, der zwei Positionen annahm, und eine Auftragsbestätigung mit der Überschrift Summe der Annahme 1.400,00 über einer Tabelle mit genau diesen zwei Positionen. Sichtbare Zeilen ergeben die gedruckte Summe. Das ist der ganze Test.

Fußnote für Shopify-App-Entwickler

Wer auf der Kundenkonto-UI-Erweiterung baut, trifft auf eine verwandte Falle. Die 64-KB-Grenze für das Bundle drängt dazu, Textbausteine zur Laufzeit zu laden statt sie einzubetten, was vernünftig ist. Dann kann ein Schlüssel aber legitim fehlen – und strings.someLabel.replace("{count}", n) wirft einen TypeError, der die Erweiterung zum Absturz bringt, was den Ladeindikator des Hosts für immer stehen lässt, ohne dass der Käufer einen Fehler sieht. Interpolieren Sie über eine null-sichere Hilfsfunktion. Fragen Sie uns, woher wir das wissen.

Wo das im Produkt auftaucht

Beide beschriebenen Funktionen sind live. Preissichtbarkeit je Position und Teilannahme liegen ab dem Professional-Tarif – siehe Verhandlung und Angebote für die Händlersicht derselben Mechanik, Teilannahme für die kaufmännische Bedeutung und Preise dafür, welcher Tarif was trägt.

Häufige Fragen

Warum darf eine Summe nicht aus den angezeigten Zeilen berechnet werden?

Weil jede Oberfläche, die eine Position ausblenden kann, dann zwei Zahlen erzeugt, die nicht zusammenpassen. Summiert man nur die sichtbaren Zeilen, sinkt die Summe, wenn eine Position ausgeblendet wird, und widerspricht dem gesendeten Angebot. Summiert man alle Zeilen und zeigt nur einige, ist die Differenz zwischen den sichtbaren Zeilen und der gedruckten Summe genau der verborgene Preis.

Warum muss eine preislich ausgeblendete Position null sein?

Weil sie sonst gar nicht verborgen ist. Bewegt eine ausgeblendete Position die sichtbare Summe, lässt sich ihr Preis durch Subtraktion zurückrechnen. Die Null-Bedingung ist das, was aus dem Ausblenden eine ehrliche Aussage macht: „Inklusive“ heißt, dass die Position den Käufer nichts extra kostet.

Verwandte Artikel

So funktioniert QuotWay in Ihrem Shop.

Wir möchten Analyse-Cookies setzen, um zu verstehen, wie die Website genutzt wird. Sie sind nicht erforderlich – eine Ablehnung ändert nichts an der Funktion der Website, und Sie können Ihre Entscheidung jederzeit ändern auf unserer Datenschutzseite.