Diskussionen über Einfachheit

8

Vor kurzem hatten wir in meiner Firma eine Debatte über Abstraktion vs. Einfachheit. Eine Denkrichtung, die ich als "TROCKEN und Abstraktion kann keinen Schaden anrichten" bezeichnen würde, führt zu Code wie diesem:

def make_foo_binary(binaryName, objFiles, fooLibsToLinkAgainst)
    make_exe_task(binaryName, objFiles.ext('.o'), fooLibsToLinkAgainst)
end

und das:

class String
    def escape_space
        return self.gsub(' ', '\ ')
    end
end

Mein Standpunkt ist, dass das Erstellen einer solchen Abstraktion, die nur an einer Stelle verwendet wird, den Code weniger lesbar macht, da Sie einen Funktionsaufruf, mit dem der Leser vertraut ist (gsub), durch einen anderen ersetzen, den er noch nie gesehen hat vor (Escape_Space), die sie lesen müssen, um zu verstehen, wie der Code tatsächlich funktioniert. Der Ersatz wird im Wesentlichen in Englisch ("Fluchtraum") beschrieben und Englisch ist notorisch vage. Ohne die Definition zu betrachten, wissen Sie beispielsweise nicht, ob sie alle Leerzeichen oder nur das Leerzeichen enthält.

Es gibt viel Schreiben, das das Lob von TROCKEN und Abstraktion singt. Kennt jemand Quellen, die die Grenzen der Abstraktion beschreiben? Das Lob singen und die Pragmatik diskutieren, Code einfach zu halten?

Bearbeiten: Ich kann Texte finden, die die Einfachheit im Leben oder beim (englischen) Schreiben fördern, z. B. Thoreaus "Simplify, Simplify!" oder Strunk and Whites "Kräftiges Schreiben ist prägnant." Wo ist das Äquivalent für die Programmierung?

Martin C. Martin
quelle
1
Ich würde empfehlen, "Clean Code" von Robert C. Martin zu lesen, wenn Sie dies nicht getan haben. Wenn ja, lesen Sie die ersten Kapitel genauer durch.
Bruno Schäpper
Ich empfehle, Simple Made Easy anzuschauen, um eine ausführliche Diskussion über das Schreiben von einfachem Code zu führen.
dan_waterworth
3
Kein Fan von "Clean Code". Übermäßig dogmatisch.
Rig

Antworten:

9

Sicher: Joel Spolsky (Sie haben vielleicht von ihm gehört) sagt :

Alle nicht trivialen Abstraktionen sind bis zu einem gewissen Grad undicht.

Abstraktionen scheitern. Mal ein bisschen, mal viel. Es gibt Leckagen. Dinge laufen schief. Es passiert überall, wenn Sie Abstraktionen haben.

Siehe auch KISS und YAGNI, die Jeff Atwood bespricht :

Als Entwickler sind wir meiner Meinung nach auch viel zu optimistisch, wenn es darum geht, die Allgemeingültigkeit unserer eigenen Lösungen zu beurteilen, und so entwickeln wir am Ende ausgefeilte [Lösungen] um Dinge, die diese Komplexität möglicherweise nicht rechtfertigen. Um diesem Drang entgegenzuwirken, schlage ich vor, der YAGNI-Doktrin (Du wirst es nicht brauchen) zu folgen. Bauen Sie, was Sie brauchen, wie Sie es brauchen, und gestalten Sie es im Laufe der Zeit aggressiv um. Verbringen Sie nicht viel Zeit mit der Planung grandioser, unbekannter Zukunftsszenarien. Gute Software kann sich zu dem entwickeln, was sie letztendlich werden wird.

(Das genaue Angebot finden Sie unter dem Link.)

Meine Interpretation dieser Zitate:

  1. Es ist wirklich schwer, gute Abstraktionen zu erstellen - es ist viel einfacher, beschissene zu erstellen.

  2. Wenn Sie eine Abstraktion hinzufügen, für die keine Notwendigkeit besteht oder die falsch ist, verfügen Sie jetzt über zusätzlichen Code, der getestet, debuggt und gewartet werden muss. Und wenn Sie zurückgehen und umgestalten müssen, haben Sie jetzt mehr Eigengewicht, das Sie nach unten zieht.


quelle
1
Gute Quellen. +1
KChaloux
7

Ich bin mit der Sprache oder Laufzeit Ihrer Beispiele nicht vertraut, habe aber für gsubmich keine Bedeutung. Ist escape_spacejedoch viel aussagekräftiger. Ich bin völlig anderer Meinung als Ihr Argument, dass bekannte Funktionsaufrufe nicht durch Aufrufe von Funktionen ersetzt werden sollten, die sie noch nie zuvor gesehen haben . Wenn ich dieses Argument auf die Spitze treiben würde, sollte man einem Programm niemals eine neue Funktion hinzufügen: Es abstrahiert immer bekannte Funktionsaufrufe und ersetzt sie immer durch diesen neuen unbekannten benutzerdefinierten Aufruf.

Ein Entwickler sollte niemals den Code lesen müssen, um zu verstehen, wie er funktioniert. Zuallererst sollte er oder sie sich nicht darum kümmern müssen, wie es funktioniert, sondern sollte verstehen können, wie man es benutzt und welche Auswirkungen es hat. Ist dies nicht der Fall, liegt ein Problem mit dem Namen und der Signatur der Funktion oder ihrer Dokumentation vor.

Für mich ist das Ziel der Abstraktion, innere Details zu entfernen und eine sauberere Schnittstelle zu einigen Funktionen bereitzustellen. In Zukunft könnte das Innenleben der escape_spaceFunktion geändert oder überschrieben werden. Es ist mir egal, dass eine Funktion gsubmit zwei Argumenten aufgerufen wird. Es ist mir wichtig, dass meine Räume entkommen. Das macht die Abstraktion also nützlich.

In meiner Lieblingssprache C # füge ich allen meinen Funktionen (auch privaten) immer eine Dokumentation hinzu, in der Dinge wie Funktion, Verwendung, verwendete Einheiten, ausgelöste Ausnahmen, Eingabevertrag und Typen beschrieben werden. Mit IntelliSense von Visual Studio kann jeder Entwickler klarer sehen, was die Funktion tut, ohne den Code zu lesen . Ohne ein Tool wie IntelliSense müssen Entwickler ihre Funktionsnamen genauer festlegen oder zusätzliche Dokumentationen an einem leicht zugänglichen Ort aufbewahren.

Ich denke nicht, dass Abstraktionen jemals eingeschränkt werden sollten, und ich kenne keine solche Quelle.

Daniel AA Pelsmaeker
quelle
2
Die Sprache, die er verwendet, ist Ruby, und gsub ist eine sehr häufig verwendete Funktion der String-Klasse. Es wird Ihnen schwer fallen, jemanden zu finden, der mehr als einen kleinen Ruby geschrieben hat, der nicht sofort weiß, dass gsub als "alles finden und ersetzen" verwendet wird.
KChaloux
@KChaloux Es ist möglicherweise auch für Nicht-Ruby-Benutzer zu erraten: Neben dem Kontext (die übergebenen Argumente) sieht es aus wie 'g' + "Ersatz" - und in Ersatzbefehlen (zum Beispiel in sedund vim :s) ist 'g' verwendet als "alle"
Izkata
Ja, meine Vermutung war tatsächlich, dass es etwas ersetzte. Aber ich mag es nicht zu erraten, was eine Funktion tut. Es muss ein Artefakt aus früheren Zeiten, aber Funktionsnamen wie gsub, atoiund wcstombsals viel schlimmer genannt werden escape_space.
Daniel AA Pelsmaeker
2
@Virtlink Andererseits ist gsub ein allgemeiner Befehl. Wenn Sie sehen "something".gsub(' ', '\ '), wissen Sie genau, welche Ausgabe Sie erhalten. Das Entkommen von Räumen sehe ich nicht sehr oft. Verwendet es das Zeichen "\" oder ein anderes Escape-Zeichen? Entgeht es Zeilenumbrüchen? Ganz zu schweigen davon, dass es sich um ein Monkeypatch der Standard-String-Klasse handelt, das von bestimmten Entwicklern als schlechte Praxis angesehen wird.
KChaloux
5

Wenn ich über Abstraktionen spreche, denke ich, dass es wichtig ist, zwei Begriffe zu definieren: Inhärente und zufällige Komplexität. Inhärente Komplexität ist die Komplexität des Problems, das die Abstraktion löst. Zufällige Komplexität ist die Komplexität, die die Abstraktion verbirgt.

Gute Abstraktionen sind solche, die viel zufällige Komplexität verbergen und versuchen, die richtigen Probleme zu lösen, indem sie ihre inhärente Komplexität verringern. Ich denke, was Sie hier frustriert, sind Abstraktionen, die zu flach sind; Sie verbergen nicht viel zufällige Komplexität und es ist genauso schwierig (wenn nicht noch schwieriger), die Abstraktionen zu verstehen, wie es ist, ihre Implementierungen zu verstehen.

dan_waterworth
quelle
2

Meine Antwort wird mehr von der persönlichen Erfahrung abweichen, aber eine schnelle Suche hat diesen Blog gefunden , der ziemlich detailliert darüber aussieht, wann die Abstraktion zu weit geht (nicht nur der verknüpfte Eintrag, sondern es gibt mehrere verwandte).

Grundsätzlich kann Abstraktion eine gute Sache sein. Wie alles andere kann man sich jedoch mitreißen lassen. Ihre Beispiele sind gute, bei denen die Abstraktion zu weit gegangen ist. Sie erstellen "Pass-Throughs", die im Grunde nichts anderes tun, als die betreffende Funktion umzubenennen.

Bei benutzerdefinierten Funktionen kann dies erforderlich sein, wenn Sie ein altes Namensschema zugunsten eines neuen verwerfen möchten (z. B. das Ändern von Escape_space in EscapeSpace oder EscWhitespace oder was auch immer). Für Bibliotheksfunktionen? Overkill. Ganz zu schweigen von kontraproduktiv. Was haben Sie durch diese Abstraktion gewonnen? Wenn das alles ist es tut, dann alles haben Sie gewonnen ist die Kopfschmerzen zu haben , die Funktion zu erinnern und dieses Wissen an jemand anderen übergeben (während mit Bibliotheksfunktionen, Entwickler sind entweder bereits Kenntnis von ihnen, oder sollte eingeben können , ruby gsubin Google und finden Sie heraus, was es bedeutet).

Ich empfehle, intelligenter zu bestimmen, wann etwas abstrahiert werden soll. Beispielsweise sind Dinge, die mehr als 2-3 Zeilen umfassen und mindestens zweimal verwendet werden (selbst wenn es geringfügige Unterschiede gibt, können diese Unterschiede häufig parametrisiert werden), im Allgemeinen gute Abstraktionskandidaten. Manchmal betrachte ich große Codeblöcke, die aus Gründen der Lesbarkeit und des "One Job" -Prinzips eine bestimmte Funktion innerhalb einer größeren Funktion ausführen, und abstrahiere sie in geschützte Funktionen innerhalb derselben Klasse (häufig unmittelbar) Nähe der Funktion, die es verwendet).

Vergessen Sie auch nicht YAGNI (Sie werden es nicht brauchen) und vorzeitige Optimierung. Wird dies derzeit mehrmals verwendet? Nein? Dann machen Sie sich jetzt keine Sorgen darüber, es zu abstrahieren (es sei denn, dies führt zu einem erheblichen Lesbarkeitsgewinn). "Aber wir könnten es später brauchen!" Dann abstrahieren Sie es, wenn Sie es tatsächlich woanders brauchen. Bis dahin haben Sie nur Zeiger auf Zeiger auf Zeiger ohne wirklichen Nutzen.

Auf der anderen Seite dieser Medaille, wenn Sie etwas abstrahieren, tun Sie dies, sobald die Notwendigkeit offensichtlich wird. Es ist einfacher, zwei oder drei Codeblöcke zu verschieben und zu optimieren als 15. Im Allgemeinen denke ich, sobald ich Codeblöcke von einem anderen Ort kopiere oder etwas schreibe, das mir sehr vertraut erscheint, darüber nach, wie ich es abstrahieren kann.

Shauna
quelle
2
Vor einigen Jahren hatte ich Ihnen vollkommen zugestimmt und so gearbeitet, wie Sie es beschrieben haben. Zwischendurch las ich einige Dinge von Robert Martin und begann meine Programmierweise zu überdenken. Das Erstellen sehr kleiner Funktionen mit guten Namen, selbst wenn die Funktion einmal verwendet wird, hat einen echten Vorteil: Sie benötigen viel weniger Kommentare. Und es hilft Ihnen, die Anrufer dieser Funktion einfacher und lesbarer zu halten. Das ist definitiv keine "vorzeitige Optimierung" (Knuth sprach von Leistung, nicht von Codequalität). YAGNI sollte natürlich in Betracht gezogen werden, aber gute Refactoring-Tools haben solche Änderungen heute sehr einfach gemacht.
Doc Brown
1
@DocBrown - Ich denke, wir sind uns vielleicht mehr einig als Sie denken. Wir könnten wahrscheinlich stunden- oder tagelang über die Vor- und Nachteile der Abstraktion sprechen. Ich habe versucht, prägnanter zu sein, aber ich habe Ihre Besorgnis angesprochen, als ich die Lesbarkeit und das Prinzip "Ein Job" / Einzelverantwortung erwähnte. Im Allgemeinen zeichne ich die Grenze, wenn eine Funktion lediglich als Durchgang für eine andere Funktion fungiert (was für mich in den meisten Fällen eine Abstraktion um der Abstraktion willen ist).
Shauna
2

Schauen Sie sich zunächst dieses Beispiel von Robert C. Martin an:

http://blog.objectmentor.com/articles/2009/09/11/one-thing-extract-till-you-drop

Der Grund für diese Art des Refactorings besteht darin, Abstraktionen zu erstellen, um sehr kleine Funktionen zu erstellen, sodass jede Funktion nur eines tut . Das führt zu Funktionen wie den, die Sie uns oben gezeigt haben. Ich halte dies für eine gute Sache - es hilft Ihnen, Code zu erstellen, bei dem sich jede aufgerufene Funktion innerhalb einer anderen Funktion auf derselben Abstraktionsebene befindet.

Wenn Sie der Meinung sind, dass Sie Namen wie verwenden make_foo_binaryoder escape_spaceden Code weniger lesbar machen, sollten Sie versuchen, bessere Namen zu wählen (zum Beispiel escape_space_chars_with_backslash:), anstatt die Abstraktion aufzugeben.

Doc Brown
quelle