- Statt den Abstraktionsgrad durch Aufteilen in kleine Funktionen zu trennen, ist linearer Code, der von oben nach unten verläuft, leichter entlang des Gesamtflusses zu verfolgen
- Wenn man durch Funktionsauslagerung eine Top-down-Struktur erzeugt, muss man möglicherweise hin- und herwechseln, um zwischen ähnlich benannten Funktionen wie
bakeundbakePizzanachzusehen - Kleine Funktionen können zwar die Absicht offenlegen, zugleich aber das tatsächliche Verhalten verbergen, etwa wo das Vorheizen des Ofens stattfindet oder was passiert, wenn man dieselbe Pizza zweimal weitergibt
- Wenn man linearen Code mit schrittweisen Kommentaren versieht, kann man die Arbeitsabsicht erklären, ohne indirekte Verweise zu vermehren; das kann lesbarer sein als zusätzliche Abstraktion
- Das Auslagern kleiner Funktionen, die nur einmal verwendet werden, führt zu Verlust an Linearität; wie bei der Art, den Ofen im Beispiel zu erzeugen, können in realem Code sogar Performance-Probleme sichtbar werden
Wenn Linearität wichtiger ist als Funktionsauslagerung
- Das Beispiel im Google Testing Blog vergleicht zwei Implementierungen von
createPizzaund bewertet die rechte Implementierung als leichter lesbar und Top-down, weil sie keine Abstraktionsebenen mischt - Die Gegenposition hält es für wichtiger, dass die linke Implementierung linear lesbarer Code ist, der sich auf dem Bildschirm von oben nach unten lesen lässt
- Um das Gesamtverhalten der rechten Implementierung zu verstehen, muss man zu mehreren kleinen Funktionsdefinitionen springen
- Auch in der Darstellung wirkt die Größe beider Implementierungen ähnlich, weil ein Teil des rechten Codes ausgelassen wird; tatsächlich ist die rechte Version länger
- Funktionsauslagerung kann es erschweren, das Verhalten allein anhand des Namens ausreichend zu verstehen
- Wenn es sowohl
bakeals auchbakePizzagibt, ist nicht sofort klar, welche Funktion den Ofen aufheizt - Ob das zweimalige Weitergeben derselben Pizza idempotent ist oder das Ergebnis ruiniert, lässt sich nur durch Blick in die Implementierung prüfen
- Wenn es sowohl
Linearer Code mit Kommentaren und das Ofenbeispiel
- Die am leichtesten lesbare Form wird als eine Version des linken linearen Codes bewertet, bei der die Funktionsnamen der rechten Seite als Kommentare eingefügt sind
- Kommentare wie
Prepare pizza,Add toppings,Heat oven,Bake pizza,Box and slicemachen die Absicht jedes Schritts sichtbar - Lesbarkeit entsteht nicht durch zusätzliche Abstraktionsebenen und indirekte Verweise, sondern dadurch, dass klar erklärt wird, was gerade geschieht
- Kommentare wie
- Das Fazit geht eher in die Richtung, kleine Funktionen, die nur einmal genutzt werden, nicht aus linearem Code herauszulösen
- Der Nutzen kleiner ausgelagerter Funktionen gleicht den Verlust an Linearität nicht aus
- Auch die Ofenbehandlung im Beispiel wirkt strukturell ungeschickt
- Das Vorheizen des Ofens ist ein in sich abgeschlossener Vorgang und wäre daher passender als Methode des Ofens selbst
- Für jede einzelne Pizza einen neuen Ofen zu erzeugen und vorzuheizen, passt nicht zu einem realistischen Nutzungsverhalten
- Auch in echtem Code taucht eine solche Struktur auf und kann manchmal Performance-Probleme verursachen
- Der Ofen sollte wahrscheinlich eher als Parameter übergeben werden, statt innerhalb von
createPizzaneu erzeugt zu werden- Das Bereitstellen des Ofens liegt eher in der Verantwortung des Aufrufers
- Wenn der Ablauf darin besteht, die Pizza in eine Schachtel zu legen, könnte ein Interface, das nicht die Pizza, sondern die Schachtel zurückgibt, natürlicher sein
1 Kommentare
Hacker-News-Kommentare
Es ist eine Frage des Stils, und wie beim Kochen ruiniert sowohl zu viel als auch zu wenig Salz das Gericht.
Ich hoffe, hier schlägt niemand eine einzelne 1000-Zeilen-Gottfunktion vor, aber maximal 5 Zeilen pro Funktion sind auch nicht gut lesbar. Wo man trennt, erfordert Urteilsvermögen, ein gutes Gespür und Iteration. Nur weil die erste Abstraktion nicht besonders gut war, sollte man Abstraktion nicht aufgeben; nach ein paar Refactorings können Klassen und APIs entstehen, die gut zur Business-Domain passen.
Gleichzeitig darf man nicht zu voreilig abstrahieren oder so tun, als seien ein paar doppelte Zeilen eine tödliche Verletzung. Voreilige Abstraktion bündelt leicht Code, der sich gar nicht gemeinsam weiterentwickeln muss. Eine Funktion herauszuziehen, die nur an einer Stelle aufgerufen wird, um eine Arbeitseinheit zu verbergen, kann einen Algorithmus aufräumen und ist besonders nützlich, wenn sie Boilerplate oder die Vermischung von Business-Logik und Infrastrukturbelangen wie DB-Verbindungsbehandlung verbirgt. Man sollte es aber mit Bedacht einsetzen und vermeiden, Schritte zu zerlegen, die auf derselben Abstraktionsebene liegen sollten.
Eine Person, mit der ich gearbeitet habe, hat jede boolesche Bedingung in eine Funktion ausgelagert, weil das „lesbarer“ sei, und gar keine Kommentare geschrieben, weil „Kommentare schlecht“ seien. Ich mag dieses Buch nicht, weil es Fanatiker hervorbringt, die solche schlechten Ratschläge blind befolgen.
Manchmal kann die Domain eine 1000-Zeilen-Gottfunktion verlangen, und wenn Logik und Arbeit an einem Ort gebündelt sind, kann das viel lesbarer sein als zwanzig 50-Zeilen-Funktionen. Um das Ganze zu verstehen, muss man diese 20 Funktionen ohnehin alle lesen, und jemand könnte versuchen, Teile davon wiederzuverwenden, sie dann an zwei oder drei Anforderungen anzupassen, die es in der ursprünglichen Aufgabe gar nicht gab, und bestimmte Logik mit einem nicht verwandten Use Case verknüpfen.
Wenn diese Funktion eine pure function ist, finde ich 1000 oder auch 10000 Zeilen egal; sie kann trotzdem in Ordnung sein.
Kochrezepte sind bereits stark abstrahiert. Wenn dort steht „Zwiebeln leicht anschwitzen“, wird vorausgesetzt, dass man weiß, wie man Zwiebeln schneidet und wie der Algorithmus des leichten Anschwitzens funktioniert. Wenn man alles inline ausschreibt, wird es unlesbar.
Bei Code ist es ähnlich. Wenn man Abstraktion strikt ausschließt, landet man auf der niedrigsten Ebene, die die Sprache erlaubt, und das ist definitiv kein gut lesbarer Code. Wenn man in Python zum Beispiel statt der Methode
decodeUnicode-Decoding selbst implementieren wollte, wäre es sehr schwer zu verstehen, was das Programm tatsächlich tut. Niemand macht das, weil die Sprache einfache und gut geprüfte Abstraktionen bereitstellt; worin unterscheidet sich das davon, selbst einfache, gut geprüfte Abstraktionen zu bauen und sie über die Business-Logik hinweg zu verwenden?Der schwierige Teil ist, eine Abstraktion so gut zu wählen, dass niemand sie später noch einmal anfassen muss.
Nur weil Codezeilen jetzt ähnlich aussehen, heißt das nicht, dass sie auch künftig gleich sein müssen oder gleich gehalten werden sollten. Wenn man zwei unterschiedliche Use Cases nur deshalb gewaltsam zusammenlegt, weil „der Code fast dupliziert ist“, entsteht mit der Zeit leicht eine Abstraktion, die gar nichts mehr abstrahiert.
Wenn die Use Cases zu stark auseinanderdriften, verschiebt die Implementierung entweder viel Logik nach oben zum Aufrufer oder legt Unterschiede als Flags offen und hält intern zwei verschiedene Implementierungen nebeneinander. Ersteres ist eine flache Abstraktion mit geringem Wert, Letzteres ist weniger klar als zwei unabhängige Implementierungen.
Der Beispielcode ist so einfach, dass linearer Code natürlich besser lesbar ist, aber die Idee skaliert nicht gut.
Man muss auch Wiederverwendbarkeit und Testbarkeit mit Unit Tests berücksichtigen, und wenn man den gesamten Code in eine einzige Funktion steckt, liegen alle lokalen Variablen im Scope – auch solche, die mit dem gerade gelesenen Codeblock vielleicht gar nichts zu tun haben. Das kann das Nachvollziehen erschweren.
Wenn ich allerdings auf meine weniger erfahrene Zeit zurückblicke, habe ich oft völlig ordentlichen linearen Code zu stark modularisiert und ihn in weniger wartbaren Code verwandelt, bei dem man überall hin- und herspringen musste. Die zuerst geschriebene Form liegt näher am damaligen Gedankengang im Kopf, und Leser werden sie wahrscheinlich auch eher so interpretieren. Übermäßiges Refactoring kann diesen Vorteil zerstören.
Letztlich ist Programmieren eher ein Handwerk, und Erfahrung hilft bei der situationsgerechten Entscheidung.
Sie hatte genau einen Zweck: einzelne HTML-Seiten, die in einer Ecke der App auf einer Plattform genutzt wurden, in ein Karussell umzuwandeln, das auf einer anderen Plattform ein natives Gefühl nachahmte. Sie war extrem spezifisch für diese Plattform und diesen Bereich der App.
Man hätte die 9 Scopes jeweils zu Funktionen machen können, aber dann hätten Entwickler sie wiederverwenden wollen. Jede Stufe enthielt subtile Annahmen darüber, was in der vorherigen Stufe passiert war. Um daraus separate Funktionen zu machen, hätte man diese Annahmen erneut prüfen, verallgemeinern und verifizieren müssen, dass jede Methode unabhängig funktioniert. Es gab keinen Grund, diesen Aufwand in Code zu stecken, der fast nirgendwo anders gebraucht wurde.
Das Debugging war nicht schwieriger, es gab End-to-End-Tests, und Zwischenzustände sind nicht aus der Funktion herausgeleakt. Tatsächlich haben im Laufe der Zeit 2 andere Entwickler Änderungen beigesteuert, es funktionierte gut, und es war schnell geschrieben.
Linearer Code skaliert gut und löst Probleme. Er ist nicht immer die gewünschte Form, macht das Leben aber in mehr Situationen deutlich einfacher, als man denkt.
Als ich das 2000-Zeilen-Monster zum ersten Mal sah, war meine Reaktion nicht positiv, aber nach 5 Minuten Hinsehen war es schwer, echte Mängel zu finden; mit ein paar Tests blieben nur Ängste, die sich in der Praxis nicht bestätigten.
Irgendwann merkt man, dass diese Dutzenden Funktionen in einer bestimmten Reihenfolge aufgerufen werden müssen und jeweils nur einmal verwendet werden. Am Ende zwingt man jeden, der diese Funktionen sinnvoll nutzen will, die magische Kombinationsreihenfolge zu kennen.
Der zentrale Grund, warum riesige lineare Funktionen oft lesbarer und wünschenswert sind, liegt darin, dass man mehrere Konzepte und Beziehungen ohne Kontextwechsel als Ganzes gleichzeitig im Kopf behalten kann, was das Verständnis erleichtert. Ein extremer Vertreter ist Arthur Whitney, der Erfinder der Sprache K: Um möglichst viel auf einen Bildschirm zu bekommen, schreibt er extrem knappen Code, der für andere fast unverständlich ist.
Ein persönliches Beispiel: Eine riesige Windows-Message-Processing-Funktion, also
WndProc, mit Business-Logik in einem großenswitch-Statement zu lesen, zu verstehen und zu debuggen, war für mich viel einfacher als die Visual-C++-Version, in der die Message-Handler in separate Funktionen aufgeteilt waren.Außerdem gab es bei Beispielcode für Mikrocontroller eine Version, in der das ADC-Beispiel komplett in einer Datei stand, und eine Version, die auf mehrere Dateien wie
main.c,config.c,interrupts.c,timer.cusw. verteilt war. Obwohl es nicht einmal 200 Zeilen waren, war die zweite wegen der Kontextwechsel schwer zu verstehen.Solche Codefragmente werden meist zu
private-Funktionen einer Klasse und haben Zustand. Alsprivate-Funktionen sind sie in der Praxis auch schwer zu testen.Jetzt hat man jede Menge
private-Funktionen, die nur einmal aufgerufen werden und meist nebenwirkungsbehafteten Zustand verändern. Wenn sie direkt neben dem Aufrufer stehen, ist das in einfachen Fällen noch lesbar, aber mit der Zeit fügt jemand zwischen der aufrufenden Funktion und der herausgezogenen Funktion weitere Funktionen ein.Dann verändern Codefragmente, von denen man ohne Blick auf den Call Graph oder Suche in der Klassendatei nicht weiß, woher sie aufgerufen werden, unterschiedliche nebenwirkungsbehaftete Zustände.
Wenn man Code nichtlinear machen will, sollte man – sofern die Sprache es unterstützt – zumindest erwägen, die herausgezogene
private-Funktion zu einer inneren Funktion der aufrufenden Funktion zu machen. Dann ist klar, dass sie nicht von anderswo aufgerufen wird.In echten Codebasen ist auch das keine Entweder-oder-Frage, sondern eher die Kunst, beides so zu kombinieren, dass es lesbar und wartbar bleibt.
Werden Leute all diese Verzweigungen testen? Oder schreiben sie nur einen Test, der eine Pizza hineinsteckt, und prüfen grob, ob es funktioniert? Mehrere Verzweigungen von außen zu testen ist meist umständlich und lästiger, als kleine, spezialisierte Funktionen zu testen; daher wirkt Letzteres wahrscheinlicher.
Die Aussage „linearer Code skaliert nicht“ ist eher das Gegenteil. Was in großen Codebasen zum echten Albtraum wird, sind kleine, knappe Funktionen mit tief verschachtelten Call Stacks
Es ist nicht klar, wo neuer Code eingefügt werden soll, man muss alle Pfade nachverfolgen, über die der Code aufgerufen werden kann, und dadurch steigt die Schwierigkeit, die Auswirkungen von Änderungen zu erfassen, exponentiell; außerdem entstehen doppelte Unterroutinen
In 99 % der Fälle hat man keine gute Abstraktion geschaffen, also ist es besser, einfach linearen Code zu schreiben. Copy/Paste ist mir lieber als fragwürdige Funktionssemantik
print_table()hinzufügt, findet es jemand und verwendet es in seinem eigenen Code und hängt ein kleines Flag an, um die Ausgabe an seinen Use Case anzupassen12 Monate später sieht es so aus:
print_table(rows,headers = None,is_unicode = False,left_align = False,align = [],remove_emoji = None,max_width = 80,potato_mode = 7,_debug_frontend = not FLAGS.dont_debug,ellipsis_for = 0,no_print = False,)Wenn man die beiden Konzepte orthogonal betrachtet, abgesehen davon, dass Lesbarkeit die Skalierbarkeit beeinflussen kann, skaliert linearer Code nicht so gut wie modularer Code. Diese Dichotomie ist es wert, sie zu kennen und je nach Situation zu berücksichtigen
Trotzdem stimme ich weiterhin nicht zu. Wenn kleine Funktionen pure functions sind, verursachen sie keine Lesbarkeitsprobleme. Das bedeutet, dass sie keinen Zustand anfassen, keine Logik in den Code injizieren, und dass Dependency Injection sowie das Übergeben von Funktionen an andere Funktionen ausdrücklich minimiert werden sollten
Wenn man eine Pipeline aus pure functions baut, die nur Daten weiterreichen, wird sie gut lesbar und erweiterbar. Es kommt viel seltener vor, dass man wegen Designfehlern Logik neu schreiben muss, und wenn man pure functions kombiniert, wird Code wie Lego. Refactoring ähnelt dann eher dem Umordnen und Neukombinieren bestehender primitiver Bausteine
Der Beispielcode wäre weniger ablenkend gewesen, wenn er wenigstens versucht hätte, die Pizza-Metapher sinnvoll beizubehalten, oder wenn es kein schwacher Go-Code gewesen wäre
prepareist ein furchtbarer Funktionsname. Ein erfahrener Gopher hätte vermutlich einen Namen wieNewPizzaFromOrdergewähltIch sehe keinen Grund,
addToppingsals separate Funktion zu haben. Wenn es unbedingt nötig wäre, würde ich persönlich daraus eine Methode vonPizzamachen, etwafunc (p *Pizza) WithToppings(topping ...Topping) *Pizza { /* ... */ }. Eine echte Pizza ist veränderlich, also verändert die Methode den ReceiverIch verstehe auch nicht, warum bei jeder Pizza ein neuer Ofen instanziiert wird. Man sollte mit einem vorhandenen Ofen beginnen,
oven.Preheat()ausführen undoven.Bake(pizza)aufrufen. Noch besser könnteoven.Preheat()einen neuen Typ vonOvenzurückgeben, der.Bake()exponiert, sodass der Fehler, ohne Vorheizen zu backen, schon zur Compile-Zeit verhindert wird. An anderer Stelle könnte es einBaker-Interface geben und vielleicht eineToasterOven-Implementierung, bei der Vorheizen nicht so wichtig und daher nicht nötig istAuch ohne den Code zu ändern, hätte ich die Reihenfolge der Deklarationen so umgestellt, dass sie dem erwartbaren Ablauf entspricht. Dann muss man beim Überfliegen der Funktionen, die sich gegenseitig aufrufen, nicht auf der Seite hoch- und runterspringen
Ich höre hier auf, weil ich keine Zeit habe, aber dieser Code ist schon ein viel zu schlechtes Beispiel, um überhaupt eine Debatte darüber zu beginnen, „welche Seite leichter zu lesen ist“
John Carmack hat fast dasselbe gesagt, und seitdem halte ich mich daran. Linearer Code ist natürlich leicht zu lesen, weil er der Ausführungsreihenfolge folgt, und minimiert Blicksprünge
Manche Codeabschnitte müssen für Wiederverwendung nichtlinear sein, und dann wird die Ausführung zu einem Graphen. Wenn der Code die Wiederverwendung einer Graphstruktur nicht nutzt, muss man keinen Knoten einführen, wo eine einzige Kante ausreicht
http://number-none.com/blow/blog/programming/2014/09/26/carm...
In diesem Fall wäre der Code links wohl besser gewesen, wenn er etwas wie
pizza.Toppings = get_pizza_toppings(order.kind)gemacht hätte; dann wäre die Änderung an der Pizza in der Hauptfunktion im Mittelpunkt gebliebenIch stimme bis zu einem gewissen Grad zu, dass linearer Code leichter zu lesen ist, aber das allein macht ihn noch nicht zu guter Code-Praxis.
Guten linearen Code halte ich zwar für leichter lesbar, aber Wartbarkeit und Testbarkeit sind deutlich schlechter. Ich habe jahrzehntelange Erfahrung und begutachte auch CS-Studierende extern; über viele Jahre hinweg war die einzige gute Praxis, die ich in der Realität sicher gesehen habe: Funktionen klein halten.
Ich mag Abstraktionen nicht besonders, und ich glaube auch nicht, dass man Code-Duplikation um jeden Preis vermeiden muss. Aber wenn man Funktionen so weit wie möglich auf einen einzelnen Zweck zuschneidet, wird das zukünftige Ich einem dankbar sein.
Wenn Code wie im Beispiel zehn Jahre lang in Produktion läuft, ändern sich einzelne Abschnitte. Mit Glück werden auch die Kommentare aktualisiert, meistens aber nicht. Unit-Tests werden groß und schwer handhabbar, dabei nach und nach schlampiger, und jemand kann vergessen, einen Teil eines Tests anzupassen, der nicht offensichtlich mit der Änderung zusammenhängt. Auch der Code selbst wird mit der Zeit sehr wahrscheinlich weniger gut lesbar. Nicht aus Absicht oder Unfähigkeit, sondern aus menschlichen Gründen wie Zeitdruck.
In einer perfekten Welt müssten wir Concerns nicht trennen, aber wir leben in einer unvollkommenen Welt. Je kleiner Funktionen sind und je weniger Verantwortung sie tragen, desto leichter lässt sich diese Unvollkommenheit über die Zeit handhaben.
Wenn man ein Objekt durch eine Abfolge bestimmter Zustände schleust, sollte man das meiner Meinung nach entweder aufteilen und die Übergänge über Typen ausdrücken, oder es als eine große Funktion schreiben. Wenn zum Beispiel
bakePizzaeineRawPizzaentgegennimmt und eineBakedPizzazurückgibt, lässt sich die Aufrufreihenfolge zur Compile-Zeit erzwingen.Aus Gründen der Lesbarkeit, Korrektheit und Testbarkeit bevorzuge ich Ersteres. In den meisten Programmiersprachen muss man jedoch ein neues Objekt erstellen, um den Objekttyp zu ändern, was Laufzeitkosten verursacht. In einem heißen Codepfad ist In-place-Mutation sinnvoll, und in diesem Fall ist es besser, alles in einer linearen Funktion zu belassen.
https://mitpress.mit.edu/9780262045490/
Eine einschlägige E-Mail von John Carmack: http://number-none.com/blow/blog/programming/2014/09/26/carm...
Diskussion: https://news.ycombinator.com/item?id=12120752
Stimme stark zu. Früher war ich im gegnerischen Lager.
Die grundlegende Spannung liegt hier zwischen Locality of Behaviour auf der einen Seite und dem Wunsch, auf der anderen Seite eine High-Level-„Inhaltsverzeichnis“-Ansicht klar sichtbar zu machen. Für lesbaren Code ist Lokalität wichtiger. Wie im Text gesagt, lässt sich die Inhaltsverzeichnis-Perspektive mit Abschnittskommentaren ausreichend deutlich machen.
Es gibt noch einen wichtigeren Grund, linearen Code zu bevorzugen. Wenn man sich durch eine gesamte Codebase bewegt, ist es viel einfacher, wenn „Brocken“, also Funktionen, Klassen oder sprachlich erzwungene Einheiten, grob den Business-Use-Cases entsprechen. Andernfalls wird der Suchraum zu groß, und man muss sich aus den Einzelteilen selbst das Ganze rekonstruieren. Die Codestruktur sollte diese Arbeit übernehmen.
Wenn mehrere „Dinge“ alle zu einer einzigen Aufgabe gehören, etwa Registrierung oder Kauf, sollte man sie auch im Code zusammenlassen. Das ist viel leichter zu finden und zu ändern. Nur wenn Wiederverwendung nötig ist, sollte man in Unterfunktionen aufteilen, nicht bloß zur Organisation.
[0] https://htmx.org/essays/locality-of-behaviour/
Der wichtigste Grund ist Zustand. Je länger eine Funktion ist, desto größer ist der Scope lokaler Variablen. Überall in der Funktion kann jede Variable geändert werden, und der Datenfluss ist nicht sofort klar. Mit mehr Funktionen bleibt der Scope klein, und der Datenfluss ist expliziter.
Als Nebeneffekt gibt es auch weniger Einrückung.
Gleichzeitig mag ich keine zu kleinen Funktionen, weil es dann schwer wird, herauszufinden, wo die eigentliche Arbeit passiert.
Stell dir eine Tagesabschlussverarbeitung mit 10 nicht wiederverwendbaren Schritten vor, die der Reihe nach ausgeführt werden müssen und jeweils 100 Zeilen lang sind. Jeder Schritt verwendet Daten, die den vorherigen ähneln, aber nicht identisch sind. Würdest du wirklich eine einzelne Funktion mit 1000 Zeilen wählen?
Beides liest sich linear. Die Version mit herausgezogenen kleinen Funktionen hat oben auf der Seite ein Inhaltsverzeichnis und fasst den Datenfluss zwischen den Schritten zusammen. Wenn man das Ganze lesen will, wirkt das wie eine ansprechende Lesereihenfolge.
Um diese Lesbarkeit zu erhalten, muss man allerdings auch die Position der Funktionen verschieben, wenn sich die Reihenfolge der Schritte ändert. Wenn es
private-Funktionen sind und sie nur aus dem Inhaltsverzeichnis heraus aufgerufen werden, ist das in Ordnung. Aber nichts erzwingt, dass die Reihenfolge erhalten bleibt, und nichts zwingt einen, an den gesamten Lesefluss zu denken.Sobald Funktionen wiederverwendet werden, lässt sich das oft nicht mehr linearisieren. Manchmal geben Leute dann auf und sortieren alphabetisch, oder es wird einfach zufällig.
Erfahrungsgemäß glauben manche: Je vertrauter jemand mit dem Code ist, desto eher hält er es für den richtigen Weg, Code in kleine Funktionen zu schieben.
Weil diese Person das mentale Modell dieses Codes bereits aufgebaut hat, ist für sie die sauberste Implementierung eine mit sehr wenigen Zeilen.
Wenn aber die nächste Person dazukommt, muss sie ohne den ursprünglichen Kontext hin und her springen und ihren gedanklichen Stack pushen/poppen, um dasselbe mentale Modell aufzubauen — und das ist deutlich schwieriger.
Wie oft liest man zum Beispiel den Sourcecode der Standardbibliothek der verwendeten Sprache? Fast nie; normalerweise schaut man sich die Methodensignatur an und liest die Dokumentation, wenn es etwas komplexer oder neu ist.
Der Kern eines Interfaces ist, dass man sich nicht darum kümmern muss, wie eine Methode implementiert ist, sondern nur darum, was sie tut. Das wird durch eine Kombination aus Kontext, Namen und Dokumentation erklärt. Viele Entwickler verstehen das jedoch nicht oder kümmern sich nicht darum und schreiben daher Code, der keinen Sinn ergibt — egal ob linear oder modular.
Wenn man zum Beispiel in einer Service-Klasse eine Methode aufrufen muss, um bestimmte Daten zu bekommen, eine andere Methode für weitere Daten und eine dritte Methode für Daten, die mit den beiden ersten kombiniert werden müssen — was ist dann die Bedeutung dieses Service? Damit legt man die gesamte interne Komplexität nach außen offen.
Es geht nicht darum, kleine Methoden zu erzwingen. Zwanzig Fünfzeiler-Funktionen, die jeweils nur einmal aufgerufen werden, eine sehr spezifische Aufgabe erledigen und in der richtigen Reihenfolge aufgerufen werden müssen, ergeben keinen Sinn. Das ist kein sauberer Code, sondern eher Cargo-Cult-Programmierung.
Entscheidend ist, angemessen zu abstrahieren, sodass der Code sowohl für neue als auch für erfahrene Teammitglieder Sinn ergibt, leicht nachvollziehbar ist und Komplexität an der passenden Stelle verborgen wird. Das ist nicht einfach, aber möglich.
Mein Sohn war durchaus klug, hatte aber Schwierigkeiten in der Schule, und einer von mehreren Fachleuten erklärte, dass Schulen meist bottom-up unterrichten, mein Sohn aber ein sehr stark top-down orientierter Lerner sei. Er braucht zuerst einen Überblick, bevor er in Details einsteigt; andere erfassen zuerst die Details und setzen daraus den Überblick zusammen. Schulen unterrichten normalerweise für die zweite Gruppe.
Unter Programmierern könnte es ähnliche Unterschiede geben.
Wenn der vorherige Entwickler eine Funktion
BakePizzageschrieben hat, kann man davon ausgehen, dass die Pizza richtig gebacken wird, und zur nächsten Zeile weitergehen. Wenn man beim Versuch, den Betrieb eines Restaurants zu verstehen, in Details wie der Ofentemperatur stecken bleibt, versteht man am Ende weder, wie das Restaurant funktioniert, noch behält man die genaue Ofentemperatur.Der Editor sollte einen Toggle haben, mit dem Funktionen temporär inline angezeigt werden können. Dann muss man nicht mehr hin und her springen.