Evitar líneas excesivamente largas
Evite el uso de líneas de código que requieran que los lectores hagan desplazamiento horizontal o giren excesivamente el documento.
Recomendamos limitar la longitud de la línea a 99 caracteres. Se recomienda que los autores dividan la línea antes de este límite, pero no es una regla estricta. Es permisible que el código exceda este límite.
Coherencia
Algunos de los estándares descritos en este documento se basan en juicios subjetivos, escenarios o contextos. Sin embargo, el aspecto más crucial es mantener la coherencia.
Un código consistente es más fácil de mantener, más racional, demanda menos costo de aprendizaje y es más fácil de migrar, actualizar y corregir errores cuando surgen nuevas convenciones o errores.
Por el contrario, incluir múltiples estilos de código completamente diferentes o conflictivos en una base de código conlleva a un aumento en los costos de mantenimiento, incertidumbre y sesgos cognitivos. Todo esto resulta directamente en una velocidad más lenta, revisiones de código dolorosas y un aumento en el número de errores.
Al aplicar estos estándares a una base de código, se recomienda realizar cambios a nivel del paquete (o superior). Aplicar múltiples estilos a nivel de subpaquete viola las preocupaciones anteriores.
Agrupar declaraciones similares
El lenguaje Go admite la agrupación de declaraciones similares.
No recomendado:
import "a"
import "b"
Recomendado:
import (
"a"
"b"
)
Esto también se aplica a las declaraciones de constantes, variables y tipos:
No recomendado:
const a = 1
const b = 2
var a = 1
var b = 2
type Area float64
type Volume float64
Recomendado:
const (
a = 1
b = 2
)
var (
a = 1
b = 2
)
type (
Area float64
Volume float64
)
Solo agrupe declaraciones relacionadas y evite agrupar declaraciones no relacionadas.
No recomendado:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
EnvVar = "MY_ENV"
)
Recomendado:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
const EnvVar = "MY_ENV"
No hay restricciones sobre dónde usar la agrupación. Por ejemplo, puede usarla dentro de una función:
No recomendado:
func f() string {
red := color.New(0xff0000)
green := color.New(0x00ff00)
blue := color.New(0x0000ff)
...
}
Recomendado:
func f() string {
var (
red = color.New(0xff0000)
green = color.New(0x00ff00)
blue = color.New(0x0000ff)
)
...
}
Excepción: Si las declaraciones de variables están adyacentes a otras variables, especialmente dentro de las declaraciones locales de una función, deben agruparse juntas. Realice esto incluso para variables no relacionadas declaradas juntas.
No recomendado:
func (c *client) request() {
caller := c.name
format := "json"
timeout := 5*time.Second
var err error
// ...
}
Recomendado:
func (c *client) request() {
var (
caller = c.name
format = "json"
timeout = 5*time.Second
err error
)
// ...
}
Agrupación de importaciones
Las importaciones deben agruparse en dos categorías:
- Biblioteca estándar
- Otras bibliotecas
Por defecto, esta es la agrupación aplicada por goimports.
No recomendado:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
Recomendado:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
Nombre del paquete
Al nombrar un paquete, siga estas reglas:
- Todo en minúsculas, sin letras mayúsculas ni guiones bajos.
- En la mayoría de los casos, no es necesario cambiar el nombre al importar.
- Corto y conciso. Recuerde que el nombre es totalmente calificado dondequiera que se use.
- Evite los plurales. Por ejemplo, use
net/url
en lugar denet/urls
. - Evite usar "common," "util," "shared," o "lib." Esto no es lo suficientemente informativo.
Nombrar Funciones
Seguimos la convención de la comunidad Go de usar MixedCaps para nombres de funciones. Se hace una excepción para agrupar casos de prueba relacionados, donde el nombre de la función puede contener guiones bajos, como: TestMyFunction_WhatIsBeingTested
.
Alias de Importación
Si el nombre del paquete no coincide con el último elemento de la ruta de importación, se debe usar un alias de importación.
import (
"net/http"
client "example.com/client-go"
trace "example.com/trace/v2"
)
En todos los demás casos, se deben evitar los alias de importación a menos que haya un conflicto directo entre las importaciones. No recomendado:
import (
"fmt"
"os"
nettrace "golang.net/x/trace"
)
Recomendado:
import (
"fmt"
"os"
"runtime/trace"
nettrace "golang.net/x/trace"
)
Agrupación y Orden de Funciones
- Las funciones deben estar ordenadas aproximadamente en el orden en que se llaman.
- Las funciones dentro del mismo archivo deben estar agrupadas por receptor.
Por lo tanto, las funciones exportadas deben aparecer primero en el archivo, ubicadas después de las definiciones struct
, const
y var
.
Un newXYZ()
/NewXYZ()
puede aparecer después de las definiciones de tipo pero antes de los métodos restantes del receptor.
A medida que las funciones se agrupan por receptor, las funciones de utilidad general deben aparecer al final del archivo. No recomendado:
func (s *something) Cost() {
return calcCost(s.weights)
}
type something struct{ ... }
func calcCost(n []int) int {...}
func (s *something) Stop() {...}
func newSomething() *something {
return &something{}
}
Recomendado:
type something struct{ ... }
func newSomething() *something {
return &something{}
}
func (s *something) Cost() {
return calcCost(s.weights)
}
func (s *something) Stop() {...}
func calcCost(n []int) int {...}
Reducir Anidamiento
El código debe reducir el anidamiento manejando casos de error/especiales lo antes posible y ya sea devolviendo o continuando el bucle. Reducir el anidamiento disminuye la cantidad de código en varios niveles.
No recomendado:
for _, v := range data {
if v.F1 == 1 {
v = process(v)
if err := v.Call(); err == nil {
v.Send()
} else {
return err
}
} else {
log.Printf("Invalid v: %v", v)
}
}
Recomendado:
for _, v := range data {
if v.F1 != 1 {
log.Printf("Invalid v: %v", v)
continue
}
v = process(v)
if err := v.Call(); err != nil {
return err
}
v.Send()
}
'else' Innecesario
Si una variable se establece en ambas ramas de un 'if', puede ser reemplazada por una sola declaración 'if'.
No recomendado:
var a int
if b {
a = 100
} else {
a = 10
}
Recomendado:
a := 10
if b {
a = 100
}
Declaración de Variables de Nivel Superior
En el nivel superior, utilizar la palabra clave estándar var
. No especificar el tipo a menos que difiera del tipo de la expresión.
No recomendado:
var _s string = F()
func F() string { return "A" }
Recomendado:
var _s = F()
// Dado que F devuelve explícitamente un tipo string, no es necesario especificar explícitamente el tipo para _s
func F() string { return "A" }
Especificar el tipo si no coincide exactamente con el tipo necesario para la expresión.
type myError struct{}
func (myError) Error() string { return "error" }
func F() myError { return myError{} }
var _e error = F()
// F devuelve una instancia de tipo myError, pero necesitamos el tipo error
Usar '_' como prefijo para constantes y variables de nivel superior no exportadas
Para las vars
y consts
de nivel superior no exportadas, prefíjelas con un guion bajo _
para indicar explícitamente su naturaleza global cuando se usen.
Razón básica: Las variables y constantes de nivel superior tienen ámbito a nivel de paquete. El uso de nombres genéricos puede llevar fácilmente a usar accidentalmente el valor incorrecto en otros archivos.
No recomendado:
// foo.go
const (
defaultPort = 8080
defaultUser = "user"
)
// bar.go
func Bar() {
defaultPort := 9090
...
fmt.Println("Puerto por defecto", defaultPort)
// No veremos un error de compilación si se elimina la primera línea de
// Bar().
}
Recomendado:
// foo.go
const (
_defaultPort = 8080
_defaultUser = "user"
)
Excepción: Los valores de error no exportados pueden usar el prefijo err
sin guion bajo. Consulte la nomenclatura de errores.
Incrustación en estructuras
Los tipos incrustados (como mutex) deben colocarse en la parte superior de la lista de campos dentro de la estructura y deben tener una línea vacía que separe los campos incrustados de los campos regulares.
No recomendado:
type Cliente struct {
version int
http.Client
}
Recomendado:
type Cliente struct {
http.Client
version int
}
La incrustación debe proporcionar beneficios tangibles, como agregar o mejorar la funcionalidad de una manera semánticamente apropiada. Debe usarse sin ningún impacto adverso en el usuario. (Consulte también: Evite incrustar tipos en estructuras públicas)
Excepciones: Incluso en tipos no exportados, el Mutex no debe usarse como campo incrustado. Consulte también: El Mutex de valor cero es válido.
La incrustación no debe:
- Existir únicamente por estética o conveniencia.
- Dificultar la construcción o el uso del tipo exterior.
- Afectar el valor cero del tipo exterior. Si el tipo exterior tiene un valor cero útil, todavía debería haber un valor cero útil después de incrustar el tipo interior.
- Tener el efecto secundario de exponer funciones o campos no relacionados del tipo incrustado interior.
- Exponer tipos no exportados.
- Afectar la forma de clonar del tipo exterior.
- Cambiar la API o la semántica del tipo exterior.
- Incrustar el tipo interior en una forma no estándar.
- Exponer detalles de implementación del tipo exterior.
- Permitir a los usuarios observar o controlar el tipo interno.
- Cambiar el comportamiento general de las funciones internas de una manera que pueda sorprender a los usuarios.
En resumen, incruste de manera consciente y con un propósito. Una buena prueba es: "¿Se añadirán directamente todos estos métodos/campos exportados del tipo interno al tipo externo?" Si la respuesta es algunos
o no
, no incruste el tipo interno, use campos en su lugar.
No recomendado:
type A struct {
// Mal: A.Lock() y A.Unlock() ahora están disponibles
// No aporta ningún beneficio funcional y permite al usuario controlar detalles internos de A.
sync.Mutex
}
Recomendado:
type countingWriteCloser struct {
// Bien: Write() se proporciona a nivel externo con un propósito específico,
// y delega el trabajo al método Write() del tipo incrustado.
io.WriteCloser
count int
}
func (w *countingWriteCloser) Write(bs []byte) (int, error) {
w.count += len(bs)
return w.WriteCloser.Write(bs)
}
Declaraciones de variables locales
Si una variable se establece explícitamente en un valor, se debe utilizar la forma abreviada de declaración de variables (:=
).
No recomendado:
var s = "foo"
Recomendado:
s := "foo"
Sin embargo, en algunos casos, el uso de la palabra clave var
para valores predeterminados puede ser más claro.
No recomendado:
func f(list []int) {
filtered := []int{}
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
Recomendado:
func f(list []int) {
var filtered []int
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
nil
es una slice válida
nil
es una slice válida con una longitud de 0, lo que significa:
- No debes devolver explícitamente una slice con una longitud de cero. En su lugar, devuelve
nil
.
No recomendado:
if x == "" {
return []int{}
}
Recomendado:
if x == "" {
return nil
}
- Para verificar si una slice está vacía, siempre utiliza
len(s) == 0
en lugar denil
.
No recomendado:
func isEmpty(s []string) bool {
return s == nil
}
Recomendado:
func isEmpty(s []string) bool {
return len(s) == 0
}
- Las slices con valor cero (slices declaradas con
var
) pueden usarse inmediatamente sin llamar amake()
.
No recomendado:
nums := []int{}
// o, nums := make([]int)
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
Recomendado:
var nums []int
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
Recuerda, aunque una slice nula es una slice válida, no es igual a una slice con una longitud de 0 (una es nula y la otra no) y pueden tratarse de manera diferente en diferentes situaciones (por ejemplo, serialización).
Reducir el alcance de las variables
Si es posible, intenta reducir el alcance de las variables, a menos que entre en conflicto con la regla de reducir la anidación.
No recomendado:
err := os.WriteFile(name, data, 0644)
if err != nil {
return err
}
Recomendado:
if err := os.WriteFile(name, data, 0644); err != nil {
return err
}
Si se necesita usar el resultado de una llamada a una función fuera de la instrucción if, no intentes reducir el alcance.
No recomendado:
if data, err := os.ReadFile(name); err == nil {
err = cfg.Decode(data)
if err != nil {
return err
}
fmt.Println(cfg)
return nil
} else {
return err
}
Recomendado:
data, err := os.ReadFile(name)
if err != nil {
return err
}
if err := cfg.Decode(data); err != nil {
return err
}
fmt.Println(cfg)
return nil
Evitar parámetros no explícitos
Los parámetros poco claros en las llamadas de funciones pueden afectar la legibilidad. Cuando el significado de los nombres de los parámetros no es obvio, agrega comentarios al estilo C (/* ... */
) a los parámetros.
No recomendado:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true, true)
Recomendado:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true /* isLocal */, true /* done */)
Para el ejemplo anterior, un enfoque mejor podría ser reemplazar los tipos bool
con tipos personalizados. De esta manera, el parámetro potencialmente puede admitir más de solo dos estados (true/false) en el futuro.
type Region int
const (
UnknownRegion Region = iota
Local
)
type Status int
const (
StatusReady Status= iota + 1
StatusDone
// Quizás tendremos un StatusInProgress en el futuro.
)
func printInfo(name string, region Region, status Status)
Utilizar literales de cadena sin escapar
Go admite el uso de literales de cadena sin escapar, que se indica con " ` " para representar cadenas sin escapar. En escenarios donde se requiere escapar, debemos usar este enfoque para reemplazar las cadenas escapadas manualmente que son más difíciles de leer.
Pueden abarcar varias líneas e incluir comillas. El uso de estas cadenas puede evitar las cadenas escapadas manualmente que son más difíciles de leer.
No recomendado:
wantError := "error desconocido:\"test\""
Recomendado:
wantError := `error desconocido:"test"`
Inicializar estructuras
Inicializar estructuras usando nombres de campos
Cuando se inicializa una estructura, casi siempre se deben especificar los nombres de los campos. Actualmente esto es obligatorio según go vet
.
No recomendado:
k := User{"John", "Doe", true}
Recomendado:
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}
Excepción: Cuando hay 3 o menos campos, pueden omitirse los nombres de los campos en las tablas de pruebas.
tests := []struct{
op Operation
want string
}{
{Add, "add"},
{Subtract, "subtract"},
}
Omitir campos con valor cero en estructuras
Al inicializar una estructura con campos nombrados, a menos que se proporcione un contexto significativo, se deben omitir los campos con valor cero. Es decir, dejemos que se establezcan automáticamente a sus valores nulos.
No recomendado:
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
Recomendado:
user := User{
FirstName: "John",
LastName: "Doe",
}
Esto ayuda a reducir las barreras de lectura al omitir los valores predeterminados en el contexto. Solo se deben especificar valores significativos.
Incluir el valor cero donde los nombres de los campos proporcionen un contexto significativo. Por ejemplo, los casos de prueba en una prueba basada en tablas pueden beneficiarse de nombrar los campos, incluso si son valores cero.
tests := []struct{
give string
want int
}{
{give: "0", want: 0},
// ...
}
Utilizar var
para estructuras con valores cero
Si se omiten todos los campos de una estructura en la declaración, use var
para declarar la estructura.
No recomendado:
user := User{}
Recomendado:
var user User
Esto distingue las estructuras con valores cero de aquellas con campos con valores distintos de cero, de manera similar a como preferimos cuando se declara una lista vacía.
Inicializar referencias de estructuras
Al inicializar referencias de estructuras, use &T{}
en lugar de new(T)
para que sea consistente con la inicialización de estructuras.
No recomendado:
sval := T{Name: "foo"}
// inconsistente
sptr := new(T)
sptr.Name = "bar"
Recomendado:
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}
Inicializar Mapas
Para un mapa vacío, use make(..)
para inicializarlo, y el mapa se llena programáticamente. Esto hace que la inicialización del mapa se vea diferente a la declaración, y también permite agregar indicadores de tamaño después de make
.
No recomendado:
var (
// m1 es seguro para lectura-escritura;
// m2 entra en pánico al escribir
m1 = map[T1]T2{}
m2 map[T1]T2
)
Recomendado:
var (
// m1 es seguro para lectura-escritura;
// m2 entra en pánico al escribir
m1 = make(map[T1]T2)
m2 map[T1]T2
)
| La declaración y la inicialización lucen muy similares. | La declaración y la inicialización lucen muy diferentes. |
Cuando sea posible, proporcionar el tamaño de capacidad del mapa durante la inicialización, consulte Especificación de la Capacidad del Mapa para obtener más detalles.
Además, si el mapa contiene un conjunto fijo de elementos, use literales de mapa para inicializarlo.
No recomendado:
m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3
Recomendado:
m := map[T1]T2{
k1: v1,
k2: v2,
k3: v3,
}
La guía básica es usar literales de mapas para agregar un conjunto fijo de elementos durante la inicialización. De lo contrario, use make
(y, si es posible, especifique la capacidad del mapa).
Formato de Cadena para Funciones al Estilo Printf
Si declara una cadena de formato de función al estilo Printf
fuera de una función, establézcala como una constante const
.
Esto ayuda a que go vet
realice un análisis estático de la cadena de formato.
No recomendado:
msg := "valores inesperados %v, %v\n"
fmt.Printf(msg, 1, 2)
Recomendado:
const msg = "valores inesperados %v, %v\n"
fmt.Printf(msg, 1, 2)
Nomenclatura de funciones al estilo Printf
Al declarar funciones al estilo Printf
, asegúrese de que go vet
pueda detectar y verificar la cadena de formato.
Esto significa que debe usar nombres predefinidos de funciones al estilo Printf
tanto como sea posible. go vet
los verificará de forma predeterminada. Para obtener más información, consulte Familia Printf.
Si no se pueden usar nombres predefinidos, termine el nombre seleccionado con f
: Wrapf
en lugar de Wrap
. go vet
puede solicitar verificar nombres específicos en estilo Printf, pero el nombre debe terminar con f
.
go vet -printfuncs=wrapf,statusf