Volver al blog

13 errores comunes al aprender a programar (y cómo corregirlos)

Los 13 errores más comunes de los programadores principiantes, desde ignorar los mensajes de error hasta no escribir nunca un test, cada uno con un ejemplo corto en Go y su solución.

13 errores comunes al aprender a programar (y cómo corregirlos)

Los errores más comunes de quienes empiezan a programar tienen poco que ver con la sintaxis. Los principiantes se saltan el mensaje de error, escriben cincuenta líneas antes de ejecutar ninguna, ignoran el caso en que la entrada es incorrecta y adivinan dónde está el bug en lugar de mirar los valores. Son hábitos, y los hábitos se pueden corregir pronto.

Este artículo cubre 13 de ellos. Cada uno incluye un ejemplo corto en Go cuando el código ayuda, y la solución. Go es un buen lenguaje para enseñar esto porque su compilador y sus herramientas rechazan directamente varios de estos errores. La sección cerca del final se ocupa de ellos.

Resumen rápido

  • Lee el mensaje de error completo. Te dice el archivo, la línea y qué salió mal.
  • Ejecuta tu código cada pocas líneas. Con pasos pequeños, los bugs son fáciles de encontrar.
  • No pegues código, incluido el que genera una IA, que no puedas explicar línea por línea.
  • Gestiona el camino de error. En Go, comprueba cada err antes de usar el valor que venía con él.
  • Pon nombres que digan lo que contiene cada cosa, mantén las funciones pequeñas y prueba los casos vacíos y los límites.
  • Usa Git desde el primer día, guarda los secretos en variables de entorno y escribe un test antes de creer que lo necesitas.
  • Depura imprimiendo valores o avanzando paso a paso con Delve, no cambiando código al azar.
  • Haz que funcione antes de abstraerlo o hacerlo rápido. Después construye algo que no sea un tutorial.

Índice

1. No leer el mensaje de error

Muchos principiantes ven texto en rojo y vuelven directamente al código a cambiar algo. El mensaje de error es lo más útil que hay en la pantalla. Normalmente indica el archivo, la línea, la columna y qué salió mal.

Aquí tienes un pequeño cargador de configuración de servidor que no compila:

example.gogo
package main

import (
	"fmt"
	"os"
)

func main() {
	port := os.Getenv("PORT")
	fmt.Println("starting server")
}
example.texttext
./main.go:9:2: declared and not used: port

Léelo de izquierda a derecha. main.go es el archivo, 9 es la línea, 2 es la columna, y el resto dice que la variable port nunca se usa. Úsala o bórrala. No hay nada más mal en el programa.

Los errores en tiempo de ejecución funcionan igual. Cuando un programa en Go entra en pánico, la traza de la pila muestra la función y la línea donde ocurrió, empezando por la llamada más reciente. Busca la primera línea que apunte a uno de tus archivos y empieza por ahí.

Si los mensajes de error te parecen crípticos, no eres el único. Un grupo de trabajo de ITiCSE que revisó la investigación en 2019 concluyó que los mensajes de error del compilador «presentan una dificultad considerable» para los principiantes (Becker et al., 2019). Leerlos despacio y al pie de la letra es una habilidad, y mejora rápido con la práctica.

2. Escribir mucho código antes de ejecutar nada

Escribir un programa entero y ejecutarlo por primera vez al final parece eficiente, pero hace que los bugs sean muy difíciles de encontrar. Cuando fallan 80 líneas nuevas, el bug puede estar en cualquiera de ellas. Cuando fallan 5 líneas nuevas, está en esas 5.

La solución es un ciclo corto. Escribe unas pocas líneas, ejecútalas, lee la salida y repite. Imprime valores intermedios mientras construyes, y borra esas impresiones cuando la pieza funcione. Go compila tan rápido que ejecutar go run . después de cada cambio pequeño te cuesta uno o dos segundos.

3. Copiar y pegar código que no sabes explicar

Copiar de Stack Overflow o de un asistente de IA no tiene nada de malo. El error es pegar código que no sabes explicar. Cuando se rompa, y se va a romper, no tendrás ni idea de por dónde empezar. Además te saltas la parte en la que aprendes.

Ponte una regla. Antes de pegar, lee cada línea y di qué hace. Si no puedes, pide a la IA que te explique esa línea, o reescríbela tú a partir de la explicación. La regla importa todavía más ahora que la mayoría de la gente aprende con ayuda de la IA. Entre quienes están aprendiendo a programar, el 73,3 % usa o piensa usar herramientas de IA, y el 39,5 % las usa a diario (Stack Overflow Developer Survey, 2025). El argumento completo está en ¿todavía deberías escribir código a mano?.

4. Programar solo el camino feliz

El código de principiante suele dar por hecho que el archivo existe, que la red funciona y que el usuario escribió un número. La entrada real es más caótica. Este programa lee un número de puerto e ignora el error:

example.gogo
package main

import (
	"fmt"
	"strconv"
)

func main() {
	input := "80a0"
	port, _ := strconv.Atoi(input)
	fmt.Println("listening on port", port)
}
example.texttext
listening on port 0

La errata se convirtió en el puerto 0 sin ningún aviso. Go devuelve los errores como valores normales, así que la solución es comprobarlos y decir qué falló:

example.gogo
package main

import (
	"fmt"
	"os"
	"strconv"
)

func parsePort(raw string) (int, error) {
	port, err := strconv.Atoi(raw)
	if err != nil {
		return 0, fmt.Errorf("invalid port %q: %w", raw, err)
	}
	if port < 1 || port > 65535 {
		return 0, fmt.Errorf("port %d out of range 1-65535", port)
	}
	return port, nil
}

func main() {
	port, err := parsePort("80a0")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Println("listening on port", port)
}
example.texttext
invalid port "80a0": strconv.Atoi: parsing "80a0": invalid syntax
exit status 1

Para cada entrada, pregúntate qué pasa si falta, está vacía, es del tipo equivocado o es demasiado grande. Cada respuesta se convierte en una comprobación en tu código.

5. Nombres poco claros

data, tmp, res, x2 y thing no le dicen nada al siguiente que lea el código, y dentro de dos semanas ese lector serás tú. Un nombre debería decir qué contiene el valor, con las palabras del problema.

example.gogo
// Hard to follow
for _, d := range data {
	if d.s == 500 {
		tmp++
	}
}

// Clear
for _, req := range requests {
	if req.Status == 500 {
		serverErrors++
	}
}

La convención en Go es usar nombres cortos para ámbitos cortos y nombres más largos para ámbitos más amplios. i está bien para un bucle de tres líneas. Una variable a nivel de paquete llamada c, no. Si te cuesta ponerle nombre a algo, suele ser señal de que hace dos cosas.

6. Una función enorme que lo hace todo

Un main de 200 líneas que lee un archivo, lo analiza, calcula totales e imprime un informe es difícil de probar y más difícil aún de cambiar. Divídelo por tareas. Aquí tienes una pequeña CLI que cuenta los códigos de estado HTTP de un log de accesos:

example.gogo
package main

import (
	"bufio"
	"fmt"
	"io"
	"maps"
	"os"
	"slices"
	"strings"
)

func main() {
	if len(os.Args) != 2 {
		fmt.Fprintln(os.Stderr, "usage: logstats <access.log>")
		os.Exit(2)
	}
	f, err := os.Open(os.Args[1])
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	defer f.Close()

	counts, err := countStatusCodes(f)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	printReport(os.Stdout, counts)
}

// countStatusCodes reads access log lines and counts the status code,
// which is the last field on each line.
func countStatusCodes(r io.Reader) (map[string]int, error) {
	counts := make(map[string]int)
	sc := bufio.NewScanner(r)
	for sc.Scan() {
		fields := strings.Fields(sc.Text())
		if len(fields) == 0 {
			continue
		}
		counts[fields[len(fields)-1]]++
	}
	return counts, sc.Err()
}

func printReport(w io.Writer, counts map[string]int) {
	for _, code := range slices.Sorted(maps.Keys(counts)) {
		fmt.Fprintf(w, "%s %d\n", code, counts[code])
	}
}
example.texttext
200 2
404 1
500 1

Ahora main solo conecta las piezas. countStatusCodes recibe un io.Reader en lugar de un nombre de archivo, así que un test puede pasarle un strings.Reader sin tocar el disco. printReport recibe un io.Writer por la misma razón. Cada función cabe en una pantalla y hace una sola cosa.

7. Errores off-by-one y entradas vacías

Los bucles que dan un paso de más son un clásico. Este analiza una fila de CSV y usa <= donde necesita <:

example.gogo
package main

import (
	"fmt"
	"strings"
)

func main() {
	fields := strings.Split("alice,admin,active", ",")
	for i := 0; i <= len(fields); i++ {
		fmt.Println(fields[i])
	}
}
example.texttext
alice
admin
active
panic: runtime error: index out of range [3] with length 3

goroutine 1 [running]:
main.main()
	/home/you/csvrow/main.go:11 +0xc0
exit status 2

El pánico dice exactamente qué pasó. Se usó el índice 3 en un slice de longitud 3, cuyo último índice válido es 2, en la línea 11. En Go, la solución más sencilla es no gestionar el índice: for _, field := range fields no puede pasarse del final.

Un fallo parecido, pero más silencioso, aparece con la entrada vacía. Esta función calcula la latencia media de las peticiones:

example.gogo
package main

import "fmt"

func averageLatency(ms []float64) float64 {
	var total float64
	for _, v := range ms {
		total += v
	}
	return total / float64(len(ms))
}

func main() {
	fmt.Println(averageLatency([]float64{120, 80, 100}))
	fmt.Println(averageLatency(nil))
}
example.texttext
100
NaN

Sin peticiones, divides entre cero, lo que da NaN con números de coma flotante y un pánico con enteros. Pregúntate siempre qué hace tu función con cero elementos, con uno y con el máximo. Con esos tres casos se cazan muchos bugs de límites.

8. No usar control de versiones desde el primer día

Muchos principiantes posponen Git hasta tener un proyecto «de verdad». Luego rompen algo que funcionaba hace una hora y no tienen forma de volver atrás. Git es el botón de deshacer de todo tu proyecto, y empezar no cuesta casi nada:

example.bashbash
git init
git add .
git commit -m "Parse port from PORT env var"

Haz un commit cada vez que algo funcione, con un mensaje que diga qué cambió. Cuando lo rompas, git diff te muestra lo que cambiaste desde el último estado bueno. Sube el repositorio a GitHub u otro servicio y además tendrás una copia de seguridad y un portfolio.

9. Dejar la configuración y los secretos fijos en el código

Poner una contraseña de base de datos o una clave de API directamente en el código fuente funciona hasta que lo subes a un repositorio público. GitGuardian contó 28,65 millones de secretos nuevos incrustados en el código en commits públicos de GitHub en 2025, un 34 % más que el año anterior (GitGuardian, 2026). Los puertos, las URL y las credenciales también cambian entre tu portátil y producción, así que dejarlos fijos te obliga a editar el código para desplegar.

Léelos del entorno:

example.gogo
package main

import (
	"cmp"
	"fmt"
	"os"
)

func main() {
	addr := ":" + cmp.Or(os.Getenv("PORT"), "8080")
	apiKey := os.Getenv("PAYMENTS_API_KEY")
	if apiKey == "" {
		fmt.Fprintln(os.Stderr, "PAYMENTS_API_KEY is not set")
		os.Exit(1)
	}
	fmt.Println("listening on", addr)
}

cmp.Or devuelve el primer valor no vacío, así que PORT recibe un valor por defecto razonable. La clave de API no tiene valor por defecto, y el programa se niega a arrancar sin ella. Si guardas valores locales en un archivo .env, añádelo a .gitignore antes de tu primer commit.

10. No escribir nunca un test

Los principiantes prueban ejecutando el programa y mirando la salida. Eso funciona una vez. No te avisa cuando un cambio posterior rompe algo que antes funcionaba. Go incluye los tests en su toolchain estándar, así que no hay nada que instalar. Primero, corrige averageLatency del error 7 para que indique si había datos:

example.gogo
func averageLatency(ms []float64) (float64, bool) {
	if len(ms) == 0 {
		return 0, false
	}
	var total float64
	for _, v := range ms {
		total += v
	}
	return total / float64(len(ms)), true
}

Después añade un test de tabla junto a ella:

example.gogo
func TestAverageLatency(t *testing.T) {
	tests := []struct {
		name   string
		in     []float64
		want   float64
		wantOK bool
	}{
		{"three requests", []float64{120, 80, 100}, 100, true},
		{"one request", []float64{42}, 42, true},
		{"no requests", nil, 0, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, ok := averageLatency(tt.in)
			if got != tt.want || ok != tt.wantOK {
				t.Errorf("averageLatency(%v) = %v, %v, want %v, %v", tt.in, got, ok, tt.want, tt.wantOK)
			}
		})
	}
}
example.texttext
--- PASS: TestAverageLatency (0.00s)
    --- PASS: TestAverageLatency/three_requests (0.00s)
    --- PASS: TestAverageLatency/one_request (0.00s)
    --- PASS: TestAverageLatency/no_requests (0.00s)
PASS

Ponlo en un archivo que termine en _test.go y ejecuta go test ./.... Cada fila es un caso, así que añadir un nuevo caso límite es una línea más. La fila no requests es la entrada que devolvía NaN en el error 7. Ahora tiene una respuesta definida y un test que la mantiene así.

11. Depurar a base de adivinar

El ciclo de depuración del principiante es así: cambias algo, lo ejecutas, sigue roto, cambias otra cosa. Después de diez rondas, el código tiene bugs nuevos y el original sigue ahí. En un estudio con 21 estudiantes de siete universidades, algunos «usaron pocas estrategias y las aplicaron de forma poco eficaz», e introdujeron bugs nuevos mientras depuraban (Murphy et al., 2008).

Mira antes de cambiar. Formula una hipótesis sobre qué falla y compruébala imprimiendo los valores reales:

example.gogo
log.Printf("order=%+v total=%d items=%d", order, total, len(order.Items))

%+v imprime los nombres de los campos del struct, lo que te evita adivinar qué número es cuál. Cuando las impresiones se vuelvan demasiadas, usa un depurador. Delve es el estándar en Go, y tanto VS Code como GoLand lo usan por debajo:

example.bashbash
dlv debug . -- access.log
(dlv) break main.go:43
(dlv) continue
(dlv) print fields
[]string len: 3, cap: 3, [
	"GET",
	"/api/orders",
	"200",
]

La línea 43 es la línea counts[...]++ del contador de logs del error 6. El programa se detiene ahí y puedes inspeccionar el valor real de fields en lugar de adivinarlo.

Cuando encuentres el bug, escribe un test que lo reproduzca antes de corregirlo. Así no podrá volver sin que te enteres.

12. Abstraer u optimizar antes de que funcione

Es tentador diseñar interfaces, sistemas de configuración y capas de plugins para un programa que todavía no funciona. O sustituir un bucle claro por algo ingenioso porque quizá sea más rápido. Las dos cosas hacen que el código sea más difícil de cambiar antes de saber qué tiene que hacer.

Haz que funcione con el código más sencillo que puedas escribir. Espera a haber escrito lo mismo tres veces antes de moverlo a una función compartida. En Go, define una interface cuando aparezca de verdad una segunda implementación, no cuando te la imagines. Optimiza solo después de medir, y Go te da las herramientas para eso: go test -bench para benchmarks y pprof para perfiles.

13. Quedarse en los tutoriales y pedir ayuda sin un ejemplo reproducible

Los tutoriales te hacen sentir productivo porque cada paso funciona. La primera vez que abres un archivo en blanco descubres cuánto has retenido de verdad. En algún momento tienes que construir algo sin que nadie te guíe: una CLI que renombra archivos, una pequeña API HTTP, un bot para una app de chat. Va a quedar desordenado, y aprenderás más con él que con los próximos tres tutoriales. Cómo aprender a programar en 2026 explica cómo organizar esa práctica, y cómo aprender Go presenta un plan completo para Go.

Cuando te atasques, pedirás ayuda, y la forma de pedirla importa. «Mi código no funciona» no te lleva a ninguna parte. Reduce el problema al programa más pequeño que siga mostrando el bug y comparte ese programa, la salida de error exacta, lo que esperabas y lo que has probado. Muchas veces encontrarás el bug tú mismo mientras lo reduces. Para Go, el Go Playground te da un enlace para compartir un programa ejecutable.

¿Qué errores de esta lista detecta Go por ti?

Go se diseñó para bases de código grandes mantenidas por muchas personas, y varias de sus reglas bloquean de paso errores de principiante antes de que el código se ejecute.

  • Las variables y los imports sin usar no compilan. El compilador rechaza declared and not used y "strings" imported and not used. El FAQ de Go lo describe como «sacrificar la comodidad a corto plazo a cambio de velocidad de compilación y claridad del programa a largo plazo», y señala que una variable sin usar puede indicar un bug.
  • gofmt formatea todos los archivos Go de la misma manera, así que nunca discutes sobre espacios o la posición de las llaves, y una indentación desordenada no puede esconder un bug (gofmt).
  • go vet detecta código sospechoso. Señala cosas como un verbo %d que recibe un string, algo que compila sin problema pero imprime basura (cmd/vet).
  • Los errores son valores que tienes que gestionar. Una función que puede fallar devuelve un error. Cuando devuelve un valor y un error, no puedes usar el valor sin recibir también el error, aunque luego lo descartes con un _ bien visible. El error del camino feliz de la sección 4 pasa a ser algo que tienes que escribir a propósito (Errors are values).
  • Los tests vienen incluidos. go test y el paquete testing vienen con Go, así que no hay que elegir un framework antes de tu primer test (testing).
  • El detector de carreras encuentra las condiciones de carrera. Ejecuta tests o programas con -race y Go informa de los accesos concurrentes a memoria compartida con el archivo y la línea de ambos accesos (Data Race Detector).

Aquí tienes go vet detectando un bug de formato que compila:

example.gogo
package main

import "fmt"

func main() {
	active := "12"
	fmt.Printf("%d active users\n", active)
}
example.texttext
./main.go:7:14: fmt.Printf format %d has arg active of wrong type string

Go no te impide escribir una función de 200 líneas ni llamar tmp a una variable. Lo que sí hace es detectar los errores mecánicos, y eso te deja más tiempo para los que requieren criterio. Para trampas específicas de Go, como los errores sombreados o las sorpresas con append, consulta errores comunes en Go que debes evitar.

Preguntas frecuentes

¿Cuál es el error más común de los programadores principiantes?

En el mayor estudio sobre errores de compilación de principiantes, con 37 millones de compilaciones de más de 250.000 estudiantes de Java, el error más frecuente fueron los paréntesis, llaves o comillas sin cerrar. Esos se corregían rápido. Los errores caros eran los que el compilador no señalaba, como ignorar un valor de retorno, que normalmente tardaban más de 17 minutos en corregirse o nunca llegaban a corregirse (Altadmri & Brown, 2015). Como hábito, no leer el mensaje de error es el que ralentiza todo lo demás.

¿Cómo dejo de cometer los mismos errores al programar?

Escribe un test por cada bug que corrijas, para que no pueda volver sin que te des cuenta. Lleva una lista corta de los errores que has cometido más de una vez y revisa tu código con ella antes de hacer commit. Las herramientas también ayudan. En Go, go vet y gofmt detectan automáticamente toda una categoría de errores.

¿Es Go un buen lenguaje para principiantes que cometen muchos errores?

Sí. El compilador de Go rechaza las variables y los imports sin usar, sus errores son valores de retorno explícitos, y go vet, gofmt y go test vienen con el lenguaje. Sus mensajes de error son cortos y apuntan a un archivo y una línea. Consulta ¿es Go un buen primer lenguaje de programación? para ver la comparación completa.

¿Deberían los principiantes usar la IA para escribir código?

Úsala para que te explique código, errores y conceptos, no para escribir código que no puedes seguir. Una buena regla es no pegar nunca código que no serías capaz de reescribir de memoria al día siguiente. Vas a necesitar esa comprensión para arreglar el código de la IA cuando esté casi bien.

¿Cuánto se tarda en dejar de cometer errores de principiante?

Para la mayoría de la gente, estos hábitos desaparecen tras unos meses escribiendo código con regularidad, porque cada uno te cuesta tiempo hasta que lo corriges. Practicar a diario con programas reales da resultado antes que las sesiones largas y esporádicas. Los errores mecánicos son los primeros en irse. Las decisiones de criterio, como los nombres y el tamaño de las funciones, tardan más.

Empieza a escribir Go hoy

Cada error de esta lista es más fácil de evitar cuanto más código escribes y cuanto antes ves qué salió mal. Esa es la idea detrás de LevelUpGo. Cada lección es un ejercicio de Go que se ejecuta en una toolchain real de Go en tu navegador, así que desde el primer día lees errores de compilador de verdad, gestionas valores err y ves cómo fallan los tests. Empieza con la ruta Go Fundamentals.

Escribe Go como un ingeniero sénior

Lecciones interactivas en tu navegador. Las primeras son gratis.

Prueba una lección gratisO crea una cuenta gratuita