"Einfach zu überlegen" - was bedeutet das? [geschlossen]

49

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?

Fabio
quelle
4
@MartinMaat Eine präzisere Formulierung, die weit verbreitet ist, ist das Denken mit Gleichungen. Ich würde vorschlagen, dass Fabio danach
strebt
3
Ich benutze gerne den Ausdruck "kognitive Belastung" für solche Dinge.
Baldrickk
16
Wissen Sie, was das Denken über Programme bedeutet?
Bergi
5
Im nicht-formalen Sinne meine ich damit, dass eine Lösung einfach genug ist, um (im Allgemeinen) zu verstehen, welche Ergebnisse für eine bestimmte Eingabe erzielt werden, ohne sie zu testen. Dies bedeutet, dass die Ergebnisse für alle Eingaben nicht überraschend sind. Lösungen, die nicht offensichtliche Eckfälle aufweisen, sind zum Beispiel schwer zu überlegen. Hauptsächlich verwende ich dies in Bezug auf Robustheit.
JimmyJames
7
Ich bin sehr schuldig, häufig "einfacher zu argumentieren" zu verwenden; Ich stellt fest , aber dass ich versuche , den Vergleich zu sagen, vorsichtig zu sein einfachen , anstatt die absoluten leicht . Es gab einen Tag in meinem Leben, an dem ich über keine Software nachdenken konnte, also war es an diesem Tag nicht einfach ; es wurde einfach, nur indem viel Zeit und Mühe aufgewendet wurde. Zu sagen, dass jedes Programmierproblem einfach ist, ist eine ablehnende Haltung gegenüber jedem, der es (noch) nicht leicht findet. Zu sagen, dass ein Modell einfacher ist als ein anderes, bedeutet, dass weniger Konzepte, weniger bewegliche Teile usw. betroffen sind.
Eric Lippert

Antworten:

58

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.

David Arno
quelle
29
Ein Programm, das versucht, Goldbachs Vermutung zu widerlegen, ist mit einer leichten Einschränkung, egal wie gut Sie Ihre Variablen benennen, von Natur aus schwer in Ihrem Kopf oder anderswo "auszuführen". Aber es kann immer noch leicht zu überlegen sein, in dem Sinne, dass man sich leicht davon überzeugen kann, dass es die Wahrheit sagt, wenn es behauptet, ein Gegenbeispiel gefunden zu haben ;-)
Steve Jessop
4
Ich würde niemals Code in meinem Kopf ausführen wollen. Das wäre für mich die ultimative Show von "nicht einfach zu überlegen". Ich möchte vorausschauende Aussagen darüber machen können, was der Computer tun würde, ohne ihn auszuführen. Code, über den man "leicht nachdenken kann", ist Code, der nicht im Kopf ausgeführt werden muss, sondern über den man nachdenken kann.
Cort Ammon
1
Wie kann man eine Frage zur Argumentation über Code beantworten, ohne auch nur die formale Verifizierung zu erwähnen ? Diese Antwort legt nahe, dass das Denken über Code informell und ad-hoc ist. es ist nicht so, es wird normalerweise mit sehr großer Sorgfalt und mathematischen Ansätzen gemacht. Es gibt bestimmte mathematische Eigenschaften, die den Code objektiv "leicht zu denken" machen (reine Funktionen, um ein sehr einfaches Beispiel zu nennen). Variablennamen haben nichts damit zu tun, wie einfach es ist, über Code zu "argumentieren", zumindest nicht in formalem Sinne.
Polygnome
3
@Polygnome Das Überlegen von Code wird normalerweise nicht mit größter Sorgfalt und mathematischen Ansätzen durchgeführt. Während ich dies schreibe, sind die Leute, die informell über Code nachdenken, mindestens millionenfach zahlreicher als die mathematischen Annäherungsversuche, wie ich glaube.
Kaz
2
@Polygnome "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.
Dukeling
47

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.

Michael Borgwardt
quelle
9
Ich stimme dieser Antwort zu, aber selbst bei reinen Funktionen können deklarative Ansätze (wie CSS, XSLT makeoder 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.
Steve Jessop
4
Ich möchte hinzufügen, dass Sie im Multithread-Szenario auch ein ziemlich tiefes Verständnis dafür haben müssen, zu welchen Anweisungen auf niedrigerer Ebene Ihr Code gehört: Eine Operation, die in der Quelle atomar aussieht, kann unerwartete Unterbrechungspunkte bei der tatsächlichen Ausführung haben.
Jared Smith
6
@SteveJessop: In der Tat wird dieser Punkt oft übersehen. Es gibt einen Grund, warum Sie in C # angeben müssen , dass eine Methode überschreibbar sein soll, anstatt im Hintergrund die Überschreibbarkeit als Standard festzulegen. Wir möchten ein Flag setzen, das besagt, dass "die Richtigkeit Ihres Programms möglicherweise von Code abhängt, den Sie zur Kompilierungszeit nicht finden können". (Das heißt, ich wünschte auch, dass "versiegelt" die Standardeinstellung für Klassen in C # war.)
Eric Lippert
@EricLippert Was waren die letzten Gründe dafür, sealednicht die Standardeinstellung zu sein?
Zev Spitz
@ZevSpitz: Diese Entscheidung wurde lange vor meiner Zeit getroffen; Ich weiß es nicht.
Eric Lippert
9

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.

dagnelies
quelle
5

Vermeiden Sie eine breitere Diskussion und gehen Sie auf die spezifische Frage ein:

Können Sie einige Beispiele für "Nicht einfach zu argumentieren" angeben, damit Sie sie mit "Einfach zu argumentieren" -Beispielen vergleichen können?

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:

Tatsächlich war eine scheinbare Endlosschleife so codiert worden, dass ein Übertrag-Überlauf-Fehler ausgenutzt wurde. Das Hinzufügen von 1 zu einem Befehl, der als "Laden von Adresse x" dekodiert wurde, ergab normalerweise "Laden von Adresse x + 1". Wenn jedoch x bereits die höchstmögliche Adresse war, wurde nicht nur der Adressumlauf auf Null durchgeführt, sondern es wurde eine 1 in die Bits übertragen, aus denen der Opcode gelesen werden würde, wodurch der Opcode von "Laden von" auf "Springen nach" geändert wurde dass der vollständige Befehl von "Laden von der letzten Adresse" auf "Springen zu Adresse Null" geändert wurde.

Dies ist ein Beispiel für Code, über den man nur schwer nachdenken kann.

Natürlich würde Mel nicht zustimmen ...

AakashM
quelle
1
+1 für die Bezugnahme auf die Geschichte von Mel, einem meiner beständigen Favoriten.
John Bollinger
3
Lesen Sie hier die Geschichte von Mel , da der Wikipedia-Artikel nicht darauf verweist.
TRiG
@TRiG Fußnote 3 auf der Seite, nein?
AakashM
@AakashM Irgendwie ist es gelungen, das zu verpassen.
TRiG
5

Ich kann ein Beispiel geben, und ein sehr verbreitetes.

Betrachten Sie den folgenden C # -Code.

// items is List<Item>
var names = new List<string>();
for (var i = 0; i < items.Count; i++)
{
    var item = items[i];
    var mangled = MyMangleFunction(item.Name);
    if (mangled.StartsWith("foo"))
    {
        names.Add(mangled);
    }
}

Betrachten Sie nun diese Alternative.

// items is List<Item>
var names = items
    .Select(item => MyMangleFunction(item.Name))
    .Where(s => s.StartsWith("foo"))
    .ToList();

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 sehe Where, weiß ich, dass bestimmte Elemente herausgefiltert werden. Auf einen Blick kann ich verstehen, was es namesist, und es effektiv nutzen.

Wenn ich eine forSchleife 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 Selectund Where. 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 einer forSchleife, 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.

Kasey Speakman
quelle
2

Eine verwandte Phrase ist (ich paraphrase),

Es reicht nicht aus, dass der Code " keine offensichtlichen Fehler " aufweist: Stattdessen sollte er " offensichtlich keine Fehler " aufweisen.

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.

ChrisW
quelle
1
Hmm. Ich bin mir nicht sicher, ob es so einfach ist, über RAII nachzudenken. Sicher, es ist konzeptionell leicht zu verstehen , aber es wird schwieriger, über das Verhalten von Code, der RAII ausgiebig nutzt, nachzudenken (dh es vorherzusagen) . Ich meine, es sind im Grunde unsichtbare Funktionsaufrufe auf Scope-Ebene. Die Tatsache, dass viele Leute Schwierigkeiten haben, darüber nachzudenken, ist offensichtlich, wenn Sie jemals eine COM-Programmierung durchgeführt haben .
Cody Grey
Ich meinte relativ einfach (C ++ im Vergleich zu C): Zum Beispiel bedeutet die Existenz eines
sprachunterstützten
Dieses COM-basierte Beispiel ist problematisch, weil es Stile mischt, dh C ++ - Stil Smart Pointer ( 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 in mainoder in DllMain, und nicht in einem winzigen kurzlebig Umfang lokalen Funktion wie in dem Beispiel gezeigt , .
ChrisW
Es ist ein stark vereinfachtes Beispiel zur Veranschaulichung. Sie haben vollkommen Recht, dass COM im Modulbereich initialisiert wird. Stellen Sie sich jedoch das Beispiel von Raymond (wie das Beispiel von Larry) als Einstiegspunkt ( 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.
Cody Grey
Ich würde argumentieren, dass die COM-Programmierung in C in vielerlei Hinsicht einfacher zu durchdenken ist, da alles ein expliziter Funktionsaufruf ist. Hinter deinem Rücken ist nichts verborgen oder unsichtbar. Es ist ein bisschen mehr Arbeit (dh mehr langweilig), da Sie alle diese Funktionsaufrufe manuell schreiben müssen und gehen Sie zurück und überprüfen Sie Ihre Arbeit zu sehen , dass Sie es richtig gemacht haben, aber es ist alles offen gelegt, die der Schlüssel ist , um es einfach zu machen Grund über. Mit anderen Worten, "manchmal sind intelligente Zeiger einfach zu intelligent" .
Cody Grey
2

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.

Kaz
quelle
0

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.

Kilian Foth
quelle
13
-1: wirklich, wirklich schlechtes Beispiel, das mich denken lässt, dass Sie nicht verstehen, was der Ausdruck selbst bedeutet. "Kritische Abschnitte, die durch Mutexe erzwungen werden", sind in der Tat eines der schwierigsten Argumente - so ziemlich jeder, der sie verwendet, führt Rennbedingungen oder Deadlocks ein. Ich gebe Ihnen die Möglichkeit, ohne Sperren zu programmieren, aber der verdammte Punkt des Darstellermodells ist, dass es viel, viel einfacher ist, darüber nachzudenken.
Michael Borgwardt
1
Das Problem ist, dass Parallelität selbst für Programmierer ein sehr schwieriges Thema ist, weshalb sie kein besonders gutes Beispiel darstellt. Sie haben völlig Recht, dass kritische Abschnitte, die durch Mutexe erzwungen werden, eine relativ einfache Möglichkeit sind, Parallelität zu implementieren, verglichen mit der sperrenfreien Programmierung. Die meisten Programmierer sind jedoch wie Michael, und ihre Augen leuchten, wenn Sie über kritische Abschnitte und Mutexe sprechen sicherlich scheint es nicht einfach zu verstehen. Ganz zu schweigen von den vielen Fehlern.
Cody Grey
0

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:

  • Innere Begriffe haben vernünftige, leicht zu unterscheidende und zu definierende Namen. Wenn Elemente eine Hierarchie aufweisen, sollte sich der Unterschied zwischen Eltern- und Kindernamen vom Unterschied zwischen Geschwisternamen unterscheiden.
  • Die Anzahl der Arten von Strukturelementen ist gering
  • Verwendete Arten von Strukturelementen sind einfache Dinge, an die wir gewöhnt sind.
  • Die schwer verständlichen Elemente (Rekursionen, Metaschritte, 4+ dimensionale Geometrie ...) werden isoliert - nicht direkt miteinander kombiniert. (Wenn Sie zum Beispiel versuchen, eine Änderung der Rekursionsregeln für 1,2,3,4..n..dimensionale Würfel vorzunehmen, ist dies sehr kompliziert. Wenn Sie jedoch jede dieser Regeln auf eine Formel übertragen, ist dies sehr schwierig In Abhängigkeit von n haben Sie für jeden n-Würfel eine eigene Formel und für eine solche Formel eine eigene Rekursionsregel.
  • Typen von Strukturelementen unterscheiden sich offensichtlich (zum Beispiel, wenn keine gemischten Arrays ab 0 und ab 1 verwendet werden)

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.

Gangnus
quelle
0

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.

Pete Kirkham
quelle
-2

Ü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:

#!/bin/perl -sp0777i<X+d*lMLa^*lN%0]dsXx++lMlN/dsM0<j]dsj
$/=unpack('H*',$_);$_=`echo 16dio\U$k"SK$/SM$n\EsN0p[lN*1
lK[d2%Sa2/d0$^Ixp"|dc`;s/\W//g;$_=pack('H*',/((..)*)$/)

Auch hier ist der Begriff aus verständlichen Gründen sehr kulturell. Sie müssen berücksichtigen:

  • Welche Fähigkeiten hat der Reasoner? Wie viel erfahrung
  • Welche Art von Fragen könnte der Reasoner zum Code haben?
  • Wie sicher muss der Reasoner sein?

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 stdBibliothek 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.

Cort Ammon
quelle
12
Der Perl-Code ist schwer zu lesen . kein Grund über. Wenn ich daran interessiert wäre, es verstehen zu müssen, würde ich den Code entschlüsseln. Code, über den man nur schwer nachdenken kann, ist derjenige, über den man nur schwer nachdenken kann, wenn er gut formatiert ist, mit eindeutigen Bezeichnern für alles und ohne Code-Golf-Tricks.
Kaz