Varios autores (un ejemplo) recomiendan NO establecer el valor de GOROOT cuando usamos la localización estándar. Así que he comentado esa parte en mi fichero ~/.profile
Para dejar configurado el entorno de trabajo en Go conviene conocer las siguientes variables de entorno (aunque hay más):
GOROOT
Es el directorio donde se localiza la biblioteca estándar de Go. Por defecto Go asume la ruta /usr/local/go, así que estrictamente hablando NO es necesario establecer esta variable si has instalado en /usr/local. (Con retoques de esta variable es posible instalar varias versiones de Go)
GOBIN
Es el directorio donde quedan instalados los binarios de Go cuando ejecutamos un comando go install .... Solo deberías tocar esta variable si no quieres instalar los binarios en la localización por defecto: $GOPATH/bin.
GOOS
especifica el sistema operativo, es opcional. En mi caso tiene valor linux
GOARCH
especifica la arquitectura del procesador de nuestra máquina, también es opcional, en mi caso es amd64
GOPATH
La ruta a nuestro directorio de trabajo (nuestro workspace). Esta variable es obligatoria y no tiene valor por defecto, podeis ponerla en donde tenga sentido para vosotros, por ejemplo /home/salvari/code/go
Evidentemente tenemos que dejar las variables de entorno exportadas y añadir al PATH de nuestro usuario las que correspondan (ver punto anterior)
Ver el entorno
Puedes ver el entorno completo de Go con el comando go env
Más de GOPATH
Abandonando GOPATH
Estamos usando ya go 1.17. Hay planes firmes para abandonar el tratamiento de dependencias via GOPATH y centrarse exclusivamente en gestionarlas a través de módulos.
Todo lo que comento aquí de GOPATH hay que tratarlo con pinzas.
Go espera que en el directorio $GOPATH haya tres subdirectorios:
1
2
3
bin/
pkg/
src/
bin/ contiene los ejecutables que se generan cuando ejecutamos go install ...
pkg/ contiene los paquetes instalados en nuestro sistema con go get ...
src/aquí es donde deberíamos alojar nuestro código fuente (no es obligatorio)
Como hemos dicho en $GOPATH/src/ es donde tenemos que programar, ahí es donde tiene que estar nuestro código fuente, pero ojo la ruta exacta de nuestros proyectos es función de nuestra plataforma de control de código, y tiene que tener la forma Source-Control-Platform/User/Repository.
Por ejemplo podríamos tener los siguientes proyectos en el directorio src/:
go get -u golang.org/x/lint/golint
go get -u golang.org/x/tools/cmd/godoc
El comando go get se usa exclusivamente para añadir dependencias al módulo que estemos creando.
Los binarios que queremos usar en nuestro sistema se instalan con el nuevo estilo de instalación recomendado desde go v1.17:
1
2
go install golang.org/x/lint/golint@latest
go install golang.org/x/tools/cmd/godoc@latest
Alternativamente podemos instalar todas las herramientas de Go con:
1
go get -u golang.org/x/tools/...
Pero con esta opción me falla la instalación de gopls, una herramienta que necesito en mi configuración de Emacs
gopls para protocolos LSP en editores
Desde un directorio que no sea el GOPATH
1
2
# GO111MODULE=on # not needed with 'go install'go install golang.org/x/tools/gopls@latest
Herramientas adicionales
gopkgs
go-outline
dlv
dlv-dap
staticcheck
1
2
3
4
5
6
go install github.com/uudashr/gopkgs/v2/cmd/gopkgs@latest
go install github.com/ramya-rao-a/go-outline@latest
go install github.com/go-delve/delve/cmd/dlv@latest
go install github.com/go-delve/delve/cmd/dlv@master
go install honnef.co/go/tools/cmd/staticcheck@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
Alias para zsh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# golang aliasesaliasgob='go build'# Build your codealiasgoc='go clean'# Removes object files from package source directoriesaliasgod='go doc'# Prints documentation commentsaliasgof='go fmt'# Gofmt formats (aligns and indents) Go programs.aliasgofa='go fmt ./...'# Run go fmt for all packages in current directory, recursivelyaliasgog='go get'# Downloads packages and then installs them to $GOPATHaliasgoi='go install'# Compiles and installs packages to $GOPATHaliasgol='go list'# Lists Go packagesaliasgom='go mod'# Access to operations on modulesaliasgop='cd $GOPATH'# Takes you to $GOPATHaliasgopb='cd $GOPATH/bin'# Takes you to $GOPATH/binaliasgops='cd $GOPATH/src'# Takes you to $GOPATH/srcaliasgor='go run'# Compiles and runs your codealiasgot='go test'# Runs testsaliasgov='go vet'# Vet examines Go source code and reports suspicious constructs
Go Modules
Básicamente un módulo (Go Module) es una colección de paquetes (packages) almacenados en un arbol de directorios que tiene un fichero go.mod en su raiz. El fichero go.mod especifica el module path que es la ruta canónica al módulo que se usará para importarlo, y todas las depencias requeridas por el módulo, es decir el conjunto de módulos (otros módulos) que serán necesarios para compilar con éxito este módulo. Cada dependencia se especifica con su correspondiente module path y un número de versión semántica (semantic version)
Crear un módulo
El comando para crear un módulo es: go mod init
Antiguamente el compilador distinguía dos formas de funcionamiento, el go module mode y el gopath mode. A estas alturas tenemos que usar el go module mode el otro está en desuso. De hecho el compilador ahora funciona en go module mode por defecto.
Si estamos en un directorio por debajo del $GOPATH nuestro directorio de proyecto debería tener esta forma:
1
$GOPATH/src/example.com/$USER/moduleName
En este caso el comando go mod init no necesita parámetros, genera un module path basado en la estructura de directorios.
Si estamos trabajando en un directorio fuera del $GOPATH tendremos que especificar el module path en el comando:
En general hace que escribamos un código más desacoplado (tiendes a aislar más las cosas para facilitar los test)
Tiene la ventaja obvia de tener todo controlado con test fáciles de repetir
Bien escritos valen como documentación de bajo nivel
Previene regresiones (fallos que reaparecen al avanzar en el desarrollo)
Potencia la arquitectura y diseño modulares
Tres reglas
Se suele hablar del ciclo: ““rojo, verde y refactorizar” (red, green and refactor)
Primero hay que escribir un test que falla
Hay que programar lo justo para que el test no falle. No se puede escribir más código que el necesario para que el test no falle.
Se revisa el código para refinarlo (tanto el código producto como el código de los test)
Los pasos que nos proponen en la página de Learn Go with Test:
Escribe un test
Haz lo necesario para que el compilador pase sin errores
Ejecuta el test, comprueba que falla y que el mensaje de fallo es significativo
Escribe el código suficiente para que el test no falle
Refina el código (Refactor)
Nos dicen literalmente:
Este ciclo puede parecer tedioso pero es importante mantenerlo.
Al hacerlo no solo te aseguras de tener test significativos, sino que aseguras un buen diseño del software respaldado por la seguridad que dan los test.
Ver fallar el test es un paso importante por qué permite comprobar el mensaje de error (que debe ser significativo) Como desarrollador puede ser muy difícil trabajar con código cuando los mensajes de fallo de los test no dan una idea clara de que está pasando.
Asegurándote de que los test se ejecutan rápido y estableciendo un entorno de trabajo que facilite escribir los test puedes "entrar en sintonía" y programar de la forma más eficiente posible.
Si no escribes los test, te verás obligado a comprobar el funcionamiento del código ejecutándolo manualmente, eso rompera tu concentracion y a la larga te hara perder bastante mas tiempo que escribir correctamente los test.
Sintáxis para test
Escribir un test es como escribir una función pero:
Tiene que estar en un fichero de la forma xxx_test.go
El nombre de la función que implementa el test tiene que empezar por Test
La función que implementa el test tiene que tener un único argumento: t *testing.T
Para poder usar ese tipo de argumento y otras facilidades de testeo es necesario hacer import "testing"
Código independiente es más fácil de testear
En el ejemplo Hello World es mejor escribir una funcion que devuelve la cadena con el saludo que una función que escribe el saludo. Por que así somos independientes de la salida que puede ser por pantalla, por una página web, por un mensaje, escrita, etc. etc. Y de paso es más simple de testear.
Go
_
Es el black identifier podemos usarlo para pasar de variables que
no vamos a usar.
1
2
3
4
a="Una cadena"for_,r:=rangea{fmt.Println(r)}
make
Parece que vale para hacer un alloc de memoria.
1
counts:=make(map[string]int)
time
La especificación de formatos tiene su gracia ¬_¬ ver referencia
Arrays
1
2
3
4
5
6
7
8
varmyArray1=[3]int// will be filled with so called
// zero values, for integers: 0
varmyArray2=[5]int{1,2,3,4,5}// number of values between { } can
// not be larger than size (ofc)
varmyArray3=[…]int{1,2,3,4}// the compiler will count the
// array elements for you
varfooint// declaración sin inicialización
varfooint=42// declaración con inicialización
varfoo,barint=42,1302// declaración e inicialización múltiples
varfoo=42// se omite el tipo, será inferido
foo:=42// abreviado, sólo es válido en el cuerpo de las funciones,
// el tipo siempre es implícito
constconstant="Esto es una constante"// iota se puede usar para números que se incrementan, empezando por cero
const(_=iotaabc=1<<iotad)fmt.Println(a,b)// 1 2 (0 is skipped)
fmt.Println(c,d)// 8 16 (2^3, 2^4)
// una función simple
funcfunctionName(){}// función con parámetros (aquí también va el tipo después de la variable)
funcfunctionName(param1string,param2int){}// multiples parámetros del mismo tipo
funcfunctionName(param1,param2int){}// podemos especificar el tipo devuelto por la función
funcfunctionName()int{return42}// Pueden devolver multiples valores
funcreturnMulti()(int,string){return42,"foobar"}varx,str=returnMulti()// si los valores devueltos tienen nombre no hace falta especificarlos en el Return
funcreturnMulti2()(nint,sstring){n=42s="foobar"// n and s will be returned
return}varx,str=returnMulti2()
funcmain(){// asignar una función a una variable
add:=func(a,bint)int{returna+b}// usar el nombre para llamar a la función
fmt.Println(add(3,4))}// 'Closures', lexically scoped: Las funciones pueden acceder valores que estában dentro del alcance (scope)
// cuando se definió la función
funcscope()func()int{outer_var:=2foo:=func()int{returnouter_var}returnfoo}funcanother_scope()func()int{// won't compile because outer_var and foo not defined in this scope
outer_var=444returnfoo}// Closures
funcouter()(func()int,int){outer_var:=2inner:=func()int{outer_var+=99// outer_var from outer scope is mutated.
returnouter_var}inner()returninner,outer_var// return inner func and mutated outer_var 101
}
Variadic Functions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
funcmain(){fmt.Println(adder(1,2,3))// 6
fmt.Println(adder(9,9))// 18
nums:=[]int{10,20,30}fmt.Println(adder(nums...))// 60
}// Usando ... antes del nombre del tipo del último parámetro indicamos que la función acepta cero o mas parámetros de ese tipo
// La función se usa como cualquier otra, excepto que podemos pasar tantos parámetros como queramos del último parámetro definido
funcadder(args...int)int{total:=0for_,v:=rangeargs{// Iterates over the arguments whatever the number.
total+=v}returntotal}
Tipos Built-in
1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32 ~= a character (Unicode code point) - very Viking
float32 float64
complex64 complex128
Conversión de tipos
1
2
3
4
5
6
7
8
variint=42varffloat64=float64(i)varuuint=uint(f)// alternative syntax
i:=42f:=float64(i)u:=uint(f)
Las unidades de encapsulado en Go de menor a mayor nivel serían:
Funciones
Paquetes
Módulos
Los paquetes nos permiten organizar los ficheros de código para hacerlos modulares y reutilizables además de facilitar el mantenimiento del software.
Cada fichero de código Go debe pertenecer a un paquete. La pertenencia se declara con package al principio de cada uno de los ficheros fuente, evidentemente un paquete puede “poseer” varios ficheros.
Los ejecutables están en el paquete main. Es un paquete especial.
Por convención los programas ejecutables (los del paquete main) se llaman comandos (commands). El resto se llaman simplemente paquetes (packages)
Por convención: <nombre_del_paquete> == último nombre del import path (import path math/rand => package rand) Lo normal es que todos los ficheros del paquete rand se guarden en el directorio rand/
Si el identificador empieza con mayúscula: se exporta el símbolo (será visible desde otros paquetes)
Si el identificador empieza con minúscula: privado (no será visible desde otros paquetes)
Los módulos son coleciones de paquetes. Son imprescindibles para crear nuestros propios paquetes por qué la ruta de nuestros paquetes viene dada por el módulo. En cuanto quieras empezar a organizar tu código en paquetes inevitablemente tendrás que crear también módulos.
El flujo típico de trabajo para un proyecto estructurado con Packages sería el siguiente:
Supongamos que nuestro proyecto será un “pomodoro” y que va a gestionar Timers y Notifications (por decir algo)
Creamos el directorio del proyecto que vamos a llamar pomodoro, dentro de ese directorio tendremos el package main con los “comandos”.
1
2
3
mkdir pomodoro
cd pomodoro
touch main.go
Iniciamos el modulo en la raiz del proyecto
1
2
3
4
go mod init pomodoro
# Aunque lo mas correcto sería iniciarlo comogo mod init gitlab/salvari/pomodoro
Creamos los subdirectorios para los packages: timer y notification. Dentro de los subdirectorios creamos un fichero de código (pueden ser tantos ficheros como queramos)
El contenido del fichero go.mod será (la versión de go depende de lo que tengas instalado):
1
2
3
modulepomodorogo1.17
Cada fichero con extensión .go debe empezar siempre con la declaración del package. En nuestro caso main.go declarará package main y por ejemplo notification.go declarará package notification.
Ya tenemos todo estructurado, ahora en nuestros ficheros de código podremos hacer imports de este estilo
funcmain(){// Basic one
ifx>10{returnx}elseifx==10{return10}else{return-x}// You can put one statement before the condition
ifa:=b+c;a<42{returna}else{returna-42}// Type assertion inside if
varvalinterface{}val="foo"ifstr,ok:=val.(string);ok{fmt.Println(str)}}
// Solo hay bucle `for`, no hay `while`, ni `until`
fori:=1;i<10;i++{}for;i<10;{// bucle while
}fori<10{// se pueden omitir los ; si solo hay una condición
}for{// Si omitimos la condición tenemos un while (true)
}// Podemos usar use break/continue en el bucle activo
// o usar break/continue con etiquetas (para bucles más externos)
here:fori:=0;i<2;i++{forj:=i+1;j<3;j++{ifi==0{continuehere}fmt.Println(j)ifj==2{break}}}there:fori:=0;i<2;i++{forj:=i+1;j<3;j++{ifj==1{continue}fmt.Println(j)ifj==2{breakthere}}}
// switch statement
switchoperatingSystem{case"darwin":fmt.Println("Mac OS Hipster")// cases break automatically, no fallthrough by default
case"linux":fmt.Println("Linux Geek")default:// Windows, BSD, ...
fmt.Println("Other")}// al igual que con el 'for' y el 'if' podemos tener una sentencia de asignación justo antes de la variable del switch
switchos:=runtime.GOOS;os{case"darwin":...}// se pueden hacer comparaciones en los casos del switch
number:=42switch{casenumber<42:fmt.Println("Smaller")casenumber==42:fmt.Println("Equal")casenumber>42:fmt.Println("Greater")}// los casos pueden ser listas de valores separados por comas
varcharbyte='?'switchchar{case' ','?','&','=','#','+','%':fmt.Println("Should escape")}
Arrays, Slices, Ranges
Arrays
Tienen longitud fija. Los arrays de longitudes son tipos diferentes
vara[10]int// declara un array de enteros (int) con longitud 10. ¡La longitud determina el tipo!
a[3]=42// establece el valor de un elemento
i:=a[3]// lee el valor de un elemento
// array literals
vara=[2]int{1,2}a:=[2]int{1,2}//shorthand
a:=[...]int{1,2}// elipsis -> El compilador infiere la longitud del array
// no hay paso por referencia
arr1:=arr2// Se hace una copia
arr1:=&arr2// Se copia la referencia
// multidimensionales
// var variable_name [SIZE1][SIZE2]…[SIZEN] variable_type
a:=[3][4]int{{0,1,2,3},// initializers for row indexed by 0
{4,5,6,7},// initializers for row indexed by 1
{8,9,10,11}// initializers for row indexed by 2}
Slices
Un slice es una abstracción apuntando a un array.
Se pueden crear a partir de un array existente, en caso contrario Go creará el array detrás de las bambalinas
No se especifica la dimensión
Realmente un slice son tres datos:
Un puntero a la secuencia de datos en memoria
Una longitud (lenght, len(a)) que almacena el número de elementos
Una capacidad (capacity, cap(a)) que es el total de posiciones reservadas en memoria
Cuando se asigna un slice en realidad se copian esos valores, así que se copia la referencia
Si un slice tiene que crecer el compilador normalmente es conservador y duplica la capacidad. Eso implicará re-localizaciones del array subyacente en memoria
vara[]int// declare a slice - similar to an array, but length is unspecified
vara=[]int{1,2,3,4}// declare and initialize a slice (backed by the array given implicitly)
a:=[]int{1,2,3,4}// shorthand
chars:=[]string{0:"a",2:"c",1:"b"}// ["a", "b", "c"]
varb=a[lo:hi]// creates a slice (view of the array) from index lo to hi-1
varb=a[1:4]// slice from index 1 to 3
varb=a[:3]// missing low index implies 0
varb=a[3:]// missing high index implies len(a)
a=append(a,17,3)// append items to slice a
c:=append(a,b...)// concatenate slices a and b
// create a slice with make
a=make([]byte,5,5)// first arg length, second capacity
a=make([]byte,5)// capacity is optional
// create a slice from an array
x:=[3]string{"Лайка","Белка","Стрелка"}s:=x[:]// a slice referencing the storage of x
Operaciones sobre Arrays y Slices
len(a) devuelve la longitud de un array o slicea. Es un built-in no un atributo o método del array.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// iterar sobre un array o slice
fori,e:=rangea{// i es el index, e es el elemento
}// si no vamos a usar el índice:
for_,e:=rangea{// e es el elemento
}// si solo queremos usar el índice
fori:=rangea{}// Desde Go 1.4 se puede iterar sin variables:
forrangetime.Tick(time.Second){// hacer algo cada segundo
}
Maps
Los mapas (maps) son inmutables
No se garantiza ningún orden
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
varmmap[string]intm=make(map[string]int)m["key"]=42fmt.Println(m["key"])delete(m,"key")elem,ok:=m["key"]// test if key "key" is present and retrieve it, if so
// map literal
varm=map[string]Vertex{"Bell Labs":{40.68433,-74.39967},"Google":{37.42202,-122.08408},}// iterate over map content
forkey,value:=rangem{}
Structs
No hay clases en Go, solo structs. Las structs pueden tener métodos.
// Una struct es un tipo. También es una colección de campos
// Declaración
typeVertexstruct{X,Yint}// Creación
varv=Vertex{1,2}varv=Vertex{X:1,Y:2}// Creación de la struct definiendo sus valores con clave
varv=[]Vertex{{1,2},{5,2},{5,5}}// Creación de un slice de structs
// Acceso a miembros de la struct
v.X=4// Se pueden declarar métodos sobre structs. La referencia a la struct que recibe el método
// tiene que ir entre la palabra clave 'func' y el nombre del método
// LA ESTRUCTURA SE COPIA EN CADA LLAMADA AL MÉTODO
func(vVertex)Abs()float64{returnmath.Sqrt(v.X*v.X+v.Y*v.Y)}// Invocando el método
v.Abs()// Para los métodos que mutan la estructura usamos punteros a esa struct
// como tipos. De esta forma EVITAMOS LA COPIA de la estructura al invocar el método
func(v*Vertex)add(nfloat64){v.X+=nv.Y+=n}
Tenemos un caso especial con los contructores. Si queremos evitar que se construyan objetos sin usar el constructor:
1
2
3
4
5
6
7
8
9
10
11
12
packagematrixtypematrixstruct{// NO EXPORTADO
....}funcNewMatrix(rows,colsint)*matrix{m:=new(matrix)m.rows=rowsm.cols=colsm.elems=make([]float,rows*cols)returnm}
Como no exportamos la estructura base, solo se pueden construir nuevos objetos a través del constructor.
La función NewMatrix puede simplificarse fácilmente:
Si el nombre de la struct o de alguno de sus campos empieza por minúscula no serán visibles fuera de la propia struct
Anonymous structs:
Más económicas y seguras que usar: map[string]interface{}.
1
2
3
point:=struct{X,Yint}{1,2}
Punteros
1
2
3
4
5
6
7
p:=Vertex{1,2}// p es de tipo Vertex
q:=&p// q es un puntero a un Vertex
r:=&Vertex{1,2}// r también es un puntero a un Vertex
// El tipo de un puntero a un Vertex es *Vertex
vars*Vertex=new(Vertex)// new crea un puntero a una nueva instancia de Vertex
Interfaces
Un interface es una especie de “contrato”.
Dentro del interface especificamos las funciones que tiene que soportar un tipo para satisfacer el interface
El interface nos da una capa extra de abstracción, podemos definir una función que procese o devuelva el “tipo” definido por el interface
1
2
3
4
5
6
7
8
9
10
11
12
// declaración
typeAwesomizerinterface{Awesomize()string}// los tipos no declaran en ningún sitio implementar un interface
typeFoostruct{}// los tipos que implementan todos los métodos de un interface, satifacen ese interface implicitamente
func(fooFoo)Awesomize()string{return"Awesome!"}
Embedding
There is no subclassing in Go. Instead, there is interface and struct embedding.
// ReadWriter implementations must satisfy both Reader and Writer
typeReadWriterinterface{ReaderWriter}// Server exposes all the methods that Logger has
typeServerstruct{HoststringPortint*log.Logger}// initialize the embedded type the usual way
server:=&Server{"localhost",80,log.New(...)}// methods implemented on the embedded struct are passed through
server.Log(...)// calls server.Logger.Log(...)
// the field name of the embedded type is its type name (in this case Logger)
varlogger*log.Logger=server.Logger
Errores
No hay gestión de excepciones en Go. Las funciones que pueden lanzar un Error simplemente devuelven un valor adicional de tipo Error.
El interfaceError tiene esta pinta:
1
2
3
typeerrorinterface{Error()string}
Una función que puede devolver un error:
1
2
3
4
5
6
7
8
9
10
11
funcdoStuff()(int,error){}funcmain(){result,err:=doStuff()iferr!=nil{// handle error
}else{// all is good, use result
}}
Concurrency
Goroutines
Goroutines are lightweight threads (managed by Go, not OS threads). go f(a, b) starts a new goroutine which runs f (given f is a function).
1
2
3
4
5
6
7
8
9
10
11
12
13
// just a function (which can be later started as a goroutine)
funcdoStuff(sstring){}funcmain(){// using a named function in a goroutine
godoStuff("foobar")// using an anonymous inner function in a goroutine
gofunc(xint){// function body goes here
}(42)}
ch:=make(chanint)// create a channel of type int
ch<-42// Send a value to the channel ch.
v:=<-ch// Receive a value from ch
// Non-buffered channels block. Read blocks when no value is available, write blocks until there is a read.
// Create a buffered channel. Writing to a buffered channels does not block if less than <buffer size> unread values have been written.
ch:=make(chanint,100)close(ch)// closes the channel (only sender should close)
// read from channel and test if it has been closed
v,ok:=<-ch// if ok is false, channel has been closed
// Read from channel until it is closed
fori:=rangech{fmt.Println(i)}// select blocks on multiple channel operations, if one unblocks, the corresponding case is executed
funcdoStuff(channelOut,channelInchanint){select{casechannelOut<-42:fmt.Println("We could write to channelOut!")casex:=<-channelIn:fmt.Println("We could read from channelIn")case<-time.After(time.Second*1):fmt.Println("timeout")}}
Channel Axioms
A send to a nil channel blocks forever
1
2
3
varcchanstringc<-"Hello, World!"// fatal error: all goroutines are asleep - deadlock!
A receive from a nil channel blocks forever
1
2
3
varcchanstringfmt.Println(<-c)// fatal error: all goroutines are asleep - deadlock!
A send to a closed channel panics
1
2
3
4
5
varc=make(chanstring,1)c<-"Hello, World!"close(c)c<-"Hello, Panic!"// panic: send on closed channel
A receive from a closed channel returns the zero value immediately
fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ")// basic print, plus newline
p:=struct{X,Yint}{17,2}fmt.Println("My point:",p,"x coord=",p.X)// print structs, ints, etc
s:=fmt.Sprintln("My point:",p,"x coord=",p.X)// print to string variable
fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0)// c-ish format
s2:=fmt.Sprintf("%d %f",17,17.0)// formatted print to string variable
hellomsg:=`
"Hello" in Chinese is 你好 ('Ni Hao')
"Hello" in Hindi is नमस्ते ('Namaste')
`// multi-line string literal, using back-tick at beginning and end
Reflection
Type Switch
A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
funcdo(iinterface{}){switchv:=i.(type){caseint:fmt.Printf("Twice %v is %v\n",v,v*2)casestring:fmt.Printf("%q is %v bytes long\n",v,len(v))default:fmt.Printf("I don't know about type %T!\n",v)}}funcmain(){do(21)do("hello")do(true)}
packagemainimport("fmt""net/http")// define a type for the response
typeHellostruct{}// let that type implement the ServeHTTP method (defined in interface http.Handler)
func(hHello)ServeHTTP(whttp.ResponseWriter,r*http.Request){fmt.Fprint(w,"Hello!")}funcmain(){varhHellohttp.ListenAndServe("localhost:4000",h)}// Here's the method signature of http.ServeHTTP:
// type Handler interface {
// ServeHTTP(w http.ResponseWriter, r *http.Request)
// }
Para que funcionen correctamente los comandos go build, go clean y go test parece que es necesario haber definido correctamente go.mod (con go mod init)
go test buscará ficheros nombrados como *_test.go para ejecutarlos.
Dentro de esos de test habrá bloques funcionales con la firma: func TestXxxx(t *testing.T)
Los test fallidos se informan con t.Errorf
Ciclo de test
El ciclo de trabajo se resume en Red, Green, Refactor.
Para implementar una nueva funcionalidad el primer paso debe ser escribir los test que capturan los requisitos de esa nueva funcionalidad. Estos test obviamente fallarán (Red o rojo de fallo).
La segunda fase consiste en implementar el código necesario para cumplir con los test y queden todos en verde (Green). El código debe minimizarse para cumplir los test sin “sobreimplementar” nada.
El último paso es el refactoring. No implementamos nueva funcionalidad en este paso, se trata simplemente de hacer un código de calidad re-escribiendo el código de la segunda fase. No es un paso opcional, es imprescindible revisar el código y garantizar la coherencia y calidad del mismo.
Vicios en TDD
Escribir demasiados test a la vez
Concentrarse exclusivamente en los happy path y la cobertura del código
No considerar los diferentes escenarios
Demasiadas comprobaciones (asserts) en un sólo test
Probar cosas diferentes en el mismo caso de prueba
Escribir test triviales para mantener el código cubierto
No ejecutar los test con frecuencia
No seguir siempre las tres fases. Especialmente implementando código sin escribir antes el test
Hacer asserts que no prueban nada
Crear test difíciles de mantener
Dobles de prueba (test doubles)
Una implementación simplificada de algún tipo para facilitar las pruebas
Casos posibles:
Dummies
Tipos sin ningún comportamiento, se implementan únicamente para cumplir con la firma de alguna función que queremos probar
Stubs (“breves”)
Tipos que implementan el comportamiento mínimo para pasar un test
Mocks (parodias)
Implementaciones parciales que permiten definir como suponemos que serán los métodos sobre el tipo
Spies (espías)
Implementaciones parciales que nos permiten comprobar que métodos han sido invocados
Fakes (falsificaciones)
Implementaciones ligeras pero completas, por ejemplo implementar una base de datos en memoria a efectos de pruebas
Paquetes útiles
flag
Este paquete nos permite leer parámetros pasados en la llamada al programa por linea de comandos.
encoding/json
Este paquete nos permite salvar o recuperar estructuras de datos desde ficheros json
packagemainimport("encoding/json""fmt")typeBookstruct{NamestringAuthorstring}funcmain(){//--------------------------------------------------
// Marshalling
book:=Book{"C++ programming language","Bjarne Stroutsrup"}my_json,err:=json.Marshal(book)iferr!=nil{fmt.Println(err)}fmt.Printf("Json for book is: %s\n",string(my_json))// {"Name":"C++ programming language","Author":"Bjarne Stroutsrup"}
//--------------------------------------------------
// Unmarshalling
codString:=`{"Name":"The Name of the Wind","Author":"Patrick Rothfuss"}`varcodBookerr=json.Unmarshal([]byte(codString),&cod)iferr!=nil{fmt.Println(err)}fmt.Printf("Book for json is: %v\n",cod)//--------------------------------------------------
// Arrays and Slices
// You can marshall/unmarshall to array or slice
varbooks[]BookbooksJson:=`[{"Name": "Hacking for Dummies", "Author": "Kevin Beaver"},
{"Name": "Kerberos", "Author": "Jason Garman"}]`err=json.Unmarshal([]byte(booksJson),&books)iferr!=nil{fmt.Println(err)}fmt.Printf("Slice for json is: %v\n",books)some_books:=[]Book{{Name:"Libro Uno",Author:"Author Uno"},{Name:"Libro Dos",Author:"Author Dos"},}some_books_json,err:=json.Marshal(some_books)fmt.Printf("json for slice is: %v\n",string(some_books_json))}
Atributos “a medida” para json
Podemos añadir atributos json a una struct que vamos a leer o a salvar con json.
packagemainimport("encoding/json""fmt")typeBookstruct{Namestring`json:"title"`// IMPORTANTE: Nada de espacios al definir los atributos
Authorstring`json:"artist"`}funcmain(){//--------------------------------------------------
// Marshalling
book:=Book{"C++ programming language","Bjarne Stroutsrup"}my_json,err:=json.Marshal(book)iferr!=nil{fmt.Println(err)}fmt.Printf("Json for book is: %s\n",string(my_json))// {"title":"C++ programming language","artist":"Bjarne Stroutsrup"}
}
Además de cambiar los nombres de los campos podemos especificar un par de atributos json más:
.omitempty: nos permite saltarnos los datos si el campo está vacio
"-" : nos permite saltarnos este campo por completo
packagemainimport("bytes""fmt""os""os/exec""strings""syscall")// printCommand prints command
funcprintCommand(cmd*exec.Cmd){fmt.Printf("==> Executing: %s\n",strings.Join(cmd.Args," "))}// printError print human friendly error objects
funcprintError(errerror){iferr!=nil{os.Stderr.WriteString(fmt.Sprintf("==> Error: %s\n",err.Error()))}}// printOutput prints command output
funcprintOutput(outs[]byte){iflen(outs)>0{fmt.Printf("==> Output: %s\n",string(outs))}}// main el cuerpo ppal del programa
funcmain(){// Create an *exec.Cmd
// Capture Stdout and Stderr together
cmd:=exec.Command("echo","Called from Go!")printCommand(cmd)output,err:=cmd.CombinedOutput()// runs cmd and return combined output
printError(err)printOutput(output)// => go version
// Create an *exec.Cmd
cmd=exec.Command("go","version")// Stdout buffer
cmdOutput:=&bytes.Buffer{}// Attach buffer to command
cmd.Stdout=cmdOutput// Execute command
printCommand(cmd)err=cmd.Run()// will wait for command to return
printError(err)// Only output the commands stdout
printOutput(cmdOutput.Bytes())// create a command that will fail
cmd=exec.Command("ls","/imaginary/dir")varwaitStatussyscall.WaitStatusiferr:=cmd.Run();err!=nil{printError(err)// Did the command fail because of an unsuccessful exit code
ifexitError,ok:=err.(*exec.ExitError);ok{waitStatus=exitError.Sys().(syscall.WaitStatus)printOutput([]byte(fmt.Sprintf("%d",waitStatus.ExitStatus())))}}else{// Command was successful
waitStatus=cmd.ProcessState.Sys().(syscall.WaitStatus)printOutput([]byte(fmt.Sprintf("%d",waitStatus.ExitStatus())))printOutput(cmdOutput.Bytes())}}
Para el último caso comandos de larga duración, no queremos que nuestro programa espere al resultado del comando. Es mejor hacerlo de forma asíncrona.
packagemainimport("bytes""fmt""os""os/exec""time")// printError print human friendly error objects
funcprintError(errerror){iferr!=nil{os.Stderr.WriteString(fmt.Sprintf("==> Error: %s\n",err.Error()))}}// printOutput prints command output
funcprintOutput(outs[]byte){iflen(outs)>0{fmt.Printf("==> Output: %s\n",string(outs))}}// main() programa ppal
funcmain(){cmd:=exec.Command("cat","/dev/random")// creamos un comando
randomBytes:=&bytes.Buffer{}// creamos un buffer
cmd.Stdout=randomBytes// asociamos la salida estándar al buffer
err:=cmd.Start()// Lanzamos el comando de forma asíncrona
printError(err)// Create a ticker that outputs elapsed time
ticker:=time.NewTicker(time.Second)gofunc(ticker*time.Ticker){now:=time.Now()for_=rangeticker.C{printOutput([]byte(fmt.Sprintf("%s",time.Since(now))),)}}(ticker)// Create a timer that will kill the process
timer:=time.NewTimer(time.Second*4)gofunc(timer*time.Timer,ticker*time.Ticker,cmd*exec.Cmd){for_=rangetimer.C{err:=cmd.Process.Signal(os.Kill)printError(err)ticker.Stop()}}(timer,ticker,cmd)// Only proceed once the process has finished
cmd.Wait()printOutput([]byte(fmt.Sprintf("%d bytes generated!",len(randomBytes.Bytes()))),)}
tcell
Este paquete nos permite implementar interfaces de usuario basados en texto.
Instalamos:
1
go get -u github.com/gdamore/tcell
Recomiendan mirar primero el ejemplo incluido mouse
Casos prácticos
Leer y escribir json
Básico:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
packagemainimport("encoding/json""fmt")// main ...
funcmain(){x:=map[string]string{"foo":"bar",}data,_:=json.Marshal(x)fmt.Printf("Data contains:\n%s\n",(data))}
packagemainimport("encoding/json""fmt")// main ...
funcmain(){typepersonstruct{Namestring`json:"name"`Ageint`json:"age"`Descriptionstring`json:"descr,omitempty"`// DON'T use spaces in tags
secretstring// Unexported fields are Unmarshaled
}x:=person{Name:"Bob",Age:32,secret:"Shhh!",}data,_:=json.Marshal(x)fmt.Printf("Data contains:\n%s\n",(data))}
Un ejemplo de unmarshalling, aunque siempre es preferible usar una struc si conocemos de antemano la estructura de los datos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
packagemainimport("encoding/json""fmt")// main test json
funcmain(){data:=[]byte(`{"foo":"bar"}`)varxinterface{}_=json.Unmarshal(data,&x)fmt.Printf("x contains:\n%s\n",x)}