Introducción
En la sección anterior, completaste un programa básico de Go, que incluyó las siguientes líneas de código:
package main
import "fmt"
¿Cómo entendemos estas dos líneas de código? ¿Cómo usamos efectivamente las declaraciones package e import?
En este laboratorio (lab), aprenderás cómo crear e importar paquetes en Go. Esto te permitirá organizar tu código en módulos reutilizables, lo que hará que tus proyectos de Go sean más mantenibles y escalables.
Puntos de conocimiento:
- Definición y declaración de un paquete
- Comprensión de los identificadores exportados (públicos) y no exportados (privados)
- Diferentes formas de importar paquetes: importación individual, agrupada, con punto, con alias y anónima
Declaración y definición de paquetes
En este paso, declararás un paquete reutilizable y distinguirás los identificadores exportados de los privados del paquete.
Un paquete en Go es similar a los módulos en Python o las bibliotecas en C. Es una colección de archivos de código fuente utilizados para organizar y reutilizar código. Cada archivo de Go debe declarar un paquete al principio del archivo.
Nota: Un ejecutable de Go se construye a partir de un paquete
mainque contiene una funciónmain. Los paquetes reutilizables no necesitan ese punto de entrada. Un módulo puede contener varios directorios de comandos, cada uno con su propio paquetemain.
Puntos clave:
Identificadores exportados (públicos): Los identificadores (variables, funciones, tipos, etc.) que comienzan con una letra mayúscula son accesibles desde otros paquetes. Puedes pensar en ellos como la interfaz pública de tu paquete.
Identificadores no exportados (privados): Los identificadores que comienzan con una letra minúscula son solo accesibles dentro del mismo paquete. Estos se consideran detalles de implementación internos del paquete.
Cohesión del paquete: Todos los archivos en la misma carpeta deben pertenecer al mismo paquete. Esto asegura que el código relacionado se mantenga junto.
Convenciones de nomenclatura de paquetes: Los nombres de los paquetes deben ser en minúsculas, cortos y descriptivos, evitando guiones bajos o letras mayúsculas.
Vamos a crear nuestro propio paquete personalizado:
Crea una carpeta llamada propagandist y un archivo propagandist.go dentro de ella:
mkdir ~/project/propagandist
touch ~/project/propagandist/propagandist.go
Escribe el siguiente código en propagandist.go:
package propagandist
var Shout = "I Love LabEx" // Public variable
var secret = "I love the dress" // Private variable
func Hit() string {
return "Don't hit me, please!"
}
Shoutes público porque comienza con una letra mayúscula. Esto significa que puedes acceder a él desde otros paquetes que importenpropagandist.secretes privado porque comienza con una letra minúscula. Solo se puede usar dentro del paquetepropagandist.Hites una función pública, accesible desde otros paquetes.
Inicializa un módulo de Go para el paquete:
cd ~/project/propagandist
go mod init propagandist
Este comando inicializa un nuevo módulo de Go en el directorio propagandist, lo que ayuda a gestionar las dependencias del paquete.
Importación de un solo elemento
En este paso, importarás y utilizarás tu paquete local mediante declaraciones de importación separadas.
Para usar el paquete propagandist, vamos a crear un nuevo programa de Go. Este paso demostrará cómo importar y usar un solo paquete en tu código de Go.
Crea un nuevo archivo de Go llamado pacExercise.go en la carpeta del proyecto:
touch ~/project/pacExercise.go
Inicializa un módulo de Go para el programa:
cd ~/project
go mod init pacExercise
Actualiza el archivo go.mod para incluir la dependencia del paquete local. Ejecuta el siguiente comando en la terminal:
echo "replace propagandist => ./propagandist" >> go.mod
Importante: Este comando agrega una directiva replace a tu archivo go.mod. Esto es crucial porque le dice a Go que el paquete propagandist debe obtenerse del directorio local ./propagandist en lugar de intentar descargarlo de un repositorio remoto. Debes ejecutar este comando en tu terminal, lo que agregará la línea replace propagandist => ./propagandist a tu archivo go.mod. No escribas directamente esta línea en el archivo manualmente.
Escribe el siguiente código en pacExercise.go para importar y usar el paquete propagandist:
package main
import "fmt"
import "propagandist"
func main() {
fmt.Println(propagandist.Shout) // Access the public variable
}
- Este código importa el paquete
fmtpara imprimir salida y el paquetepropagandist. - Luego accede a la variable pública
Shoutdel paquetepropagandistutilizandopropagandist.Shout.
Ejecuta el programa:
go mod tidy
go run pacExercise.go
El comando go mod tidy asegura que tu archivo go.mod se actualice con cualquier nueva dependencia. El comando go run pacExercise.go compila y ejecuta el programa.
Salida esperada:
I Love LabEx
Importaciones agrupadas
En este paso, agruparás las importaciones de paquetes sin cambiar el comportamiento del programa.
Cuando se importan múltiples paquetes, se pueden utilizar importaciones agrupadas para mejorar la legibilidad y la organización. Esta es una elección estilística y no cambia la funcionalidad de tu código.
Modifica pacExercise.go para utilizar importaciones agrupadas:
```go
package main
import (
"fmt"
"propagandist"
)
func main() {
fmt.Println(propagandist.Shout)
}
```
En el fragmento de código anterior, los paquetes fmt y propagandist se importan dentro de un solo bloque import encerrado entre paréntesis. Esto facilita la lectura y el manejo de múltiples importaciones de paquetes. A diferencia de las declaraciones de importación separadas del paso anterior, esta forma agrupa las rutas de ambos paquetes en una sola declaración. El programa se comporta igual.
Ejecuta el programa para confirmar que sigue funcionando:
```bash
go run pacExercise.go
```
El programa debe ejecutarse sin errores y mostrar la misma salida que antes.
Importación con punto
En este paso, examinarás las importaciones con punto y sus implicaciones para el espacio de nombres.
Al utilizar una importación con punto, puedes omitir el prefijo del nombre del paquete al llamar a sus funciones o variables. Esto generalmente se desaconseja a favor de nombres de paquetes explícitos, ya que puede provocar conflictos de espacio de nombres y reducir la legibilidad. Sin embargo, es bueno saber qué es.
Modifica pacExercise.go para utilizar una importación con punto para fmt:
```go
package main
import . "fmt"
import "propagandist"
func main() {
Println(propagandist.Shout) // No `fmt.` prefix needed
}
```
- Aquí,
import . "fmt"significa que puedes usar funciones y variables del paquetefmtdirectamente sin el prefijofmt.. - Por ejemplo, se utiliza
Printlnen lugar defmt.Println.
Ejecuta el programa:
```bash
go run pacExercise.go
```
Salida esperada:
```bash
I Love LabEx
```
Importación con alias
En este paso, asignarás un alias local a un paquete importado.
Puedes dar un alias a un paquete importado para mayor claridad o para evitar conflictos cuando dos paquetes tienen nombres similares. Esto es útil para hacer que tu código sea más legible y manejar colisiones de espacios de nombres.
Modifica pacExercise.go para dar el alias io al paquete fmt:
```go
package main
import io "fmt"
import "propagandist"
func main() {
io.Println(propagandist.Shout) // Use the alias `io` instead of `fmt`
}
```
import io "fmt"crea un aliasiopara el paquetefmt.- Ahora, se utiliza
io.Printlnen lugar defmt.Println.
Ejecuta el programa:
```bash
go run pacExercise.go
```
Importación anónima
En este paso, importarás un paquete únicamente por sus efectos secundarios de inicialización.
Las importaciones anónimas se utilizan para importar un paquete por sus efectos secundarios, como ejecutar su función init(), sin necesidad de hacer referencia directa a ninguno de sus identificadores exportados. Esto es útil para paquetes que registran controladores (drivers) o realizan otras tareas de inicialización.
Modifica pacExercise.go para incluir una importación anónima del paquete time:
```go
package main
import (
"fmt"
"propagandist"
_ "time" // Anonymous import
)
func main() {
fmt.Println(propagandist.Shout)
}
```
import _ "time"es una importación anónima. El guión bajo_se utiliza como un identificador vacío, lo que le dice al compilador que estás importando el paquete por sus efectos secundarios y no harás referencia directa a nada de él en tu código.- La función
init()del paquetetimese ejecutará cuando se ejecute este programa. El paquetetimeno tiene efectos secundarios particulares visibles aquí; sin embargo, muchos paquetes utilizan esto para registrar controladores de base de datos o configuraciones.
Ejecuta el programa:
```bash
go run pacExercise.go
```
Salida esperada:
```bash
I Love LabEx
```
Comprender la función init()
En este paso, seguirás la inicialización de las variables de paquete y las funciones init() antes de que se ejecute main().
La función init() es una función especial de Go que desempeña un papel fundamental en la inicialización de paquetes. Go la ejecuta automáticamente cuando se importa un paquete, antes de ejecutar cualquier código de la función main. En esta sección conocerás los detalles de las funciones init() y cómo intervienen en el proceso de inicialización de Go.
Puntos clave sobre las funciones init():
Definición y finalidad:
- Una función
init()no tiene parámetros ni valores de retorno:func init() {} - Se utiliza para tareas de inicialización de paquetes, como establecer estados iniciales, registrar controladores o validar requisitos previos.
Orden de ejecución:
- Go garantiza que un paquete se inicialice una sola vez, aunque se importe varias veces.
- La inicialización sigue un orden bien definido:
- Primero se inicializan las variables a nivel de paquete.
- Después se ejecutan las funciones
init(). - Por último se ejecuta la función
main()(solo en el paquete main).
Varias funciones init():
- Un solo archivo de Go puede contener varias funciones
init(). - Varios archivos del mismo paquete pueden tener cada uno sus propias funciones
init(). - Todas estas funciones
init()se ejecutan en el orden de declaración del código fuente, siguiendo el orden en que se presentan los archivos al compilador. La herramienta Go presenta los archivos del paquete en orden lexicográfico de sus nombres; evita depender del orden de los archivos para expresar dependencias.
Cadena de dependencias:
- Cuando un paquete importa otros paquetes, Go garantiza que las funciones
init()de las dependencias se ejecuten primero. - Esto crea un flujo de inicialización de abajo hacia arriba: las dependencias más profundas se inicializan primero.
Vamos a crear un ejemplo práctico para demostrar cómo funcionan las funciones init():
Primero, modifica nuestro paquete propagandist para incluir una función init(). Actualiza propagandist.go:
package propagandist
import "fmt"
var Shout = "I Love LabEx" // Public variable
var secret = "I love the dress" // Private variable
var initialized bool
func init() {
fmt.Println("Initializing propagandist package...")
initialized = true
}
func Hit() string {
return "Don't hit me, please!"
}
func IsInitialized() bool {
return initialized
}
Ahora crea otro archivo en el paquete propagandist para demostrar el uso de varias funciones init():
touch ~/project/propagandist/second.go
Añade el siguiente contenido al archivo:
package propagandist
import "fmt"
func init() {
fmt.Println("Second init function in propagandist package...")
}
Crea un nuevo paquete helper para demostrar el orden de inicialización:
mkdir -p ~/project/helper
touch ~/project/helper/helper.go
Añade el siguiente contenido al archivo:
package helper
import "fmt"
var Message = "Helper package is ready"
func init() {
fmt.Println("Initializing helper package...")
}
func GetMessage() string {
return Message
}
Añade el archivo de módulo del paquete helper:
cd ~/project/helper
go mod init helper
Actualiza pacExercise.go para utilizar ambos paquetes y demostrar el orden de inicialización:
package main
import (
"fmt"
"helper"
"propagandist"
)
func init() {
fmt.Println("Initializing main package...")
}
func main() {
fmt.Println("Main function is running")
fmt.Println(propagandist.Shout)
fmt.Println(helper.Message)
fmt.Printf("Propagandist initialized: %v\n", propagandist.IsInitialized())
}
Actualiza el archivo go.mod del proyecto principal para incluir el paquete local helper:
cd ~/project
echo "replace helper => ./helper" >> go.mod
go mod tidy
Ejecuta el programa y observa el orden de inicialización:
go run pacExercise.go
Salida esperada con los nombres de archivo proporcionados y la herramienta Go:
Initializing helper package...
Initializing propagandist package...
Second init function in propagandist package...
Initializing main package...
Main function is running
I Love LabEx
Helper package is ready
Propagandist initialized: true
Esta salida demuestra los aspectos clave del proceso de inicialización de Go:
Los paquetes de los que depende otro paquete se inicializan antes que el paquete que los importa.
Dentro de un mismo paquete, todas las funciones init() se ejecutan en orden de declaración, según se presentan los archivos fuente al compilador.
La función main() solo se ejecuta después de completar la inicialización de todos los paquetes.
Las variables a nivel de paquete se inicializan antes de ejecutar cualquier función init().
Esta secuencia de inicialización es importante al diseñar paquetes de Go, especialmente al gestionar dependencias o realizar operaciones de preparación que deben suceder en un orden concreto.
Resumen
En este laboratorio, has aprendido:
Cómo crear y definir paquetes personalizados en Go, encapsulando código reutilizable.
La diferencia entre identificadores públicos (exportados) y privados (no exportados) y cómo afectan la accesibilidad.
Varios métodos para importar paquetes, cada uno con su caso de uso:
- Importación de un solo elemento: Importar un paquete a la vez.
- Importación agrupada: Importar múltiples paquetes en un solo bloque para una mejor organización.
- Importación con punto: Importar un paquete y usar sus identificadores directamente sin el prefijo del nombre del paquete. (Usar con precaución)
- Importación con alias: Renombrar paquetes importados para una mejor legibilidad o para evitar conflictos de nombres.
- Importación anónima: Importar un paquete únicamente por sus efectos secundarios, como la inicialización.
El papel de la función
init()en los paquetes y cómo las importaciones anónimas pueden desencadenar su ejecución.El funcionamiento detallado del proceso de inicialización de Go, que incluye:
- Cómo se inicializan las variables a nivel de paquete antes de las funciones
init() - El orden de ejecución garantizado de las funciones
init()entre paquetes con dependencias - Cómo funcionan varias funciones
init()dentro de un paquete - El flujo completo de inicialización desde las dependencias hasta la función main
- Cómo se inicializan las variables a nivel de paquete antes de las funciones
Al completar este laboratorio, ahora estás capacitado para estructurar y gestionar proyectos de Go utilizando paquetes de manera efectiva. Puedes crear módulos reutilizables, controlar el acceso a los identificadores, organizar mejor tu código y comprender el proceso de inicialización, lo que conduce a aplicaciones de Go más mantenibles y escalables.



