16/07/2022

Guorkshop Go 1. Organización del código. Estructura de proyectos, módulos y paquetes

Si va a escribir un programa muy simple, en un único archivo, y no espera reusar el código, entonces no se preocupe de la organización de su proyecto y escriba todo su código en un único archivo fuente. Pero si esta trabajando en resolver un problema del mundo real, entonces hay una gran posibilidad de que necesite diseñar su proyecto de forma de poder reusar la mayor cantidad de código posible. Si este último es su caso, entonces preocupese.

Workshop. Fotografía por Daniel Mee a través de Flickr. Licencia CreativeCommons

Este es el primero de los prometidos guorkshops. Puede encontrar el guorkshop 0 aquí. Puede encontrar los otros guorkshops aca.

Si bien nadie le obliga a seguir o usar una forma de estructurar su proyecto, y ciertamente, si su proyecto es simple, o una prueba de conceptos, la estructura que presentaremos en este workshop puede ser innecesaria y un único archivo main.go donde escribir su código basta y sobra. Solo tenga presente que a medida que su código crece es importante que esté bien estructurado, de lo contrario corre el riesgo de terminar con un desorden de proporciones, con dependencias ocultas, estado global y otros malos olores.

Go nos provee con dos formas principales de organizar el código, módulos y paquetes.

Módulos

Los módulos de Go contienen uno o mas paquetes almacenados en una estructura de directorio y nos permiten entre otras cosas controlar las versiones de los paquetes que contiene, y con esto compartir o usar las funcionalidades de sus componentes.

Los módulos de Go tienen en su directorio raíz dos archivos especiales; go.mod y go.sum, donde el archivo go.mod define la ruta del modulo, indica la versión de Go con la que el módulo trabaja, y declara los paquetes de los que depende.

El archivo go.sum es generado automáticamente por Go cuando empezamos a agregar dependencias Contiene el checksum de las dependencias del módulo para determinar que estas no hayan sido modificadas y nosotros no deberiamos modificarlo manualmente.

Con ciertos reparos, podríamos decir que cada vez que vaya a empezar un nuevo proyecto de Go debería crear un módulo.

Para crear un módulo de Go solo tenemos que crear un directorio que será la raíz del proyecto, y usar go mod init nombre_del_modulo.

$ mkdir mi_modulo
$ cd mi_modulo
$ go mod init mi_modulo

Se considera buena practica que el nombre del módulo contenga su ruta de importación para que pueda compartirse usando las herramientas de Go. Esta ruta de importación es la ruta que Go usa para importar desde otros los paquetes contenidos en el módulo.

La especificación de Go indica sobre la ruta de importación:

La ruta de importación es el nombre canónico de un módulo, declarado con la directiva de módulo en el archivo go.mod. La ruta de un módulo es el prefijo para importar los paquetes que contiene.

Se ha vuelto práctica de la comunidad de desarrolladores el usar la url del repositorio que contiene el módulo, incluyendo el username del autor si se tratara de un repositorio como los de github, con la forma host/usuario/repositorio. Por ejemplo github.com/profe-ajedrez/my-awesome-module corresponde al módulo my-awesome-module del usuario profe-ajedrez, alojado en github.com. Otra convención muy usada es la forma dominio organizacion/nombre del modulo, por ejemplo calcutarasterware.com/a-pandaric-module.

Ejemplo:

Crearemos un módulo llamado mi_modulo y lo esperamos alojar en github en el repositorio del mismo nombre. Usaré mi usuario de github para este ejercicio.

$ mkdir mi_modulo
$ cd mi_modulo
$ go mod init github.com/profe-ajedrez/mi_modulo

El comando go mod init github.com/profe-ajedrez/mi_modulo crea al módulo en nuestro directorio local con el nombre github.com/profe-ajedrez/mi_modulo, note que aún no hemos creado ningún repositorio en github, generando al archivo go.mod, el cual contiene la ruta de importación del módulo y la versión de Go que usa nuestro proyecto.

# cat go.mod
module github.com/profe-ajedrez/mi_modulo

go 1.18

Dandole estructura a nuestro módulo

La comunidad de desarrolladores de Go ha probado con distintas aproximaciones a la hora de organizar el código de los proyectos, y si bien existen varias formas de lograr esta organización, creemos que la mas extendida es la siguiente, que si bien no es un estándar oficial definido por el equipo de desarrolladores de Go, es un conjunto de patrones históricos en el ecosistema del lenguaje.

directorio raíz del proyecto o módulo/
  cmd/
  internal/
  pkg/

  go.mod
  go.sum

  • El subdirectorio cmd/ contiene los paquetes main que son los puntos de entrada de la aplicación, pudiendo haber varios de ellos, cada uno en su propio subdirectorio cuyo nombre es el mismo que el del binario que generaran.
  • El subdirectorio internal/ contiene el código privado del módulo, es decir, los paquetes que se encuentren en este directorio no se podrán importar desde otros módulos, pero si pueden ser usados en el módulo actual.
  • El subdirectorio pkg/ contiene los paquetes que si serán importables desde otros módulos.

Paquetes

Los paquetes contienen archivos fuente y otros paquetes, y se usan para organizar código Go mejorando su legibilidad y reusabilidad, encapsulando el código de los archivos fuente que se encuentran en un mismo directorio.

Los paquetes corresponen a directorios que contienen archivos fuente y otros paquetes, y se usan para organizar código Go logrando, si se usan bien, mejorar su legibilidad y reusabilidad. Esto lo hacen encapsulando el código de los archivos fuente que se encuentran en un mismo directorio. Por lo tanto, todos loas archivos fuentes que se encuentren ene l mismo directorio deben pertenecer al miosmo paquete, en caso contrario ni siquiera podremos compilar nuestro código.

Remarcamos encapsulando debido a que en Go el paquete es la unidad de encapsulación en oposición a la clase que ofrecen otros lenguajes.

En Go, encontramos dos tipos de paquetes:

1 El paquete main que se usa para generar el ejecutable. Es responsable de contener a la función main que indica el punto de entrada a la aplicación.

// main.go
package main

import (
	"fmt"
)


func main() {
	fmt.Println("Hola Go")
}

2 El paquete orientado a reusar código, o paquete de librería que contiene código que será reusado.

// recepcion/recepcion.go
package recepcion

import (
	"fmt"
)


func Recibir() {
	fmt.Println("Recibiendo")
}

Podemos ver que los archivos fuente de Go empiezan con una línea indicando el paquete al que pertenece. Todo código Go debe pertenecer a uno, siendo nombrado generalmente con el mismo nombre que el del directorio que lo aloja.

El código dentro de un paquete puede referir a cualquier elemento (variables, constantes, funciones y otros tipos de dato) definido dentro de el, mientras que código de otros paquetes solo puede referir a los elementos que hayan sido exportados. Esta referencia siempre incluye el nombre el paquete como un prefijo; puertas.Abrir() refiere a función exportada Abrir del paquete puertas.

Effective Go recomienda que los nombres de paquete sean cortos, concisos y evocativos, y presenta la convención de que los nombres de paquetes sean palabras únicas en minúscula, sin subrayados o mixedCap.

Otra convención es que el paquete tenga el mismo nombre que su directorio fuente.


Gracias por aguantar hasta este punto. ¡Ahora ensuciemonos las manos!

Workshop Hora 1

Para realizar este workshop debe tener Go instalado, usar la terminal de su sistema y estar ubicado en algún directorio de trabajo.

1 En su terminal cree una nuevo directorio para el módulo con el nombre workshop_1
2 Acceda a la nueva carpeta
3 Cree un módulo de nombre workshop_1
4 Dentro del módulo cree la estructura de directorios presentada arriba
5 Abra el directorio con una instancia de su editor de texto favorito
6 Dentro del directorio internal cree el directorio xmath
7 En internal/xmath/ cree el archivo fuente xmathsourcefile.go
8 En el archivo internal/xmath/xmathsourcefile.go agregue el siguiente fragmento de código
package xmath

func SubstractInt(a int, b int) int {
  return a - b
}

9 En el directorio pkg cree el directorio xmathclient
10 En el directorio pkg/xmathclient/ cree el archivo xmathclientsourcefile.go
11 En el archivo pkg/xmathclient/xmathclientsourcefile.go agregue el siguiente fragmento de código
package xmathclient

import (
	"fmt"
	"workshop_1/internal/xmath"   // ¡Esto es importante! Note como se importa el paquete xmath
)

func Client() {
	fmt.Println(xmath.SubstractInt(10, 5))
}

Observe como se importa al paquete xmath. ¿Recuerda cuando comentamos sobre la ruta de importación del módulo? Pues se trataba de esto, la ruta de importación del módulo es la raíz de donde parte la ruta para encontrar los paquetes que importamos. Note que la ruta de importación del paquete xmath, incluye al directorio internal

12 En el directorio raíz del módulo, cree el archivo main.go y agreguele el siguiente código
package main

import "workshop_1/pkg/xmathclient" // Note como desde main importamos al paquete xmathclient

func main() {
	xmathclient.Client()
}

¿Vio como desde main.go importamos al paquete xmathclient? ¡Exacto! a través de la ruta de importación del módulo pasando por el directorio pkg que lo aloja.

13 En el directorio raíz del módulo, en la terminal ejecute el programa con go run main.go. Debería obtener la salida 5
  • ¿Que hemos hecho?

Hemos creado un módulo Go llamado mi_modulo, en el hemos creado la estructura de directorios propuesta para proyectos Go. En el directorio internal, creamos el directorio/paquete xmath, y en el directorio pkg creamos el directorio/paquete xmathclient. Finalmente creamos el archivo main.go en la raíz del directorio y lo ejecutamos para ver la salida del programa.

Muy bien, ahora rompamos algunos huevos y agreguemos un pequeño error que resolveremos posteriormente.

14 Modifique el archivo internal/xmath/xmathsourcefile.go para que se vea como el siguiente código
package xmath

func SubstractInt(a int, b int) int {
  return a - b
}

func addInt(a int, b int) int {
  return a + b
}

15 Modifique el archivo pkg/xmathclient/xmathclientsourcefile.go de la siguiente manera
package xmathclient

import (
	"fmt"
	"workshop_1/internal/xmath"
)

func Client() {
	fmt.Println(xmath.SubstractInt(10, 5))
	fmt.Println(xmath.addInt(10, 5))
}


16 Ejecute al programa en la terminal, en el directorio raíz del módulo con go run main.go. Debería ver el mensaje undefined: xmath.addInt

Esto ocurre debido a que el nombre de la función addInt del paquete xmath empieza con una letra minúscula, lo que le indica al compilador que es una función privada del paquete, esto es, solo puede accederse a ella desde dentro del paquete donde fue declarada. Código alojado en otros paquetes no podrá acceder a ella.

15 Repare el error para obtener en consola la salida Cambiando la inicial de la función addInt por una letra mayúscula, en su declaración y en su llamada.

Una vez corregido el problema, al ejecutar el programa debería obtener la siguiente salida:

5
15

En este punto ya debería tener un módulo de Go funcionando correctamente y haber entendido la forma de importar y usar paquetes.

Debe recordar
  • Los módulos almacenan paquetes y nos permiten versionar nuestro código
  • Paquetes encapsulan nuestro código Go y definen ámbito
  • El paquete main indica cual archivo será convertido en ejecutable
  • import permite usar funcionalidad de un paquete en otros paquetes
  • Para exportar un identificador debemos iniciar su nombre con una letra mayúscula

Hello Go

Hello Go

Un pequeño recordatorio. Si de verdad completó el tour de Go puede pasar de inmediato al siguiente artículo.

Tomemos el siguiente programa (puede ejecutarlo en el Go playground en https://go.dev/play/p/lBlmIOs1svX)

package main



import "fmt"

var myGlobal = 12

func main() {
	fmt.Println(myGlobal)
}

Y analisemoslo línea a línea. Primero nos encontramos con:

package main

Lo cual es la declaración de nuestro paquete. En terminos planos, un paquete es un directorio que contiene archivos fuente de go u otros paquetes. Toda función, variable o tipos que definamos, debe pertenecer a un paquete. Podemos verlos como contenedores que guardan piezas reusables de código.

El package main es un paquete especial que le indica al compilador Go que el archivo será convertido en un ejecutable. Si ud. quiere ejecutar el código de su paquete directamente, necesita llamarlo main.

import "fmt"

import permite incorporar código de otros paquetes. La línea anterior dice Trae el código encapsulado en el paquete fmt y dejame usarlo a través del nombre del paquete. Los imports aplican solo en el archivo en el cual se declaran, si necesita importar el mismo paquete a través de distintos archivos fuente, debe declarar el import en cada uno de ellos.

var myGlobal = 12

En esta línea, declaramos una variable de paquete privada. Es una variable de paquete porqué fue definida en el ámbito de este sin estar dentro de alguna función. Por lo tanto todo el código que pertenezca al mismo paquete podrá acceder a ella aunque sea desde un archivo fuente distinto al que fue declarada

Note que la variable myGlobal comienza con una letra minúscula. En Go, si queremos que nuestras definiciones (variables, funciones, otros) no puedan ser accedidos desde fuera del paquete, sean privadas de este, simplemente debemos comenzarlas con una letra minúscula.

  func main() {

En esta línea, definimos una función, pero no cualquier función. main es una función especial para Go, y le indica al compilador el punto de partida de nuestra aplicación. Es decir, nuestra aplicación comenzará en la función main del paqeute main.

fmt.Println(myGlobal)

Esta línea imprime el contenido de la variable myGlobal usando la función Print del paquete fmt. Note como se accede a la funcionalidad ofrecida por un paquete usando la notación nombrePaquete.nombreFuncion

Los Guorkshops de Go del Programador Pobre

Pues la cosa detrás de estos guorkshops es que sean un grupo de artículos donde podamos ensuciarnos las manos trabajando en algunos conceptos e ideas que tengo dando vueltas hace rato en la cabeza sobre el lenguaje Go. Si tuviera que poner un objetivo concreto, sería el repasar las caracteristicas principales de Go aprendiendo buenas prácticas, haciendo cosas y usando un leguaje coloquial que permita presentar contenidos sin tanto término rimbombante. En pocas pálabras, Go al hueso (aunque suene gracioso!).

Workshop. Fotografía por Daniel Mee a través de Flickr. Licencia CreativeCommons

Los llamo guorkshops, con la palabra en Inglés mal escrita intentando darle un toque artesanal y DIY, y también para no tomarme demasiado en serio y permanecer siempre en el lado más humilde, pues estos guorkshop per se tienen que ser opinionados, en el sentido de que a través de la experiencia personal y de la comunidad presenta prácticas que se consideran correctas las cuales pueden ir cambiando con el correr del tiempo.

La metodología que uso es incremental y repetitiva a propósito, pues habiendo sido varios años profesor de ajedréz, es la que me ha dado mejores resultados, entregando ídeas y habilidades de forma acumulativa. Cosas que podrían parecer errores en un workshop, son usadas para ejemplificar una ídea específica de este y son corregidas en los siguientes junto con un comentario explicativo del error o mala práctica que ejemplifica.

Sin perjuicio de lo anterior, aunque a veces no lo parezca, soy un ser humano. Me reservo el derecho a equivocarme.

Cabe señalar que cada vez me vuelvo un mas firme creyente del Manifiesto, y creo que lo notará en los guorkshops.

Requisitos previos

Go instalado

Dirijase a https://go.dev/doc/install e instale la última versión estable del lenguaje correspondiente a su sistema operativo siguiendo las instrucciones que el sitio le ofrece.
Si usa Linux, recomiendo instalar Go manualmente y no usar al manejador de paquetes de su distribución o utilidades como Snap, para garantizar el uso de la última versión estable del lenguaje.

Tour de Go

Go es un lenguaje muy sencillo de aprender, pero como todo lo que vale la pena, es díficil lograr maestría con el. El primer paso para su dominio debería siempre ser el Tour de Go que enseña de manera interactiva los aspectos básicos del lenguaje.

En estos workshops, asumimos que ya ha completado el tour de Go. Si aún no lo ha hecho completelo y luego regrese.

Uso de terminal

En estos workshops asumimos que tiene un manejo básico de la terminal y los comando de su sistema operativo. Basta con que sepa abrír una línea de comandos, navegar a través de directorios, listar archivos y crear subdirectorios.

Herramientas

IDES

La wiki oficial de Go tiene una pagina dedicada a presentar y configurar ides para hacer más eficiente el desarrollo con este lenguaje. Ud puede usar el de su preferencia, pero los ejemplos de este workshop serán escritos en Visual Studio Code usando el plugin oficial

Toolset

Go viene con una serie de herramientas para ayudar a los desarrolladores a ser productivos. Estas pueden llamarse desde la línea de comandos o integrarse con su ide favorito (Esto lo hace el plugin oficial de Visual Studio Code. por ejemplo) Aún así, no está de más conocerlas y saberlas usar desde la terminal.

Puede obtener en su terminal esta lista de herramientas con:

$ go tool

El toolset de Go es muy completo y tiene muchas otras herramientas. Puede verlas en su pagina de documentación oficial


Enlaces directos a los guorkshops

08/04/2022

Structs y algunas notas

Los tipos de datos de toda la vida; enteros, cadenas de texto, booleanos, etc. funcionan muy bien para realizar tareas triviales, pero el desarrollo de software no siempre trata acerca de encontrar el mayor de dos números. Hay muchos problemas que resolver y muchas entidades que modelar.

Una struct es un tipo de datos definido por el usuario que agrupa un conjunto de campos que también tienen tipos definidos.

En efecto, gran parte de la solución de un problema, cuando programamos, pasa por representar de manera adecuada a una entidad del mundo real y a su flujo de vida. Pues bien, ¿De que forma representamos un automóvil y su flujo de fabricación? ¿Como modelamos una factura o un chocolate? y más allá, ¿Como modelamos dos chocolates de distintas marcas?

Cada lenguajes de programación provee su propia forma de lidiar con este problema, la cual se puede resumir en algún tipo de datos personalizado. Algunos ponen a nuestra disposición arreglos asociativos, otros clases, etc. Go por su parte nos deja utilizar structs, los cuales ya mencionabamos en un artículo anterior.

Una struct es un tipo de datos definido por el usuario (en el sentido de que quien programa es el usuario) que agrupa un conjunto de campos que también tienen tipos definidos.

Retomando el ejemplo del automóvil, si lo abstraemos, vemos que posee atributos cómo marca, modelo, año de fabricación, color, etc. Usemos este caso para ejemplificar la declaración de structs

Declaración de structs
  

type Automovil struct {
    Marca string
    Color string
    Ano int
}
  

playground

De esta forma declaramos una struct type conteniendo los campos Marca, Color y Ano cómo una estructura de datos personalizada llamada Automovil.


  
  
package main

import "fmt"

type Automovil struct {
	Marca string
	Color string
        Ano int
}

func(a Automovil) Acelerar() {
    fmt.Println("Este automóvil " + a.Marca + " de color " + a.Color + " esta acelerado")
}

func main() {
	myCar := Automovil{
		Marca: "Suzuki",
		Color: "Rojo",
                Ano: 1999,      // Notese la coma al final de la última línea
	}
          
	fmt.Println("Mi Auto: ", myCar)
 
} 
  

playground

¿Se dio cuenta de cómo definimos una variable de tipo struct Automovil justo al principio de la función Main? Esta forma de inicialización es conocida con el nombre de inicialización por struct literal. También podemos inicializar variables con nuestro struct con el estamento new


  
	var myCar *Automovil
	myCar = new(Automovil)
	myCar.Marca = "Suzuki"
	myCar.Color = "Rojo"
	myCar.Ano = 1999

	fmt.Println("Mi Auto: ", myCar)

  

playground

Asociando métodos a nuestro struct

En Go, las structs puede tener métodos asociados.


  
package main

import "fmt"

type Automovil struct {
	Marca string
	Color string
	Ano   int
}

func (a Automovil) Acelerar() {
	fmt.Println("Este automóvil " + a.Marca + " de color " + a.Color + " esta acelerado")
}

func main() {
	myCar := Automovil{
		Marca: "Suzuki",
		Color: "Rojo",
		Ano:   1999,
	}

	myCar.Acelerar()
}
  


playground

 

Structs, clases y herencia
Go usa la composición para no repetir la implementación de comportamientos, que muy resumidamente, es armar nuestros objetos a partir de otros

Ud podrá pensar; Si un struct tiene campos y también métodos ¿En que se diferencia de una clase? No se equivoque; son parecidos pero no iguales, ambos esconden ideas diferentes en su concepción aunque a nosotros cómo usuarios nos permiten hacer cosas parecidas.

La primera diferencia es que Go no implementa mecanismos de herencia, con esto, surge la segunda diferencia, el encapsulamiento en Go se gestiona a nivel de paquetes, donde un elemento es exportable o no según si su letra inicial es mayúscula o minúscula; con la consecuencia de la no existencia del ámbito protected ¡Porque no existe herencia!

Como no hay herencia, necesitamos una forma para no repetir la implementación de comportamiento. En Go se tomó la decisión de diseño de hacerlo por la vía de la composición, que muy resumidamente es armar nuestros objetos a partir de otros. Si Ud ha trabajado con React o Flutter, entonces este concepto no le será nuevo, pues este patrón forma parte intrínseca de ellos para la creación de componentes.

Un pequeña nota sobre composición sobre herencia

Hay una rica y variada discusión acerca de composición sobre herencia, la cual es muy grande y escapa de las pretenciones de este artículo. Si no ha escuchado sobre el asunto, baste decir que podemos pensar cuando hablamos de composición en una relación del tipo tiene un, esta formado por, mientras que cuando hablamos de herencia, nos referimos a una relación del tipo es un. Fuente: McConnell, Steve (2004). Code Complete: A Practical Handbook of Software Construction. Microsoft Press; 2nd edición.

También puede encontrar una jugosa discusión sobre el tema en este hilo de Stackoverflow.



Composición

Para los diseñadores de Go la decisión de optar por la composición como la forma idiomática de compartir comportamiento y estado es tan importante, que el lenguaje nos permite incrustar en un struct en otro. Ej.


  
package main

import "fmt"

// Automovil el struct base que incrustaremos
type Automovil struct {
	Marca string
	Color string
	Ano   int
}

func (a Automovil) Acelerar() {
	fmt.Println("Este automóvil " + a.Marca + " de color " + a.Color + " esta acelerado")
}

// Delorean Porqué si vas a construir una máquina del tiempo ¿por qué no hacerlo con estilo? Dr. Emmet Lathrop Brawn
type Delorean struct {
	Automovil
}

func (d Delorean) TimeSkip() {
	fmt.Println("Viajando al 5 de noviembre de 1955 ¿Esa es la granja de Peabody?")
}

func NewDelorean() Delorean {
	d := Delorean{}
	d.Marca = "Delorean"
	d.Color = "Plata"
	d.Ano = 1988
	return d
}

func main() {
    myCar := Automovil{
        Marca: "Suzuki",
        Color: "Rojo",
        Ano:   1999,
    }

    myCar.Acelerar()
    
    autoGenial := NewDelorean()
    autoGenial.Acelerar()
    autoGenial.TimeSkip()
}

  


playground

 

Esto nos abre la puerta a sofisticadas formas de modelar las entidades que son parte de nuestro modelo de negocio. Pero eso es conversaciónm para otro artículo.

16/03/2022

Punteros en Go


Si completó el Tour de Go, recordará que Go posee punteros, y que un puntero contiene la dirección de memoria en la que está contenido un valor.

Un puntero es simplemente una variable que contiene la dirección de memoria de otra variable

Si declaramos en un programa Go una variable de tipo int y otra variable como un puntero a int se vería así:


  
    package main
    
    func main() {
        var i int  = 42
        var p *int = &i
    }
  

Donde en la variable i almacenamos el número 42, mientras que en la variable p almacenamos la dirección de memoría donde está alojada la variable i


diagrama 1

¿Se fijó en el ampersand & antes de la i en la definición de la variable puntero? Su nombre es operador de referencia y es la forma de obtener el puntero a un elemento en Go.

Seguramente también observó el asterisco * en la declaración del tipo de la variable puntero p. Es el operador de dereferencia o puntero, indica que la variable contiene un puntero, y también sirve para obtener el dato almacenado en la dirección de memoria a la que el puntero apunta.


  
    package main
    
    import "fmt"
    
    func main() {
        var i int  = 42
        var p *int = &i
        
        fmt.Println(*p) // prints 42
        
    }
    
  
playground

new

Para nuestra conveniencia, existe otra forma de obtener un puntero. la función new, la cual toma un tipo como argumento y devuelve un puntero a él. La expresión new(T) crea una variable sin nombre de tipo T y devuelve su dirección, la cual, como indicamos, es un puntero a T, o, diciéndolo de otra forma, un valor de tipo *T



package main

import "fmt"

func main() {
    p := new(int) // p, de tipo *int, apunta a una variable anónima de tipo int
    fmt.Println(*p) // imprime 0
    *p = 2 // Asigna el valor 2 a la variable anónima
    fmt.Println(*p) // imprime 2
}


playground

Las variables anónimas creadas con new no son distintas a las variables comunes y silvestres de toda la vida, con la diferencia de que no es necesario darles un nombre


¿Y para qué?

Todo esto está muy bien, pero, ¿De qué me sirve? ¿Por qué no usar las variables como lo hemos hecho toda la vida si es más simple?

Los punteros nos ayudan entre otras cosas a acceder a elementos a los cuales normalmente no tendríamos accesos. Imaginemos que quiere escribir una función que intercambie el contenido de dos variables enteras. La primera aproximación podría ser algo como:


  
package main

import "fmt"

func DaSwap(i1 int, i2 int) {
	i1, i2 = i2, i1
}

func main() {
	var a int = 42
	var b int = 16

	DaSwap(a, b)

	fmt.Println(a, " ", b)
}
  

playground

¿Que imprime el programa anterior? Si esperaba que imprimiera 16 42 lamento decirle que espere sentado. Como el intercambio de las variables se hace en la función DaSwap, este se hizo sobre copias de los valores de las variables que le fueron pasadas como argumentos, por lo que estas no fueron afectadas. Si usted es un veterano de las trincheras, entonces de seguro ya está pensando en la frase paso de variables por valor.

Si quisiéramos ver que el intercambio se realice correctamente, nos vemos obligados a usar punteros:


  

package main

import "fmt"

func DaSwap(i1 *int, i2 *int) {
	*i1, *i2 = *i2, *i1
}

func main() {
	var a int = 42
	var b int = 16

	DaSwap(&a, &b)

	fmt.Println(a, " ", b)
}
  

playground

Si de verdad completó el Tour de Go, usted podría debatirme que no necesita punteros, pues podría escribir la función de tal manera que devuelva los valores como una tupla en orden inverso al pasado como argumento, ¡Y yo me vería forzado a darle la razón!

  
package main

import "fmt"

func DaSwap(i1 int, i2 int) (int, int) {
	return i2, i1
}

func main() {
	var a int = 42
	var b int = 16

	a, b = DaSwap(a, b)

	fmt.Println(a, " ", b)
}

  

playground

Entonces, otra vez ¿De que me sirven los punteros? En muchos lenguajes de programación los punteros se usan, a veces sin que nos demos cuenta, por ejemplo para implementar listas y tipos de datos especiales que requieren aumento o disminución dinámica de capacidad; y para manipular objetos y arreglos. También permiten pasar funciones como argumento a otras funciones, pues estando definido un bloque de código en una posición de memoria (o en una ubicación del stack), parece lógico poder acceder a él a través de una referencia a dicha posición de memoria.



  
# Pseudo código

function HolaSoyUnCallback() {
  # Realizo alguna acción
}

func f1(callback) {
  # Realizo alguna acción en f1
  callback()
  # Mas acciones en f1
} 
 
  


Específicamente en Go, entre otras cosas, los punteros nos dan acceso a una característica muy poderosa del lenguaje, sobre la cual se pueden construir muchas cosas muy bonitas; los structs



Resumiendo

  • Un puntero se llama así porqué sirve para apuntar
  • ¿Donde apunta? Apunta a una dirección de memoria

14/03/2022

Historias de las trincheras. Una aplicación exitosa

Armas al Hombro. Charles Chaplin

Permítanme antes de ir al hueso de nuestro tema una pequeña historia

Dígame si le parece familiar el siguiente relato ficticio. Se empieza a trabajar en un proyecto de software. Siguiendo las buenas prácticas de la época se intenta separar las capas de la aplicación en modelos, vistas y controladores. Se decide usar la versión más moderna del lenguaje de moda, y la última versión disponible de su framework más reconocido el cual viene integrado con su propio ORM. Se decide además usar el motor de base de datos XSQL como capa de persistencia de datos. La aplicación es un éxito. Cada vez más clientes contratan sus servicios y el número de estos comienza a crecer exponencialmente, hasta contarse algunos miles de usuarios; y después de algunos años hasta empiezan a haber planes de internacionalización.

En ese momento, las peticiones de soporte empiezan a crecer, y un buen número de estas trata sobre la velocidad y tiempos de respuesta de la aplicación. La primera idea es actualizar los componentes del sistema a versiones recientes, pero ¡Oh, sorpresa! el componente que se encarga de manejar xml tiene como dependencia un paquete descontinuado, y el ORM integrado depende del paquete que maneja xml. Como imaginará, si no actualiza el ORM no puede actualizar el framework que lo integra, y las nuevas versiones del lenguage soportan el framework desde la versión 3, cuando su aplicación se basa en la versión 2

La actualización de las dependencias de software no es posible.

Surge entonces la idea de montar la aplicación en máquinas más poderosas, si no podemos actualizar el software, actualicemos el hardware. Entonces contratamos máquinas en el proveedor de servicios cloud mas conveniente. También se empieza a escuchar sobre un nuevo trending en la industria del software, los microservicios. Si descomponemos la aplicación en un conjunto de pequeñas utilidades que realizan un solo trabajo, lo realizan bien, y gatillamos su inicio con llamadas a api, mejoraran los tiempos de respuesta por la simple razón de que puedo levantar muchas máquinas en nuestro proveedor cloud con los microservicios mas demandados.

Todo va viento en popa, los tiempos de respuesta mejoran ostensiblemente y la internacionalización es un hecho. Sin embargo, la moneda del nuevo país donde tenemos clientes posee una submoneda; centavos, centimos, peniques o como se llame. Es decir, los montos que la aplicación debe manejar son números con decimales. La moneda del país original no contaba con algo parecido y todos los cálculos monetarios eran redondeados a números sin decimales.

Rápidamente se desarrolla una corrección, que consiste en cambiar el redondeo desde no conservar decimales a mantener un número arbitrario de estos. Esta corrección hace los cálculos monetarios usando el tipo de datos de números de coma flotante, los cuales no pueden representar con exactitud números decimales debido a la impĺementación de su aritmética, por lo que cuando se ven involucradas cantidades muy grandes, aparecen diferencias inaceptables en los monto calculados.

Usar un tipo de dato especial, del tipo BigDecimal o Currency esta fuera de discusión porque prácticamente significa reescribir la aplicación.

Por otro lado, como seguimos vendiendo los servicios del sistema, los clientes siguen multiplicándose, volviendo a aumentar los tiempos de respuesta. En esos momentos, un miembro del equipo propone migrar el manejo de la capa de persistencia de datos a ZSQL o Y-NOSQL cuyos benchmarks demuestran una velocidad con la que el viejo y querido XSQL solo puede soñar. Pero, tras hacer los experimentos necesarios, vemos con horror que el ORM del framework no soporta ninguna de estas nuevas alternativas.

A estas alturas, uno como programador no puede hacer mucho más que resistir en las trincheras. Después de todo hay que poner pan en la mesa y se nos paga por escribir código, bugfixes y nuevas features, no por ventilar nuestras preocupaciones. Tampoco pueden hacer mucho líderes de equipo ni product owners, pues deben responder ante críterios comerciales. No, en esta situación no hay nada mas que hacer que resistir en el frente sacando correcciones, con la esperanza de alcanzar a ver el armisticio que marque el cese al fuego. Y a veces cae fuego pesado desde todos los frentes.

13/03/2022

Lavaplatos DIY


Este 2022 hice realidad un viejo sueño y me vine a vivir al campo. Escapar de la ciudad era una idea que acariciabamos hace mucho tiempo con mi bruja y finalmente pudimos hacerla realidad, pero eso será material para otra historia

Si bien la casa aún está en construcción, los restos de materiales de la obra se van acumulando con el paso de los días. Mucho del volumen de estos restos usados corresponden a trozos de madera de diferentes dimensiones, y llevo algún tiempo pensando en que hacer con ellos. Por la zona, es común usar los palos sobrantes como leña, pero yo tenía algunos otros usos en mente.

Como este resbalín que le hice a mi hija con sobras 



El segundo proyecto fue un mueble base de lavaplatos. Esto debido a que están muy caros y opino que la melamina no es durable ni tampoco un material muy noble. Yo quería algo con un toque tradicional, pero que fuera práctico y usable, y por sobre todo que pudiera hacer reutilizando materiales sobrantes.

Materiales

  • 8 tablas de 90cm de largo que al estar juntas una junto a otra sumen 48 cm de ancho para los dos laterales del mueble. Tuve que cepillar el borde de una de ellas en cada lateral para alcanzar esta medida. Si puede, intente usar tablas de 1x5 pulgadas para tener menos trabajo. Tenga en cuenta que si bien las tablas se comercializan en dimensiones indicadas en pulgadas, si compra una tabla cepillada de 5 pulgadas será mas angosta que una table en bruto del mismo tamaño, esto debido a que la medida se calcula a partir de la tabla en bruto y el cepillado remueve material.
  • 4 tablas de 1x3 pulgadas de 48 cm, a los que hay que cortar en cada extremo 1 pulgada. Estas serán usadas como tablas de soporte contra las cuales apernar las tablas que formarán los laterales. Estas se pueden reesplazar por pletinas de fierro de 2mm de grosos de 2cm de ancho x 44 cm de largo
  • 8 tablas de 1x4 pulgadas de 114.5 cm. Estas se convertiran en los largueros que unen los laterales y en el piso del mueble
  • 16 pernos coches pavonados con golilla y tuerca Estos serán usados para armar los letrales contra las tablas de soporte
  • 50 tornillos soberbios de 3/16 x 2 pulgadas

Herramientas

  • Taladro eléctrico inalámbríco
  • Cepillo eléctrico (Podría usarse un cepillo manual, pero es un trabajo que te encargo)
  • Lijadora eléctrica. Idealmente de banda, una orbital funcionaría muy bien también. Yo me vi obligado a usar un accesorio para taladro que es un poco díficil de usar, pero igual me dió buenos resultados
  • Martillo
  • Serrucho
  • Atornilladores de punta phillips para apretar los soberbios, y de paleta para empujar y soltar las tablas
  • Llave inglesa para apretar las tuercas de los pernos coche. También sirve una llave punta corona del tamaño adecuado, o hasta un alicate

Busqué las medidas estandares de este tipo de muebles y decidí hacer uno de 120x90x50 (largo, alto, fondo) cuya construcción comento a continuación.



De la pila de maderas sobrantes, elegí las que estabán en mejor estado 

 


Cepille y pulí concienzudamente las maderas elegidas. Esta fue la parte más pesada de esta aventura



Hice los laterales del mueble cortando 4 maderos de 90 cm de largo y uniendolos con otro madero cruzado, apernandolos con pernos coche con golilla y tuerca de 1/4 pulgada de calibre y 2.5 pulgadas de largo. Estos dan un toque rustico y a la vez firmeza y soporte a la estructura.



De esa manera forme los laterales con dimensiones de 90 cm de alto por 48 cm de ancho.




Posteriormente, corté una pulgada de madera a cada lado del madero de soporte, para encajar en ellos un madero de 114.5cm a guisa de larguero, con el fin de dar forma y soporte al mueble. Notese que esta maniobra la hice a serrucho limpio





Había construido antes una sencilla banquita de madera en bruto para poder trabajar comodamente. En ella fui uniendo los largueros a los laterales usando tornillos soberbios, que son los usados en mueblería.



La ventaja de los tornillos soberbios es que apretan mas que los tornillos de madera, por eso son muy usados en mueblería, sobre todo en el armado de muebles de tableros aglomerado y terciados.


Ademas es posible ocultar sus cabezas usando tapas plasticas



Finalmente, corté, cepillé y presenté las tablas que formarían el piso del lavaplatos. Con esto el mueble está casi listo


Terminé dandole una nueva pulida con la lijadora al mueble y presentando la cubierta de acero inoxidable para asegurarme de que las medidas estuvieran correctas




Como pueden ver, todo quedó muy bien. Aún me falta construir las puertas, pero como estas tienen una complejidad mayor, las haré otro día. También me falta aplicar aceite de linaza y barniz, lo cual haré una vez tenga construidas las puertas.

A modo de comentario y conclusión, si tiene interés en realizar un proyecto diy, como este o parecido, le recomiendo que pase algún tiempo diseñandolo en papel, que calcule lo más ajustadamente posible las medidas y piense con anticipación en los materiales y técnicas que usará. En mi caso, originalmente tenía pensado unir las maderas usando una pletina de fierro de 2mm, pero no encontré ningún lugar donde me las vendieran con el largo que necesitaba (44cm) pues normalmente las comercializan en tiras de 6 metros, y lamentablemente por el momento no cuento con un esmeril angular para cortarlas (y cortarlas con sierra de mano es muchisisimo trabajo)

Si necesita hacer uniones invisibles puede hacer las uniones con tarugos de madera y cola fria, o buscar sistema de sujeción especializados como los agarres que se insertan en las paredes del mueble y apretan el tornillo a ser girados

Como última recomendación, hagase un favor y use una sierra eléctrica con un disco de precisión, de al menos 60 dientes. Yo me ví obligado a usar serrucho manual por haber prestado mi sierra eléctrica.

12/03/2022

De historias, errores y humildad

Hace muchos años, a finales de los noventas, trabajaba como guardia de seguridad cuidando el edificio de un centro de formación técnica que había quebrado. Este se encontraba en Moneda, entre Amunategui y San Martín. En su tejado pasé la noche del año nuevo del 1999 al 2000, mirando los fuegos artificiales de la Torre Entel comiéndome un pollo asado que me habían regalado unos transeúntes que iban a ver los fuegos.

En sus instalaciones se encontraban restos de materiales para la formación de técnicos en odontología, como modelos de maxilares con dientes de goma, maniquíes, y hasta un modelo a tamaño real de un cerebro hecho no se de que materiales, pero que parecía muy real.

Inevitablemente cometeremos errores, por lo que debemos estar abiertos y preparados a que aparezcan. Porqué la programación es una actividad humana, realizada por humanos, y no somos infalibles.

Sin embargo, lo que mas llamaba mi atención era la biblioteca del extinto centro. Un salón enorme repleto de libros tirados por el suelo y amontonados en viejos estantes conteniendo la sabiduría de épocas perdidas.

Era una delicia pasar las noches explorándola buscando que leer hasta encontrar un libro que llamase mi atención y tirarme cuan largo era en un viejo sillón después de poner un casete de Iron Maiden en una radio igual de vieja.

Fue en una de esas bonitas sesiones que encontré un libro, el cual en ese momento me puse a leer de puro intruso. El título era curioso; The psychology of computer programming, de Gerald Weinberg ¿Como se va a aplicar psicología a las computadoras? fue la primera idea que se me ocurrió cuando empecé a ojearlo. Después de unas paginas ya entendía que donde se hablaba de psicología, se refería a las personas que programaban, es decir, el autor veía la programación como una actividad humana y social, intentando entender como las personas crean software y se reunen para hacerlo, dentro de una organización; un ambiente donde se desempeñan.

Uno de sus capítulos trataba sobre aceptar los errores entendiendo que estos no significan un menoscabo a la persona del programador; pues es muy distinto decir encontré errores en mi código, que decir encontré errores en el código que escribí. Lo que trae a colación el concepto de la propiedad del código. Los programadores no son dueños del código que escriben en el mismo sentido que los pintores son dueños de la obra que pintan, aunque la pinten para otros. Ejemplificando, Leonardo siempre estará asociado al cuadro de La Gioconda, pero los programadores que participan en un equipo escribiendo código para un proyecto no tienen propiedad sobre este y lo mas seguro es que nadie los recordará cuando se hayan ido del equipo y hayan pasado algunos años.

Ahora, muchos años después de esos eventos, y siendo yo mismo después de muchas vueltas de la vida un programador que comete muchos errores, recordar esa lectura me hace pensar en los problemas que enfrentaban los programadores del pasado, y en como estos problemas no han cambiado tanto aunque hayan pasado hartos años desde que se publicara el libro (1971) Es curioso como aunque en este tiempo han aparecido cientos de lenguajes, las máquinas han aumentado exponencialmente su potencia, y se hayan descubierto nuevos e interesantes patrones, los problemas que enfrentan las personas al programar siguen siendo, citando al autor:

  • Instrospección sin validación
  • Observaciones sobre una base muy estrecha
  • Observar las variables equivocadas, o fallar en detactar las correctas
  • Interferir con el fenomeno observado
  • Generar muchos datos y poca información
  • No fijarse en efectos grupales y comportamientros de grupo
  • Medir solo lo simple de medir
  • Transferir resultados a situaciones inaplicables

¿Le suenan conocidas algunas de estas proposiciones? Desde que trabajo como programador las veo una y otra vez, cayendo en ellas. Me hacen sentir una cierta hermandad con el viejo investigador que las propusiera.

Después de meditar, la conclusión a la que llego trata sobre la humildad que deberíamos adoptar quienes escribimos código. Inevitablemente cometeremos errores, por lo que debemos estar abiertos y preparados a que aparezcan. Porqué la programación es una actividad humana, realizada por humanos, y no somos infalibles.

Dejo el enlace a Google books y a amazon de The Psychology of Computer Programming para quien pudiera interesarse.

Usted no necesita un curso de Go


Al menos no al principio de su viaje de aprendizaje.
Como con toda tecnología seria, puede aprender las bases en un fin de semana, pero necesitará una vida de esfuerzo y aprendizaje continuo para dominarla.

Si escribimos en el buscador de internet preferido de Pedro, Juan y Diego la frase Go course, aparecen de inmediato una miríada de resultados donde se ofrecen talleres, especializaciones, guías completas de cero a héroe; tanto gratuitas como de pago; prometiendo desde introducirlo en el, hasta convertirlo en un maestro en este lenguaje. No se ustedes pero yo desconfío de forma natural en cualquier cosa que prometa convertirme en ninja-wizard-valar-ponga aquí su término preferido para denotar maestría técnica, en un par de horas.

La verdad no necesita pagar para aprender Go, sus mantenedores y comunidad han preparado todo lo que necesita para conseguir una competencia técnica básica, y escribo básica porqué como con toda tecnología seria, puede aprender las bases en un fin de semana, pero necesitará una vida de esfuerzo y aprendizaje continuo para sentirse seguro y competente al usarla.

Una vez que domine las bases del lenguaje, por supuesto necesitará recursos para aprender y profundizar en técnicas avanzadas e implementaciones de ideas. Es posible que necesite un buen libro sobre construir micro servicios con Go, otro sobre como escribir apis, o sobre como implementar un repositorio para desacoplar sus fuentes de datos. Pero si lo piensa, estos recursos que sin duda necesitará tratan más sobre una tecnología o patrón que sobre el lenguaje en sí mismo.

Así que no tire su dinero en cursos que le venden algo que es gratuito y ya está a su disposición. Y si quiere saberlo, lo único que necesita para comenzar su viaje con Go es un computador conectado a internet, un navegador decente, ir al sitio del Tour de Go y seguir el tutorial interactivo que le enseñará desde declarar variables hasta como manejar concurrencia.

En serio, insisto de la forma más respetuosa posible. ¿Quiere aprender Go? No gaste dinero en cursos y abra en su navegador la página del Tour.