Como escrever código Clojure legível?

13

Eu sou novo no Clojure. Eu posso entender o código que escrevo, mas fica muito difícil entendê-lo mais tarde.
Torna-se difícil combinar parênteses.

Quais são as convenções genéricas a seguir em relação às convenções de nomenclatura e recuo em várias situações?

Por exemplo, escrevi um exemplo de exemplo de desestruturação para entender, mas parece completamente ilegível na segunda vez.

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

Em caso de desestruturação, é melhor fazê-lo diretamente no nível do parâmetro ou iniciar um formulário let e continuar lá?

Amogh Talpallikar
fonte
3
Há uma boa resposta sobre a legibilidade no Stack Overflow. Você pode conferir. stackoverflow.com/a/1894891/1969106
yfklon:
2
Escrever código Lisp legível é difícil em geral. Eles inventaram o sobrenome "Lost In Supfluous Parenthesis" por um motivo.
Mason Wheeler

Respostas:

23

Convenções de nomenclatura

  • fique em minúsculas para funções
  • use -para hifenização (o que seria sublinhado ou camelo em outros idiomas).

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

  • Predicados (ou seja, funções retornando verdadeiro ou falso) terminam com ? Exemplos:odd? even? nil? empty?

  • Os procedimentos de mudança de estado terminam em !. Você se lembra set!né? ouswap!

  • Escolha comprimentos curtos de nomes de variáveis, dependendo do alcance deles. Isso significa que, se você tiver uma variável auxiliar muito pequena, poderá usar apenas um nome de uma letra. (map (fn [[k v]] (inc v)) {:test 4 :blub 5})escolha nomes de variáveis ​​mais longos, conforme necessário, especialmente se eles forem usados ​​para muitas linhas de código e você não puder adivinhar imediatamente o seu objetivo. (minha opinião).

    Eu sinto que muitos programadores de clojure tendem a usar nomes genéricos e abreviados. Mas é claro que isso não é realmente uma observação objetiva. O ponto é que muitas funções de clojure são realmente bastante genéricas.

Funções Lambda

  • Você pode realmente nomear funções lambda. Isso é conveniente para depuração e criação de perfil (minha experiência aqui é com o ClojureScript).

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

  • Use funções lambda em linha #()como conveniente

Espaço em branco

  • Não deve haver linhas apenas parênteses. Ou seja, feche os parênteses imediatamente. Lembre-se de que existem parênteses para editor e compilador, o recuo é para você.

  • As listas de parâmetros de função entram em uma nova linha

   (definir contras
     [ab]
     (lista ab))

Isso faz sentido se você pensar nas seqüências de documentos. Eles estão entre o nome da função e os parâmetros. A seguinte sequência de documentos provavelmente não é a mais sábia;)

   (definir contras
     "Emparelhando as coisas"
     [ab]
     (lista ab))
  • Os dados emparelhados podem ser separados por uma nova linha, desde que você mantenha o emparelhamento
  (defn f 
    [{x: x 
      y: y 
      z: z  
      [abc]: coll}] 
    (imprima x "" y "" z "" a "" b "" c)) 

(Você também pode entrar ,como quiser, mas isso não parece nada comum).

  • Para indentação, use um editor suficientemente bom. Anos atrás, este foi o emacs para edição do lisp, o vim também é ótimo hoje. IDEs de clojura típicos também devem fornecer essa funcionalidade. Só não use um editor de texto aleatório.

    No vim no modo de comando, você pode usar o =comando para recuar adequadamente.

  • Se o comando ficar muito longo (aninhado etc.), você poderá inserir uma nova linha após o primeiro argumento. Agora, o código a seguir não faz sentido, mas ilustra como você pode agrupar e recuar expressões:

(+ (if-let [idade (: colagem de idade pessoal)]
     (se (> 18 anos)
       era
       0))
   (contagem (intervalo (- 3 b)
                 (reduzir + 
                         (intervalo b 10)))))

Um bom recuo significa que você não precisa contar colchetes. Os colchetes são para o computador (para interpretar o código fonte e recuá-lo). Recuo é para sua fácil compreensão.

Ordem superior funções contra fore doseqformas

Vindo de um plano de fundo do Scheme, fiquei bastante orgulhoso de ter entendido mape funções lambda, etc. Com muita frequência, eu escrevia algo assim

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

Isso é muito difícil de ler. O forformulário é bem melhor:

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

`map tem muitos usos e é muito bom se você estiver usando funções nomeadas. Ou seja,

(map inc [12 30 10]

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

Usar macros de encadeamento

Use as macros de encadeamento ->e ->>, dotoquando aplicável.

O ponto é que as macros de encadeamento fazem o código fonte parecer mais linear que a composição da função. O seguinte trecho de código é bastante ilegível sem a macro de segmentação:

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

compare com

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

Usando a macro threading, pode-se evitar a introdução de variáveis ​​temporárias que são usadas apenas uma vez.

Outras coisas

  • Use documentos
  • mantenha as funções curtas
  • leia outro código clojure
wirrbel
fonte
Essa função com a desestruturação fica linda com recuo!
Amogh Talpallikar
+1 para manter as funções curtas. Muitas pequenas funções são muito mais auto documentando
Daniel Gratzer
1
I fortemente discordar de que é uma boa idéia usar nomes de variáveis curtos, mesmo em funções "curto alcance". Bons nomes de variáveis ​​são críticos para facilitar a leitura e não custam nada, exceto pressionamentos de tecla. Essa é uma das coisas que mais me incomoda na comunidade Clojure. Há muitas pessoas com uma resistência quase hostil a nomes de variáveis ​​descritivos. O núcleo do Clojure está repleto de nomes de variáveis ​​de uma letra para argumentos de função, e isso torna muito mais difícil o aprendizado do idioma (por exemplo, executando docou sourceem um REPL). Final do discurso, para uma outra forma excelente resposta
Nathan Wallace
@NathanWallace De certa forma, eu concordo com você, mas em alguns aspectos eu não. Nomes longos às vezes tendem a tornar as funções superespecíficas. Portanto, você pode achar que alguma operação geral de filtro é de fato geral, enquanto quando o argumento era em applesvez de xs, você pensava que era específico para maçãs. Então, eu também consideraria os nomes dos argumentos das funções mais abrangentes do que digamos uma variável de loop for. portanto, se necessário, você pode tê-los por mais tempo. Como um último pensamento: Eu vou deixar você com "Código Nome não valoriza" concatenative.org/wiki/view/Concatenative%20language/...
wirrbel
Eu poderia adicionar um parágrafo em algo como interfaces privadas versus públicas. Especialmente em relação às bibliotecas. É um aspecto da qualidade do código que não é discutido o suficiente e eu aprendi muito sobre isso desde que escrevi essa resposta.
wirrbel