Come scrivere il codice Clojure leggibile?


13

Sono nuovo di Clojure. Riesco a capire il codice che scrivo ma diventa troppo difficile capirlo in seguito.
Diventa difficile abbinare le parentesi.

Quali sono le convenzioni generiche da seguire riguardo alle convenzioni di denominazione e al rientro in varie situazioni?

Ad esempio, ho scritto un esempio di destrutturazione per capire, ma sembra completamente illeggibile la seconda volta.

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

In caso di destrutturazione, è meglio farlo direttamente a livello di parametro o avviare un modulo let e poi continuare lì?


3
C'è una buona risposta sulla leggibilità in Stack Overflow. Puoi verificarlo. stackoverflow.com/a/1894891/1969106
yfklon

2
Scrivere codice Lisp leggibile è difficile in generale. Hanno inventato il backronym "Lost In Superfluous Parenthesis" per un motivo.
Mason Wheeler,

Risposte:


23

Convenzioni di denominazione

  • rimanere in minuscolo per le funzioni
  • utilizzare -per la sillabazione (quale sarebbe il trattino basso o il caso del cammello in altre lingue).

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

  • I predicati (ovvero le funzioni che restituiscono vero o falso) terminano con ? esempi:odd? even? nil? empty?

  • Le procedure per il cambio di stato finiscono in !. Ti ricordi set!giusto? oswap!

  • Scegli lunghezze di nomi variabili brevi a seconda della loro portata. Ciò significa che se si dispone di una variabile ausiliaria davvero piccola, spesso è possibile utilizzare semplicemente un nome di una lettera. (map (fn [[k v]] (inc v)) {:test 4 :blub 5})scegli nomi di variabili più lunghi in base alle esigenze, soprattutto se utilizzati per molte righe di codice e non puoi indovinarne immediatamente lo scopo. (la mia opinione).

    Sento che molti programmatori di clojure tendono piuttosto a usare nomi generici e brevi. Ma questa non è ovviamente un'osservazione obiettiva. Il punto è che molte funzioni di clojure sono in realtà piuttosto generiche.

Funzioni lambda

  • Puoi effettivamente nominare le funzioni lambda. Questo è utile per il debug e la profilazione (la mia esperienza qui è con ClojureScript).

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

  • Utilizzare le funzioni lambda in linea nel modo #()più comodo

Lo spazio bianco

  • Non dovrebbero esserci righe solo per parentesi. Vale a dire chiudere subito le parentesi. Ricorda che le parentesi ci sono per editor e compilatore, il rientro è per te.

  • Gli elenchi dei parametri delle funzioni vanno su una nuova riga

   (defn cons
     [AB]
     (elenco ab))

Questo ha senso se pensi alle stringhe di documenti. Sono tra il nome della funzione e i parametri. La seguente stringa doc non è probabilmente la più saggia;)

   (defn cons
     "Abbinamento delle cose"
     [AB]
     (elenco ab))
  • I dati accoppiati possono essere separati da una nuova riga purché si mantenga l'associazione
  (defn f 
    [{x: x 
      y: y 
      z: z  
      [abc]: coll}] 
    (stampa x "" y "" z "" a "" b "" c)) 

(Puoi anche entrare ,come preferisci, ma sembra poco lusinghiero).

  • Per il rientro utilizzare un editor sufficientemente buono. Anni fa questo era emacs per l'editing lisp, anche oggi vim è fantastico. Anche gli IDE di clojure tipici dovrebbero fornire questa funzionalità. Basta non usare un editor di testo casuale.

    In vim in modalità comando è possibile utilizzare il =comando per rientrare correttamente.

  • Se il comando diventa troppo lungo (nidificato, ecc.) È possibile inserire una nuova riga dopo il primo argomento. Ora il seguente codice è piuttosto insensato ma illustra come è possibile raggruppare e indentare le espressioni:

(+ (if-let [age (: personal-age coll)]
     (se (> 18 anni)
       età
       0))
   (conta (intervallo (- 3 b)
                 (riduci + 
                         (intervallo b 10)))))

Un buon rientro significa che non è necessario contare le parentesi. Le parentesi sono per il computer (per interpretare il codice sorgente e per rientrarlo). Il rientro è per la tua facile comprensione.

Funzioni di ordine superiore vs. fore doseqmoduli

Provenendo da un background di Scheme ero piuttosto orgoglioso di aver capito mape funzioni lambda, ecc. Molto spesso, scrivevo qualcosa del genere

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

Questo è abbastanza difficile da leggere. Il formodulo è molto più bello:

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

`map ha molti usi ed è davvero bello se stai usando funzioni con nome. ie

(map inc [12 30 10]

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

Usa le macro di threading

Utilizzare le macro di threading ->e ->>anche dotoquando applicabile.

Il punto è che le macro di threading rendono il codice sorgente più lineare della composizione della funzione. Il seguente pezzo di codice è piuttosto illeggibile senza la macro threading:

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

confrontare con

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

Utilizzando la macro threading, si può in genere evitare di introdurre variabili temporanee che vengono utilizzate una sola volta.

Altre cose

  • Usa dotstrings
  • mantenere le funzioni brevi
  • leggi altro codice clojure

Quella funzione con la destrutturazione sembra bella con il rientro!
Amogh Talpallikar,

+1 per mantenere le funzioni brevi. Molte piccole funzioni sono molto più auto-documentate
Daniel Gratzer,

1
Non sono assolutamente d' accordo sul fatto che sia una buona idea usare nomi di variabili brevi, anche nelle funzioni "di breve durata". I nomi di variabili validi sono fondamentali per la leggibilità e non costano nulla tranne i tasti. Questa è una delle cose che mi preoccupa di più della comunità Clojure. Ci sono molte persone con una resistenza quasi ostile ai nomi delle variabili descrittive. Il core di Clojure è disseminato di nomi di variabili di una lettera per argomenti di funzioni, e ciò rende molto più difficile l'apprendimento della lingua (ad es. In esecuzione doco sourcein un REPL). Fine del rant, per una risposta altrimenti eccellente
Nathan Wallace

@NathanWallace In un certo senso sono d'accordo con te, ma in alcuni aspetti non lo faccio. I nomi lunghi a volte tendono a rendere le funzioni troppo specifiche. Quindi potresti scoprire che alcune operazioni di filtro generali sono in realtà generali, mentre quando l'argomento era applesinvece di xs, pensavi fosse specifico per le mele. Quindi, considererei anche i nomi degli argomenti delle funzioni più vicini rispetto a una variabile for loop. quindi, se necessario, puoi averli più a lungo. Come ultimo pensiero: ti lascio con "Nome codice non valori" concatenative.org/wiki/view/Concatenative%20language/…
wirrbel

Potrei aggiungere un paragrafo su qualcosa come interfacce private vs pubbliche. Soprattutto per quanto riguarda le biblioteche. È un aspetto della qualità del codice di cui non si parla abbastanza e ne ho imparato moltissimo da quando ho scritto quella risposta.
Wirrbel,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.