One place for hosting & domains

      pacotes

      Como escrever pacotes em Go


      Um pacote é composto por arquivos em Go que ficam no mesmo diretório e têm a mesma instrução de pacotes no início. Você pode incluir funcionalidades adicionais dos pacotes para tornar seus programas mais sofisticados. Alguns pacotes estão disponíveis através da Biblioteca Padrão de Go e, por isso, vêm instalados com sua instalação de Go. Outros podem ser instalados com o comando go get de Go. Você também pode compilar seus próprios pacotes Go, criando arquivos em Go no mesmo diretório em que quiser compartilhar códigos, usando a instrução de pacote necessária.

      Este tutorial irá explicar sobre como escrever pacotes em Go para usar dentro de outros arquivos de programação.

      Pré-requisitos

      • Configurar um ambiente de programação em Go, seguindo um dos tutoriais da série sobre Como instalar e configurar um ambiente de programação local para a linguagem Go. Criar seu espaço de trabalho em Go seguindo o Passo 5 dos tutoriais sobre Ambiente de programação local. Para seguir o exemplo e as convenções de nomenclatura neste artigo, leia a primeira seção do artigo Criando e importando pacotes.
      • Para aprofundar seu conhecimento sobre o GOPATH, leia nosso artigo Entendendo o GOPATH.

      Criando e importando pacotes

      Escrever um pacote é muito parecido com escrever qualquer outro arquivo em Go. Os pacotes podem conter definições de funções, tipos e variáveis que podem então ser usados em outros programas em Go.

      Antes de criar um novo pacote, precisamos estar no nosso espaço de trabalho em Go. Normalmente, esse espaço fica em nosso gopath. Por exemplo, neste tutorial, chamaremos o pacote de greet. Para tanto, criamos um diretório chamado greet em nosso gopath no nosso espaço de projeto. Se nossa organização fosse a gopherguides e quiséssemos criar o pacote greet sob a organização enquanto estivéssemos usando o Github como nosso repositório de códigos, então nosso diretório se pareceria com este:

      └── $GOPATH
          └── src
              └── github.com
                  └── gopherguides
      

      O diretório greet está dentro do diretório gopherguides:

      └── $GOPATH
          └── src
              └── github.com
                  └── gopherguides
                      └── greet
      

      Por fim, podemos adicionar o primeiro arquivo em nosso diretório. É considerada prática comum nomear o arquivo primary ou entry point de um pacote com o mesmo nome do diretório. Neste caso, criaríamos um arquivo chamado greet.go dentro do diretório greet:

      └── $GOPATH
          └── src
              └── github.com
                  └── gopherguides
                      └── greet
                          └── greet.go
      

      Com o arquivo criado, podemos começar a escrever nosso código que queremos reutilizar ou compartilhar entre projetos. Neste caso, criaremos uma função chamada Hello que imprime Hello World.

      Abra o arquivo greet.go no seu editor de texto e adicione o seguinte código:

      greet.go

      package greet
      
      import "fmt"
      
      func Hello() {
          fmt.Println("Hello, World!")
      }
      

      Vamos desdobrar este primeiro arquivo. A primeira linha de cada arquivo precisa do nome do package [pacote] em que você está trabalhando. Como você está no pacote greet, você vai usar a palavra-chave package, seguida do nome do pacote:

      package greet
      

      Isso dirá ao compilador para tratar tudo no arquivo como sendo parte do pacote greet.

      Em seguida, declare quaisquer outros pacotes que precisar usar com a instrução import. Você está usando apenas um neste arquivo — o pacote fmt:

      import "fmt"
      

      Por fim, crie a função Hello. Ela usará o pacote fmt para imprimir o Hello, World!:

      func Hello() {
          fmt.Println("Hello, World!")
      }
      

      Agora que você escreveu o pacote greet, você pode usá-lo em qualquer outro pacote que você criar. Vamos criar um novo pacote no qual você usará seu pacote greet.

      Você criará um pacote chamado example, que significa que você precisa de um diretório chamado example. Crie este pacote em sua organização gopherguides, de maneira que a estrutura de diretório fique parecida com esta:

      └── $GOPATH
          └── src
              └── github.com
                  └── gopherguides
                          └── example
      

      Agora que você tem um diretório para seu novo pacote, você pode criar o arquivo de ponto de entrada. Como este será um programa executável, é considerada como melhor prática nomear o arquivo o ponto de entrada como main.go:

      └── $GOPATH
          └── src
              └── github.com
                  └── gopherguides
                      └── example
                          └── main.go
      

      No seu editor de texto, abra o main.go e adicione o seguinte código para chamar o pacote greet:

      main.go

      package main
      
      import "github.com/gopherguides/greet"
      
      func main() {
          greet.Hello()
      }
      

      Como você está importando um pacote, você precisa chamar a função, referenciando o nome do pacote em notação de ponto. A notação de ponto é a prática de colocar um ponto . entre o nome do pacote que estiver usando e o recurso dentro daquele pacote que você vai querer usar. Por exemplo, em seu pacote greet, você tem a função Hello como um recurso. Se quiser chamar aquele recurso, utilize a notação de ponto de greet.Hello().

      Agora, abra seu terminal e execute o programa na linha de comando:

      Quando fizer isso, você receberá o seguinte resultado:

      Output

      Hello, World!

      Para ver como pode usar variáveis em um pacote, vamos adicionar uma definição de variável ao seu arquivo greet.go:

      greet.go

      package greet
      
      import "fmt"
      
      var Shark = "Sammy"
      
      func Hello() {
          fmt.Println("Hello, World!")
      }
      

      Em seguida, abra seu arquivo main.go e adicione a seguinte linha destacada para chamar a variável do greet.go em uma função fmt.Println():

      main.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/greet"
      )
      
      func main() {
          greet.Hello()
      
          fmt.Println(greet.Shark)
      }
      

      Assim que você executar o programa novamente:

      Você receberá o seguinte resultado:

      Output

      Hello, World! Sammy

      Por fim, vamos também definir um tipo no arquivo greet.go. Você criará o tipo Octopus com os campos name e color e uma função que irá imprimir os campos quando chamada:

      greet.go

      package greet
      
      import "fmt"
      
      var Shark = "Sammy"
      
      type Octopus struct {
          Name  string
          Color string
      }
      
      func (o Octopus) String() string {
          return fmt.Sprintf("The octopus's name is %q and is the color %s.", o.Name, o.Color)
      }
      
      func Hello() {
          fmt.Println("Hello, World!")
      }
      

      Abra o main.go para criar uma instância desse tipo no final do arquivo:

      main.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/greet"
      )
      
      func main() {
          greet.Hello()
      
          fmt.Println(greet.Shark)
      
          oct := greet.Octopus{
              Name:  "Jesse",
              Color: "orange",
          }
      
          fmt.Println(oct.String())
      }
      

      Assim que tiver criado uma instância do tipo Octopus com oct := greet.Octopus, você poderá acessar as funções e os campos do tipo dentro do namespace do arquivo main.go. Isso permite que você escreva o oct.String() na última linha sem invocar o greet. Seria possível também, por exemplo, chamar um dos campos dos tipos como oct.Color, sem se referir ao nome do pacote greet.

      O método String no tipo Octopus usa a função fmt.Sprintf para criar uma frase e returns [retorna] o resultado, uma string, para o chamador (neste caso, seu programa principal).

      Quando executar o programa, você receberá o seguinte resultado:

      Output

      Hello, World! Sammy The octopus's name is "Jesse" and is the color orange.

      Ao criar o método String no Octopus, você agora terá uma maneira reusável de imprimir informações sobre seu tipo personalizado. Se quiser alterar o comportamento deste método no futuro, você só precisará edicar esse método específico.

      Código exportado

      Você pode ter notado que todas as declarações no arquivo greet.go que você chamou estavam em letra maiúscula. A linguagem Go não tem o conceito dos modificadores public [públicos], private [privados], ouprotected[protegidos] como outras linguagens têm. A visibilidade externa é controlada pelo uso da letra maiúscula. Os tipos, variáveis, funções e assim por diante, que começam com uma letra maiúscula estão disponíveis publicamente, fora de seu pacote atual. Um símbolo visível fora do seu pacote é considerado comoexported`.

      Caso adicione um novo método para o Octopus chamado reset, você pode chamá-lo de dentro do pacote greet, mas não do seu arquivo main.go, que está fora do pacote greet:

      greet.go

      package greet
      
      import "fmt"
      
      var Shark = "Sammy"
      
      type Octopus struct {
          Name  string
          Color string
      }
      
      func (o Octopus) String() string {
          return fmt.Sprintf("The octopus's name is %q and is the color %s.", o.Name, o.Color)
      }
      
      func (o *Octopus) reset() {
          o.Name = ""
          o.Color = ""
      }
      
      func Hello() {
          fmt.Println("Hello, World!")
      }
      
      

      Se tentar chamar o reset do arquivo main.go:

      main.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/greet"
      )
      
      func main() {
          greet.Hello()
      
          fmt.Println(greet.Shark)
      
          oct := greet.Octopus{
              Name:  "Jesse",
              Color: "orange",
          }
      
          fmt.Println(oct.String())
      
          oct.reset()
      }
      

      Você receberá o seguinte erro de compilação:

      Output

      oct.reset undefined (cannot refer to unexported field or method greet.Octopus.reset)

      Para export [exportar] a funcionalidade reset do Octopus, escreva reset com R maiúsculo:

      greet.go

      package greet
      
      import "fmt"
      
      var Shark = "Sammy"
      
      type Octopus struct {
          Name  string
          Color string
      }
      
      func (o Octopus) String() string {
          return fmt.Sprintf("The octopus's name is %q and is the color %s.", o.Name, o.Color)
      }
      
      func (o *Octopus) Reset() {
          o.Name = ""
          o.Color = ""
      }
      
      func Hello() {
          fmt.Println("Hello, World!")
      }
      

      Como resultado, você pode chamar o Reset do seu outro pacote sem receber um erro:

      main.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/greet"
      )
      
      func main() {
          greet.Hello()
      
          fmt.Println(greet.Shark)
      
          oct := greet.Octopus{
              Name:  "Jesse",
              Color: "orange",
          }
      
          fmt.Println(oct.String())
      
          oct.Reset()
      
          fmt.Println(oct.String())
      }
      

      Agora, se executar o programa:

      Você receberá o seguinte resultado:

      Output

      Hello, World! Sammy The octopus's name is "Jesse" and is the color orange The octopus's name is "" and is the color .

      Ao chamar o Reset, você limpou todas as informações nos campos Name e Color. Quando chamar o método String, ele não imprimirá nada onde o Name e Color normalmente aparecem, pois os campos estão agora vazios.

      Conclusão

      Criar um pacote em Go é o mesmo que escrever qualquer outro arquivo em Go, mas colocá-lo em outro diretório permite que você isole o código para ser reusado em outro lugar. Este tutorial tratou de como escrever definições dentro de um pacote, demonstrou como usar essas definições dentro de outro arquivo de programação em Go e explicou as opções de onde manter o pacote para acessá-lo.



      Source link

      Importando pacotes em Go


      Introdução

      Haverá ocasiões em que o seu código irá precisar de funcionalidades adicionais, fora do seu programa atual. Nesses casos, utilize pacotes para tornar seu programa mais sofisticado. Um pacote representa todos os arquivos em um único diretório em disco. Os pacotes podem definir funções, tipos e interfaces que você pode referenciar em outros arquivos ou pacotes em Go.

      Este tutorial irá explicar sobre como instalar, importar e gerar pacotes de alias.

      Pacotes da biblioteca padrão

      A biblioteca padrão que vem com o Go é um conjunto de pacotes. Esses pacotes contêm muitos dos blocos de compilação essenciais para escrever softwares modernos. Por exemplo, o pacote fmt contém funções básicas para a formatação e impressão de strings. O pacote net/http contém funções que permitem que um desenvolvedor crie serviços para a Web, envie e recupere dados através do protocolo http, entre outros.

      Para usar as funções em um pacote, você precisa acessar o pacote com uma instrução import. Uma instrução import é composta pela palavra-chave import junto com o nome do pacote.

      Como exemplo, no arquivo do programa Go random.go você pode importar o pacote math/rand para gerar números aleatórios desta maneira:

      random.go

      import "math/rand"
      

      Quando importamos um pacote, nós o disponibilizamos em nosso programa atual como um namespace separado. Isso significa que teremos que nos referir à função em notação de ponto, como em package.function.

      Na prática, uma função do pacote math/rand poderia se parecer com estes exemplos:

      • rand.Int() que chama a função para retornar uma número inteiro aleatório.
      • rand.Intn() que chama a função para retornar um elemento aleatório de 0 até o número especificado fornecido.

      Vamos criar um loop for para mostrar como chamaremos uma função do pacote math/rand dentro de nosso programa random.go:

      random.go

      package main
      
      import "math/rand"
      
      func main() {
        for i := 0; i < 10; i++ {
          println(rand.Intn(25))
        }
      }
      

      Esse programa importa primeiro o pacote math/rand na terceira linha e, depois, inicia um loop for que será executado 10 vezes. Dentro do loop, o programa imprimirá um número inteiro aleatório no intervalo de 0 a 25. O inteiro 25 é enviado para o rand.Intn() como seu parâmetro.

      Quando executamos o programa com go run random.go, receberemos 10 inteiros aleatórios como resultado. Como eles são aleatórios, é provável que você receba diferentes números inteiros sempre que executar o programa. O resultado se parecerá com este:

      Output

      6 12 22 9 6 18 0 15 6 0

      Os inteiros nunca serão menores que 0 ou maiores que 24.

      Ao importar mais de um pacote, utilize o () para criar um bloco. Ao usar um bloco, você pode evitar a repetição da palavra-chave import a cada linha. Isso dará uma aparência mais limpa ao seu código:

      random.go

      
      import (
        "fmt"
        "math/rand"
      )
      

      Para usar o pacote adicional, podemos agora formatar o resultado e imprimir a iteração em que cada número aleatório foi gerado durante o loop:

      random.go

      package main
      
      import (
        "fmt"
        "math/rand"
      )
      
      func main() {
        for i := 0; i < 10; i++ {
          fmt.Printf("%d) %dn", i, rand.Intn(25))
        }
      }
      

      Agora, quando executarmos nosso programa, receberemos um resultado que se parece com este:

      Output

      0) 6 1) 12 2) 22 3) 9 4) 6 5) 18 6) 0 7) 15 8) 6 9) 0

      Nesta seção, aprendemos a importar pacotes e usá-los para escrever um programa mais sofisticado. Até agora, usamos apenas pacotes da biblioteca padrão. Em seguida, vamos ver como instalar e usar pacotes que são escritos por outros desenvolvedores.

      Instalando pacotes

      Embora a biblioteca padrão venha com muitos pacotes notáveis e úteis, eles foram intencionalmente concebidos para ser de propósito geral e não específico, em natureza. Isso permite que os desenvolvedores compilem seus próprios pacotes além da biblioteca padrão para suas próprias necessidades específicas.

      A cadeia de ferramentas em Go vem com o comando go get. Esse comando permite que você instale pacotes de terceiros em seu ambiente de desenvolvimento local e os utilize em seu programa.

      Ao usar o go get para instalar pacotes de terceiros, é comum que um pacote seja referenciado através de seu caminho canônico. Esse caminho também pode ser o caminho de um projeto público hospedado em um repositório de códigos como o GitHub. Como tal, se quisesse importar o pacote flect, você usaria o caminho canônico completo:

      • go get github.com/gobuffalo/flect

      A ferramenta go get irá encontrar o pacote – neste caso, no GitHub – e irá instalá-lo em seu $GOPATH.

      Neste exemplo, o código seria instalado neste diretório:

      $GOPATH/src/github.com/gobuffalo/flect
      

      Os pacotes frequentemente são atualizados pelos autores originais para corrigir bugs ou adicionar novos recursos. Quando isso acontece, você pode querer usar a versão mais recente daquele pacote para se beneficiar de novos recursos ou de bugs já solucionados. Para atualizar um pacote, você pode usar um sinalizador -u com o comando go get:

      • go get -u github.com/gobuffalo/flect

      Esse comando também fará o Go instalar o pacote se ele não for encontrado localmente. Se ele já estiver instalado, o Go tentará atualizar o pacote para a versão mais recente.

      O comando go get sempre recupera a versão mais recente do pacote disponível. No entanto, pode haver atualizações para as versões anteriores do pacote, que são ainda mais atuais do que a que você está usando – e seria útil atualizá-las em seu programa. Para recuperar essa versão específica do pacote, você precisaria usar uma ferramenta de Gerenciamento de Pacotes, como a Go Modules.

      A partir da versão Go 1.11, os Go Modules são usados para gerenciar qual versão do pacote você quer importar. O tema de gerenciamento de pacotes está além do âmbito deste artigo, mas você pode ler mais a respeito na página Go Modules do GitHub.

      Gerando aliases de pacotes importados

      Talvez você queira alterar o nome de um pacote, caso você já estiver usando localmente um pacote com o mesmo nome do pacote do terceiro. Quando isso acontece, gerar um alias para a sua importação é a melhor maneira de lidar com esse conflito. Você pode modificar os nomes dos pacotes e suas funções dentro de Go, colocando um alias [pseudônimo] na frente do nome do pacote importado.

      A construção dessa instrução se parece com esta:

      import another_name "package"
      

      Neste exemplo, modifique o nome do pacote fmt no arquivo do programa random.go. Vamos alterar o nome do pacote de fmt para f para abreviá-lo. Nosso programa modificado se parecerá com este:

      random.go

      package main
      
      import (
       f "fmt"
        "math/rand"
      )
      
      func main() {
        for i := 0; i < 10; i++ {
          f.Printf("%d) %dn", i, rand.Intn(25))
        }
      }
      

      Dentro do programa, agora nos referimos à função Printf como f.Printf em vez de fmt.Printf.

      Embora outras linguagens favoreçam a geração de aliases para os pacotes, facilitando sua utilização posterior no programa, a linguagem Go não age assim. Por exemplo, a criação de alias f para o pacote fmt não seria consistente com o guia de estilos.

      Ao renomear importações para evitar conflito de nomes, você deve procurar renomear a importação mais local ou específica do projeto. Por exemplo, se tivesse um pacote local chamado strings e você também precisasse importar o pacote* sistema* igualmente chamado strings, você escolheria renomear o seu pacote local e não o pacote do sistema. Sempre que possível, é melhor evitar, de um modo geral, o conflito entre nomes.

      Nesta seção, aprendemos como podemos gerar um alias para uma importação para evitar que coincida com outra importação em nosso programa. É importante lembrar que a legibilidade e a clareza do seu programa é importante e, portanto, você deve apenas gerar aliases para tornar o código mais legível ou precisar evitar um conflito entre nomes.

      Formatando as importações

      Ao formatar as importações, você pode classificar os pacotes em uma ordem específica que tornará o seu código mais consistente. Além disso, isso impedirá que confirmações aleatórias aconteçam quando a única coisa que mudar for a ordem de classificação das importações. Como a formatação impedirá confirmações aleatórias, isso evitará as análises desnecessárias da variação e confusão dos códigos.

      A maioria dos editores formatará as importações para você automaticamente, ou permitirá que você configure seu editor para usar o goimports. O uso do goimports no editor é prática considerada padrão, uma vez que as tentativas de se manter manualmente a ordem de classificação das importações podem provar-se tediosas e propensas a erros. Além disso, caso qualquer alteração de estilo seja feita, o goimports será atualizado para refletir essa alteração. Isso garante que você e qualquer um que trabalhe em seu código, aplicará estilos de modo consistente em seus blocos de importação.

      A seguir, apresentamos um exemplo da aparência que um bloco de importação tem antes da formatação:

      import (
        "fmt"
        "os"
        "github.com/digital/ocean/godo"
        "github.com/sammy/foo"
        "math/rand"
        "github.com/sammy/bar"
      )
      

      Executando a ferramenta goimport (ou com a maioria dos editores que a tiver instalada, salvar o arquivo executará a ferramenta para você) você terá, então, o seguinte formato:

      import (
        "fmt"
        "math/rand"
        "os"
      
        "github.com/sammy/foo"
        "github.com/sammy/bar"
      
        "github.com/digital/ocean/godo"
      )
      

      Observe que ela agrupa todos os pacotes da biblioteca padrão primeiro e então agrupa pacotes de terceiros juntos, com linhas em branco. Isso torna mais fácil ler e entender quais pacotes estão sendo usados.

      Nesta seção, aprendemos que usar o goimports manterá todos os nossos blocos importados devidamente formatados e evitará a variação desnecessária do código entre os desenvolvedores que trabalhem nos mesmos arquivos.

      Conclusão

      Quando importamos pacotes, conseguimos chamar funções que não são implícitas para a linguagem Go. Alguns pacotes fazem parte da biblioteca padrão que vem instalada com o Go e alguns deles nós instalaremos através do go get.

      Fazer uso de pacotes nos permite tornar nossos programas mais robustos e poderosos, já que estamos potencializando o código existente. Também podemos criar nossos próprios pacotes para usu próprio e para que outros programadores possam usá-los em programas futuros.



      Source link

      Entendendo a visibilidade de pacotes em Go


      Introdução

      Ao criar um pacote em Go, normalmente, o objetivo final é tornar o pacote acessível para uso por outros desenvolvedores, seja em pacotes de ordem mais elevada ou em programas inteiros. Ao importar o pacote, seu código poderá servir como o bloco de construção para outras ferramentas mais complexas. No entanto, apenas certos pacotes estão disponíveis para importação. Isso é determinado pela visibilidade do pacote.

      Neste contexto, a visibilidade representa o espaço do arquivo a partir do qual é possível referenciar um pacote ou outro contructo. Por exemplo, se definirmos uma variável em uma função, a visibilidade (escopo) daquela variável fica apenas dentro da função na qual ela foi definida. Analogamente, se você definir uma variável em um pacote, você poderá torná-la visível apenas para aquele pacote ou permitir que ela fique visível também fora do pacote.

      Desse modo, é importante controlar cuidadosamente da visibilidade do pacote ao se escrever códigos ergonômicos, especialmente levando-se em conta as futuras alterações que possam vir a ser feitas no pacote. Se você precisar corrigir um erro, melhorar o desempenho ou alterar funcionalidades, você irá querer fazer a mudança de maneira a não quebrar o código de quem estiver usando o seu pacote. Uma maneira de minimizar alterações problemáticas é permitir o acesso somente às partes do pacote necessárias para que seja usado corretamente. Ao limitar o acesso, você pode alterar o pacote internamente, com menor chance de afetar a forma como outros desenvolvedores o estão usando.

      Neste artigo, você aprenderá como controlar a visibilidade de pacotes, além de como proteger as partes do seu código que devem ser usadas apenas dentro do seu pacote. Para fazer isso, vamos criar um agente básico para registrar e depurar mensagens, usando pacotes com graus diferentes de visibilidade dos itens.

      Pré-requisitos

      Para seguir os exemplos neste artigo, será necessário:

      .
      ├── bin
      │
      └── src
          └── github.com
              └── gopherguides
      

      Itens exportados e não exportados

      Ao contrário do que ocorre em outras linguagens de programação,como Java e Python – que usam modificadores de acesso, como public, private ou protected para especificar o escopo, a linguagem Go determina se um item é exported [exportado] e unexported [não exportado] pela forma como é declarado. Exportar um item neste caso torna ele visible [visível] fora do pacote atual. Caso não seja exportado, ele fica apenas visível e utilizável de dentro do pacote em que foi definido.

      Esta visibilidade externa é controlada colocando-se a primeira letra do item declarado em maiúscula. Todas as declarações, tais como Types, Variables, Constants, Functions etc., que começam com uma letra maiúscula, ficam visíveis fora do pacote atual.

      Vejamos o código a seguir, prestando muita atenção ao uso de letras maiúsculas e minúsculas:

      greet.go

      package greet
      
      import "fmt"
      
      var Greeting string
      
      func Hello(name string) string {
          return fmt.Sprintf(Greeting, name)
      }
      

      Esse código declara que ele está no pacote greet. Depois, ele declara dois símbolos, uma variável chamada Greeting e uma função chamada Hello. Como ambas começam com uma letra maiúscula, as duas são exported e disponibilizadas para qualquer programa exterior. Como indicado anteriormente, a criação de um pacote que limita o acesso permitirá um melhor design da API, além de facilitar a atualização do seu pacote internamente sem quebrar o código de quem estiver contando com o seu pacote.

      Definindo a visibilidade de pacotes

      Para examinar melhor como a visibilidade de pacotes funciona em um programa, vamos criar um pacote logging, tendo em mente o que queremos tornar visível fora do nosso pacote e o que não vamos tornar visível. Esse pacote de registro será responsável por registrar todas as mensagens de nosso programa no console. Ele também examinará em qual nível estamos registrando. Um nível descreve o tipo de registro e tem um destes três status: info, warning ou error.

      Primeiro, dentro do seu diretório src, vamos criar um diretório chamado logging, onde colocaremos nossos arquivos de registro:

      Acesse aquele diretório em seguida:

      Então, usando um editor como o nano, crie um arquivo chamado logging.go:

      Coloque o código a seguir no arquivo logging.go que acabamos de criar:

      logging/logging.go

      package logging
      
      import (
          "fmt"
          "time"
      )
      
      var debug bool
      
      func Debug(b bool) {
          debug = b
      }
      
      func Log(statement string) {
          if !debug {
              return
          }
      
          fmt.Printf("%s %sn", time.Now().Format(time.RFC3339), statement)
      }
      

      A primeira linha desse código declarou um pacote chamado logging. Nesse pacote, há duas funções exported: Debug e Log. Essas funções podem ser chamadas por qualquer outro pacote que importe o pacote logging. Também há uma variável privada chamada debug. Essa variável é acessível apenas de dentro do pacote logging. É importante notar que, apesar da função Debug e da variável debug terem a mesma grafia, a função tem letra maíuscula e a variável não. Isso as torna declarações distintas com escopos diferentes.

      Salve e saia do arquivo.

      Para usar esse pacote em outras áreas do nosso código, podemos usar o import nele em um novo pacote. Criaremos esse novo pacote, mas vamos precisar de um novo diretório para armazenar esses arquivos fonte em primeiro lugar.

      Vamos sair do diretório logging, criar um novo diretório chamado cmd e ir até esse novo diretório:

      Crie um arquivo chamado main.go no diretório cmd que acabamos de criar:

      Agora, podemos adicionar o seguinte código:

      cmd/main.go

      package main
      
      import "github.com/gopherguides/logging"
      
      func main() {
          logging.Debug(true)
      
          logging.Log("This is a debug statement...")
      }
      

      Agora, temos nosso programa escrito por completo. No entanto, antes de executarmos este programa, também precisaremos criar alguns dos arquivos de configuração para que o nosso código funcione corretamente. A linguagem Go usa Módulos Go para configurar as dependências de pacotes para a importação de recursos. Os módulos Go são arquivos de configuração colocados no seu diretório de pacotes que dizem ao compilador de onde importar os pacotes. Embora o aprendizado acerca dos módulos ultrapasse o âmbito deste artigo, podemos escrever apenas algumas linhas de configuração para fazer com que este exemplo funcione localmente.

      Abra o arquivo go.mod no diretório cmd:

      Então, coloque o seguinte conteúdo no arquivo:

      go.mod

      module github.com/gopherguides/cmd
      
      replace github.com/gopherguides/logging => ../logging
      

      A primeira linha desse arquivo diz ao compilador que o pacote cmd tem o caminho de arquivo github.com/gopherguides/cmd. A segunda linha diz ao compilador que o pacote github.com/gopherguides/logging pode ser encontrado localmente em disco, no diretório ../logging.

      Também precisaremos de um arquivo go.mod para o nosso pacote logging. Vamos voltar ao diretório logging e criar um arquivo go.mod:

      • cd ../logging
      • nano go.mod

      Adicione o conteúdo a seguir ao arquivo:

      go.mod

      module github.com/gopherguides/logging
      

      Isso diz ao compilador que o pacote logging que criamos é, na verdade, o pacote github.com/gopherguides/logging. Isso torna possível importar o pacote no nosso pacote main com a seguinte linha que escrevemos anteriormente:

      cmd/main.go

      package main
      
      import "github.com/gopherguides/logging"
      
      func main() {
          logging.Debug(true)
      
          logging.Log("This is a debug statement...")
      }
      

      Agora, você deve ter a seguinte estrutura de diretório e layout de arquivo:

      ├── cmd
      │   ├── go.mod
      │   └── main.go
      └── logging
          ├── go.mod
          └── logging.go
      

      Agora que temos toda a configuração completa, podemos executar o programa main a partir do pacote cmd, com os seguintes comandos:

      Você irá obter um resultado similar ao seguinte:

      Output

      2019-08-28T11:36:09-05:00 This is a debug statement...

      O programa imprimirá o tempo atual no formato RFC 3339, seguido de qualquer instrução que tivermos enviado ao agente de log. RFC 3339 é um formato de hora que foi concebido para representar a hora na internet e é comumente usado em arquivos de registro.

      Como as funções Debug e Log são exportadas do pacote logging, podemos usá-las em nosso pacote main. No entanto, a variável debug no pacote logging não será exportada. A tentativa de referenciar uma declaração não exportada resultará em um erro de tempo de compilação.

      Adicione a seguinte linha destacada ao main.go:

      cmd/main.go

      package main
      
      import "github.com/gopherguides/logging"
      
      func main() {
          logging.Debug(true)
      
          logging.Log("This is a debug statement...")
      
          fmt.Println(logging.debug)
      }
      

      Salve e execute o arquivo. Você receberá um erro similar ao seguinte:

      Output

      . . . ./main.go:10:14: cannot refer to unexported name logging.debug

      Agora que vimos como se comportam os itens exported e unexported dos pacotes, vamos examinar, a seguir, como os fields [campos] e methods [métodos] podem ser exportados de structs.

      Visibilidade dentro de structs

      Embora o esquema de visibilidade no agente de log - que construímos na última seção - possa funcionar para programas simples, ele compartilha estados demais para ser útil dentro de vários pacotes. Isso acontece porque as variáveis exportadas ficam acessíveis a vários pacotes que poderiam modificar as variáveis em estados contraditórios. Permitir que o estado do seu pacote seja alterado dessa maneira torna difícil prever como seu programa se comportará. Com o design atual, por exemplo, um pacote poderia definir a variável Debug como true [verdadeiro] e outro poderia defini-la como false [falso] na mesma instância. Isso criaria um problema, uma vez que ambos os pacotes que estão importando o pacote logging são afetados.

      Podemos deixar o agente de log isolado, criando uma struct e, na sequência, recuando os métodos para longe dela. Isso nos permitirá criar uma instance [instância] de um agente de log a ser usada de maneira independente em cada pacote que a consome.

      Altere o pacote logging para o seguinte, no intuito de refatorar o código e isolar o agente de log.

      logging/logging.go

      package logging
      
      import (
          "fmt"
          "time"
      )
      
      type Logger struct {
          timeFormat string
          debug      bool
      }
      
      func New(timeFormat string, debug bool) *Logger {
          return &Logger{
              timeFormat: timeFormat,
              debug:      debug,
          }
      }
      
      func (l *Logger) Log(s string) {
          if !l.debug {
              return
          }
          fmt.Printf("%s %sn", time.Now().Format(l.timeFormat), s)
      }
      

      Neste código, criamos uma struct chamada Logger. Ela irá abrigar nosso estado não exportado, incluindo o formato de hora a ser impresso e a definição da variável debug como true ou false. A função New define o estado inicial para usar na criação do agente de log, como o formato de hora e o estado de depuração. Então, ele armazena os valores que demos a ele internamente para as variáveis não exportadas timeFormat e debug. Também criamos um método chamado Log no tipo Logger que usa uma instrução que queremos imprimir. Dentro do método Log há uma referência à sua variável de método local l para retomar o acesso aos seus campos internos, como a l.timeFormat e l.debug.

      Essa abordagem nos permitirá criar uma struct Logger em diversos pacotes e usá-la independentemente da forma como os outros pacotes a estiverem usando.

      Para usá-la em outro pacote, vamos alterar o cmd/main.go para que se pareça com o seguinte:

      cmd/main.go

      package main
      
      import (
          "time"
      
          "github.com/gopherguides/logging"
      )
      
      func main() {
          logger := logging.New(time.RFC3339, true)
      
          logger.Log("This is a debug statement...")
      }
      

      Ao executar esse programa, você terá o seguinte resultado:

      Output

      2019-08-28T11:56:49-05:00 This is a debug statement...

      Nesse código, criamos uma instância do agente de log ao chamar a função exportada New. Armazenamos a referência a essa instância na variável logger. Agora, podemos chamar logging.Log para imprimir as instruções.

      Se tentarmos referenciar um campo não exportado do Logger, como o campo timeFormat, receberemos um erro de tempo de compilação. Tente adicionar a seguinte linha destacada e executar o cmd/main.go:

      cmd/main.go

      
      package main
      
      import (
          "time"
      
          "github.com/gopherguides/logging"
      )
      
      func main() {
          logger := logging.New(time.RFC3339, true)
      
          logger.Log("This is a debug statement...")
      
          fmt.Println(logger.timeFormat)
      }
      

      Isso dará o seguinte erro:

      Output

      . . . cmd/main.go:14:20: logger.timeFormat undefined (cannot refer to unexported field or method timeFormat)

      O compilador reconhece que o logger.timeFormat não foi exportado, e, desta forma, não pode ser recuperado do pacote logging.

      Visibilidade dentro de métodos

      Assim como ocorre com os campos struct, os métodos também podem ser exportados ou não exportados.

      Para ilustrar isso, vamos adicionar registro nivelado ao nosso agente de log. O registro nivelado é um meio de categorizar seus registros para que você possa procurar seus registros para tipos específicos de eventos. Os níveis que vamos colocar no nosso agente de log são:

      • O nível info, que representa eventos do tipo informação que informam o usuário de uma ação, como Program started [Programa iniciado] ou Email sent [E-mail enviado]. Isso nos ajuda a depurar e rastrear partes do nosso programa para ver se o comportamento esperado está acontecendo.

      • O nível warning [aviso]. Esses tipos de eventos identificam quando algo inesperado está acontecendo e que não é um erro, como Email failed to send, retrying [Falha ao enviar e-mail, tentando novamente]. Eles nos ajudam a ver partes do nosso programa que não estão rodando tão bem quanto esperávamos que estivessem.

      • O nível error [erro], que significa que o programa encontrou um problema, como File not found [Arquivo não encontrado]. Frequentemente, isso resultará na falha da operação do programa.

      Você também pode querer ligar e desligar certos níveis de registro, especialmente se o seu programa não estiver com o desempenho esperado e você quiser depurar o programa. Vamos adicionar essa funcionalidade, alterando o programa para que - quando debug for definido como true - ele imprima todos os níveis de mensagens. Caso contrário, se for false, imprimirá apenas mensagens de erro.

      Adicione o registro nivelado, fazendo as seguintes alterações em logging/logging.go:

      logging/logging.go

      
      package logging
      
      import (
          "fmt"
          "strings"
          "time"
      )
      
      type Logger struct {
          timeFormat string
          debug      bool
      }
      
      func New(timeFormat string, debug bool) *Logger {
          return &Logger{
              timeFormat: timeFormat,
              debug:      debug,
          }
      }
      
      func (l *Logger) Log(level string, s string) {
          level = strings.ToLower(level)
          switch level {
          case "info", "warning":
              if l.debug {
                  l.write(level, s)
              }
          default:
              l.write(level, s)
          }
      }
      
      func (l *Logger) write(level string, s string) {
          fmt.Printf("[%s] %s %sn", level, time.Now().Format(l.timeFormat), s)
      }
      

      Nesse exemplo, introduzimos um novo argumento ao método Log. Agora, podemos enviar o level [nível] da mensagem de registro. O método Log determina o nível em que a mensagem está. Se for uma mensagem de status info ou warning e o campo debug for true, então, ele escreve a mensagem. Caso contrário, a mensagem é ignorada. Se estiver em qualquer outro nível, como error, a mensagem será escrita de qualquer forma.

      Grande parte da lógica - para determinar se a mensagem será impressa, encontra-se no método Log. Também introduzimos um método não exportado chamado write. O método write é o que, de fato, produz o registro.

      Agora, podemos usar o registro nivelado em nosso outro pacote, mudando o cmd/main.go para que se pareça com o seguinte:

      cmd/main.go

      package main
      
      import (
          "time"
      
          "github.com/gopherguides/logging"
      )
      
      func main() {
          logger := logging.New(time.RFC3339, true)
      
          logger.Log("info", "starting up service")
          logger.Log("warning", "no tasks found")
          logger.Log("error", "exiting: no work performed")
      
      }
      

      Ao executar isso, você terá:

      Output

      [info] 2019-09-23T20:53:38Z starting up service [warning] 2019-09-23T20:53:38Z no tasks found [error] 2019-09-23T20:53:38Z exiting: no work performed

      Neste exemplo, o cmd/main.go usou o método exportado Log.

      Agora, podemos enviar o level de cada mensagem, alterando o debug para false:

      main.go

      package main
      
      import (
          "time"
      
          "github.com/gopherguides/logging"
      )
      
      func main() {
          logger := logging.New(time.RFC3339, false)
      
          logger.Log("info", "starting up service")
          logger.Log("warning", "no tasks found")
          logger.Log("error", "exiting: no work performed")
      
      }
      

      Agora, veremos que apenas as mensagens do nível error foram impressas:

      Output

      [error] 2019-08-28T13:58:52-05:00 exiting: no work performed

      Se tentarmos chamar o método write de fora do pacote logging, receberemos um erro de tempo de compilação:

      main.go

      package main
      
      import (
          "time"
      
          "github.com/gopherguides/logging"
      )
      
      func main() {
          logger := logging.New(time.RFC3339, true)
      
          logger.Log("info", "starting up service")
          logger.Log("warning", "no tasks found")
          logger.Log("error", "exiting: no work performed")
      
          logger.write("error", "log this message...")
      }
      

      Output

      cmd/main.go:16:8: logger.write undefined (cannot refer to unexported field or method logging.(*Logger).write)

      Quando o compilador vê que você está tentando referenciar algo de outro pacote - que começa com uma letra em caixa baixa, ele sabe que se trata de algo que não está exportado e, portanto, gera um erro de compilação.

      O agente de log neste tutorial ilustra como podemos escrever códigos que expõem apenas as partes que queremos que outros pacotes consumam. Como controlamos quais partes do pacote ficam visíveis fora do pacote, agora conseguiremos fazer alterações no futuro sem afetar nenhum código que dependa do nosso pacote. Por exemplo, se quiséssemos desligar apenas mensagens do nível info quando o debug estiver definido como falso, você poderia fazer essa mudança sem afetar nenhuma outra parte da sua API. Também podemos fazer alterações na mensagem de registro para incluir mais informações com segurança, tal como o diretório do qual o programa está sendo executado.

      Conclusão

      Este artigo mostrou como compartilhar códigos entre os pacotes, ao mesmo tempo protegendo os detalhes da implementação do seu pacote. Isso permite que você exporte uma API simples que raramente será alterada a título de compatibilidade com versões anteriores, mas que permitirá alterações de maneira reservada, dentro do seu pacote e conforme necessário, para que ele funcione melhor no futuro. Tal procedimento é considerado como prática recomendada na criação de pacotes e suas APIs correspondentes.

      Para aprender mais sobre os pacotes em Go, leia nossos artigos Importando pacotes em Go e Como escrever pacotes em Go, ou explore toda a nossa série de artigos sobre Como programar em Go.



      Source link