Mostrando las entradas con la etiqueta guorkshop. Mostrar todas las entradas
Mostrando las entradas con la etiqueta guorkshop. Mostrar todas las entradas

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