it-swarm.com.de

Lesbarkeit versus Wartbarkeit, Sonderfall beim Schreiben verschachtelter Funktionsaufrufe

Mein Codierungsstil für verschachtelte Funktionsaufrufe lautet wie folgt:

var result_h1 = H1(b1);
var result_h2 = H2(b2);
var result_g1 = G1(result_h1, result_h2);
var result_g2 = G2(c1);
var a = F(result_g1, result_g2);

Ich bin kürzlich zu einer Abteilung gewechselt, in der der folgende Codierungsstil sehr häufig verwendet wird:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

Das Ergebnis meiner Codierungsmethode ist, dass Visual Studio im Falle einer Absturzfunktion den entsprechenden Speicherauszug öffnen und die Zeile angeben kann, in der das Problem auftritt (ich bin besonders besorgt über Zugriffsverletzungen).

Ich befürchte, dass ich im Falle eines Absturzes aufgrund des gleichen Problems, das auf die erste Weise programmiert wurde, nicht wissen kann, welche Funktion den Absturz verursacht hat.

Je mehr Verarbeitung Sie in eine Zeile einfügen, desto mehr Logik erhalten Sie auf einer Seite, was die Lesbarkeit verbessert.

Ist meine Angst richtig oder fehlt mir etwas und im Allgemeinen, was in einem kommerziellen Umfeld bevorzugt wird? Lesbarkeit oder Wartbarkeit?

Ich weiß nicht, ob es relevant ist, aber wir arbeiten in C++ (STL)/C #.

57
Dominique

Wenn Sie sich gezwungen fühlten, einen Einzeiler wie zu erweitern

 a = F(G1(H1(b1), H2(b2)), G2(c1));

Ich würde dir keine Vorwürfe machen. Das ist nicht nur schwer zu lesen, es ist auch schwer zu debuggen.

Warum?

  1. Es ist dicht
  2. Einige Debugger heben das Ganze nur auf einmal hervor
  3. Es ist frei von beschreibenden Namen

Wenn Sie es mit Zwischenergebnissen erweitern, erhalten Sie

 var result_h1 = H1(b1);
 var result_h2 = H2(b2);
 var result_g1 = G1(result_h1, result_h2);
 var result_g2 = G2(c1);
 var a = F(result_g1, result_g2);

und es ist immer noch schwer zu lesen. Warum? Es löst zwei der Probleme und führt ein viertes ein:

  1. Es ist dicht
  2. Einige Debugger heben das Ganze nur auf einmal hervor
  3. Es ist frei von beschreibenden Namen
  4. Es ist voll mit unbeschreiblichen Namen

Wenn Sie es mit Namen erweitern, die eine neue, gute, semantische Bedeutung hinzufügen, noch besser! Ein guter Name hilft mir zu verstehen.

 var temperature = H1(b1);
 var humidity = H2(b2);
 var precipitation = G1(temperature, humidity);
 var dewPoint = G2(c1);
 var forecast = F(precipitation, dewPoint);

Zumindest erzählt dies eine Geschichte. Es behebt die Probleme und ist eindeutig besser als alles andere, was hier angeboten wird, aber Sie müssen sich die Namen einfallen lassen.

Wenn Sie es mit bedeutungslosen Namen wie result_this Und result_that Tun, weil Sie einfach nicht an gute Namen denken können, dann würde ich es wirklich vorziehen, wenn Sie uns die bedeutungslose Namensunordnung ersparen und sie mit einigen guten erweitern altes Leerzeichen:

int a = 
    F(
        G1(
            H1(b1), 
            H2(b2)
        ), 
        G2(c1)
    )
;

Es ist genauso lesbar, wenn nicht sogar besser als das mit den bedeutungslosen Ergebnisnamen (nicht, dass diese Funktionsnamen so großartig sind).

  1. Es ist dicht
  2. Einige Debugger heben das Ganze nur auf einmal hervor
  3. Es ist frei von beschreibenden Namen
  4. Es ist voll mit unbeschreiblichen Namen

Wenn Sie nicht an gute Namen denken können, ist das so gut wie es nur geht.

Aus irgendeinem Grund Debugger lieben neue Zeilen sollten Sie also feststellen, dass das Debuggen nicht schwierig ist:

(enter image description here

Wenn das nicht genug ist, stellen Sie sich vor, G2() wurde an mehr als einer Stelle aufgerufen, und dann geschah Folgendes:

Exception in thread "main" Java.lang.NullPointerException
    at composition.Example.G2(Example.Java:34)
    at composition.Example.main(Example.Java:18)

Ich finde es schön, dass, da jeder Aufruf von G2() auf einer eigenen Leitung steht, dieser Stil Sie direkt zum beleidigenden Aufruf in main führt.

Verwenden Sie also bitte die Probleme 1 und 2 nicht als Entschuldigung, um uns an Problem 4 zu halten. Verwenden Sie gute Namen, wenn Sie an sie denken können. Vermeiden Sie bedeutungslose Namen, wenn Sie nicht können.

Leichtigkeitsrennen in Orbits Kommentar weist richtig darauf hin, dass diese Funktionen künstlich sind und selbst tote schlechte Namen haben. Hier ist ein Beispiel für die Anwendung dieses Stils auf Code aus der Wildnis:

var user = db.t_ST_User.Where(_user => string.Compare(domain,  
_user.domainName.Trim(), StringComparison.OrdinalIgnoreCase) == 0)
.Where(_user => string.Compare(samAccountName, _user.samAccountName.Trim(), 
StringComparison.OrdinalIgnoreCase) == 0).Where(_user => _user.deleted == false)
.FirstOrDefault();

Ich hasse es, diesen Rauschstrom zu betrachten, selbst wenn kein Zeilenumbruch erforderlich ist. So sieht es unter diesem Stil aus:

var user = db
    .t_ST_User
    .Where(
        _user => string.Compare(
            domain, 
            _user.domainName.Trim(), 
            StringComparison.OrdinalIgnoreCase
        ) == 0
    )
    .Where(
        _user => string.Compare(
            samAccountName, 
            _user.samAccountName.Trim(), 
            StringComparison.OrdinalIgnoreCase
        ) == 0
    )
    .Where(_user => _user.deleted == false)
    .FirstOrDefault()
;

Wie Sie sehen können, funktioniert dieser Stil gut mit dem Funktionscode, der sich in den objektorientierten Raum bewegt. Wenn Sie sich gute Namen einfallen lassen, um dies im Zwischenstil zu tun, dann haben Sie mehr Macht. Bis dahin benutze ich das. Aber bitte finden Sie auf jeden Fall einen Weg, um bedeutungslose Ergebnisnamen zu vermeiden. Sie machen meine Augen weh.

111
candied_orange

Je mehr Verarbeitung Sie in eine Zeile einfügen, desto mehr Logik erhalten Sie auf einer Seite, was die Lesbarkeit verbessert.

Ich bin damit überhaupt nicht einverstanden. Wenn Sie sich nur Ihre beiden Codebeispiele ansehen, wird dies als falsch bezeichnet:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

ist zu lesen zu hören. "Lesbarkeit" bedeutet nicht Informationsdichte; es bedeutet "leicht zu lesen, zu verstehen und zu pflegen".

Manchmal ist der Code einfach und es ist sinnvoll, eine einzelne Zeile zu verwenden. In anderen Fällen ist das Lesen nur schwieriger, da es keinen offensichtlichen Vorteil gibt, wenn Sie mehr in eine Zeile packen.

Ich möchte Sie jedoch auch darauf hinweisen, dass "einfach zu diagnostizierende Abstürze" bedeuten, dass Code einfach zu warten ist. Code, der nicht abstürzt, ist viel einfacher zu pflegen. "Einfach zu warten" wird in erster Linie durch den Code erreicht, der leicht zu lesen und zu verstehen ist und durch eine Reihe guter automatisierter Tests unterstützt wird.

Wenn Sie also einen einzelnen Ausdruck in einen mehrzeiligen Ausdruck mit vielen Variablen umwandeln, nur weil Ihr Code häufig abstürzt und Sie bessere Debug-Informationen benötigen, hören Sie damit auf und machen Sie den Code stattdessen robuster. Sie sollten lieber Code schreiben, der nicht debuggt werden muss, als Code, der einfach zu debuggen ist.

50
David Arno

Ihr erstes Beispiel, das Einzelzuweisungsformular, ist nicht lesbar, da die ausgewählten Namen absolut bedeutungslos sind. Dies könnte ein Artefakt des Versuchs sein, interne Informationen von Ihrer Seite nicht preiszugeben. Der wahre Code könnte in dieser Hinsicht in Ordnung sein, können wir nicht sagen. Auf jeden Fall ist es aufgrund der extrem geringen Informationsdichte langwierig, was sich im Allgemeinen nicht für ein leichtes Verständnis eignet.

Ihr zweites Beispiel ist zu einem absurden Grad verdichtet. Wenn die Funktionen nützliche Namen hätten, wäre dies möglicherweise in Ordnung und gut lesbar, da nicht zu viel davon vorhanden ist, aber wie es ist, ist es verwirrend in der andere Richtung.

Nachdem Sie aussagekräftige Namen eingeführt haben, können Sie prüfen, ob eine der Formen natürlich erscheint oder ob es gibt eine goldene Mitte, nach dem Sie schießen müssen.

Jetzt, da Sie lesbaren Code haben, werden die meisten Fehler offensichtlich sein, und die anderen haben es zumindest schwerer, sich vor Ihnen zu verstecken.

25
Deduplicator

Wie immer, wenn es um Lesbarkeit geht, ist der Fehler extrem . Sie können any gute Programmierhinweise nehmen, daraus eine religiöse Regel machen und damit völlig unlesbaren Code erstellen. (Wenn Sie mir diesbezüglich nicht glauben, schauen Sie sich diese beiden IOCCC Gewinner borsanyi und goren an = und schauen Sie sich an, wie unterschiedlich sie Funktionen verwenden, um den Code völlig unlesbar zu machen. Hinweis: Borsanyi verwendet genau eine Funktion, goren viel, viel mehr ...)

In Ihrem Fall sind die beiden Extreme 1) Verwenden nur einzelner Ausdrucksanweisungen und 2) Zusammenfügen von allem zu großen, knappen und komplexen Anweisungen. Beide extremen Ansätze machen Ihren Code unlesbar.

Ihre Aufgabe als Programmierer ist es, ein Gleichgewicht zu finden . Für jede Aussage, die Sie schreiben, ist es Ihre Aufgabe, die Frage zu beantworten: "Ist diese Aussage leicht zu verstehen und dient sie dazu, meine Funktion lesbar zu machen?"


Der Punkt ist, dass es keine einzige messbare Aussagekomplexität gibt, die entscheiden kann, was gut ist, um in eine einzelne Aussage aufgenommen zu werden. Nehmen Sie zum Beispiel die Zeile:

double d = sqrt(square(x1 - x0) + square(y1 - y0));

Dies ist eine recht komplexe Aussage, aber jeder Programmierer, der sein Geld wert ist, sollte sofort verstehen können, was dies bewirkt. Es ist ein ziemlich bekanntes Muster. Als solches ist es viel besser lesbar als das Äquivalent

double dx = x1 - x0;
double dy = y1 - y0;
double dxSquare = square(dx);
double dySquare = square(dy);
double dSquare = dxSquare + dySquare;
double d = sqrt(dSquare);

dies bricht das bekannte Muster in eine scheinbar bedeutungslose Anzahl einfacher Schritte. Allerdings die Aussage aus Ihrer Frage

var a = F(G1(H1(b1), H2(b2)), G2(c1));

scheint mir zu kompliziert , obwohl es eine Operation weniger ist als die Entfernungsberechnung . Das ist natürlich eine direkte Folge davon, dass ich nichts über F(), G1(), G2(), H1() oder H2(). Ich könnte mich anders entscheiden, wenn ich mehr über sie wüsste. Aber genau das ist das Problem: Die empfehlenswerte Komplexität einer Aussage hängt stark vom Kontext und den damit verbundenen Operationen ab. Und Sie als Programmierer sind derjenige, der sich diesen Kontext ansehen und entscheiden muss, was in eine einzelne Anweisung aufgenommen werden soll. Wenn Sie Wert auf Lesbarkeit legen, können Sie diese Verantwortung nicht auf eine statische Regel übertragen.

@Dominique, ich denke, in der Analyse Ihrer Frage machen Sie den Fehler, dass "Lesbarkeit" und "Wartbarkeit" zwei getrennte Dinge sind.

Ist es möglich, Code zu haben, der wartbar, aber nicht lesbar ist? Umgekehrt, wenn Code extrem lesbar ist, warum sollte er aufgrund seiner Lesbarkeit nicht mehr gewartet werden können? Ich habe noch nie von einem Programmierer gehört, der diese Faktoren gegeneinander ausspielte und sich für den einen oder anderen entscheiden musste!

Bei der Entscheidung, ob Zwischenvariablen für verschachtelte Funktionsaufrufe verwendet werden sollen, würde ich bei 3 angegebenen Variablen, bei Aufrufen von 5 separaten Funktionen und bei einigen 3 tief verschachtelten Aufrufen dazu tendieren, mindestens einige zu verwenden Zwischenvariablen, um das aufzuschlüsseln, wie Sie es getan haben.

Aber ich gehe sicherlich nicht so weit zu sagen, dass Funktionsaufrufe niemals verschachtelt werden dürfen. Es ist eine Frage des Urteils unter den gegebenen Umständen.

Ich würde sagen, dass die folgenden Punkte das Urteil betreffen:

  1. Wenn die aufgerufenen Funktionen mathematische Standardoperationen darstellen, können sie besser verschachtelt werden als Funktionen, die eine obskure Domänenlogik darstellen, deren Ergebnisse unvorhersehbar sind und vom Leser nicht unbedingt mental bewertet werden können.

  2. Eine Funktion mit einem einzelnen Parameter kann besser an einem Nest teilnehmen (entweder als innere oder äußere Funktion) als eine Funktion mit mehreren Parametern. Das Mischen von Funktionen verschiedener Aritäten auf verschiedenen Verschachtelungsebenen führt dazu, dass Code wie ein Schweineohr aussieht.

  3. Ein Nest von Funktionen, die Programmierer gewohnt auf eine bestimmte Weise ausgedrückt sehen - vielleicht weil es eine mathematische Standardtechnik oder -gleichung darstellt, die eine Standardimplementierung aufweist - kann schwieriger zu lesen und zu überprüfen sein, wenn dies der Fall ist wird in Zwischenvariablen zerlegt.

  4. Ein kleines Nest von Funktionsaufrufen, das einfache Funktionen ausführt und bereits klar lesbar ist und dann übermäßig zerlegt und atomisiert wird, kann sein schwieriger zu lesen als eines, das überhaupt nicht zerlegt wurde .

14
Steve

Beide sind suboptimal. Kommentare berücksichtigen.

// Calculating torque according to Newton/Dominique, 4th ed. pg 235
var a = F(G1(H1(b1), H2(b2)), G2(c1));

Oder eher spezifische als allgemeine Funktionen:

var a = Torque_NewtonDominique(b1,b2,c1);

Berücksichtigen Sie bei der Entscheidung, welche Ergebnisse formuliert werden sollen, die Kosten (Kopie vs. Referenz, L-Wert vs. R-Wert), die Lesbarkeit und das Risiko für jede Aussage einzeln.

Zum Beispiel gibt es keinen Mehrwert, wenn einfache Einheiten-/Typkonvertierungen in ihre eigenen Zeilen verschoben werden, da diese leicht zu lesen sind und äußerst unwahrscheinlich sind, dass sie fehlschlagen:

var radians = ExtractAngle(c1.Normalize())
var a = Torque(b1.ToNewton(),b2.ToMeters(),radians);

In Bezug auf Ihre Sorge, Crash-Dumps zu analysieren, ist die Eingabevalidierung in der Regel weitaus wichtiger - der tatsächliche Crash tritt eher innerhalb dieser Funktionen auf als in der Leitung, in der sie aufgerufen werden, und selbst wenn nicht, müssen Sie normalerweise nicht genau wissen, wo Dinge explodierten. Es ist viel wichtiger zu wissen, wo die Dinge auseinanderzufallen begannen, als zu wissen, wo sie schließlich explodierten, was die Eingabevalidierung erfasst.

4
Peter

Meiner Meinung nach ist selbstdokumentierender Code unabhängig von der Sprache sowohl für die Wartbarkeit als auch für die Lesbarkeit besser.

Die obige Aussage ist dicht, aber "selbstdokumentierend":

double d = sqrt(square(x1 - x0) + square(y1 - y0));

Wenn es in Stufen unterteilt wird (sicherlich einfacher zum Testen), verliert es jeglichen Kontext wie oben angegeben:

double dx = x1 - x0;
double dy = y1 - y0;
double dxSquare = square(dx);
double dySquare = square(dy);
double dSquare = dxSquare + dySquare;
double d = sqrt(dSquare);

Und natürlich ist die Verwendung von Variablen- und Funktionsnamen, die ihren Zweck klar angeben, von unschätzbarem Wert.

Selbst "wenn" -Blöcke können bei der Selbstdokumentation gut oder schlecht sein. Das ist schlecht, weil Sie die ersten beiden Bedingungen nicht einfach zwingen können, die dritte zu testen ... alle haben nichts miteinander zu tun:

if (Bill is the boss) && (i == 3) && (the carnival is next weekend)

Dieser macht mehr "kollektiven" Sinn und ist einfacher, Testbedingungen zu erstellen:

if (iRowCount == 2) || (iRowCount == 50) || (iRowCount > 100)

Und diese Aussage ist nur eine zufällige Folge von Zeichen, aus einer selbstdokumentierenden Perspektive betrachtet:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

In Anbetracht der obigen Aussage ist die Wartbarkeit immer noch eine große Herausforderung, wenn die Funktionen H1 und H2 dieselben "Systemzustandsvariablen" ändern, anstatt zu einer einzigen "H" -Funktion vereinheitlicht zu werden, da irgendwann jemand H1 ändert, ohne zu glauben, dass es eine gibt H2-Funktion zu betrachten und könnte H2 brechen.

Ich glaube, gutes Code-Design ist eine große Herausforderung, da es keine festen Regeln gibt, die systematisch erkannt und durchgesetzt werden können.

1
Ozymandias

Die Lesbarkeit ist der Hauptteil der Wartbarkeit. Zweifel an mir? Wählen Sie ein großes Projekt in einer Sprache aus, die Sie nicht kennen (vorzugsweise sowohl die Programmiersprache als auch die Sprache der Programmierer), und sehen Sie, wie Sie es umgestalten würden ...

Ich würde die Lesbarkeit auf einen Wert zwischen 80 und 90 der Wartbarkeit setzen. Die anderen 10 bis 20 Prozent geben an, wie zugänglich es für Refactoring ist.

Das heißt, Sie übergeben effektiv 2 Variablen an Ihre endgültige Funktion (F). Diese 2 Variablen werden mit 3 anderen Variablen erstellt. Es wäre besser gewesen, wenn Sie b1, b2 und c1 an F übergeben würden. Wenn F bereits vorhanden ist, erstellen Sie D, das die Komposition für F erstellt und das Ergebnis zurückgibt. An diesem Punkt geht es nur darum, D einen guten Namen zu geben, und es spielt keine Rolle, welchen Stil Sie verwenden.

In einem verwandten Fall sagen Sie, dass mehr Logik auf der Seite die Lesbarkeit verbessert. Das ist falsch, die Metrik ist nicht die Seite, sondern die Methode, und je WENIGER die Logik einer Methode ist, desto besser ist sie lesbar.

Lesbar bedeutet, dass der Programmierer die Logik (Eingabe, Ausgabe und Algorithmus) im Kopf halten kann. Je mehr es tut, desto WENIGER kann ein Programmierer es verstehen. Informieren Sie sich über die zyklomatische Komplexität.

1
jmoreno

Unabhängig davon, ob Sie sich in C # oder C++ befinden, besteht eine mögliche Lösung darin, die Funktionen zu verpacken, solange Sie sich in einem Debug-Build befinden

var a = F(G1(H1(b1), H2(b2)), G2(c1));

Sie können einen Online-Ausdruck schreiben und trotzdem durch Betrachten der Stapelverfolgung feststellen, wo das Problem liegt.

returnType F( params)
{
    returnType RealF( params);
}

Wenn Sie dieselbe Funktion mehrmals in derselben Zeile aufrufen, können Sie natürlich nicht wissen, welche Funktion Sie dennoch identifizieren können:

  • Funktionsparameter betrachten
  • Wenn die Parameter identisch sind und die Funktion keine Nebenwirkungen hat, werden aus zwei identischen Aufrufen zwei identische Aufrufe usw.

Dies ist keine Silberkugel, aber auf halbem Weg nicht so schlimm.

Ganz zu schweigen davon, dass das Umschließen von Funktionsgruppen für die Lesbarkeit des Codes sogar noch vorteilhafter sein kann:

type CallingGBecauseFTheorem( T b1, C b2)
{
     return G1( H1( b1), H2( b2));
}

var a = F( CallingGBecauseFTheorem( b1,b2), G2( c1));
1
CoffeDeveloper