initial commit

This commit is contained in:
2026-09-30 10:22:42 +02:00
commit 3b0f5e365f
32 changed files with 4498 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
# Go escrito con mentalidad de C
Experimento: el mismo generador de tickets ESC/POS escrito en Go de dos
formas, midiendo el resultado (i9-9900K, Go 1.26, 30 líneas por ticket). Las
dos versiones producen exactamente los mismos bytes.
| | ns/ticket | bytes reservados/ticket | reservas/ticket |
|---|---|---|---|
| Hábitos de JS (`+=` en strings, `fmt.Sprintf`, `[]*Item`) | ~9.600 | 14.378 | 126 |
| Hábitos de C (un buffer reutilizado, `strconv.Append*`) | ~550 | 0 | 0 |
## Por qué
- **Los strings son inmutables.** `s += x` crea un string nuevo y copia todo
lo anterior en él. En un bucle, cada iteración copia más bytes que la
anterior: el coste crece con el cuadrado del tamaño.
- **`fmt.Sprintf`** reserva memoria para el resultado y usa reflexión.
`strconv.AppendInt` escribe directamente en tu buffer.
- **Reutilizar el buffer**: `buf = buf[:0]` pone la longitud a 0 pero
conserva la capacidad, es decir, la memoria ya reservada. Tras el primer
ticket no se vuelve a reservar nada. Es lo que hará el buffer de la fase 2.
## Padding: el orden de los campos cambia el tamaño
El procesador exige que un `int64` empiece en una dirección múltiplo de 8. El
compilador rellena con huecos (*padding*) para cumplirlo:
- `{bool, int64, bool, int64, bool}` ocupa **40 bytes**.
- `{int64, int64, bool, bool, bool}` ocupa **24 bytes**.
Con un millón de elementos son 38 MB frente a 22 MB. En C pasa exactamente lo
mismo.
## Un slice es `{puntero, longitud, capacidad}`
```go
header := make([]byte, 0, 16)
header = append(header, 0x1b, '@')
a := append(header, 'A')
b := append(header, 'B') // a también termina en 'B'
```
Hay capacidad libre, así que `append` no copia: escribe en el mismo array.
`a` y `b` comparten memoria. Quien piensa en punteros lo ve venir; quien
piensa en arrays de JS, no.
## Dinero en `float64`
`10 × 0.10` da `0.9999999999999999`. El `number` de JS es un `float64`. Para
dinero se usan enteros en céntimos.
## El precio de la versión rápida
`BuildTicket` devuelve una **vista del buffer interno**, que deja de ser
válida en la siguiente llamada. Hay que documentar quién es el dueño de esa
memoria. Es exactamente la regla de la API de la librería: documentar en cada
función quién es dueño de cada puntero.
## Tercera versión: una IA a partir de una spec escueta
Spec: "cabecera en negrita, una línea por producto, total, corte, precios con
dos decimales, tests". El código resultante es idiomático (`bytes.Buffer`,
`fmt.Fprintf`, dinero en céntimos) y **pasa todos los criterios de
aceptación**.
| | ns/ticket | bytes/ticket | reservas/ticket |
|---|---|---|---|
| IA con spec escueta | ~8.300 | 3.186 | 99 |
Casos que la spec no menciona:
| Caso | IA | Estilo C | Estilo JS |
|---|---|---|---|
| Descuento de −1,50 | `-1.-50` ❌ | `-1.+0` ❌ | `-1.50` ✅ (de casualidad) |
| `é`, `ñ`, `€` | UTF-8 ❌ | UTF-8 ❌ | UTF-8 ❌ |
| Nombre más largo que el papel (48/32 columnas) | no lo trata | no lo trata | no lo trata |
- **Negativos**: en Go (y en C) el resto de un número negativo es negativo:
`-150 % 100 == -50`. Las versiones con enteros lo formatean mal.
- **Codificación**: la impresora no entiende UTF-8. `é` se envía como `C3 A9`
y en papel sale como dos símbolos extraños. Hay que convertir a la página de
códigos de la impresora (fase 6).
Conclusión: el resultado nunca es mejor que la spec más la capacidad de quien
lo revisa. Las decisiones que no se piden las toma el modelo "por defecto", y
solo las detecta quien sabe que existen. Una spec de experto incluiría:
presupuesto de reservas de memoria, quién es dueño del buffer, importes
negativos, página de códigos y ancho de línea según el modelo de impresora.
## Cuándo no merece la pena
Si el ticket se genera una vez por venta, 9 µs frente a 0,5 µs da igual. La
versión rápida compensa en rutas calientes: miles de peticiones por segundo o
bucles sobre millones de elementos. Primero se mide (`go test -bench
-benchmem`, `pprof`) y después se optimiza.