#Documentación GoLang Español
- Escribo todo al mismo tiempo que nos capacitan para poder reflejar y expresar bien lo que nos dicen los chicos de GrupoEsfera.
- Me inspiré mucho con los ejemplos y dichos de GoTour, pero traté de complementarlos con el conocimiento de Diego.
- Separé Variables, Punteros, Estructuras, Slices y Mapas porque me parecen conceptos deberían tener sus propias secciones para no confundir(me).
- Si me confundo Pointer Receiver y Pointer Retriever, no es mi culpa.
En GoLang todos los proyectos están en sus respectivas carpetas en el mismo lugar: src.
En bin están los archivos ejecutables. Está tanto el "main" como también los tests automatizados.
En pkg están las dependencias utilizadas.
Hay un directorio de la arquitectura con la que está compilando compilando.
Una variable de entorno es una variable que se define en el sistema operativo para caracterizar un valor para que aquellos programas que corran en ese entorno (shell). Comunican el entorno con un programa: GOPATH nos permite saber dónde están las cosas que queremos ejecutar, por ejemplo si se usa go build hello
. Si no seteamos GOPATH, no vamos a encontrar ninguna de las herramientas que necesitamos. PATH (conceptual) es una variable que dice dónde están los ejecutables.
Resumiendo:
- GOPATH es dónde buscar nuestros archivos para compilarlos y ejecutarlos.
- GOROOT es la instalación de Go.
Un IMPORT PATH es un string que identifica unívocamente a un paquete. Los paquetes de la librería estándar tienen import paths cortos tales como "fmt" y "net/http". Se tiene que elegir un PATH (nombre, entre otras cosas) que sea poco probable que se repita.
###Compilar y correr
Para compilar el código se puede usar cualquiera de los dos siguientes comandos: go install [nombre]
o bien go build [nombre]
. Si los PATH están bien especificados los binarios van a ir a la carpeta bin
. El comando go run [nombre]
compila y corre el archivo.
- Las variables y funciones se escriben en CamelCase.
holaComoEstas
- Las constantes se escriben con el primer caracter en mayúscula.
SoyUnaConstante
- Aquellas funciones y variables que son exportadas deberían tener la primera letra mayúscula (por ejemplo:
Hola
).SoyUnaFuncionONombreExportada
- Aquellas funciones y variables que no son exportadas deberían estar en minúscula, siguiendo las convenciones de CamelCase.
- Los paquetes van todos en minúscula y en lo posible con nombres cortos.
github.com/juan/hello
- Es buena idea declarar las constantes a nivel global.
- Es mejor que hayan más líneas pero que se entienda más el código.
- La lógica de negocio debe ser explícita para terceros y por eso está bueno evitar usar #magitrucos del código para cosas de tanta relevancia.
- Si buscamos claridad en nuestor código y hay cruces entre los nombres de variables/funciones/etc a utilizar es bueno juntarse y llegar a un concenso en el que se plantea un concepto superador que satisfaga a ambos extremos.
GoLang es un lenguaje tipado, si bien existen formas en las que se infieren los tipos.
Hay varias formas de declarar variables:
var i int = 1
es una forma explícita de declarar (y asignar) una variable.
i := 1
es otra forma más corta de declarar variables, en la cual el tipo es inferido.
Los tipos de datos básicos son:
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128
Las constantes se declaran como variables, pero se usa la palabra clave const
para definirlas. No se pueden declarar con :=
.
Un ejemplo sería: const Pi = 20
- Un puntero es una variable que contiene la dirección de memoria de otra.
- Es un tipo atómico.
- El tipo
*T
es un puntero aT
. Su valor 0 esnil
. - El operador
&
genera un puntero a su operador. Devuelve su dirección en memoria. - No hay aritmética de punteros, pero sí asignación.
i := 42
p = &i // Dirección de memoria donde está guardada i.
fmt.Println(*p) // Referencia al valor de la dirección que estoy almacenando.
*p = 21 // Ahora i pasa a valer 21.
&
es el operador recíproco: de indirección.*
es el operador de "dereferencia", apunta al valor que almacena.
- Es una colección de campos.
- Por ende podemos decir que es un valor compuesto, estructurado.
- Es un valor asociativo: tiene campos a los que le asigna un valor particular.
- La declaración
type
declara un nuevo tipo que definimos nosotros. En C eratypedef
. Es lo mismo. - En el bloque de
struct
(la estructura) se definen las variables que la componen.
Ejemplo de definición de una estructura (struct
)
type Vertex struct {
X int // ↓
Y int // Valores que componen a la estructura.
}
Ejemplo de uso
import "fmt"
func main() {
fmt.Println(Vertex{1,2})
fmt.Println(Vertex{})
}
Vertex{}
inicializa una variable con los valores por default de los tipos que la componen.- Se puede acceder a los valores dentro de la
struct
de la siguiente manera:[variable].[nombre de la propiedad]
, por ejemplo:v.X
donde v referencia una estructura.
func main() {
v := Vertex{1,2}
v.X = 4
fmt.Println(v.X)
}
- Los campos de una estructura pueden ser accedidos mediante punteros.
- Para acceder al campo
X
de una estructura cuando tenemos el punterop
a la estructura podemos escribir simplementep.X
, evitándonos usar la referenciación explícita(*p).X
- Me permiten modificar a qué apunta, es decir, cambiar referencias.
Por ejemplo:
import "fmt"
func main() {
v := Vertex{1, 2}
p := &v
p.X = 2
fmt.Println(p.X)
}
- Podemos decir que cuando trabajamos con estructuras los punteros están implícitos.
- Son internal pointers o punteros internos.
- Un valor literal es aquel que lo único que entiende es lo que explicitamente está escrito.
- La diferencia entre un literal y una variable es que un literal solo tiene su valor. Su valor es inmediato. Las variables tienen un salto, los punteros dos saltos, los literales no tienen. Una variable es
p
, un literal es1
. Un puntero*p
tiene dos saltos para llegar a su valor.p
tiene uno solo.1
no tiene, es bien explícito.
Un ejemplo de diferentes asignaciones:
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // has type *Vertex
)
- Los maps son asociativas, tales como las estructuras. Mapean claves a valores.
- El zero-value de un map es
nil
. No tiene llaves, y no se pueden agregar keys. - La función
make
devuelve un mapa de un tipo dado.
Un ejemplo de un map:
package main
import "fmt"
type Vertex struct { // Estructura que voy a usar con el map
Lat, Long float64
}
var m map[string]Vertex
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
- Son como los literales de
struct
, pero las keys son requeridas. En cierta forma es parecido a JSON.
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{ // Qué lindo, ¿no?
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
- Si Vertex ya es explícito se puede evitar poner `"Bell Labs": Vertex`` Véase:
var m = map[string]Vertex{ // Más lindo aún.
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
- Insertar o actualizar un elemento en un mapa:
m[key] = elem
- Recuperar un elemento de un mapa:
elem = m[key]
- Borrar un elemento de un mapa:
delete(m[key])
- Ver si un valor está dentro de un mapa es una función que retorna dos valores: por un lado, si está el elemento (si no está es
nil
), y por otro ladotrue
si se encuentra ofalse
si no.
Ejemplo de ver si un valor está dentro de un mapa:
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
v
va a contener el valor si está dentro del mapa, si no esnil
.ok
va a devolvertrue
si el valor está dentro del mapa, yfalse
si no lo está.
- El tipo
[n]T
es un array den
elementos del tipoT
. - La longitud del array es parte del tipo.
- No se puede modificar su tamaño.
- Digamos que es estático, al menos en su tamaño.
var a [10]int
declara la variable a
como un array de diez enteros (integers).
Ejemplo de un array:
package main
import "fmt"
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
- Un array tiene un tamaño fijo.
- En cambio, un slice es una vista flexible y de tamaño dinámico dentro de los elementos de un array.
- En la práctica los slices son mucho más comunes que los arrays.
- El tipo
[]T
es un slice con elementos del tipoT
. - La expresión
a[0:5]
crea un slice de los primeros cinco elementos del arraya
. - Los slices aportan comportamiento basados en referencia.
- No almacenan datos, sino que describen una sección del array sobre el que actúan como wrapper. Cambiar los elementos de un slice modifica los elementos correspondientes de éste.
- Por ende no hacen copias, son referencias.
- El zero value de un slice es
nil
y no wrappea ningún array. - Se puede hacer un slice de un slice.
Ejemplo:
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
/* output:
[3 5 7]
Devuelve desde la posición 1 hasta la 3.
"Los valores desde 1 hasta 4".
*/
- Cuando se hace un slice se pueden omitir los límites que se pueden definir para usar los defaults.
- El default para el límite inferior es
0
y para el límite superior la longitud del array.
Ejemplo de expresiones slice:
Dado un array var a [10]int
a[0:10]
→ valores desde 0 hasta 10.a[:10]
→ valores hasta 10.a[0:]
→ valores desde 0.a[:]
→ todos los valores.
- Un slice tiene tanto longitud (length) como capacidad (capacity).
- La longitud de un slice es el número de elementos que contiene el mismo.
- La capacidad de un slice es el número de elementos que tiene el array al que referencia.
- La longitud de un slice se obtiene con
len(s)
. - La capacidad de un slice se obtiene con
cap(s)
.
- Esto es un array literal:
[3]bool(true, true, false)
. - Esto es un slice literal:
[]bool(true, true, false)
. Donde crea un array y un slice de ese array. Devuelve el slice.
Ejemplo de varios slices:
import "fmt"
func main() {
q := []int{2, 3, 5, 7, 11, 13} // Devuelve un slice de ints.
fmt.Println(q)
r := []bool{true, false, true, true, false, true} // Devuelve un slice de bools.
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
} // Todo esto devuelve un slice de tuplas del tipo de la estructura anónima que se declaró.
fmt.Println(s)
}
- Los slices pueden ser creados con la función
make
. Así es como se vuelve posible crear arrays de tamaño dinámico. - La función
make
ubica un array "zeroed" y devuelve un slice que referencia a ese array.
a := make([]int, 5) // donde len(a) = 5
Para especificar una capacidad se puede escribir:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
- La función
append
permite agregar nuevos elementos a un slice. - Cumple las expectativas de dinamismo de programador del Siglo XXI.
- El primer parámetro
s
deappend
es un slice de tipoT
, y el resto son valoresT
a agregar al slice. - El valor resultante de
append
es un slice que contiene todos los elementos del slice original más los valores provistos. - Si el valor que wrappea el slice es demasiado chico para que entren todos los nuevos valores, se alocará un array más grande. El slice retornado va a apuntar al nuevo array.
Ejemplo de append:
package main
import "fmt"
func main() {
var s []int
printSlice(s)
// append works on nil slices.
s = append(s, 0)
printSlice(s)
// The slice grows as needed.
s = append(s, 1)
printSlice(s)
// We can add more than one element at a time.
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
Las funciones pueden tomar cero o más argumentos (como se podrán imaginar). El tipo de retorno es explícito y pueden retornar más de un valor. Se definen con la palabra clave func
.
Por ejemplo: una función que retorna un único valor.
func sum(x int, y int) int {
return x + y
}
Los argumentos se pueden acortar si son del mismo tipo, por ejemplo en la función anterior: func sum(x, y int) int
.
Hay dos formas de expresar esto, la más explícita es:
func swap(x, y string) (string, string) {
return y, x
}
y otra en la cual se determina de antemano qué variables se retornarán:
func twoValues(number int) (x, y int) {
x = number * 4 / 9
y = number * 3
return
}
Una declaración return
sin argumentos devuelve los valores definidos al principio de la función. A la declaración return
sin argumentos se le dice "naked return" o "retorno desnudo" ⚆ _ ⚆.
- Las funciones también son valores y se pueden tratar como tales.
- Los valores pueden ser usados como argumentos de funciones y como valores de retorno.
Por ejemplo:
import(
"fmt"
"math"
)
// Es una función que recibe una función con dos argumentos float64 que duelve un float64. La función que recibe esta función devuelve un float64. Copado, ¿no?
func compute(fn func(float64, float64) float64) float64 {
return fn(3,4) // Ejecuta la función que le llegó como parámetro.
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12)) // Ejecuta hypot.
fmt.Println(compute(hypot)) // Pasa la función como parámetro pero no la ejecuta. La ejecución está dentro de la variable compute.
fmt.Println(compute(math.Pow))
}
- Algo muy interesante de cómo se trata la función contenida en la variable
hypot
es su scope. Esta función no se puede exportar y la única forma de llegar a ella es mediante la referencia que mantienehypot
. - Este tipo de funciones se usan mucho para tareas que en un único contexto tienen sentido.
- Un uso más realista para compute podría ser ver el tiempo que tardan las funciones.
- Una clausura o closure es una función que referencia y hace uso de variables fuera de su cuerpo o estructura.
- La función puede acceder y asignar estas funciones fuera de su cuerpo.
Por ejemplo:
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int { // Clausura
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder() // Referencio
for i := 0; i < 10; i++ {
fmt.Println(
pos(i), // llamo
neg(-2*i), // llamo
)
}
}
- La función
adder
devuelve una clausura. Cada clausura está ligada a su variablesum
.
- Go no tiene clases, pero se pueden definir métodos sobre los tipos, las estructuras.
- Los métodos tienen un argumento especial que va entre medio de su nombre y la declaración de función. A éste argumento se le llama receiver.
Por ejemplo:
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
Abs()
tiene un receiver del tipoVertex
.- Son básicamente una función con un argumento más (especial) que es el receiver.
- Yo puedo evitar el receiver y poner el tipo como un parámetro más siguiente la estructura normal de una función, pero no voy a poner llamar a la función (método en realidad) haceindo
type.Method()
, sino que va a serMethod(type)
. - Puedo enviar punteros de ese tipo al receiver en vez de el tipo. Esto es importante ya que normalmente se suelen usar más puntero de ese tipo que el valor en sí, ya que los métodos a veces buscan modificar las referencias en vez de usar sus valores.
- La diferencia entre un pointer receiver y un value receiver es que value receiver utiliza una copia de del valor original, mientras que el pointer receiver utiliza la referencia en sí. Me deja modificar los valores de lo que recibí.
- Con estructuras se interepreta automáticamente que es un puntero, no hay que hacer
&
explícito. Solamente funciona con métodos. - Si uso el puntero optimizo ya que no hago copias de valores si no que manejo el "valor" que me llegó original.
- "Si querés una copia, hacetela."
- "En caso de dudas sobre qué vas a hacer, usá punteros."
- Una interfaz es un conjunto de firmas de métodos.
- Permiten implementar el polimorfismo.
- Un valor del tipo de interface puede contener a cualquier valor que la implemente.
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f
a = &v
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
- Podemos decir que por atrás el valor de una interfaz tiene una tupla que mapea un valor y un tipo concreto.
Sorprendentemente, en Go no existen los ciclos while. Sin embargo, se pueden definir dentro de un for. No usan paréntesis.
Ejemplo de un for genérico:
while := 0 // Dónde está mi while? Acá está.
for i := 0; i < 100; i++ {
while += i
}
Un for que se comporta como un while
:
while := 0 // No planeo dejar de usar variables que se llamen while en estos ejemplos.
for while < 1000 {
while += 1 // Se está tornando confuso esto.
}
// Por las dudas algo más claro:
sum := 0
for sum < 1000 {
sum += 1 // Gracias por estar Go.
}
Un for ever (while para los amigos), o un ciclo infinito:
import fmt
for {
fmt.Println("Extraño mis whiles.")
}
Se pueden hacer asignaciones y definir varios steps en un único for, por ejemplo:
import fmt
for i, j := 0, 10; i < j; i, j -= 1, i += 1 {
// Pensé que la idea era que se entienda más.
fmt.Pritntln(i, j)
}
range
es una forma del ciclofor
que itera sobre un slice o un mapa (map).- En cada iteración
range
retorna dos valores: el índice, y una copia del valor en ese índice.
Ejemplo:
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
- El ejemplo anterior trabaja con tanto los índices como los valores del slice.
- Si se desea solamente obtener el valor, y no el índice:
for _, value := range pow {
fmt.Printf("%d\n", value)
}
- Si se desea utilizar solamente el índice puede directamente no incluirse
value
, se saca el, value
.
for i := range pow {
pow[i] = 1 << uint(i) // == 2**i
}
Tampoco usan paréntesis (ツ)_/¯.
A continuación pueden ver varios ejemplos de la declaración if
, incluyendo if-else
, if-elseif
.
const number = 1
if number < 1 {
return -1
} else {
return 1
}
// o bien
if number < 1 {
return -1
}
return 1
// o con el milenario else if
if number < 1 {
return -1
} else if number > 1 {
return 1
}
return 0
switch
es muy parecido a como es en otros lenguajes de programación, un ejemplo mostrando sistemas operativos, pero no tiene break:
import (
"fmt"
"runtime"
)
os := runtime.GOOS
switch os { // también podíamos ⧸⧸escribirlo así: switch os := runtime.GOOS; os { ... }
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
Amo los ejemplos de A tour of Go
Como siempre, switch
evalúa desde arriba para abajo, si se usa fallthrough
no se sale del switch en un break.
Si bien hay formas muchísimo más elegantes (y en cierta forma más entendibles que usar un switch
para ver sistemas operativos), sus ventajas son:
- Es más rapido.
- Está todo en un mismo lugar.
Un ejemplo más agradable:
import (
"fmt"
"runtime"
)
var osLabel string
switch os := runtime.GOOS; os {
case "darwin":
osLabel = "OS X"
case "linux":
osLabel = "Linux"
default:
osLabel = os
}
fmt.Printf("%s. \n", osLabel)
- Acá no repetimos código.
- En el caso default se usa la descripción propia del sistema operativo.
- No tiene sentido que se repita el switch. Debería estar en una función.
- Un
switch
sin condición es equivalente a unswitch true
(no era muy legible eso). - Esto puede resultar como una mejor forma de reescribir cadenas
if-else
muy largas.
import "fmt"
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
- Marca un statement que se va a ejecutar cuando la función en la que está declarado finalice.
- Está bueno para hacer cleanup de cosas que necesito cuando está el contexto pero que después se cierre.
- ¿Cuál es la diferencia entre escribirlo al final y usar
defer
? Que condefer
las cosas se evalúan al principio (donde está la instrucción condefer
, aún si se ejecuta después). - Vive en el mismo scope de la función que lo contiene.
Ejemplo:
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
/* output:
hello
world
*/
- No conviene usarlas mucho para lógica de negocio ya que no es muy explícito a primera vista lo que hacen.
- Las cosas que no son explícitas en cuanto al orden de ejecución conviene usarlas con cuidado.
- Los defers se pueden apilar en una estructura de datos que conocemos muy bien. Cuando una función retorna, sus llamadas diferidas (deferred calls) son ejecutadas en un orden Last In First Out (LIFO).
Ejemplo:
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
/*output:
counting
done 9 8 7 6 5 4 3 2 1 0
# le saqué los \n para ocupar menos espacio.
*/
- Se puede apreciar que no es tan claro lo que hace, si bien es un ejemplo simple.
- Es buena idea evitar usar #magitrucos del lenguaje para resolver problemas, para que el código sea entendible para terceros.
// Package stringutil contains utility functions for working with strings.
package stringutil
// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(s string) string {
r := []rune(s) // Integers that map strings to their unicode value.
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
En la siguiente línea: for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1
se inicializan tanto i
como j
, sin embargo hay un detalle: se trabaja como una tupla cuando en realidad no lo es. Esto se traduce en que i tiene la siguiente asignación: i := 0
y j la siguiente: j := len(r)-1
.
Si tuviésemos que hacer un mapa de las asignaciones, quedaría así:
i → 0
j → len(r)-1
o bien representado en tuplas:
(i, j)
↓ ↓
(0, len(r)-1)
Si bien es un poco confuso en esa estructura for
, lo mismo ocurre en la línea dentro del ciclo:
r[i], r[j] = r[j], r[i]
donde
r[i] -> r[j]
y
r[j] -> r[i]
**Aclaración: ** Nótese que uso → y ↓ para representar una asignación.
En Go, los nombres definen si son exportados (provienen de otro paquete), o bien si son internos (propios del paquete actual).
Externos son aquellos cuyo nombre comienza con su primer caracter en mayúscula, por ejemplo: Hola
Internos, en cambio, son aquellos cuyo nombre está completamente en minúscula, por ejemplo: adios
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.Pi)
}
Nótese que tanto Pi
como Println
, donde muy posiblemente Pi
sea una variable ya que es un valor constante y donde Println
es una función o método empiezan ambos con su primer caracter en mayúscula.
GoLang provee una humilde plataforma de testing, cuyo paquete se llama testing. Un código que se utliza para testear la fución anterior para revertir una cadena de caracteres es el siguiente:
package stringutil
import "testing"
func TestReverse(t *testing.T) {
cases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{"Hello, 世界", "界世 ,olleH"},
{"", ""},
}
for _, c := range cases {
got := Reverse(c.in)
if got != c.want {
t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
}
}
}
Veamos la primer sección, donde se declara un array de estructuras.
cases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{"Hello, 世界", "界世 ,olleH"},
{"", ""},
}
Nótese cómo aquí también se manipula cases
, que es un array de estructuras y se define sus valores mediante tuplas, tal como en la función.
Un mapeo de esto sería:
in -> {"Hello, world",
want -> "dlrow ,olleH"},
in -> {"Hello, 世界",
want -> "界世 ,olleH"},
int -> {"",
want -> ""}
Ahora vamos a hablar de la otra sección:
for _, c := range cases {
got := Reverse(c.in)
if got != c.want {
t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
}
}
La función range
en for _, c := range cases
devuelve dos valores, donde el primero es irrelevante ya que simplemente queremos el valor de cada tupla.
En got := Reverse(c.in)
se llama a la función sobre lo que queremos revertir para testear.
if got != c.want {
t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
}
En cada iteración se corrobora si la string invertida provista por la estructura de testo por la función Reverse
es igual a lo que se esperaba. De no serlo, el test falla.
- Tiene una librería para manejar SQL y un driver para cada DBMS distinto.
- Para 'abrir' la base de datos se usa
db, err := sql.Open("mysql", "user:password@/dbname?parseTime=True")
"mysql"
es el tipo de base de datos que es, la otra línea define el usuario, la contraseña, el nombre de la DB y por último que parsee los DateTime a un tipo de fecha propio de Go.- Casi siempre, independientemente del driver (porque nótese que no usamos el driver
mysql
, lo usadatabase/sql
que es el paquete cuyas funciones nosotros hacemos uso de. - Está bueno buscar errores en cada lugar que es probable que devuelva un error, por ende:
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
db, err := sql.Open("mysql", "user:password@/dbname?parseTime=True")
if err != nil {
panic(err.Error())
}
defer db.Close()
- La idea es que maneje los errores de forma apropiada y no mediante un panic.
db.Open
no maneja la conexión propiamente dicha,db.Ping()
testea la conexión.- El _ en
import _ "github.com/go-sql-driver/msql"
hace que el paquete sea invisible para nuestro programa, pero lo usadatabase/sql
, por lo que es requerido por atrás. - La query la ejecutamos haceindo:
rows, err := db.Query("SELECT * FROM table")
- Con su respectivo manejo de errores.
- Sin embargo tenemos que iterar para poder recibirlo:
import "time" // No se olviden de esto
type Cliente struct {
Id int
Nombre string
Apellido string
Categoria string
FechaNacimiento time.Time
}
var clientes []Cliente
for rows.Next(){
c := new(Cliente)
err = rows.Scan(&c.Id, &c.Nombre, &c.Apellido, &c.Categoria, &c.FechaNacimiento)
if err != nil {
panic(err.Error())
}
append(clientes, *c)
}
- Lo que se hace en el último bloque de código es "escanear" y "mapear" los resultados de los registros devueltos por la query que hicimos en
row, err := db.Query("SELECT * FROM table")
- Scan recibe punteros a los valores de una estructura a la que queremos mapear los resultados de cada fila.
- Para insertar filas las sentencias son un poco diferentes.
- Vamos a crear una función aparte para chequear por errores (que en realidad deberían ser específicas para cada error) porque es un poco insoportable verlos en todos lados.
func checkForErrors(err error) {
if err != nil {
panic(err.Error())
}
}
func AddCliente(cliente Cliente) {
query, err := db.Prepare("INSERT INTO Cliente (nombre, apellido, categoria, fechaNacimiento) VALUES (?, ?, ?, ?)")
checkForErrors(err)
_, err = query.Exec(cliente.Nombre, cliente.Apellido, cliente.Categoria, cliente.FechaNacimiento)
checkForErrors(err)
}
Query.Exec
ejecuta la query y es donde se pasan los parámetros que se ubican en los placeholders la parte de la inserción (VALUES (?, ?, ?, ?)
),db.Prepare
(que se asigna a query) se hace antes para definir cómo será la query.UPDATE
yDELETE
son muy similares.
- Es un framework muy simple para crear aplicaciones web.
- La verdad es que la documentación (al final del documento) es mucho mejor para explicar cómo funciona, pero...
- Es un framework para aplicaciones web que usa (naturalmente) el protocolo HTTP y sus verbos.
- Se pueden hacer tanto sitios webs como APIs.
- Los verbos HTTP son:
GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE
- No tiene un framework fuerte encima, tal como lo es MVC, pero lo podemos codear.
- La forma en que se acceden a los recursos es mediante rutas mapeadas a respectivos verbos HTTP.
- Miren la docu que es re clara.
##Paquetes
Es un concepto central para hacer sistemas grandes, a diferencia de C que tenía namespaces limitados.
Todos los fuentes comienzan con la palabra clave package
. Por ejemplo, package name
. Para importar hacer uso de sus funcionalidades debo referenciar al paquete, tal como name.operation
. Dicho de otra manera, el nombre del paquete se usa para acceder a las funcionalidades contenidas dentro de sí.
La convención de nombrado de paquetes dicta lo siguiente:
-
El nombre del archivo debe ser el último elemento del import path. El nombre del paquete debe ser el mismo que el directorio en el que está, tal como java.
-
Los comandos ejecutables siempre tienen que declarar el
package main
-
No es necesario que los nombres de los paquetes difieran siempre y cuando el import path sea único.
Gracias por el contenido que publicaste, estoy buscando información sobre como solucionar problemas de N+1 querys en GoLang, ¿tendrás alguna información?