Skip to content

Latest commit

 

History

History
1688 lines (1319 loc) · 47.9 KB

README.md

File metadata and controls

1688 lines (1319 loc) · 47.9 KB

Guía de estilo para Clojure

Los models a seguir son importantes.
-- Oficial Alex J. Murphy / RoboCop

Esta guía de estilo para Clojure recomienda mejores practicas con el fin de que programadores de Clojure puedan desarrollar código que pueda ser mantenido por otros programadores del lenguaje. Una guía de estilo que refleja correctamente usos reales del lenguaje será usada. Mientras que una guía que trate de imponer un estilo rechazado por aquellos individuos a quién la guía trata de ayudar, está destinada a que nunca se use sin importar que tan buena sea.

Esta guía está separada en varias secciones de reglas relacionadas. He intentado proveer razones fundamentales tras las reglas aquí expuestas. Si es el caso de que la razón es omitida, es porque la he considerado evidente.

Cabe decir que estas reglas no salen de la nada. Las reglas son, en su gran mayoría basadas en mi extensiva carrera como ingeniero de software profesional, comentarios y sugerencias de miembros de la comunidad de Clojure, y varios prestigiosos recursos de programación de Clojure tales como "Clojure Programming" y "The Joy of Clojure".

La guía es todavía un trabajo en progreso ya que algunas secciones aún faltan y otras están incompletas. Existen también unas reglas que se beneficiarían de mejores ejemplos. A su debido tiempo, estos y otros problemas serán solucionados mas por ahora, vale la pena tenerlos en mente.

Ten en cuenta que la comunidad de desarrollo de Clojure también mantiene una lista de estándares de código para librerías.

Tú tienes la opción de generar un archivo PDF o una copia HTML de esta guía usando Pandoc.

Traducciones de esta guía están disponibles en los siguientes idiomas:

Tabla de Contenido

Diseño y organización de código fuente

Casi todos están convencido que todos los estilos, salvo el suyo, es feo e ilegible. Elimina "salvo el suyo" y probablemente estén en lo correcto...
-- Jerry Coffin (acerca de indentación/sangría)

  • Para indentación/sangría usa espacios y no tabuladores. [link]

  • Usa 2 espacios de sangría en los cuerpos de formas con argumentos. Esto incluye todos las formas def, formas especiales y macros que incluyen enlaces locales como loop, let, when, cond, as->, cond->, case, with-*, etc. [link]

    ;; bueno
    (when algo
      (algo-mas))
    
    (with-out-str
      (println "Hola ")
      (println "mundo!"))
    
    ;; malo - cuatro espacios
    (when algo
        (algo-mas))
    
    ;; malo - un solo espacio
    (with-out-str
     (println "Hola")
     (println "mundo!"))
  • Alinea verticalmente argumentos de funciones o macros que abarcan múltiples líneas. [link]

    ;; bueno
    (filter even?
            (range 1 10))
    
    ;; malo
    (filter even?
      (range 1 10))
  • Usa un solo espacio de sangría para argumentos de funciones o macros cuando no aparezcan en la misma línea que el nombre de la función. [link]

    ;; bueno
    (filter
     even?
     (range 1 10))
    
    (or
     uno
     dos
     tres)
    
    ;; malo - sangría de dos espacios
    (filter
      even?
      (range 1 10))
    
    (or
      uno
      dos
      tres)
  • Alinea verticalmente enlaces let y palabras clave "keywords", en mapas. [link]

    ;; bueno
    (let [cosa1 "algo"
          cosa2 "algo más"]
      {:cosa1 cosa1
       :cosa2 cosa2})
    
    ;; malo
    (let [cosa1 "algo"
      cosa2 "algo más"]
      {:cosa1 cosa1
      :cosa2 cosa2})
  • La linea entre el nombre de una función y su vector de argumentos es opcional y puede ser excluida cuando no conlleve documentación. [link]

    ;; bueno
    (defn foo
      [x]
      (bar x))
    
    ;; bueno
    (defn foo [x]
      (bar x))
    
    ;; malo
    (defn foo
      [x] (bar x))
  • Incluye el dispatch-val o valor de envío de un multimétodo en la misma linea que el nombre de la función. [link]

    ;; bueno
    (defmethod foo :bar [x] (baz x))
    
    (defmethod foo :bar
      [x]
      (baz x))
    
    ;; malo
    (defmethod foo
      :bar
      [x]
      (baz x))
    
    (defmethod foo
      :bar [x]
      (baz x))
  • Al agregar documentación, en especial a una función con la forma del ejemplo anterior, asegúrate que la documentación aparezca inmediatamente después del vector de argumentos. De otra forma, éste no será parte de la documentación. [link]

    ;; bueno
    (defn foo
      "cadena de documentación"
      [x]
      (bar x))
    
    ;; malo
    (defn foo [x]
      "cadena de documentación"
      (bar x))
  • La línea entre el vector de argumento de una función y su cuerpo es opcional si el cuerpo es pequeño. [link]

    ;; bueno
    (defn foo [x]
      (bar x))
    
    ;; bueno para una función pequeña
    (defn foo [x] (bar x))
    
    ;; bueno para funciones de varias aridades
    (defn foo
      ([x] (bar x))
      ([x y]
       (if (predicate? x)
         (bar x)
         (baz x))))
    
    ;; malo
    (defn foo
      [x] (if (predicate? x)
            (bar x)
            (baz x)))
  • Agrega sangría para cada aridad de una función. De igual forma, alinea verticalmente sus argumentos. [link]

    ;; bueno
    (defn foo
      "Yo tengo dos aridades."
      ([x]
       (foo x 1))
      ([x y]
       (+ x y)))
    
    ;; malo - sangría innecesaria
    (defn foo
      "I have two arities."
      ([x]
        (foo x 1))
      ([x y]
        (+ x y)))
  • Ordena las aridades de una función de mínimo a máximo número de argumentos. Lo que ocurre frecuentemente en funciones con distintas aridades es que existe un número K de argumentos los cuales completamente especifican el comportamiento de la función. Para el caso de aridades N < K, la función es parcialmente aplicada y para aridades N > K, la función provee un fold sobre la versión con K aridades sobre varargs, o argumentos variables. [link]

    ;; bueno - fácilmente encontramos la aridad que necesitamos
    (defn foo
      "Yo tengo dos aridades."
      ([x]
       (foo x 1))
      ([x y]
       (+ x y)))
    
    ;; regular - todas las aridades están basadas en la función con aridad 2
    (defn foo
      "Yo tengo dos aridades."
      ([x y]
       (+ x y))
      ([x]
       (foo x 1))
      ([x y z & mas]
       (reduce foo (foo x (foo y z)) mas)))
    
    ;; malo - en desorden
    (defn foo
      ([x] 1)
      ([x y z] (foo x (foo y z)))
      ([x y] (+ x y))
      ([w x y z & mas] (reduce foo (foo w (foo x (foo y z))) mas)))
  • Agrega sangría en cada línea de documentación si ésta abarca más de una sola línea [link]

    ;; bueno
    (defn foo
      "Hola. Esto es una documentación
      que abarca varias líneas."
      []
      (bar))
    
    ;; malo
    (defn foo
      "Hola. Esto es una documentación
    que abarca varias líneas."
      []
      (bar))
  • Usa las terminaciones de línea de estilo Unix. (Usuarios de BDS, Solaris, Gnu Linux, y OSX ya las usan por defecto. Usuarios de Windows tendrán que ser más cuidadosos.) [link]

    • Si estas usando Git, es posible que quieras agregar el siguiente cambio de configuración para ayudar a prevenir que terminaciones de línea de estilo Windows se introduzcan al proyecto:
    bash$ git config --global core.autocrlf true
    
  • Si existe texto inmediatamente antes o después de una sección entre paréntesis (...), corchetes [...] o llaves {...}, agrega un espacio antes de la apertura y después de la clausura del mismo. [link]

    ;; bueno
    (foo (bar baz) quux)
    
    ;; malo
    (foo(bar baz)quux)
    (foo ( bar baz ) quux)
  • No agregues comas , entre elementos de una secuencia literal. [link]

    ;; bueno
    [1 2 3]
    (1 2 3)
    
    ;; malo
    [1, 2, 3]
    (1, 2, 3)
  • Considera mejorar la legibilidad de mapas literales a través del uso prudente de comas y líneas. [link]

    ;; bueno
    {:nombre "Bruce Wayne" :identidad-secreta "Batman"}
    
    ;; bueno y discutiblemente más legible
    {:nombre "Bruce Wayne"
     :identidad-secreta "Batman"}
    
    ;; bueno y discutiblemente más compacto
    {:nombre "Bruce Wayne", :identidad-secreta "Batman"}
  • Agrega todos los paréntesis que cierran una expresión en una sola línea. [link]

    ;; bueno
    (when algo
      (algo-mas))
    
    ;; malo. Los paréntesis están en distintas líneas
    (when algo
      (algo-mas)
    )
  • Usa líneas vacías entre formas de nivel superior. [link]

    ;; bueno
    (def x ...)
    
    (defn foo ...)
    
    ;; malo
    (def x ...)
    (defn foo ...)

    Una excepción de esta regla es durante el agrupamiento de definiciones relacionadas tipo def.

    ;; bueno
    (def min-filas 10)
    (def max-filas 20)
    (def min-columnas 15)
    (def max-columnas 30)
  • No agregues líneas vacías en medio de la definición de una función o un macro. Una excepción a esta regla es cuando se desea indicar grupos de construcciones en pares como aquellas encontradas en let y cond. [link]

  • Donde sea factible, evita tener líneas con más de 80 caracteres. [link]

  • Evita tener espacio en blanco al final. [link]

  • Usa un archivo por cada espacio de nombre o namespace. [link]

  • Comienza cada namespace con una forma comprensiva de tipo ns. Si es necesario, ésta debe estar compuesta de líneas para refer, require, e import, en ese orden. [link]

    (ns ejemplos.ns
      (:refer-clojure :exclude [next replace remove])
      (:require [clojure.string :as s :refer [blank?]]
                [clojure.set :as set]
                [clojure.java.shell :as sh])
      (:import java.util.Date
               java.text.SimpleDateFormat
               [java.util.concurrent Executors
                                     LinkedBlockingQueue]))
  • En la forma ns, debes preferir el uso de :require :as en lugar de :require :refer en lugar de :require :refer :all. También debes preferir :require sobre :use, ya que la forma :use es considerada obsoleta. [link]

    ;; bueno
    (ns ejemplos.ns
      (:require [clojure.zip :as zip]))
    
    ;; bueno
    (ns ejemplos.ns
      (:require [clojure.zip :refer [lefts rights]]))
    
    ;; aceptable según lo garantizado
    (ns ejemplos.ns
      (:require [clojure.zip :refer :all]))
    
    ;; malo
    (ns ejemplos.ns
      (:use clojure.zip))
  • Evita el uso de namespaces de un solo segmento. [link]

    ;; bueno
    (ns ejemplos.ns)
    
    ;; malo
    (ns ejemplos)
  • Evita el uso de namespaces demasiado largos, es decir, con más de 5 segmentos. [link]

  • Evita funciones con más de 10 líneas de código. Idealmente, la mayoría de tus funciones deben de tener menos de 5 líneas de código. [link]

  • Evita tener listas de argumentos que requieren más de 3 o 4 argumentos en orden. [link]

  • Evita el uso de referencias directas o adelantadas ya que son muy raramente necesarias. [link]

Sintaxis

  • Evita el uso de funciones como require y refer que alteran tu namespace. Estas funciones son completamente innecesarias fuera de un ambiente REPL. [link]

  • Usa la forma declare para referencias directas o adelantas cuando éstas sean necesarias. [link]

  • Debes preferir funciones de orden superior como map en lugar de loop/recur. [link]

  • Debes preferir pre y post condiciones sobre pruebas o tests dentro del cuerpo de una función. [link]

    ;; bueno
    (defn foo [x]
      {:pre [(pos? x)]}
      (bar x))
    
    ;; malo
    (defn foo [x]
      (if (pos? x)
        (bar x)
        (throw (IllegalArgumentException. "x must be a positive number!")))
  • No definas vars dentro de tus funciones. [link]

    ;; muy malo
    (defn foo []
      (def x 5)
      ...)
  • No ocultes los nombres de clojure.core con nuevas definiciones o enlaces locales. [link]

    ;; malo - necesitamos clojure.core/map para referirnos a la funcion
    (defn foo [map]
      ...)
  • Usa alter-var-root en lugar de def para cambiar el valor de una var. [link]

    ;; bueno
    (def cosa 1) ; valor de cosa es ahora 1
    ; algunos cambios a cosa
    (alter-var-root #'cosa (constantly nil)) ; valor de cosa es ahora nil
    
    ;; malo
    (def cosa 1)
    ; algunos cambios a cosa
    (def cosa nil)
    ; valor de cosa es ahora nil
  • Usa la forma seq como condición de terminación para probar que una secuencia está vacía. [link]

    ;; bueno
    (defn imprimir-seq [s]
      (when (seq s)
        (prn (first s))
        (recur (rest s))))
    
    ;; malo
    (defn imprimir-seq [s]
      (when-not (empty? s)
        (prn (first s))
        (recur (rest s))))
  • Usa vec en lugar de into cuando se necesite convertir una secuencia a un vector. [link]

    ;; bueno
    (vec alguna-seq)
    
    ;; malo
    (into [] alguna-seq)
  • Usa la forma when en lugar de (if ... (do ...). [link]

    ;; bueno
    (when predicado
      (foo)
      (bar))
    
    ;; malo
    (if predicado
      (do
        (foo)
        (bar)))
  • Usa if-let en lugar de let + if. [link]

    ;; bueno
    (if-let [resultado (foo x)]
      (algo-con resultado)
      (algo-mas))
    
    ;; malo
    (let [resultado (foo x)]
      (if resultado
        (algo-con resultado)
        (algo-mas)))
  • Usa when-let en lugar de let + when. [link]

    ;; bueno
    (when-let [resulatado (foo x)]
      (haz-algo-con resulatado)
      (haz-algo-mas-con resulatado))
    
    ;; malo
    (let [resulatado (foo x)]
      (when resulatado
        (haz-algo-con resulatado)
        (haz-algo-mas-con resulatado)))
  • Usa if-not en lugar de (if (not ...) ...). [link]

    ;; bueno
    (if-not predicado
      (foo))
    
    ;; malo
    (if (not predicado)
      (foo))
  • Usa when-not en lugar de (when (not ...) ...). [link]

    ;; bueno
    (when-not pred
      (foo)
      (bar))
    
    ;; malo
    (when (not pred)
      (foo)
      (bar))
  • Usa when-not en lugar de (if-not ... (do ...). [link]

    ;; bueno
    (when-not predicado
      (foo)
      (bar))
    
    ;; malo
    (if-not predicado
      (do
        (foo)
        (bar)))
  • Usa not= en lugar de (not (= ...)). [link]

    ;; bueno
    (not= foo bar)
    
    ;; malo
    (not (= foo bar))
  • Usa printf en lugar de (print (format ...)). [link]

    ;; bueno
    (printf "Hola, %s!\n" nombre)
    
    ;; ok
    (println (format "Hola, %s!" nombre))
  • Al hacer comparaciones, recuerda que la funciones en Clojure como <, >, etc. aceptan un numero variable de argumentos. [link]

    ;; bueno
    (< 5 x 10)
    
    ;; malo
    (and (> x 5) (< x 10))
  • Debes preferir % en lugar de %1 en funciones literales con solo un argumento. [link]

    ;; bueno
    #(Math/round %)
    
    ;; malo
    #(Math/round %1)
  • Debes preferir %1 en lugar de % en funciones literales con más de un argumento. [link]

    ;; bueno
    #(Math/pow %1 %2)
    
    ;; malo
    #(Math/pow % %2)
  • No envuelvas funciones en otras funciones anónimas cuando no sea necesario. [link]

    ;; bueno
    (filter even? (range 1 10))
    
    ;; malo
    (filter #(even? %) (range 1 10))
  • No uses funciones literales si el cuerpo de la función consiste en más de una forma. [link]

    ;; bueno
    (fn [x]
      (println x)
      (* x 2))
    
    ;; malo ya que necesitas usa la forma `do`
    #(do (println %)
         (* % 2))
  • Debes preferir el uso de complement en lugar de una función anónima. [link]

    ;; bueno
    (filter (complement predicado?) coll)
    
    ;; malo
    (filter #(not (predicado? %)) coll)

    Esta regla debe de ser ignorada si el complemento de un predicado existe como otra función como es el caso con even? y odd?

  • Aplica el uso de comp cuando su uso produzca código más simple [link]

    ;; Asumiendo `(:require [clojure.string :as str])`...
    
    ;; bueno
    (map #(str/capitalize (str/trim %)) ["tope " " prueba "])
    
    ;; mejor
    (map (comp str/capitalize str/trim) ["tope " " prueba "])
  • Aplica el uso de partial cuando su uso produzca código más simple [link]

    ;; bueno
    (map #(+ 5 %) (range 1 10))
    
    ;; discutiblemente mejor
    (map (partial + 5) (range 1 10))
  • Debes preferir el uso de threading macros como -> o ->> en vez de tener muchas expresiones una dentro de la otra. [link]

    ;; bueno
    (-> [1 2 3]
        reverse
        (conj 4)
        prn)
    
    ;; no tan bueno
    (prn (conj (reverse [1 2 3])
               4))
    
    ;; bueno
    (->> (range 1 10)
         (filter even?)
         (map (partial * 2)))
    
    ;; no tan bueno
    (map (partial * 2)
         (filter even? (range 1 10)))
  • Usa :else como la última expresión de prueba en cond. [link]

    ;; bueno
    (cond
      (neg? n) "negativo"
      (pos? n) "positivo"
      :else "cero")
    
    ;; malo
    (cond
      (neg? n) "negativo"
      (pos? n) "positivo"
      true "cero")
  • Debes preferir el uso de condp en lugar de cond cuando el predicado y la expresión no cambian. [link]

    ;; bueno
    (cond
      (= x 10) :diez
      (= x 20) :veinte
      (= x 30) :treinta
      :else :desconocido)
    
    ;; much better
    (condp = x
      10 :diez
      20 :veinte
      30 :treinta
      :desconocido)
  • Debes preferir case en lugar de cond o condp cuando las expresiones de prueba son contantes durante el tiempo de compilación. [link]

    ;; bueno
    (cond
      (= x 10) :diez
      (= x 20) :veinte
      (= x 30) :treinta
      :else :dunno)
    
    ;; mejor
    (condp = x
      10 :diez
      20 :veinte
      30 :treinta
      :dunno)
    
    ;; lo mejor
    (case x
      10 :diez
      20 :veinte
      30 :treinta
      :dunno)
  • Usa formas cortas en cond y formas relacionadas. Cuando esto no sea posible, trata de proveer pistas visuales indicando los grupos que son pares a través de comentarios o líneas vacías. [link]

    ;; bueno
    (cond
      (prueba1) (accion1)
      (prueba2) (accion2)
      :else   (accion-por-defecto))
    
    ;; más o menos
    (cond
      ;; prueba caso 1
      (prueba1)
      (funcion-larga-que-require-una-nueva-linea
        (subforma-complicada
          (-> 'que-ocupa multiples-lineas)))
    
      ;; prueba caso 2
      (prueba2)
      (otra-funcion-larga
        (con-otra-subforma
          (-> 'que-ocupa multiples-lineas)))
    
      :else
      (el-caso-de-accion-por-defecto
        (que-tambien-abarca 'multiples
                          lineas)))
  • Usa un conjunto o set como predicado cuando sea apropiado [link]

    ;; bueno
    (remove #{1} [0 1 2 3 4 5])
    
    ;; malo
    (remove #(= % 1) [0 1 2 3 4 5])
    
    ;; bueno
    (count (filter #{\a \e \i \o \u} "un elefante se balanceaba"))
    
    ;; malo
    (count (filter #(or (= % \a)
                        (= % \e)
                        (= % \i)
                        (= % \o)
                        (= % \u))
                   "un elefante se balanceaba"))
  • Usa (inc x) y (dec x) en lugar de (+ x 1) y (- x 1). [link]

  • Usa (pos? x), (neg? x) y (zero? x) en lugar de (> x 0), (< x 0) y (= x 0). [link]

  • Usa list* en lugar de una lista en línea de invocaciones de cons. [link]

    ;; bueno
    (list* 1 2 3 [4 5])
    
    ;; malo
    (cons 1 (cons 2 (cons 3 [4 5])))
  • Usa formas de interoperación con Java que han sido endulzadas sintácticamente [link]

    ;;; Creación de objetos
    ;; bueno
    (java.util.ArrayList. 100)
    
    ;; malo
    (new java.util.ArrayList 100)
    
    ;;; Invocación de método estático
    ;; bueno
    (Math/pow 2 10)
    
    ;; malo
    (. Math pow 2 10)
    
    ;;; invocación de método de instancia
    ;; bueno
    (.substring "hola" 1 3)
    
    ;; malo
    (. "hola" substring 1 3)
    
    ;;; acceso a campo o valor estático
    ;; bueno
    Integer/MAX_VALUE
    
    ;; malo
    (. Integer MAX_VALUE)
    
    ;;; acceso de campo o valor de instancia
    ;; bueno
    (.algunCampo algun-objeto)
    
    ;; malo
    (. algun-objeto algunCampo)
  • Usa una anotación compacta para metadatos para quienes sus valores han de ser booleanos. [link]

    ;; bueno
    (def ^:private a 5)
    
    ;; malo
    (def ^{:private true} a 5)
  • Denota secciones privadas de tu código como tal. [link]

    ;; bueno
    (defn- funcion-privada [] ...)
    
    (def ^:private var-privada ...)
    
    ;; malo
    (defn funcion-privada [] ...) ; no es privada en lo absoluto
    
    (defn ^:private funcion-privada [] ...) ; demasiado verboso
    
    (def var-privada ...) ; no es privada en lo absoluto
  • Para acceder una var privada, por ejemplo, durante tus pruebas, usa la forma @# algun.ns/var [link]

  • Ten cuidado con respecto a exactamente que se le adjuntan metadatos. [link]

    ;; se le adjuntan metadatos a la var referenciada por `a`
    (def ^:private a {})
    (meta a) ;=> nil
    (meta #'a) ;=> {:private true}
    
    ;; se le adjuntan metadatos al valor vacío del hash-map
    (def a ^:private {})
    (meta a) ;=> {:private true}
    (meta #'a) ;=> nil

Nombramiento

Las únicas dificultades reales en la programación son la invalidación de caché y el nombrar las cosas.
-- Phil Karlton

  • Al nombrar tus namespaces, usa las siguientes dos esquemas: [link] organizacion

    • proyecto.modulo
    • organizacion.proyecto.modulo
  • Usa el nombramiento estilo lisp con segmentos compuestos de namespaces como por ejemplo diego.project-euler) [link]

  • Usa el nombramiento estilo lisp para nombres de funciones y variables. [link]

    ;; bueno
    (def alguna-var ...)
    (defn alguna-funcion ...)
    
    ;; malo
    (def algunaVar ...)
    (defn algunafuncion ...)
    (def alguna_funcion ...)
  • Usa el nombramiento estilo CamelCase para protocolos, records, y tipos y asegúrate de mantener en mayúscula siglas como HTTP, RFC, XML. [link]

  • Al nombrar métodos que funcionan como predicados, es decir, aquellos que retornan un valor booleano de true o false, deben de terminar con un signo de interrogación. [link]

    ;; bueno
    (defn palindromo? ...)
    
    ;; malo
    (defn palindromo-p ...) ; Estilo de Common Lisp
    (defn es-palindromo ...) ; Estilo de Java
  • Los nombres de funciones o macros que no son seguras bajo transacciones STM deben de terminar con un signo de admiración. [link]

  • Usa -> al nombrar funciones de conversion. [link]

    ;; bueno
    (defn f->c ...)
    
    ;; no tan bueno
    (defn f-a-c ...)
  • Agrégale asteriscos al principio y al final de los nombres de variables dinámicas. [link]

    ;; bueno
    (def ^:dynamic *a* 10)
    
    ;; malo
    (def ^:dynamic a 10)
  • Recuerda que no es necesario usar ninguna anotación especial para denotar constantes. Esto es debido a que se asume que todo es constante a menos que se especifique lo contrario. [link]

  • Usa un guión bajo_ para denotar argumentos o variables de deconstrucción cuyo valor no es usado por el resto del cuerpo. [link]

    ;; bueno
    (let [[a b _ c] [1 2 3 4]]
      (println a b c))
    
    (dotimes [_ 3]
      (println "Hello!"))
    
    ;; malo
    (let [[a b c d] [1 2 3 4]]
      (println a b d))
    
    (dotimes [i 3]
      (println "Hola!"))
  • Sigue el ejemplo del namespace clojure.core usando nombres como pred y coll.

    • en funciones, usa
      • f, g, h - para representar argumentos
      • n - para representar una cantidad
      • index, i - para representar un índice numeral
      • x, y - para números
      • xs - para secuencias
      • m - para mapas
      • s - para strings
      • re - para expresiones regulares o regex
      • coll - para colecciones
      • pred - para predicados
      • & more - para representar argumentos variados
      • xf - para una xforma o un transducer
    • en macros, usa
      • expr - para expresiones
      • body - para el cuerpo del macro
      • binding - para el vector de enlace en el macro

Colecciones

Es mejor tener 100 funciones que operan una sola estructura de datos que tener 10 funciones que operan en 10 estructuras de datos
-- Alan J. Perlis

  • A menos de que su estructura sea necesaria, evita usar listas para uso genérico. [link]

  • Debes preferir el uso de keywords como claves o keys. [link]

    ;; bueno
    {:nombre "Daniel" :edad 30}
    
    ;; malo
    {"nombre" "Daniel" "edad" 30}
  • Debes preferir el uso del sintaxis literal para colecciones en vez de sus respectivos constructores. Sin embargo, al definir conjuntos o sets, solo usa el sintaxis literal si los valores son constantes al momento de compilación. [link]

    ;; bueno
    [1 2 3]
    #{1 2 3}
    (hash-set (func1) (func2)) ; Valores determinados en compilación
    
    ;; malo
    (vector 1 2 3)
    (hash-set 1 2 3)
    #{(func1) (func2)} ; Lanzará una excepción si (func1) = (func2)
  • En lo posible, evita tener que acceder miembros de una colección usando su índice. [link]

  • Debes preferir el uso de keywords como funciones al acceder valores de mapas. [link]

    (def m {:nombre "Daniel" :edad 30})
    
    ;; bueno
    (:nombre m)
    
    ;; más verboso de lo necesario
    (get m :nombre)
    
    ;; malo - suceptible a un NullPointerException
    (m :nombre)
  • Utiliza el hecho de que la mayoría de colecciones actúan como funciones de sus miembros. [link]

    ;; bueno
    (filter #{\a \e \o \i \u} "esto es una prueba")
    
    ;; malo - demasiado feo para ser compartido
  • Utiliza el hecho de que keywords pueden actuar como funciones de una colección [link]

    ((juxt :a :b) {:a "ala" :b "bala"})
  • Evita hacer tus colecciones "transient", a menos que se trate de pociones de tu código que necesitan muy alto rendimiento. [link]

  • Evita el uso de colecciones de Java. [link]

  • Evita el uso de arrays de Java, excepto en escenarios de interoperabilidad o código de alto rendimiento que trata con tipos de Java primitivos. [link]

Mutación

Refs

Considera envolver todas tus usos de input/output, o I/O con el macro io! para evitar malas sorpresas si accidentalmente llamas ese tipo de código en una transacción. Esto es debido a que las transacciones pueden ser llamadas varias veces y, por ende, el código que contienen debe ser puro (sin efectos externos). [link]

  • Evita el uso de ref-set en lo posible. [link]

    (def r (ref 0))
    
    ;; bueno
    (dosync (alter r + 5))
    
    ;; malo
    (dosync (ref-set r 5))
  • Intenta de mantener al mínimo el tamaño de tus transacciones, es decir, la cantidad de trabajo encapsulada en ellas. [link]

  • Evita tener transacciones largas y cortas interactuando con un mismo Ref. [link]

Agents

  • Usa send solamente para acciones limitadas por la CPU y que no bloquean hilos o threads, en especial el de I/O. [link]

  • Usa send'off para acciones que pueden, de alguna forma, bloquear un thread. [link]

Átomos

  • Evita actualizar átomos dentro de transacciones STM. [link]

  • En lo posible, debes preferir el uso de swap! en lugar de reset!. [link]

    (def a (atom 0))
    
    ;; bueno
    (swap! a + 5)
    
    ;; no tan bueno
    (reset! a 5)

Strings o cuerdas

  • Debes preferir el uso de funciones de manipulaciones provenientes de clojure.string en lugar de interoperar con Java o escribir tus propias versiones. [link]

    ;; bueno
    (clojure.string/upper-case "daniel")
    
    ;; malo
    (.toUpperCase "daniel")

Excepciones

  • Si es necesario lanzar una excepción, asegúrate de usar tipos de excepciones ya existentes. Por ejemplo, java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException, java.lang.IllegalStateException, java.io.IOException [link]

  • Debes preferir el uso de with-open en lugar de finally. [link]

Macros

  • No escribas un macro si una función es suficiente. [link]

  • Construye un ejemplo de uso para un macro antes de escribir el macro en sí. [link]

  • En lo posible, divide macros complicados en funciones pequeñas. [link]

  • Un macro debe proveer azúcar sintáctico, es decir, debe simplificar el formación sintáctica de la expresión. El núcleo del macro deben de ser funciones simples ya que esto incrementa su capacidad de compilación. [link]

  • Debes preferir formas con sintaxis citado en lugar de la construcción manual de listas. [link]

Comentarios

Buen código es su mejor propia documentación. Al prepararte para agregar un comentario, debes preguntarte, "¿Cómo puedo mejorar mi código de tal forma que este comentario no sea necesario?" Mejora tu código primero y después documéntalo para hacerlo aun más claro.
-- Steve McConnell

  • Haz que tu código sea tan auto-documentado como sea posible. [link]

  • Escribe comentarios de encabezadura con al menos 4 puntos y comas. [link]

  • Escribe comentarios principales con 3 puntos y comas. [link]

  • Escribe comentarios acerca de un fragmento de código con dos puntos y comas. Además, asegúrate de escribir el comentario antes del fragmento que al que hace referencia y alinéalo a él. [link]

  • Escribe comentarios marginales con un punto y coma. [link]

  • Asegúrate de siempre tener al menos un espacio entre un punto y coma y el texto que lo prosigue. [link]

    ;;;; Título
    
    ;;; Esta sección de código tiene estas importantes implicaciones:
    ;;;   1. Foo.
    ;;;   2. Bar.
    ;;;   3. Baz.
    
    (defn funcion [argumento]
      ;; Si zob, entonces veeblefitz.
      (quux zot
            mumble             ; Zibblefrotz.
            frotz))
  • Comentarios compuestos de más de una palabra deben empezar con mayúscula y usar puntuación. También separa oraciones con un un espacio. [link]

  • Evita el uso de comentarios innecesarios. [link]

    ;; malo
    (inc contador) ; Incrementa el valor del contador por uno
  • Mantén vigentes tus comentarios. Un comentario desactualizado es peor que no tener un comentario. [link]

  • Debes preferir el uso de macro lector #_ en lugar de un comentario regular al comentar una forma en su totalidad. [link]

    ;; bueno
    (+ foo #_(bar x) delta)
    
    ;; malo
    (+ foo
       ;; (bar x)
       delta)

Buen código es como un buen chiste... no necesita explicación.
-- Russ Olsen

  • Evita escribir comentarios para documentar código malo. Mejora tu código con el fin de hacer que se auto documente. ("Hacer, o no hacer. No hay intentar." --Yoda) [link]

Anotaciones de comentarios

  • Anotaciones debe de ser escritas en la línea inmediatamente antes del código al que referencia. [link]

  • La anotación debe estar compuesta por el keyword de anotacion seguido por dos puntos y un espacio, después por una nota describiendo el problema. [link]

  • Si la descripción de un problema requiere el uso de múltiples líneas, todas las líneas deben seguir la misma indentación o sangría de la primera línea. [link]

  • Marca tu anotación con fecha e iniciales para que su relevancia sea fácilmente verificada. [link]

    (defn alguna-funcion
      []
      ;; FIXME: Esto ha causado problemas ocasionalmente en v1.2.3. 
      ;;        Es posible que sea relacionado con la actualizacion
      ;;        de BarUtilidad (xz 13-1-31)
      (baz))
  • En casos donde el problema es tan evidente que cualquier tipo de documentación es redundante, anotaciones sin notas pueden ser hechas después del código al que se hace referencia. Recuerda que este uso debe ser la excepción y no la regla. [link]

    (defn bar
      []
      (sleep 100)) ; OPTIMIZE
  • Usa TODO para marcar funcionalidad que debe ser agregada después. [link]

  • Usa FIXME para marcar código fallido que debe ser arreglado. [link]

  • Usa OPTIMIZE para marcar código lento o ineficiente que puede causar problemas de rendimiento [link]

  • Usa HACK para anotar usos cuestionables de las practicas usadas que deben ser corregidas. [link]

  • Usa REVIEW para marcar código que debe ser revisado para confirmar que esta trabajando correctamente. Por ejemplo: REVIEW: ¿Estamos segurlos que el cliente untiliza X en este momento? [link]

  • Usa otros keywords de anotacion cuando sea apropriado y asegúrate de documentarlos en el README o archivo equivalente de tu proyecto. [link]

Existencial

  • Escribe código de forma funcional usando mutación solo cuando tenga sentido. [link]

  • Sé consistente con el uso de esta guía. [link]

  • Usa el sentido común. [link]

Herramientas

Aquí presentamos algunas herramientas creadas por la comunidad de Clojure que pueden ayudarte en tu camino para escribir Clojure idiomático.

  • Slamhound es una herramienta que automáticamente genera declaraciones de ns para tu código existente.

  • kibit es un analizador estático de código para Clojure que usa [core.logic](https://github.com/clojure/core.logic para encontrar patrones de código para los cuales pueden existir formas, funciones, o macros más idiomáticos.

Pruebas

  • Guarda tus pruebas en un directorio separado. Normalmente, bajo test/tuproyecto/ en lugar de src/tuproyecto/. Tu herramienta de construcción es responsable de hacer que estos archivos estén disponibles donde sean necesarios. La mayoría de modelos hacen esto automáticamente. [link]

  • Nombre tu ns tuproyecto.algo-test, un archivo que usualmente es nombrado test/tuproyecto/algo_test.clj (o .cljc, cljs). [link]

  • Al usar clojure.test, define tus prubeas con deftesty nombralesalgo-test`. Por ejemplo

    ;; bueno
    (deftest algo-test ...)
    
    ;; malo
    (deftest algo-tests ...)
    (deftest test-algo ...)
    (deftest algo ...)

    [link]

Organización de librerías

  • Si estas publicando librerías con el fin de que sean usadas por otros, asegúrate de seguir la Central Repository guidelines para escoger tu groupId and artifactId. Esto ayuda prevenir conflictos de nombre y facilita cualquier tipo de uso. Un buen ejemplo es el de Component. [link]

  • Evita incluir dependencias innecesarias. Por ejemplo, es preferible copiarle una función de 3 líneas a tu proyecto en lugar de una dependencia que conlleva cientos de vars que no piensas usar. [link]

  • Separa la funcionalidad central de tu librería y sus puntos de integración en distintos artefactos. De esta forma, usuarios pueden consumir tu librería sin tener que estar atados a preferencias de herramientas externas. Por ejemplo, Component provee functionalidad central, y reloaded provee integración con Leiningen. [link]

Contribuciones

Nada escrito en esta guía está escrito en piedra. Mi deseo es trabajar junto con todos aquellos interesados en el estilo de código Clojure con el fin de poder construir un recurso que sea beneficioso para la comunidad entera de Clojure.

No tengas miedo de abrir tickets o hacer pull requests con mejorías. De antemano, muchas gracias por tu ayuda!

Es posible contribuir a la guía de estilo financialmente a través de gittip.

Contribuye a través de Gittip

Licencia

Creative Commons License Este trabajo está licenciado bajo la licencia Creative Commons Attribution 3.0 Unported License

Difunde la palabra

Una guía de estilo escrita por la comunidad es de poco uso si la misma comunidad no sabe de su existencia. Por ende, comparte la guía con tus amigos y colegas. Cada comentario, sugerencia u opinión hace que esta guía sea solo un tanto mejor. Y queremos tener la mejor guía posible, cierto?

Salud, Bozhidar