{"id":265,"date":"2026-08-28T16:01:18","date_gmt":"2026-08-28T16:01:18","guid":{"rendered":"https:\/\/www.fabricioruch.ch\/?p=265"},"modified":"2026-09-25T21:17:28","modified_gmt":"2026-09-25T21:17:28","slug":"der-halbe-fix","status":"publish","type":"post","link":"https:\/\/www.fabricioruch.ch\/?p=265","title":{"rendered":"Der halbe Fix ist die teuerste Stelle"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>Ein Pfad ist schnell. Der andere sieht aus wie derselbe Code. Genau dort bleibt die alte Komplexit\u00e4t liegen.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Es gibt eine bestimmte Art von Optimierung, die sich im Review besonders gut anf\u00fchlt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Man hat das Problem erkannt. Man hat den Index gebaut. Man hat&nbsp;<code>ToDictionary<\/code>&nbsp;geschrieben. Der teure Scan ist weg \u2014 jedenfalls dort, wo man hingeschaut hat.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wochen sp\u00e4ter ist der Export immer noch langsam. Der Profiler zeigt dieselbe Familie von Methoden. Jemand sagt: \u00abDas haben wir doch schon gemacht.\u00bb<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Stimmt. Zum Teil.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der halbe Fix ist unbequemer als gar keiner. Er erzeugt die \u00dcberzeugung, das Thema sei geschlossen. Die teure Stelle sitzt dann oft genau neben der Stelle, die man angefasst hat.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Zwei Pfade, eine Absicht<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Exporte holen selten nur eine Sorte Zeilen. Es gibt interne und externe S\u00e4tze. Aktive und archivierte. Eine Abbildung f\u00fcr den Normalfall und eine f\u00fcr den Sonderfall.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Fachlich geh\u00f6rt das zusammen. Im Code sind es zwei Methoden, zwei Generatoren, zwei Lambdas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der eine Pfad sieht nach dem Fix so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>var byId = masters.ToDictionary(m =&gt; m.Id);\n\nforeach (var item in movements)\n{\n    if (!byId.TryGetValue(item.MasterId, out var master))\n        continue;\n\n    rows.Add(Map(item, master));\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Einmal indexieren. Dann nachschlagen. Linear in der Summe der Mengen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der andere Pfad, derselbe Export, eine andere Teilmenge, sieht oft so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>var byId = masters.ToDictionary(m =&gt; m.Id);\n\nvar rows = movements\n    .Select(item =&gt; Map(item, byId.Values.ToList()))\n    .ToList();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Oder, noch enger am Alltag: die Methode darunter erwartet eine Liste. Die Lambda erf\u00fcllt den Vertrag, indem sie das Dictionary bei jedem Element wieder auspackt.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.Select(item =&gt; Join(item, &#91;.. byId.Values]))<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der Index existiert. Die Suche ist wieder linear. Zus\u00e4tzlich entsteht pro Zeile eine neue Liste.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wer nur den ersten Pfad liest, h\u00e4lt das Modul f\u00fcr erledigt. Wer den zweiten nicht \u00f6ffnet, sieht im Profiler \u00abden Export\u00bb \u2014 nicht \u00abdie eine Abbildung, die niemand umgestellt hat\u00bb.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Warum das so leicht stehen bleibt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Der zweite Pfad war nie falsch. Er war nur der, den man nicht angefasst hat, weil der erste schon wehgetan hat.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Reviews verst\u00e4rken das. Eine sichtbare&nbsp;<code>ToDictionary<\/code>-Zeile gilt als Beweis. Die Signatur der Hilfsmethode bleibt&nbsp;<code>List&lt;T&gt;<\/code>. Damit ist der R\u00fcckfall in&nbsp;<code>Find<\/code>&nbsp;nicht nur m\u00f6glich. Er ist der Weg des geringsten Widerstands.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dazu kommt eine sprachliche Falle. \u00abWir cachen die Stammdaten.\u00bb \u00abWir laden alles einmal.\u00bb Beides kann wahr sein und den Join trotzdem unver\u00e4ndert lassen. Beschaffung und Abbildung sind zwei verschiedene Kosten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der halbe Fix optimiert die Beschaffung. Die Abbildung bleibt die alte, weil sie in einer anderen Datei steht, eine andere Teilmenge betrifft oder \u00abnur\u00bb eine Liste entgegennimmt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Woran man ihn erkennt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Drei Muster reichen meist.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Das Dictionary wird gebaut und nicht benutzt.<\/strong>&nbsp;Es gibt&nbsp;<code>ToDictionary<\/code>&nbsp;oder&nbsp;<code>TryGetValue<\/code>&nbsp;in der N\u00e4he \u2014 und in der Lambda trotzdem&nbsp;<code>Find<\/code>,&nbsp;<code>FirstOrDefault<\/code>&nbsp;oder eine Kopie von&nbsp;<code>.Values<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Dieselbe Abbildung existiert zweimal.<\/strong>&nbsp;Einmal mit Index, einmal mit Liste. Die Namen unterscheiden sich um ein Suffix: intern, extern, aktiv, alle. Die Logik ist kopiert, nicht geteilt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Die Hilfsmethode hat die alte Signatur behalten.<\/strong>&nbsp;Oben steht ein Dictionary. Unten steht&nbsp;<code>List&lt;Master&gt;<\/code>. Dazwischen eine Conversion, oft in der Lambda, oft ohne eigenen Namen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Kein Muster braucht einen Profiler. Es braucht den zweiten Blick auf den Nachbarpfad.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Was der ganze Fix ist<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nicht \u00abnoch ein Dictionary\u00bb. Sondern dieselbe Abbildung f\u00fcr alle Teilmengen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die Hilfsmethode nimmt den Index entgegen, nicht die Liste:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>static Row? Join(Item item, IReadOnlyDictionary&lt;string, Master&gt; byId)\n{\n    if (!byId.TryGetValue(item.MasterId, out var master))\n        return null;\n\n    return Map(item, master);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Beide Pfade rufen dieselbe Methode auf. Wer sp\u00e4ter eine dritte Teilmenge baut, bekommt den Lookup geschenkt \u2014 oder st\u00f6sst an der Signatur an, statt still eine Liste durchzusuchen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wenn eine Stelle wirklich eine Liste braucht, etwa f\u00fcr eine einzelne Zeile in einem anderen Use-Case, bleibt das eine eigene \u00dcberladung. Der Exportpfad darf sie nicht benutzen, indem er den Index zerst\u00f6rt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Die teuerste Stelle<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Gar nicht zu optimieren ist ehrlich. Man sieht die lineare Suche. Man kann sie sp\u00e4ter finden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der halbe Fix erzeugt eine Geschichte: das Thema sei erledigt. Die langsame Stelle tr\u00e4gt denselben Namen wie die schnelle. In der Telemetrie heisst beides \u00abExport\u00bb.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Deshalb ist er teurer. Nicht, weil er mehr CPU kostet als der Ursprungszustand \u2014 manchmal sogar weniger. Sondern weil er die Aufmerksamkeit abzieht, die man br\u00e4uchte, um den Rest zu sehen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eine Optimierung ist erst fertig, wenn alle Abbildungen derselben Daten dieselbe Komplexit\u00e4t haben. Nicht wenn die erste Methode h\u00fcbsch aussieht.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ein Pfad ist schnell. Der andere sieht aus wie derselbe Code. Genau dort bleibt die alte Komplexit\u00e4t liegen. Es gibt eine bestimmte Art von Optimierung, die sich im Review besonders gut anf\u00fchlt. Man hat das Problem erkannt. Man hat den Index gebaut. Man hat&nbsp;ToDictionary&nbsp;geschrieben. Der teure Scan ist weg \u2014 jedenfalls dort, wo man hingeschaut [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":266,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[27,26],"tags":[87,158,157],"class_list":["post-265","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-programming-principles","category-quality-assurance-software-engineering","tag-german","tag-optimization","tag-performance"],"_links":{"self":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts\/265","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=265"}],"version-history":[{"count":2,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts\/265\/revisions"}],"predecessor-version":[{"id":274,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts\/265\/revisions\/274"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/media\/266"}],"wp:attachment":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=265"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=265"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=265"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}