Wie schreibe ich lesbaren Clojure-Code?

13

Ich bin neu bei Clojure. Ich kann den Code, den ich schreibe, verstehen, aber es wird zu schwierig, ihn später zu verstehen.
Es wird schwierig, Klammern zuzuordnen.

Welche allgemeinen Konventionen sind in Bezug auf Namenskonventionen und Einrückungen in verschiedenen Situationen zu beachten?

Zum Beispiel habe ich ein Beispiel für eine De-Strukturierung geschrieben, um es zu verstehen, aber es sieht beim zweiten Mal völlig unlesbar aus.

(defn f [{x :x y :y z :z [a b c] :coll}] (print x " " y  " " z " " a " " b " " c)) 

Ist es im Falle einer De-Strukturierung besser, dies direkt auf der Parameterebene zu tun oder ein Let-Formular zu starten und dort fortzufahren?

Amogh Talpallikar
quelle
3
Bei Stack Overflow gibt es eine gute Antwort zur Lesbarkeit. Sie können es überprüfen. stackoverflow.com/a/1894891/1969106
yfklon
2
Das Schreiben von lesbarem Lisp-Code ist im Allgemeinen schwierig. Sie haben das Backronym "Lost In Superfluous Parenthesis" aus einem bestimmten Grund erfunden.
Mason Wheeler

Antworten:

23

Regeln der Namensgebung

  • für Funktionen in Kleinbuchstaben bleiben
  • Verwenden Sie -für die Silbentrennung (was in anderen Sprachen ein Unterstrich oder ein Kamelfall wäre).

    (defn add-one [i] (inc i))

  • Prädikate (dh Funktionen, die true oder false zurückgeben) enden mit ? Beispielen:odd? even? nil? empty?

  • Zustandsänderungsverfahren enden in !. Erinnerst du dich set!richtig? oderswap!

  • Wählen Sie je nach Reichweite kurze variable Namenslängen. Das heißt, wenn Sie eine wirklich kleine Hilfsvariable haben, können Sie oft nur einen einbuchstabigen Namen verwenden. (map (fn [[k v]] (inc v)) {:test 4 :blub 5})Wählen Sie nach Bedarf längere Variablennamen, insbesondere wenn sie für viele Codezeilen verwendet werden und Sie ihren Zweck nicht sofort erraten können. (meine Meinung).

    Ich habe das Gefühl, dass viele Clojure-Programmierer eher generische und kurze Namen verwenden. Aber das ist natürlich keine objektive Beobachtung. Der Punkt ist, dass viele Clojure-Funktionen eigentlich ziemlich allgemein sind.

Lambda funktioniert

  • Sie können tatsächlich Lambda-Funktionen benennen. Dies ist praktisch für das Debuggen und Profiling (meine Erfahrung hier ist mit ClojureScript).

    (fn square-em [[k v]] {k (* v v)})

  • Verwenden Sie Inline-Lambda-Funktionen #()so bequem wie möglich

Leerzeichen

  • Es sollte keine reinen Parens-Zeilen geben. Dh schließen Sie sofort die Klammern. Denken Sie daran, Parens sind für Editor und Compiler da, Einrückungen sind für Sie.

  • Funktionsparameterlisten werden in eine neue Zeile eingefügt

   (defn cons
     [ab]
     (Liste ab))

Dies ist sinnvoll, wenn Sie über die Dokumentzeichenfolgen nachdenken. Sie liegen zwischen dem Funktionsnamen und den Parametern. Die folgende Dokumentzeichenfolge ist wahrscheinlich nicht die klügste;)

   (defn cons
     "Dinge verbinden"
     [ab]
     (Liste ab))
  • Gepaart Daten können so lange durch eine neue Zeile getrennt werden, wie Sie die Paarung behalten
  (defn f 
    [{x: x 
      y: y 
      z: z  
      [abc]: coll}] 
    (drucke x y z a b c)) 

(Sie können auch eingeben, ,wie Sie möchten, aber dies fühlt sich nicht sauber an).

  • Verwenden Sie zum Einrücken einen ausreichend guten Editor. Vor Jahren war dies Emacs für die Lisp-Bearbeitung, heute ist Vim auch großartig. Typische Clojure-IDEs sollten diese Funktionalität ebenfalls bereitstellen. Verwenden Sie einfach keinen zufälligen Texteditor.

    In vim im Befehlsmodus können Sie den =Befehl zum korrekten Einrücken verwenden.

  • Wenn der Befehl zu lang wird (verschachtelt usw.), können Sie nach dem ersten Argument eine neue Zeile einfügen. Der folgende Code ist jetzt ziemlich sinnlos, zeigt jedoch, wie Sie Ausdrücke gruppieren und einrücken können:

(+ (if-let [age (: personal-age coll)]
     (wenn (> 18 Jahre)
       Alter
       0))
   (count (range (- 3 b)
                 (+ reduzieren 
                         (Bereich b 10)))))

Gute Einrückung bedeutet, dass Sie keine Klammern zählen müssen. Die Klammern sind für den Computer (um den Quellcode zu interpretieren und einzurücken). Einrückung ist für Ihr leichtes Verständnis.

Funktionen höherer Ordnung im Vergleich zu forund doseqFormen

Aus einem Scheme-Hintergrund kommend war ich ziemlich stolz darauf, mapLambda-Funktionen usw. verstanden zu haben . So etwas habe ich ziemlich oft geschrieben

(map (fn [[k x]] (+ x (k data))) {:a 10 :b 20 :c 30})

Das ist ziemlich schwer zu lesen. Die forForm ist viel schöner:

(for [[k x] {:a 10 :b 20 :c30}]
  (+ x (k data)))

`map hat viele verwendungsmöglichkeiten und ist sehr schön, wenn man benannte funktionen benutzt. Dh

(map inc [12 30 10]

(map count [[10 20 23] [1 2 3 4 5] (range 5)])

Verwenden Sie Threading-Makros

Verwenden Sie das Threading - Makros ->und ->>sowie , dotowenn anwendbar.

Der Punkt ist, dass Threading-Makros den Quellcode linearer erscheinen lassen als die Funktionskomposition. Der folgende Code ist ohne das Threading-Makro ziemlich unlesbar:

   (f (g (h 3) 10) [10 3 2 3])

vergleichen mit

   (-> 
     (h 3)
     (g 10)
     (f [10 3 2 3]))

Durch die Verwendung des Threading-Makros kann normalerweise vermieden werden, temporäre Variablen einzufügen, die nur einmal verwendet werden.

Andere Dinge

  • Verwenden Sie Docstrings
  • funktionen kurz halten
  • Lesen Sie anderen Clojure-Code
wirrbel
quelle
Diese Funktion mit De-Strukturierung sieht mit Einzug schön aus!
Amogh Talpallikar
+1 für Funktionen kurz halten. Viele kleine Funktionen sind viel selbstdokumentierender
Daniel Gratzer
1
Ich bin absolut anderer Meinung, dass es eine gute Idee ist, kurze Variablennamen zu verwenden, auch in "kurzreichenden" Funktionen. Gute Variablennamen sind für die Lesbarkeit von entscheidender Bedeutung und kosten nichts als Tastenanschläge. Dies ist eines der Dinge, die mich an der Clojure-Community am meisten stören. Es gibt viele Leute mit einem fast feindlichen Widerstand gegen beschreibende Variablennamen. Der Clojure-Kern ist mit 1-Buchstaben-Variablennamen für Funktionsargumente übersät, was das Erlernen der Sprache (z. B. beim Laufen docoder sourcein einer REPL) erheblich erschwert . Ende des Geschwätzes, für eine ansonsten ausgezeichnete Antwort
Nathan Wallace
@ NathanWallace In gewisser Weise stimme ich Ihnen zu, aber in einigen Aspekten tue ich es nicht. Lange Namen neigen manchmal dazu, Funktionen überspezifisch zu machen. So können Sie feststellen , dass einige allgemeine Filteroperation in der Tat allgemein ist, während , wenn das Argument war applesstatt xs, man dachte , es war spezifisch für Äpfel. Dann würde ich auch Funktionsargumentnamen als weiter reichend betrachten als zum Beispiel eine for-Schleifenvariable. Wenn es sein muss, können Sie sie länger haben. Als letzter Gedanke: Ich überlasse Ihnen "Name Code nicht Werte" concatenative.org/wiki/view/Concatenative%20language/…
wirrbel
Ich könnte einen Absatz über so etwas wie private oder öffentliche Schnittstellen hinzufügen. Besonders in Bezug auf Bibliotheken. Es ist ein Aspekt der Codequalität, über den nicht genug gesprochen wird, und ich habe Tonnen davon gelernt, seit ich diese Antwort geschrieben habe.
Wirrbel