Ich habe oft gehört, dass andere Entwickler diesen Ausdruck verwenden, um für einige Muster zu werben oder Best Practices zu entwickeln. Meistens wird dieser Ausdruck verwendet, wenn Sie über die Vorteile der funktionalen Programmierung sprechen.
Der Ausdruck "Einfach zu überlegen" wurde unverändert verwendet, ohne Erklärung oder Codebeispiel. Für mich wird es also wie das nächste Schlagwort, das "erfahrene" Entwickler in ihren Gesprächen verwenden.
Frage: Können Sie einige Beispiele für "Nicht einfach zu argumentieren" angeben, damit Sie sie mit "Einfach zu argumentieren" -Beispielen vergleichen können?
Antworten:
Meiner Meinung nach bezieht sich der Ausdruck "leicht zu verstehen" auf Code, der leicht "in deinem Kopf auszuführen" ist.
Wenn Sie sich einen Code ansehen, der kurz, klar und mit guten Namen und minimalen Mutationen von Werten geschrieben ist, ist es eine (relativ) einfache Aufgabe, sich im Kopf damit zu beschäftigen, was der Code tut.
Ein langes Stück Code mit schlechten Namen, Variablen, deren Wert sich ständig ändert, und verschlungenen Verzweigungen erfordern normalerweise z. B. einen Stift und ein Stück Papier, um den aktuellen Status verfolgen zu können. Ein solcher Code kann daher nicht einfach nur in Ihrem Kopf abgearbeitet werden. Daher ist es nicht einfach, über einen solchen Code nachzudenken.
quelle
"Code easy to reason about" almost exclusively alludes to its mathematical properties and formal verification
- das klingt ungefähr nach einer Antwort auf die Frage. Vielleicht möchten Sie das als Antwort posten, anstatt zu widersprechen, was die (subjektive) Antwort in den Kommentaren ist.Ein Mechanismus oder ein Teil des Codes ist leicht zu überlegen, wenn Sie einige Dinge berücksichtigen müssen, um vorherzusagen, was er tun wird, und die Dinge, die Sie berücksichtigen müssen, sind leicht verfügbar.
Echte Funktionen ohne Nebenwirkungen und ohne Status sind leicht zu beurteilen, da die Ausgabe vollständig von der Eingabe bestimmt wird, die sich genau dort in den Parametern befindet.
Umgekehrt ist es sehr viel schwieriger, über ein Objekt mit einem Zustand nachzudenken, da Sie berücksichtigen müssen, in welchem Zustand sich das Objekt befindet, wenn eine Methode aufgerufen wird, was bedeutet, dass Sie überlegen müssen, welche anderen Situationen dazu führen könnten, dass sich das Objekt in einem befindet bestimmten Zustand.
Noch schlimmer sind globale Variablen: Um über Code zu urteilen, der eine globale Variable liest, müssen Sie wissen, wo und warum diese Variable in Ihrem Code festgelegt werden kann - und es ist möglicherweise nicht einmal einfach, alle diese Stellen zu finden.
Das Schwierigste, woran man denken kann, ist die Multithread-Programmierung mit gemeinsamem Status, da nicht nur der Status, sondern auch mehrere Threads gleichzeitig geändert werden Sie müssen die Möglichkeit berücksichtigen, dass an jedem einzelnen Ausführungspunkt ein anderer Thread (oder mehrere von ihnen!) fast jeden anderen Teil des Codes ausführt und die Daten, mit denen Sie arbeiten, direkt unter Ihren Augen ändert. Theoretisch kann dies mit Mutexen / Monitoren / kritischen Abschnitten / wie auch immer - verwaltet werden, aber in der Praxis ist kein Mensch tatsächlich in der Lage, dies zuverlässig zu tun, es sei denn, sie beschränken den gemeinsamen Zustand und / oder die Parallelität drastisch auf sehr klein Abschnitte des Codes.
quelle
make
oder sogar C ++ - Template-Spezialisierung und Funktionsüberladung) Sie wieder in die Lage versetzen, das gesamte Programm zu betrachten. Selbst wenn Sie der Meinung sind, dass Sie die Definition von etwas gefunden haben, können Sie sie durch eine spezifischere Deklaration an einer beliebigen Stelle im Programm überschreiben. Ihre IDE könnte dabei helfen.sealed
nicht die Standardeinstellung zu sein?Im Falle der funktionalen Programmierung ist die Bedeutung von „Einfach zu begründen“ meist deterministisch. Damit meine ich, dass eine bestimmte Eingabe immer zur gleichen Ausgabe führt. Sie können mit dem Programm tun, was immer Sie wollen, solange Sie diesen Code nicht berühren, wird er nicht kaputt gehen.
Andererseits ist es in der Regel schwieriger, über OO nachzudenken, da die erzeugte "Ausgabe" vom internen Zustand jedes beteiligten Objekts abhängt. Typischerweise treten unerwartete Nebenwirkungen auf : Wenn ein Teil des Codes geändert wird, bricht ein scheinbar nicht verwandter Teil zusammen.
... der Nachteil der funktionalen Programmierung ist natürlich, dass in der Praxis eine Menge von dem, was Sie tun möchten, IO und das Verwalten des Zustands ist.
Es gibt jedoch viele andere Dinge, über die man schwerer nachdenken kann, und ich stimme @Kilian zu, dass Parallelität ein Paradebeispiel ist. Auch verteilte Systeme.
quelle
Vermeiden Sie eine breitere Diskussion und gehen Sie auf die spezifische Frage ein:
Ich verweise Sie auf "Die Geschichte von Mel, einem echten Programmierer" , ein Stück Programmierer-Folklore aus dem Jahr 1983, das für unseren Beruf als "Legende" gilt.
Es erzählt die Geschichte eines Programmierers, der Code schreibt, der, wo immer möglich, arkane Techniken bevorzugt, einschließlich selbstreferenziellen und selbstmodifizierenden Codes und der absichtlichen Ausnutzung von Maschinenfehlern:
Dies ist ein Beispiel für Code, über den man nur schwer nachdenken kann.
Natürlich würde Mel nicht zustimmen ...
quelle
Ich kann ein Beispiel geben, und ein sehr verbreitetes.
Betrachten Sie den folgenden C # -Code.
Betrachten Sie nun diese Alternative.
Im zweiten Beispiel weiß ich genau, was dieser Code auf einen Blick tut. Wenn ich sehe
Select
, weiß ich, dass eine Liste von Elementen in eine Liste von etwas anderem umgewandelt wird. Wenn ich seheWhere
, weiß ich, dass bestimmte Elemente herausgefiltert werden. Auf einen Blick kann ich verstehen, was esnames
ist, und es effektiv nutzen.Wenn ich eine
for
Schleife sehe , habe ich keine Ahnung, was damit los ist, bis ich den Code tatsächlich gelesen habe. Und manchmal muss ich das nachvollziehen, um sicherzugehen, dass ich alle Nebenwirkungen berücksichtigt habe. Ich muss ein bisschen arbeiten, um überhaupt zu verstehen, was Namen sind (jenseits der Typdefinition) und wie man sie effektiv einsetzt. Daher ist das erste Beispiel schwerer zu beurteilen als das zweite.Letztlich hier zu sein leicht zu Grunde hängt auch von Verständnis LINQ Methoden
Select
undWhere
. Wenn Sie sie nicht kennen, ist es anfangs schwieriger, über den zweiten Code nachzudenken. Aber Sie zahlen nur die Kosten, um sie einmal zu verstehen. Sie zahlen die Kosten für das Verständnis einerfor
Schleife, wenn Sie diese bei jeder Änderung erneut verwenden. Manchmal sind die Kosten die Zahlung wert, aber in der Regel ist es viel wichtiger, dass man "leichter überlegt" ist.quelle
Eine verwandte Phrase ist (ich paraphrase),
Ein Beispiel für relativ "leicht zu begründen" könnte RAII sein .
Ein anderes Beispiel könnte darin bestehen, eine tödliche Umarmung zu vermeiden : Wenn Sie eine Sperre halten und eine andere erwerben können und es viele Sperren gibt, können Sie nur schwer sicher sein, dass es kein Szenario gibt, in dem eine tödliche Umarmung auftreten könnte. Durch Hinzufügen einer Regel wie "es gibt nur eine (globale) Sperre" oder "Sie dürfen keine zweite Sperre erwerben, während Sie eine erste Sperre halten" ist das System relativ einfach zu überlegen.
quelle
CComPtr<>
) mit C-Stil Funktion (CoUninitialize()
). Ich kann ihm ein bizarres Beispiel finden, auch, soweit ich Dich invoke CoInitialize / CoUninitialize im Modul Umfang und für die gesamte Lebensdauer des Moduls erinnern, zum Beispiel inmain
oder inDllMain
, und nicht in einem winzigen kurzlebig Umfang lokalen Funktion wie in dem Beispiel gezeigt , .main
) für eine Anwendung vor. Sie initialisieren COM beim Start und deaktivieren es unmittelbar vor dem Beenden. Es sei denn, Sie haben globale Objekte, wie z. B. COM-Smart-Pointer, die das RAII-Paradigma verwenden. In Bezug auf Mischstile: Ein globales Objekt, das COM in seinem ctor initialisiert und in seinem dtor nicht initialisiert hat, ist funktionsfähig, und was Raymond vorschlägt, aber es ist subtil und nicht leicht zu überlegen.Der Kern der Programmierung ist die Fallanalyse. Alan Perlis bemerkte dies in Epigramm Nr. 32: Programmierer sollten nicht an ihrem Einfallsreichtum und ihrer Logik gemessen werden, sondern an der Vollständigkeit ihrer Fallanalyse.
Eine Situation ist leicht zu überlegen, wenn die Fallanalyse einfach ist. Dies bedeutet entweder, dass es nur wenige Fälle zu betrachten, oder andernfalls, einige besondere Fälle-könnte es einige große Räume der Fälle sein, die aber Kollaps aufgrund einiger Regelmäßigkeiten oder erliegen einer Argumentation Technik wie Induktion.
Beispielsweise ist eine rekursive Version eines Algorithmus in der Regel einfacher zu überdenken als eine imperative Version, da sie keine überflüssigen Fälle liefert, die durch die Mutation unterstützender Zustandsvariablen entstehen, die in der rekursiven Version nicht vorkommen. Darüber hinaus ist die Struktur der Rekursion so, dass sie in ein mathematisches Beweis-durch-Induktion-Muster passt. Wir müssen Komplexitäten wie Schleifenvarianten und schwächste strenge Voraussetzungen und so weiter nicht berücksichtigen.
Ein weiterer Aspekt ist die Struktur des Kofferraums. Es ist einfacher, über eine Situation zu urteilen, die eine flache oder größtenteils flache Aufteilung in Fälle im Vergleich zu einer hierarchischen Fallsituation aufweist: Fälle mit Unterfällen und Unterunterfällen und so weiter.
Eine Eigenschaft von Systemen, die das Denken vereinfacht, ist die Orthogonalität : Dies ist die Eigenschaft, dass die Fälle, die Subsysteme regeln, unabhängig bleiben, wenn diese Subsysteme kombiniert werden. Keine Kombination führt zu "Sonderfällen". Wenn ein Etwas mit vier Fällen orthogonal mit einem Etwas mit drei Fällen kombiniert wird, gibt es zwölf Fälle, aber im IdealfallJeder Fall ist eine Kombination von zwei Fällen, die unabhängig bleiben. In gewissem Sinne gibt es nicht wirklich zwölf Fälle; Die Kombinationen sind nur "emergente fallähnliche Phänomene", über die wir uns keine Sorgen machen müssen. Dies bedeutet, dass wir immer noch vier Fälle haben, an die wir denken können, ohne die anderen drei im anderen Subsystem zu berücksichtigen, und umgekehrt. Wenn einige der Kombinationen speziell identifiziert und mit zusätzlicher Logik ausgestattet werden müssen, ist die Argumentation schwieriger. Im schlimmsten Fall hat jede Kombination eine spezielle Behandlung, und dann gibt es tatsächlich zwölf neue Fälle, die zusätzlich zu den ursprünglichen vier und drei sind.
quelle
Sicher. Nehmen Sie Parallelität:
Kritische Abschnitte, die durch Mutexe erzwungen werden: Leicht zu verstehen, da es nur ein Prinzip gibt (zwei Ausführungsthreads können nicht gleichzeitig in den kritischen Abschnitt gelangen), das jedoch sowohl ineffizient als auch blockiert ist.
Alternative Modelle, z. B. sperrenloses Programmieren oder Schauspieler: Möglicherweise viel eleganter und leistungsfähiger, aber höllisch schwer zu verstehen, weil Sie sich nicht mehr auf (scheinbar) grundlegende Konzepte wie "Jetzt diesen Wert an diesen Ort schreiben" verlassen können.
Es ist ein Aspekt einer Methode, einfach darüber nachzudenken. Die Wahl der zu verwendenden Methode erfordert jedoch die Berücksichtigung aller Aspekte in Kombination.
quelle
Beschränken wir die Aufgabe auf das formale Denken. Weil humoristische oder erfinderische oder poetische Überlegungen unterschiedliche Gesetze haben.
Trotzdem ist der Ausdruck schwach definiert und kann nicht streng festgelegt werden. Das heißt aber nicht, dass es für uns so dunkel bleiben sollte. Stellen wir uns vor, eine Struktur besteht einen Test und bekommt Punkte für verschiedene Punkte. Die guten Noten für JEDEN Punkt bedeuten, dass die Struktur in jeder Hinsicht praktisch ist und daher "leicht zu überlegen" ist.
Die Struktur "Einfach zu überlegen" sollte für Folgendes gute Noten bekommen:
Ist der Test subjektiv? Ja, natürlich ist es das. Aber auch der Ausdruck selbst ist subjektiv. Was für eine Person einfach ist, ist für eine andere Person nicht einfach. Daher sollten die Tests für die verschiedenen Domänen unterschiedlich sein.
quelle
Die Idee, dass funktionale Sprachen begründet werden können, stammt aus ihrer Geschichte, insbesondere aus ML, das als Programmiersprache analog zu den Konstrukten entwickelt wurde, die die Logik für berechenbare Funktionen zum Begründen verwendet. Die meisten funktionalen Sprachen sind näher an formalen Programmierkalkulationen als an imperativen, so dass die Übersetzung von Code in die Eingabe eines Systems von Argumentationssystemen weniger aufwändig ist.
Als Beispiel für ein Argumentationssystem muss in pi-calculus jede veränderbare Speicherstelle in einer imperativen Sprache als separater paralleler Prozess dargestellt werden, wohingegen eine Folge von Funktionsoperationen ein einzelner Prozess ist. Vierzig Jahre nach dem LFC-Theorembeweiser arbeiten wir mit GB RAM, sodass Hunderte von Prozessen weniger problematisch sind. Ich habe Pi-Calculus verwendet, um potenzielle Deadlocks aus einigen hundert Zeilen von C ++ zu entfernen, obwohl es Hunderte von Prozessen gibt Prozesse, die der Reasoner ausgeführt hat, haben den Zustandsspeicher in etwa 3 GB erschöpft und einen zeitweise auftretenden Fehler behoben. Dies wäre in den siebziger Jahren unmöglich gewesen oder hätte Anfang der neunziger Jahre einen Supercomputer erforderlich gemacht, während der Zustandsraum eines funktionalen Sprachprogramms von ähnlicher Größe damals klein genug war, um darüber nachzudenken.
Aus den anderen Antworten geht hervor, dass die Phrase zu einem Modewort wird, obwohl ein Großteil der Schwierigkeit, die es schwierig machte, über imperative Sprachen nachzudenken, durch Moores Gesetz untergraben wird.
quelle
Über diesen kulturspezifischen Begriff lässt sich leicht nachdenken, weshalb es so schwierig ist, konkrete Beispiele zu finden. Es ist ein Begriff, der den Menschen verankert ist, die die Überlegungen anstellen sollen.
"Einfach zu überlegen" ist eigentlich eine sehr selbsterklärende Phrase. Wenn man sich den Code ansieht und überlegen will, was er tut, ist es einfach =)
Okay, es kaputt zu machen. Wenn Sie sich Code ansehen, möchten Sie normalerweise, dass er etwas bewirkt. Sie möchten sicherstellen, dass es das tut, was Sie denken, dass es tun sollte. Sie entwickeln also Theorien darüber, was der Code tun soll, und überlegen dann, warum der Code tatsächlich funktioniert. Sie versuchen, den Code wie einen Menschen (und nicht wie einen Computer) zu betrachten und die Argumente darüber zu rationalisieren, was der Code bewirken kann.
Der schlimmste Fall für "einfach zu begründen" ist, wenn der Code nur Zeile für Zeile durchlaufen werden kann, wie bei einer Turing-Maschine für alle Eingaben. In diesem Fall können Sie den Code nur begründen, indem Sie sich in einen Computer verwandeln und ihn in Ihrem Kopf ausführen. Diese Beispiele für den schlimmsten Fall lassen sich leicht in verschleierten Programmierwettbewerben erkennen, z. B. in den folgenden drei Zeilen von PERL, mit denen RSA entschlüsselt wird:
Auch hier ist der Begriff aus verständlichen Gründen sehr kulturell. Sie müssen berücksichtigen:
Jeder dieser Effekte ist "leicht zu überlegen" unterschiedlich. Nehmen Sie die Fähigkeiten des Reasoners als Beispiel. Als ich in meiner Firma anfing, wurde mir empfohlen, meine Skripte in MATLAB zu entwickeln, weil es "leicht zu überlegen ist". Warum? Nun, jeder in der Firma kannte MATLAB. Wenn ich eine andere Sprache wählen würde, könnte mich niemand besser verstehen. Macht nichts , dass MATLAB Lesbarkeit ist scheußlich für einige Aufgaben, nur weil es nicht für sie entworfen wurde. Später, im Laufe meiner Karriere, wurde Python immer beliebter. Plötzlich wurde MATLAB-Code "schwer zu verstehen" und Python war die bevorzugte Sprache für das Schreiben von Code, über den man leicht nachdenken konnte.
Überlegen Sie auch, welche Redewendungen der Leser haben könnte. Wenn Sie sich darauf verlassen können, dass Ihr Leser eine FFT in einer bestimmten Syntax erkennt, ist es "einfacher, über den Code nachzudenken", wenn Sie sich an diese Syntax halten. Damit können sie die Textdatei als Zeichenfläche betrachten, auf die Sie eine FFT gemalt haben, anstatt sich mit den Details auseinandersetzen zu müssen. Wenn Sie C ++ verwenden, finden Sie heraus, wie gut Ihre Leser mit der
std
Bibliothek vertraut sind . Wie sehr mögen sie funktionale Programmierung? Einige der Redewendungen, die aus den Container-Bibliotheken kommen, hängen stark davon ab, welchen idomatischen Stil Sie bevorzugen.Es ist auch wichtig zu verstehen, welche Fragen der Leser beantworten möchte. Sind Ihre Leser hauptsächlich mit dem oberflächlichen Verständnis des Codes beschäftigt oder suchen sie nach Fehlern tief im Darm?
Wie sicher der Leser sein muss, ist tatsächlich interessant. In vielen Fällen reicht ein trübes Denken aus, um das Produkt aus der Tür zu holen. In anderen Fällen, wie z. B. der FAA-Flugsoftware, möchte der Leser ein ironisches Denken. Ich stieß auf einen Fall, in dem ich mich für die Verwendung von RAII für eine bestimmte Aufgabe aussprach, weil "Sie es einfach einrichten und vergessen können ... es wird das Richtige tun." Mir wurde gesagt, dass ich falsch lag. Diejenigen, die sich mit diesem Code auseinandersetzen wollten, waren nicht die Art von Leuten, die "nur die Details vergessen wollen". Für sie war RAII eher ein hängender Junge, der sie dazu zwang, über alles nachzudenken, was passieren kann, wenn Sie den Spielraum verlassen.
quelle