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 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