Mir wurde die Aufgabe übertragen, eine domänenspezifische Sprache für ein Tool zu implementieren, das für das Unternehmen sehr wichtig werden kann. Die Sprache ist einfach, aber nicht trivial, sie erlaubt bereits geschachtelte Schleifen, Verkettung von Zeichenfolgen usw. und es ist praktisch sicher, dass andere Konstrukte hinzugefügt werden, wenn das Projekt voranschreitet.
Ich weiß aus Erfahrung, dass das Schreiben eines Lexers / Parsers von Hand - es sei denn, die Grammatik ist trivial - ein zeitaufwendiger und fehleranfälliger Prozess ist. Ich hatte also zwei Möglichkeiten: einen Parser-Generator à la Yacc oder eine Combinator-Bibliothek wie Parsec. Ersteres war auch gut, aber ich habe mich aus verschiedenen Gründen für Letzteres entschieden und die Lösung in einer funktionalen Sprache implementiert.
Das Ergebnis ist für mich ziemlich spektakulär, der Code ist sehr präzise, elegant und lesbar / fließend. Ich gebe zu, es mag etwas seltsam aussehen, wenn Sie nie etwas anderes als java / c # programmiert haben, aber das würde für alles gelten, was nicht in java / c # geschrieben ist.
Irgendwann wurde ich jedoch buchstäblich von einem Mitarbeiter angegriffen. Nach einem kurzen Blick auf meinen Bildschirm erklärte er, dass der Code unverständlich ist und dass ich das Parsen nicht neu erfinden sollte, sondern nur einen Stack und String.Split verwenden sollte, wie es jeder tut. Er machte viel Lärm, und ich konnte ihn nicht überzeugen, zum Teil, weil ich überrascht war und keine klare Erklärung hatte, zum Teil, weil seine Meinung unveränderlich war (kein Wortspiel beabsichtigt). Ich bot ihm sogar an, ihm die Sprache zu erklären, aber ohne Erfolg.
Ich bin mir sicher, dass die Diskussion vor dem Management wieder auftauchen wird, also bereite ich einige solide Argumente vor.
Dies sind die ersten Gründe, die mir einfallen, um eine auf String.Split basierende Lösung zu vermeiden:
- Sie brauchen eine Menge Wenns, um Sonderfälle zu bewältigen, und die Dinge geraten schnell außer Kontrolle
- Viele fest codierte Array-Indizes machen die Wartung schmerzhaft
- extrem schwer zu handhabende Dinge wie ein Funktionsaufruf als Methodenargument (zB add ((add a, b), c)
- Sehr schwierig, bei Syntaxfehlern aussagekräftige Fehlermeldungen zu liefern (sehr wahrscheinlich)
- Ich bin alle der Einfachheit, Klarheit und Vermeidung unnötiger intelligenter und kryptischer Dinge verpflichtet, aber ich bin auch der Meinung, dass es ein Fehler ist, jeden Teil der Codebasis so zu dumm zu machen, dass selbst ein Burgerflipper dies verstehen kann. Es ist das gleiche Argument, das ich höre, wenn ich keine Schnittstellen benutze, keine Trennung von Bedenken vornehme, keinen Code kopiert oder einfügt. Schließlich ist ein Minimum an technischer Kompetenz und Lernbereitschaft erforderlich, um an einem Softwareprojekt zu arbeiten. (Ich werde dieses Argument nicht verwenden, da es wahrscheinlich anstößig klingen wird und der Beginn eines Krieges niemandem helfen wird.)
Was sind Ihre Lieblingsargumente gegen das Parsen nach Cthulhu ? *
* Natürlich, wenn Sie mich überzeugen können, dass er Recht hat, werde ich auch vollkommen glücklich sein
quelle
Antworten:
Der entscheidende Unterschied zwischen den beiden Ansätzen besteht darin, dass derjenige, den er als den einzig richtigen Weg ansieht, zwingend ist und der Ihre deklarativ ist.
Ihr Ansatz deklariert explizit Regeln, dh die Regeln der Grammatik sind (fast) direkt in Ihrem Code codiert, und die Parser-Bibliothek wandelt rohe Eingaben automatisch in analysierte Ausgaben um, wobei Zustände und andere schwer zu handhabende Dinge berücksichtigt werden. Ihr Code wird in einer einzelnen Abstraktionsebene geschrieben, die mit der Problemdomäne übereinstimmt: Parsing. Es ist vernünftig anzunehmen, dass parsec korrekt ist, was bedeutet, dass der einzige Fehler darin besteht, dass Ihre Grammatikdefinition falsch ist. Andererseits verfügen Sie über vollständig qualifizierte Regelobjekte, die problemlos isoliert getestet werden können. Es kann auch erwähnenswert sein, dass ausgereifte Parser-Bibliotheken eine wichtige Funktion enthalten: die Fehlerberichterstattung. Eine anständige Fehlerbehebung, wenn das Parsen fehlgeschlagen ist, ist nicht trivial. Als Beweis rufe ich PHP auf
parse error, unexpected T_PAAMAYIM_NEKUDOTAYIM
: DSein Ansatz manipuliert Zeichenfolgen, behält den Status explizit bei und hebt die rohe Eingabe manuell in die analysierte Eingabe auf. Sie müssen alles selbst schreiben, einschließlich der Fehlerberichterstattung. Und wenn etwas schief geht, bist du total verloren.
Die Ironie besteht darin, dass die Richtigkeit eines mit Ihrem Ansatz geschriebenen Parsers relativ leicht bewiesen werden kann. In seinem Fall ist es fast unmöglich.
Ihr Ansatz ist der einfachere. Es steht ihm nur entgegen, seinen Horizont ein wenig zu erweitern. Das Ergebnis seiner Herangehensweise wird immer verworren sein, egal wie weit Ihr Horizont reicht.
Um ehrlich zu sein, hört es sich für mich so an, als sei der Typ nur ein ignoranter Dummkopf, der unter dem leidet Blub-Syndrom leidet und arrogant genug ist, um anzunehmen, dass Sie sich irren und Sie anschreien , wenn er Sie nicht versteht.
Am Ende stellt sich jedoch die Frage: Wer muss es warten? Wenn du es bist, dann ist es dein Anruf, egal was jemand sagt. Wenn er es sein wird, gibt es nur zwei Möglichkeiten: Finden Sie einen Weg, die Parser-Bibliothek zu verstehen, oder schreiben Sie einen imperativen Parser für ihn. Ich schlage vor, Sie generieren es aus Ihrer Parser-Struktur: D
quelle
Eine Parsing-Ausdrucksgrammatik (wie der Packrat-Parser-Ansatz) oder ein Parser-Kombinator erfinden das Parsing nicht neu. Dies sind gut etablierte Techniken in der funktionalen Programmierwelt, und in den richtigen Händen kann es besser lesbar sein als die Alternativen. Ich habe vor ein paar Jahren eine ziemlich überzeugende Demonstration von PEG in C # gesehen, die es tatsächlich zu meinem ersten Mittel für relativ einfache Grammatiken gemacht hätte.
Wenn Sie eine elegante Lösung mit Parser-Kombinatoren oder einer PEG haben, sollte dies ein relativ einfacher Verkauf sein: Sie ist ziemlich erweiterbar, normalerweise relativ einfach zu lesen, sobald Sie Ihre Angst vor funktionaler Programmierung überwunden haben, und manchmal einfacher zu lesen als ein typischer Parser-Generator Das Angebot an Werkzeugen hängt jedoch stark von der Grammatik und der Erfahrung ab, die Sie mit beiden Werkzeugen haben. Es ist auch ziemlich einfach, Tests zu schreiben. Natürlich gibt es einige Grammatik-Ambiguitäten, die im schlimmsten Fall zu einer ziemlich schlechten Parsing-Leistung führen können (oder zu einem hohen Speicherverbrauch mit Packrat), aber der Durchschnittsfall ist ziemlich anständig und einige Grammatik-Ambiguitäten werden mit PEG besser gehandhabt als mit LALR, wie Ich erinnere mich.
Die Verwendung von Split und eines Stacks funktioniert mit einigen einfacheren Grammatiken als eine PEG oder kann diese unterstützen, aber es ist sehr wahrscheinlich, dass Sie mit der Zeit die rekursive Abstammung schlecht neu erfinden oder dass Sie eine Reihe von Verhaltensweisen haben, die Sie mit Bandbändern beschreiben. Hilfe bei der Einreichung auf Kosten von extrem unstrukturiertem Code. Wenn Sie nur einfache Tokenisierungsregeln haben, ist dies wahrscheinlich nicht so schlimm, aber wenn Sie die Komplexität erhöhen, ist dies wahrscheinlich die am wenigsten wartbare Lösung. Ich würde stattdessen nach einem Parser-Generator greifen.
Persönlich würde meine erste Neigung, wenn ich ein DSL erstellen muss, darin bestehen, etwas wie Boo (.Net) oder Groovy (JVM) zu verwenden, da ich die gesamte Stärke einer vorhandenen Programmiersprache und eine unglaubliche Anpassbarkeit durch das Erstellen von Makros und einfache Anpassungen erhalte auf die Compiler-Pipeline, ohne die mühsamen Dinge implementieren zu müssen, die ich tun würde, wenn ich von Null anfangen würde (Schleifen, Variablen, Objektmodell usw.). Wenn ich in einem Geschäft Ruby- oder Lisp-Entwicklung betreiben würde, würde ich nur die dort sinnvollen Redewendungen verwenden (Metaprogrammierung usw.)
Aber ich vermute, dass es bei Ihrem eigentlichen Problem entweder um Kultur oder um Ego geht. Sind Sie sicher, dass Ihr Kollege nicht genauso ausgeflippt wäre, wenn Sie Antlr oder Flex / Bison verwendet hätten? Ich vermute, dass das "Streiten" für Ihre Lösung ein verlorener Kampf sein könnte. Möglicherweise müssen Sie mehr Zeit für einen sanfteren Ansatz aufwenden, der konsensbildende Techniken verwendet, anstatt sich an Ihre lokale Verwaltungsbehörde zu wenden. Das Programmieren von Paaren und das Zeigen, wie schnell Sie Anpassungen an der Grammatik vornehmen können, ohne die Wartbarkeit zu beeinträchtigen, und das Durchführen eines Brownbags, um die Technik, ihre Geschichte usw. zu erläutern, gehen möglicherweise über 10 Aufzählungspunkte und ein "unhöfliches Q & A" hinaus Konfrontationstreffen.
quelle
Ich kenne mich nicht mit Parsing-Algorithmen und dergleichen aus, aber ich denke, der Beweis für den Pudding ist das Essen. Wenn alles andere fehlschlägt, können Sie ihm anbieten, den Parser auf seine Weise zu implementieren. Dann
Damit das Testen wirklich fair ist, möchten Sie möglicherweise, dass beide Lösungen dieselbe API implementieren und ein gemeinsames Testbed (oder ein von Ihnen beiden bekanntes Unit-Testing-Framework) verwenden. Sie könnten beide eine beliebige Anzahl und Art von Funktionstestfällen schreiben und sicherstellen, dass seine eigene Lösung alle diese Anforderungen erfüllt. Und natürlich sollte im Idealfall keiner von Ihnen vor Ablauf der Frist Zugriff auf die Implementierung des anderen haben. Der entscheidende Test wäre dann, beide Lösungen mit der vom anderen Entwickler entwickelten Testsuite zu testen .
quelle
Sie haben dies so gestellt, als hätten Sie eine technische Frage, aber wie Sie wahrscheinlich bereits wussten, gibt es hier keine technische Frage. Ihr Ansatz ist weit überlegen, etwas auf Charakterebene zu hacken.
Das eigentliche Problem ist, dass Ihr (vermutlich erfahrener) Kollege unsicher ist und sich durch Ihr Wissen bedroht fühlt. Sie werden ihn nicht mit technischen Argumenten überzeugen ; das wird ihn nur defensiver machen. Stattdessen müssen Sie einen Weg finden, um seine Ängste zu lindern. Ich kann nicht viele Vorschläge machen, aber Sie werden versuchen, seine Kenntnis des Legacy-Codes zu würdigen.
Wenn Ihr Manager mit seinen technischen Argumenten einverstanden ist und Ihre Lösung verwirft, müssen Sie sich meines Erachtens nach einer anderen Stelle umsehen. Natürlich wären Sie in einer anspruchsvolleren Organisation wertvoller und wertvoller.
quelle
Ich werde mich kurz fassen:
Das Parsen der Cthulhu-Methode ist schwierig. Das ist das einfachste und überzeugendste Argument dagegen.
Es kann den Trick für einfache Sprachen tun; sagen wir, reguläre Sprachen. Es wird jedoch wahrscheinlich nicht einfacher sein als ein regulärer Ausdruck.
Es kann auch den Trick für ein bisschen komplexere Sprachen tun.
Ich würde mir jedoch einen Cthulhu-Parser für jede Sprache mit Verschachtelung oder nur "signifikantem Status" wünschen - mathematische Ausdrücke oder Ihr Beispiel (verschachtelte Funktionsaufrufe).
Stellen Sie sich vor, was passieren würde, wenn jemand versuchen würde, einen Parser für eine solche (nicht-triviale, kontextfreie) Sprache zu entwickeln. Vorausgesetzt, er ist schlau genug, einen korrekten Parser zu schreiben, würde ich wetten, dass er während des Codierens zuerst die Tokenisierung und dann die Analyse nach rekursiver Herkunft "entdeckt" - in irgendeiner Form.
Danach ist es ganz einfach: "Hey, sieh mal, Sie haben etwas geschrieben, das als rekursiver Abstiegsparser bezeichnet wird. Wissen Sie, dass es wie reguläre Ausdrücke automatisch aus einer einfachen Grammatikbeschreibung generiert werden kann?
Lange Rede, kurzer Sinn:
Das Einzige, was jemanden davon abhalten kann, den zivilisierten Ansatz anzuwenden, ist, dass er ihn nicht kennt.
quelle
Vielleicht ist es auch wichtig, an einer guten DSL-Semantik zu arbeiten (die Syntax ist wichtig, aber auch die Semantik). Wenn Sie mit diesen Themen nicht vertraut sind, empfehle ich Ihnen, einige Bücher zu lesen, z. B. Programmiersprachen Pragmatik (von M.Scott) und Christian Queinnec. Lisp in kleinen Stücken . Cambridge University Press, 1996.
Lesen Sie auch die neuesten Artikel in den DSL-Konferenzen, z. B. DSL2011 .
Das Entwerfen und Implementieren einer domänenspezifischen Sprache ist schwierig (und der größte Teil der Schwierigkeit besteht nicht im Parsen!).
Ich verstehe nicht wirklich, was du meinst, wenn du den Cthulhu-Weg analysierst . Ich nehme an, Sie wollen nur irgendwie bizarr analysieren.
quelle