Criando e Importando Pacotes Go

GolangBeginner
Pratique Agora

Introdução

Na seção anterior, você completou um programa Go básico, que incluiu as seguintes linhas de código:

package main
import "fmt"

Como entendemos essas duas linhas de código? Como usamos as declarações package e import de forma eficaz?

Neste laboratório, você aprenderá como criar e importar pacotes em Go. Isso permitirá que você organize seu código em módulos reutilizáveis, tornando seus projetos Go mais fáceis de manter e escalar.

Pontos de Conhecimento:

  • Definição e declaração de um pacote
  • Compreensão de identificadores exportados (públicos) e não exportados (privados)
  • Diferentes formas de importar pacotes: importações simples, agrupadas, ponto, alias e anônimas

Declarando e Definindo Pacotes

Nesta etapa, você declarará um pacote reutilizável e distinguirá identificadores exportados dos identificadores privados do pacote.

Um pacote em Go é semelhante aos módulos em Python ou às bibliotecas em C. É uma coleção de arquivos de código-fonte usados para organizar e reutilizar código. Cada arquivo Go deve declarar um pacote no início do arquivo.

Nota: Um executável Go é construído a partir de um pacote main que contém uma função main. Pacotes reutilizáveis não precisam desse ponto de entrada. Um módulo pode conter vários diretórios de comandos, cada um com seu próprio pacote main.

Pontos Chave:

Identificadores Exportados (Públicos): Identificadores (variáveis, funções, tipos, etc.) que começam com uma letra maiúscula são acessíveis de outros pacotes. Pense neles como a interface pública do seu pacote.

Identificadores Não Exportados (Privados): Identificadores que começam com uma letra minúscula são acessíveis apenas dentro do mesmo pacote. Estes são considerados detalhes de implementação interna do pacote.

Coesão do Pacote: Todos os arquivos na mesma pasta devem pertencer ao mesmo pacote. Isso garante que o código relacionado permaneça junto.

Convenções de Nomenclatura de Pacotes: Os nomes dos pacotes devem ser minúsculos, curtos e descritivos, evitando sublinhados ou letras maiúsculas.

Vamos criar nosso próprio pacote personalizado:

Crie uma pasta chamada propagandist e um arquivo propagandist.go dentro dela:

   mkdir ~/project/propagandist
   touch ~/project/propagandist/propagandist.go

Escreva o seguinte código em 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!"
   }
  • Shout é público porque começa com uma letra maiúscula. Isso significa que você pode acessá-lo de outros pacotes que importam propagandist.
  • secret é privado porque começa com uma letra minúscula. Ele só pode ser usado dentro do pacote propagandist.
  • Hit é uma função pública, acessível de outros pacotes.

Inicialize um módulo Go para o pacote:

   cd ~/project/propagandist
   go mod init propagandist

Este comando inicializa um novo módulo Go no diretório propagandist, o que ajuda a gerenciar as dependências do pacote.

Importação de Item Único

Nesta etapa, você importará e usará seu pacote local com declarações de importação separadas.

Para usar o pacote propagandist, vamos criar um novo programa Go. Esta etapa demonstrará como importar e usar um único pacote em seu código Go.

Crie um novo arquivo Go chamado pacExercise.go na pasta do projeto:

   touch ~/project/pacExercise.go

Inicialize um módulo Go para o programa:

   cd ~/project
   go mod init pacExercise

Atualize o arquivo go.mod para incluir a dependência do pacote local. Execute o seguinte comando no terminal:

   echo "replace propagandist => ./propagandist" >> go.mod

Importante: Este comando adiciona uma diretiva replace ao seu arquivo go.mod. Isso é crucial porque informa ao Go que o pacote propagandist deve ser obtido do diretório local ./propagandist em vez de tentar baixá-lo de um repositório remoto. Execute este comando no terminal para acrescentar a linha replace propagandist => ./propagandist ao arquivo go.mod. Não escreva essa linha diretamente no arquivo manualmente.

Escreva o seguinte código em pacExercise.go para importar e usar o pacote propagandist:

   package main

   import "fmt"
   import "propagandist"

   func main() {
       fmt.Println(propagandist.Shout) // Access the public variable
   }
  • Este código importa o pacote fmt para imprimir a saída e o pacote propagandist.
  • Em seguida, ele acessa a variável pública Shout do pacote propagandist usando propagandist.Shout.

Execute o programa:

   go mod tidy
   go run pacExercise.go

O comando go mod tidy garante que seu arquivo go.mod seja atualizado com quaisquer novas dependências. O comando go run pacExercise.go compila e executa o programa.

Saída Esperada:

   I Love LabEx

Imports Agrupados

Nesta etapa, você agrupará as importações de pacotes sem alterar o comportamento do programa.

Ao importar vários pacotes, você pode usar importações agrupadas para melhor legibilidade e organização. Esta é uma escolha estilística e não altera a funcionalidade do seu código.

Modifique pacExercise.go para usar importações agrupadas:

    package main

    import (
        "fmt"
        "propagandist"
    )

    func main() {
        fmt.Println(propagandist.Shout)
    }
    ```

No trecho de código acima, os pacotes `fmt` e `propagandist` são importados dentro de um único bloco `import` entre parênteses. Isso facilita a leitura e o gerenciamento de múltiplas importações de pacotes. Ao contrário das declarações de importação separadas da etapa anterior, esta forma agrupa os caminhos dos dois pacotes em uma declaração. O comportamento do programa permanece igual.

Execute o programa para confirmar que ele ainda funciona:

```bash
    go run pacExercise.go
    ```

O programa deve ser executado sem erros e produzir o mesmo resultado de antes.


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="dot-import"></div>

## Dot Import

Nesta etapa, você examinará as importações com ponto e suas implicações para o namespace.

Usando uma **importação com ponto** (dot import), você pode omitir o prefixo do nome do pacote ao chamar suas funções ou variáveis. Isso é frequentemente desencorajado em favor de nomes de pacotes explícitos, pois pode levar a conflitos de namespace e reduzir a legibilidade. No entanto, é bom saber o que é.

Modifique `pacExercise.go` para usar uma importação com ponto para `fmt`:

```go
    package main

    import . "fmt"
    import "propagandist"

    func main() {
        Println(propagandist.Shout) // No `fmt.` prefix needed
    }
    ```

- Aqui, `import . "fmt"` significa que você pode usar funções e variáveis do pacote `fmt` diretamente sem o prefixo `fmt.`.
- Por exemplo, você usa `Println` em vez de `fmt.Println`.

Execute o programa:

```bash
    go run pacExercise.go
    ```

Saída Esperada:

```bash
    I Love LabEx
    ```


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="alias-import"></div>

## Alias Import

Nesta etapa, você dará um alias local a um pacote importado.

Você pode criar um alias para um pacote importado para clareza ou para evitar conflitos quando dois pacotes têm nomes semelhantes. Isso é útil para tornar seu código mais legível e gerenciar colisões de namespace.

Modifique `pacExercise.go` para criar um alias `io` para `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"` cria um alias `io` para o pacote `fmt`.
- Agora, você usa `io.Println` em vez de `fmt.Println`.

Execute o programa:

```bash
    go run pacExercise.go
    ```


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="importa%C3%A7%C3%A3o-an%C3%B4nima"></div>

## Importação Anônima

Nesta etapa, você importará um pacote apenas pelos efeitos colaterais de sua inicialização.

Importações anônimas são usadas para importar um pacote por seus efeitos colaterais (side effects), como executar sua função `init()`, sem precisar referenciar diretamente nenhum de seus identificadores exportados. Isso é útil para pacotes que registram drivers ou executam outras tarefas de inicialização.

Modifique `pacExercise.go` para incluir uma importação anônima para `time`:

```go
    package main

    import (
        "fmt"
        "propagandist"
        _ "time" // Anonymous import
    )

    func main() {
        fmt.Println(propagandist.Shout)
    }
    ```

- `import _ "time"` é uma importação anônima. O sublinhado `_` é usado como um identificador em branco, dizendo ao compilador que você está importando o pacote por seus efeitos colaterais e não fará referência a nada dele diretamente em seu código.
- A função `init()` do pacote `time` será executada quando este programa for executado. O pacote `time` não tem nenhum efeito colateral particular visível aqui; no entanto, muitos pacotes usam isso para registrar drivers de banco de dados ou configurações.

Execute o programa:

```bash
    go run pacExercise.go
    ```

Saída Esperada:

```bash
    I Love LabEx
    ```


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="entendendo-a-fun%C3%A7%C3%A3o-init()"></div>

## Entendendo a Função init()

Nesta etapa, você acompanhará as variáveis dos pacotes e as funções init() antes da execução de main().

A função `init()` é uma função especial em Go que desempenha um papel crucial na inicialização de pacotes. Ela é executada automaticamente pelo Go quando um pacote é importado, antes que qualquer outro código na função `main` seja executado. Esta seção explicará os detalhes das funções `init()` e como elas funcionam dentro do processo de inicialização do Go.

**Pontos Chave Sobre as Funções `init()`:**

**Definição e Propósito:**

- Uma função `init()` não tem parâmetros e não retorna valores: `func init() {}`
- Ela é usada para tarefas de inicialização de pacotes, como configurar estados iniciais, registrar drivers ou validar pré-requisitos.

**Ordem de Execução:**

- Go garante que a inicialização do pacote ocorra apenas uma vez, mesmo que um pacote seja importado várias vezes.
- A inicialização segue uma ordem bem definida:
- Variáveis de nível de pacote são inicializadas primeiro
- Em seguida, as funções `init()` são executadas
- Finalmente, a função `main()` é executada (apenas no pacote principal)

**Múltiplas Funções `init()`:**

- Um único arquivo Go pode conter múltiplas funções `init()`
- Múltiplos arquivos no mesmo pacote podem ter suas próprias funções `init()`
- Todas essas funções `init()` são executadas na ordem de declaração no código-fonte, seguindo a ordem em que os arquivos são apresentados ao compilador. A ferramenta Go apresenta os arquivos do pacote em ordem lexicográfica dos nomes de arquivo; evite depender dessa ordem para expressar dependências.

**Cadeia de Dependência:**

- Quando os pacotes importam outros pacotes, Go garante que as funções `init()` nas dependências sejam executadas primeiro
- Isso cria um fluxo de inicialização de baixo para cima: as dependências mais profundas inicializam primeiro

Vamos criar um exemplo prático para demonstrar como as funções `init()` funcionam:

Primeiro, vamos modificar nosso pacote `propagandist` para incluir uma função `init()`. Atualize `propagandist.go`:

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

Agora, vamos criar outro arquivo no pacote propagandist para demonstrar múltiplas funções init():

   touch ~/project/propagandist/second.go

Adicione o seguinte conteúdo ao arquivo:

   package propagandist

   import "fmt"

   func init() {
       fmt.Println("Second init function in propagandist package...")
   }

Crie um novo pacote auxiliar para demonstrar a ordem de inicialização:

   mkdir -p ~/project/helper
   touch ~/project/helper/helper.go

Adicione o seguinte conteúdo ao arquivo:

   package helper

   import "fmt"

   var Message = "Helper package is ready"

   func init() {
       fmt.Println("Initializing helper package...")
   }

   func GetMessage() string {
       return Message
   }

Adicione o arquivo do módulo para o pacote auxiliar:

   cd ~/project/helper
   go mod init helper

Atualize seu pacExercise.go para usar ambos os pacotes e demonstrar a ordem de inicialização:

   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())
   }

Atualize o arquivo go.mod no projeto principal para incluir o pacote auxiliar local:

   cd ~/project
   echo "replace helper => ./helper" >> go.mod
   go mod tidy

Execute o programa e observe a ordem de inicialização:

   go run pacExercise.go

Saída esperada com os nomes de arquivo fornecidos e a ferramenta 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 saída demonstra os principais aspectos do processo de inicialização do Go:

Pacotes dependentes são inicializados antes dos pacotes que os importam

Dentro de um único pacote, todas as funções init() são executadas na ordem de declaração conforme os arquivos-fonte são apresentados ao compilador

A função main() é executada somente após a conclusão de todas as inicializações dos pacotes

Variáveis de nível de pacote são inicializadas antes que quaisquer funções init() sejam executadas

Esta sequência de inicialização é uma consideração importante ao projetar pacotes Go, especialmente ao gerenciar dependências ou executar operações de configuração que devem acontecer em uma ordem específica.

Resumo

Neste laboratório, você aprendeu:

  1. Como criar e definir pacotes personalizados em Go, encapsulando código reutilizável.
  2. A diferença entre identificadores públicos (exportados) e privados (não exportados) e como eles impactam a acessibilidade.
  3. Várias maneiras de importar pacotes, cada uma com seu caso de uso:
    • Importação de item único: Importando um pacote por vez.
    • Importação agrupada: Importando múltiplos pacotes em um único bloco para melhor organização.
    • Importação com ponto: Importando um pacote e usando seus identificadores diretamente sem o prefixo do nome do pacote. (Use com cautela)
    • Importação com alias: Renomeando pacotes importados para melhor legibilidade ou para evitar conflitos de nomes.
    • Importação anônima: Importando um pacote unicamente por seus efeitos colaterais, como inicialização.
  4. O papel da função init() em pacotes e como as importações anônimas podem acionar sua execução.
  5. O funcionamento detalhado do processo de inicialização do Go, incluindo:
    • Como as variáveis de nível de pacote são inicializadas antes das funções init()
    • A ordem de execução garantida das funções init() em pacotes dependentes
    • Como múltiplas funções init() funcionam dentro de um pacote
    • O fluxo completo de inicialização de pacotes dependentes para a função principal

Ao concluir este laboratório, você agora está equipado para estruturar e gerenciar projetos Go usando pacotes de forma eficaz. Você pode criar módulos reutilizáveis, controlar o acesso a identificadores, organizar melhor seu código e entender o processo de inicialização, levando a aplicações Go mais sustentáveis e escaláveis.