
Si cree que la coherencia es una parte importante del código de calidad, este artículo es para usted.
Esperando por ti
- Diferentes formas de hacer lo mismo en Go (operaciones equivalentes)
- Factores menos obvios que afectan la uniformidad de su código
- Formas de aumentar la consistencia de su proyecto
La consistencia es lo nuestro
Para comenzar, determinaremos lo que llamamos "consistencia".
Mientras más códigos fuente del programa parezcan haber sido escritos por una persona, más consistentes son.
Vale la pena señalar que, a menudo, incluso la misma persona puede cambiar sus preferencias con el tiempo, pero el problema de la coherencia es realmente grave en grandes proyectos en los que intervienen una gran cantidad de desarrolladores.
A veces, en lugar de la palabra "consistencia", se usa "consistencia". En este artículo, a veces usaré sinónimos contextuales para evitar tautologías frecuentes.
Existen diferentes niveles de consistencia, por ejemplo, podemos distinguir los tres más obvios:
- Consistencia de archivo de origen único
- Consistencia de nivel de paquete (o biblioteca)
- Consistencia a nivel de todo el proyecto (si está controlado por un proveedor)
Cuanto más baja sea la lista, más difícil es mantener la coherencia. Al mismo tiempo, la falta de coherencia a nivel de un archivo de código fuente parece la más repulsiva.
También puede bajar del archivo al nivel de una función o una sola declaración, pero esto, en nuestro caso, ya es demasiado detalle. Hacia el final del artículo, quedará claro por qué.
Operaciones equivalentes en Go
Go no tiene tantas operaciones idénticas con ortografía diferente (diferencia sintáctica), pero aún hay espacio para el desacuerdo. Algunos de los desarrolladores prefieren la opción A
, mientras que los segundos prueban B
Ambas opciones son válidas y tienen sus partidarios. Usar cualquier forma de operación es permisible y no es un error, pero usar más de una forma puede dañar la consistencia del código.
¿Qué piensas, cuál de estas dos formas de crear una porción de longitud 100 es utilizada por la mayoría de los programadores de Go?
La respuesta
Ninguna de las opciones son preferidas. En código real, nunca he visto el uso de ninguno de ellos.
Y use make([]T, 100)
en este caso.
Importación única
Hay dos formas de importar un solo paquete:
Al mismo tiempo, ni gofmt
ni goimports
realizan la conversión de una forma a otra. Lo más probable es que ambas opciones se encuentren en su proyecto.
Marcar un puntero a un valor nulo
Mientras Go tenga una función new
formas new
y alternativas de obtener un puntero a un nuevo objeto, se encontrará con los new(T)
y &T{}
.
Crea una rebanada vacía
Hay al menos dos formas populares de crear un segmento vacío (que no debe confundirse con un segmento nulo):
Crear una tabla hash vacía
Es posible que la separación de crear un segmento vacío y un map
no sea muy lógica, pero no todas las personas que prefieren []T{}
usarán el map[K]V{}
lugar de make(map[K]V)
. En consecuencia, la distinción aquí no es al menos excesiva.
Literales hexadecimales
Escribir tipo 0xFf
, con mayúsculas y minúsculas, no se trata de coherencia. Esto debe ser encontrado por un analizador estático (linter). Cual? Prueba, por ejemplo, gocritic .
Comprobación de rango
En matemáticas (y algunos lenguajes de programación, pero no Go), podría describir el rango como low < x < high
. El código fuente que expresará esta restricción no puede escribirse así. Al mismo tiempo, hay al menos dos formas populares de verificar la entrada en el rango:
Operador y no
¿Sabía que Go tiene el operador binario &^
? Se llama and-not
y realiza la misma operación que &
, aplicada al resultado ^
del operando derecho (segundo).
Realicé una encuesta para asegurarme de que en general puede haber diferentes preferencias. Al final, si la elección a favor de &^
fuera unánime, entonces esto tendría que ser una verificación de interferencia, y no parte de la elección en materia de consistencia del código. Para mi sorpresa, se encontraron partidarios en ambas formas.
Literales de números reales
Hay muchas formas de escribir un material literal, pero una de las características más comunes que pueden romper la consistencia, incluso dentro de una sola función, es el estilo de escritura del todo y la parte del material (acortada o completa).
¿ETIQUETA o etiqueta?
Desafortunadamente, no existen convenciones establecidas para nombrar etiquetas. Todo lo que queda es elegir una de las formas posibles y apegarse a ella.
Snake_case también es posible, pero no he visto tales etiquetas en ninguna parte, excepto en el ensamblador Go. Lo más probable es que no deba atenerse a esta opción.
Especificación de tipo para literal numérico sin tipo
Realizar el paréntesis de cierre de la función llamada
En el caso de llamadas simples que se ajustan a una línea de código, no puede haber problemas con paréntesis. Cuando, por una razón u otra, una llamada de función o método se extiende por varias líneas, aparecen varios grados de libertad, por ejemplo, deberá decidir dónde colocar el corchete para cerrar la lista de argumentos.
Verifique la longitud no cero
Para cadenas, generalmente se usa s != ""
O s == ""
( fuente ).
Ubicación de la etiqueta predeterminada en el interruptor
Hay dos opciones razonables: poner el default
primera o la última etiqueta. Otras opciones, como "en algún lugar en el medio": este es un trabajo para el linter. Si comprueba defaultCaseOrder
desde gocritic
encontrará que será útil llegar a una versión más idiomática, y go-consistent
gocritic
ofrecerá una de las dos opciones posibles que harán que el código sea más uniforme.
Hemos enumerado una lista de operaciones equivalentes arriba.
¿Cómo determinar cuál usar? La respuesta más simple: una que tiene una mayor frecuencia de uso en la parte considerada del proyecto (como un caso especial, en todo el proyecto).
El programa Go -istent analiza los archivos y paquetes especificados, contando la cantidad de uso de una u otra alternativa, ofreciendo reemplazar formas menos frecuentes por otras idiomáticas dentro de la parte analizada del proyecto, aquellas con la frecuencia más alta.
Recuento sencillo
Por el momento, el peso de cada aparición es igual a uno. A veces esto lleva al hecho de que un archivo dicta el estilo del paquete completo solo porque esta operación se usa con más frecuencia en él. Esto es especialmente notable con respecto a operaciones raras, como la creación de un map
vacío.
Hasta qué punto esta es una estrategia óptima aún no está claro. Esta parte del algoritmo no será difícil de finalizar ni permitirá a los usuarios elegir una de las varias propuestas.
Si $(go env GOPATH)/bin
está en la PATH
del sistema, entonces el siguiente comando establecerá go-consistent
:
go get -v github.com/Quasilyte/go-consistent go-consistent --help
Volviendo a los límites de la coherencia, aquí se explica cómo verificar cada uno de ellos:
- Puede verificar la consistencia dentro de un archivo ejecutando
go-consistent
en este archivo - La coherencia dentro de un paquete se calcula al inicio con un argumento de paquete único (o con todos los archivos en ese paquete)
- Calcular la coherencia global requerirá pasar todos los paquetes como argumentos
go-consistent
está diseñado de tal manera que puede dar una respuesta incluso para grandes repositorios, donde descargar todos los paquetes en la memoria al mismo tiempo es bastante difícil (al menos en una máquina personal sin una gran cantidad de RAM).
Otra característica importante es la configuración cero. Ejecutar go-consistent
sin banderas y archivos de configuración es lo que funciona para el 99% de los casos.
Advertencia : la primera ejecución de un proyecto puede generar una gran cantidad de advertencias. Esto no significa que el código esté mal escrito, solo controlar la consistencia a tal nivel micro es bastante difícil y no cuesta mano de obra si el control se realiza exclusivamente de forma manual.
Los nombres inconsistentes de los parámetros de función o variables locales pueden reducir la consistencia del código.
Para la mayoría de los programadores de Go, es obvio que erro
nombre de error menos exitoso que err
. ¿Qué pasa con s
vs str
?
La tarea de verificar la consistencia de los nombres de las variables no se puede resolver utilizando métodos go-consistent
. Es difícil prescindir de un manifiesto de convenciones locales.
go-namecheck define el formato de este manifiesto y permite su validación, lo que facilita el seguimiento de los estándares de denominación de entidades definidos en el proyecto.
Por ejemplo, puede especificar que para los parámetros de funciones de tipo cadena vale la pena usar el identificador s
lugar de str
.
Esta regla se expresa de la siguiente manera:
{"string": {"param": {"str": "s"}}}
string
es una expresión regular que captura el tipo de interésparam
- el alcance de las reglas de reemplazo (alcance). Puede haber varios- El par
"str": "s"
indica un reemplazo de str
a s
. Puede haber varios
En lugar de reemplazar 1 a 1, puede usar una expresión regular que capture más de un identificador. Por ejemplo, aquí hay una regla que requiere reemplazar el prefijo re
en variables de tipo *regexp.Regexp
con el sufijo RE
. En otras palabras, en lugar de reFile
regla requeriría el uso de fileRE
.
{ "regexp\\.Regexp": { "local+global": {"^re[AZ]\\w*$": "use RE suffix instead of re prefix"} } }
Todos los tipos se consideran ignorando los punteros. Se eliminará cualquier nivel de indirección, por lo que no es necesario definir reglas separadas para punteros al tipo y al tipo en sí.
Un archivo que describiría ambas reglas se vería así:
{ "string": { "param": { "str": "s", "strval": "s" }, }, "regexp\\.Regexp": { "local+global": {"^re[AZ]\\w*$": "use RE suffix instead of re prefix"} } }
Se supone que el proyecto comienza con un archivo vacío. Luego, en cierto punto, en una revisión de código, se realiza una solicitud para cambiar el nombre de una variable o campo en la estructura. Una reacción natural puede ser reforzar estos requisitos previamente informales en forma de una regla verificable en el archivo de la convención de nomenclatura. La próxima vez puede encontrar el problema automáticamente.
go-namecheck
instala y se usa de go-namecheck
manera que go-consistent
, excepto por el hecho de que para obtener el resultado correcto no es necesario ejecutar una verificación en todo el conjunto de paquetes y archivos.
Conclusión
Las características discutidas anteriormente no son críticas individualmente, pero afectan la consistencia general en el agregado. Examinamos la uniformidad del código a nivel micro, que no depende de la arquitectura u otras características de la aplicación, ya que estos aspectos son más fáciles de validar con casi cero falsos positivos.
Si le gustó el uso de coherencia o la comprobación de nombre de las descripciones anteriores, intente ejecutarlas en sus proyectos. La retroalimentación es un regalo realmente valioso para mí.
Importante : si tiene alguna idea o adición, ¡cuéntelo!
Hay varias formas:
Advertencia : agregar go-consistent
go-namecheck
y / o go-namecheck
en CI puede ser una acción demasiado radical. Ejecutar una vez al mes con la subsiguiente corrección de todas las inconsistencias puede ser una mejor solución.