Desde que entrei na aviação quase não postei aqui, porém agora como Community Manager na Sciensa, posso voltar a ter um material legal para compartilhar =)
Acompanhe as palestras, workshops, eventos e meetups organizados na Sciensa: Sciensa Meetup
O projeto da linguagem Go foi iniciado em 2007 pelos engenheiros da Google Rob Pike, Ken Thompson e Robert Griesemer. A linguagem foi apresentada pela primeira vez em 2009 e teve a versão 1.0 lançada em 2012.
Semântica
Go tem uma certa semelhança com C, principalmente no que diz respeito a alcançar o máximo de efeito com o mínimo de recursos.
Porém, ela não é uma versão atualizada do C. Na verdade, Go adapta boas ideia de várias linguagens, sempre descartando funcionalidades que trazem complexidade e código não confiável.
É Go compilada e estaticamente tipada.
Possui ponteiros, mas não possui aritmética de ponteiros. É uma linguagem moderna que utiliza recursos para concorrência novos e eficientes. Além de ter gerenciamento de memória automático, também conhecido como garbage collection (coleta de lixo)
Por quê Go?
Código aberto
Go é uma linguagem de código aberto, o que significa que não é restritiva e qualquer pessoa pode contribuir.
Fácil de aprender
A sintaxe de Go é pequena em comparação com outras linguagens. É muito limpa e fácil de aprender e até mesmo desenvolvedores de outras linguagens, familiarizados com C, podem aprender e entender Go facilmente.
Desempenho rápido
A pequena sintaxe e o modelo de concorrência do Go o tornam uma linguagem de programação muito rápida. Go é compilada em código de máquina e seu processo de compilação também é muito rápido.
Modelo de Concorrência Fácil
Go é construído para concorrência, o que facilita a execução de várias tarefas ao mesmo tempo. Go possui goroutines, threads leves que se comunicam através de um canais.
Portabilidade e multiplataforma
Go é uma linguagem multiplataforma. Pode-se escrever código facilmente em qualquer ambiente (OSX, Linux ou Windows). Portanto, o código escrito no Windows pode ser compilado e distribuído em um ambiente Linux.
Design explícito para a nuvem
Go foi escrito especialmente para a nuvem. Em Go todas as bibliotecas e dependências são vinculadas em um único arquivo binário, eliminando assim a instalação de dependências nos servidores. E esse é outro motivo para o seu crescimento e popularidade.
Segurança
Como o Go é estaticamente e fortemente tipada, isso implica que você precisa ser explícito no tipo de dados que está passando e também significa que o compilador conhece o tipo de cada variável, respectivamente.
Coleta de lixo
Go possui um coletor de lixo (Garbage Colletion) para gerenciamento automático de memória. Esse recurso de GC faz a alocação e a remoção de objetos sem nenhuma pausa e, portanto, aumenta a eficiência das aplicações.
Biblioteca Padrão Poderosa
A biblioteca padrão muito é poderosa e cheia de recursos. Com ela é possível facilmente construir um servidor Web, manipular E/S, criptografia e é claro, criar testes, dos quais falaremos mais ao longo do workshop.
Hello World
// Ex1package main
import"fmt"funcmain() {
fmt.Println("Hello, World")
}
Assim como em outras linguagens... em Go temos também o clássico Hello World.
Para executar este código, você pode começar com o comando:
go run hello.go
Mas... como podemos testar esse código?
Primeiramente, vamos separar o "domínio" (regras de negócio) do restante do código (efeitos colaterais). A função fmt.Println é um efeito colateral (que está imprimindo um valor no stdout - saída padrão) e a string que estamos passando para ela é o nosso domínio.
Esse é o código que criaremos para testar a nossa função Hello(). Vamos criar um arquivo chamado hello_test.go e nele coloque este código.
Percebam que não é preciso usar vários frameworks (ou bibliotecas) de testes. Tudo o que precisamos está pronto na linguagem e a sintaxe é a mesma para o resto dos códigos que você irá escrever.
Escrever um teste é como escrever uma função, com algumas regras:
O código precisa estar em um arquivo que termine com _test.go.
A função de teste precisa começar com a palavra Test.
A função de teste recebe um único argumento, que é t *testing.T.
Por enquanto, isso é o bastante para saber que o nosso t do tipo *testing.T é a nossa porta de entrada para a ferramenta de testes.
Para executar este teste, você pode começar com o comando:
go test -v hello_test.go hello.go
Tipos básicos
Go apresenta várias maneiras de organizar dados, desde tipos que correspondem aos recursos do hardware até tipo convenientes para a representação de estruturas de dados complexas.
String
Uma string é uma sequência imutável de bytes. Podem conter qualquer dado, mas normalmente contêm texto legível aos seres humanos.
Strings são convencionalmente interpretadas como sequências de pontos de código Unicode (runas) codificados em UTF-8.
Números
Os tipos numéricos em Go incluem vários tamanhos de inteiros, ponto flutuante e números complexos.
int, uint (assume o tamanho especificado pelo compilador)
byte: sinônimo para uint8
runa: sinônimo para int32
uintptr: tipo sem um tamanho espeficidado (usado em programação de baixo nível)
Ponto Flutuante
float32, float64
Segue o padrão IEEE 754
Complexos
complex64, complex128 - Podem ser criados pela função complex
Booleanos
true, false
Variáveis / Constantes / Ponteiros
Variáveis
Go é uma linguagem fortemente tipada, o que implica que todas as variáveis são elementos nomeados que estão vinculados a um valor e um tipo.
A forma longa para declarar uma variável em Go segue o seguinte formato:
var
A palavra-chave var é usada para declarar um ou mais identificadores de variáveis, seguidos do seus respectivos tipos. O trecho de código a seguir mostra a declaração de diversas variáveis:
O trecho de código anterior mostra vários exemplos de variáveis sendo declaradas com uma variedade de tipos. À primeira vista, parece que essas variáveis não têm um valor atribuído. Na verdade, isso contradiz nossa afirmação anterior de que todas as variáveis em Go estão vinculadas a um tipo e um valor.
Durante a declaração de uma variável, se um valor não for fornecido, o compilador do Go vinculará automaticamente um valor padrão (ou um valor zero) à variável para a inicialização adequada da memória.
A tabela a seguir mostra os tipos do Go e seus valores zero padrão:
Em Go, também é possível omitir o tipo, conforme mostrado no seguinte formato de declaração:
var =
O compilador do Go irá inferir o tipo da variável com base no valor ou na expressão de inicialização do lado direito do sinal de igual, conforme mostrado no trecho de código a seguir.
O tipo do map definido pelo valor literal. Neste caso map[string]int
Slice: []int{-3, 51, 134, 0}
O tipo do slice definido pelo valor literal: []int
Struct: struct{ nome string diametro int }{ "Tatooine", 10465, }
O tipo do struct definido conforme o valor literal. Neste caso: struct{nome string; diametro int}
Function: var sqr = func (v int) int { return v * v }
O tipo de function definido na definição literal da função. Neste caso, a variável sqr terá o tipo: func (v int) int
Declaração curta de variável
Em Go é possível reduzir ainda mais a sintaxe da declaração de variáveis. Neste caso, usando o formato short variable declaration. Nesse formato, a declaração perde a palavra-chave var e a especificação de tipo e passa a usar o operador := (dois-pontos-igual), conforme mostrado a seguir:
Existem algumas restrições quando usamos a declaração curta de variáveis e é muito importante estar ciente para evitar confusão:
Em primeiro lugar, ela só pode ser usada dentro de um bloco de funções;
o operador := declara a variável e atribui os valores;
:= não pode ser usado para atualizar uma variável declarada anteriormente;
Declaração de variável em bloco
A sintaxe do Go permite que a declaração de variáveis seja agrupada em blocos para maior legibilidade e organização do código. O trecho de código a seguir mostra a reescrita de um dos exemplos anteriores usando a declaração de variável em bloco:
Uma constante é um valor com uma representação literal de uma string, um caractere, um booleano ou números. O valor para uma constante é estático e não pode ser alterado após a atribuição inicial.
Constantes tipadas
Usamos a palavra chave const para indicar a declaração de uma constante. Diferente da declaração de uma variável, a declaração deve sempre incluir o valor literal a ser vinculado ao identificador, conforme mostrado a seguir:
const tipo =
O seguinte trecho de código mostra algumas constantes tipadas sendo declaradas:
// const01.go
...
const a1, a2 string = "Workshop", "Go"const b rune = 'G'const c bool = falseconst d int32 = 2020const e float32 = 2.020const f float64 = math.Pi * 2.0e+3const g complex64 = 20.0i
const h time.Duration = 20 * time.Second
...
Note que cada constante declarada recebe explicitamente um tipo. Isso implica que a constantes só podem ser usada em contextos compatíveis com seus tipos. No entanto, isso funciona de maneira diferente quando o tipo é omitido.
Constantes não tipadas
Constantes são ainda mais interessantes quando não são tipada. Uma constante sem tipo é declarada da seguinte maneira:
const =
Neste formato, a especificação de tipo é omitida na declaração. Logo, uma constante é meramente um bloco de bytes na memória sem qualquer tipo de restrição de precisão imposta. A seguir, algumas declarações de constantes não tipificadas:
A constante m4 recebe um valor muito grande (m3 * 20.0e+400) que é armazenado na memória sem qualquer perda de precisão. Isso pode ser útil em aplicações onde realizar cálculos com um alto nível de precisão é extremamente importante.
Atribuindo constantes não tipadas
Mesmo Go sendo uma linguagem fortemente tipada, é possível atribuir uma constante não tipada a diferentes tipos de precisão diferentes, embora compatíveis, sem qualquer reclamação do compilador, conforme mostrada a seguir:
O exemplo anterior mostra a constante não tipada m2 sendo atribuída a duas variáveis de ponto flutuante com diferentes precisões, u1 e u2, e a uma variável sem tipo, u3. Isso é possível porque a constante m2 é armazenada como um valor não tipado e, portanto, pode ser atribuída a qualquer variável compatível com sua representação (um ponto flutuante).
Como u3 não tem um tipo específico, ele será inferido a partir do valor da constante, e como m2 representa um valor decimal, o compilador irá inferir seu tipo padrão, um float64.
A declaração de constantes também podem ser organizadas em blocos, aumentando a legibilidade do código, conforme a seguir:
// const04.go
...
const (
a1, a2 string = "Workshop", "Go"
b rune = 'G'
c bool = false
d int32 = 2020
e float32 = 2.020
f float64 = math.Pi * 20.0e+3
g complex64 = 20.0i
h time.Duration = 20 * time.Second
)
...
Enumerações
Um interessante uso para constantes é na criação de enumerações. Usando a declaração de blocos, é facilmente possível criar valore inteiros que aumentam numericamente. Para isso, basta atribuir o valor constante pré-declarado iota a um identificador de constante na declaração de bloco, conforme mostrado no exemplo a seguir:
Declarar cada membro no bloco como um valor constante inteiro não tipado;
Inicializar a iota com o valor zero;
Atribuir a iota, ou zero, ao primeiro membro (EstrelaHiperGigante);
Cada constante subsequente recebe um int aumentado em um.
Assim, as constantes da lista receberão os valores de zero até nove.
É importante ressaltar que, sempre que const aparecer em um bloco de declaração, o contador é redefinido para zero. No trecho de código seguinte, cada conjunto de constantes é enumerado de zero a quatro:
Por padrão, uma constante enumerada é declarada como um tipo inteiro não tipado. Porém, podemos substituir o tipo padrão provendo explicitamente um tipo numérico, como mostrado a seguir:
É possível especificar qualquer tipo numérico que pode representar um inteiro ou um ponto flutuante. No exemplo anterior, cada constante será declarada como um tipo byte.
Usando iota em expressões
Quando a iota aparece em uma expressão, o compilador irá aplicar a expressão para cada valor sucessivo. O exemplo a seguir atribui números pares aos membros do bloco de declaração:
É possível ignorar certos valores em uma enumeração simplesmente atribuindo a iota a um identificador em branco (_). No trecho de código a seguir, o valor 0 é ignorado:
// pont01.go
...
varp *interaOuroSith, epIV:=42, 37// ponteiro para eraOuroSith
p = &eraOuroSith
// valor de eraOuroSith por meio do ponteiro
fmt.Printf("Era de Ouro dos Sith - %d anos antes do Ep.IV (%#x)\n", *p, p)
// atualiza o valor de eraOuroSith por meio do ponteiro
*p = 5000// o novo valor de eraOuroSith
fmt.Printf("Era de Ouro dos Sith - %d anos antes do Ep.IV | Atualizado (%#x)\n", *p, p)
// ponteiro para epIV
p = &epIV
// divide epIV por meio do ponteiro
*p = *p / 38// o novo valor de epIV
fmt.Printf("Star Wars: Ep.IV é o Marco %d (%#x)\n", epIV, p)
...
IMPORTANTE: Go não permite aritmética de ponteiros.
A função new()
Outra forma de criar variáveis em Go, é usando a função new().
A expressão new(T) cria uma variável sem nome do tipo T, inicializa ela com seu valor zero e devolve seu endereço de memória.
// new01.go
...
// epIV, do tipo *int, aponta para uma variável sem nomeepIV:=new(int)
// eraOuroSith, do tipo *int, também aponta para uma variável sem nomeeraOuroSith:=new(int)
// "0" zero
fmt.Println(*eraOuroSith)
// novo valor para o int sem nome
*eraOuroSith = *epIV - 5000// "-5000"
fmt.Println(*eraOuroSith)
...
O uso da função new() é relativamente raro.
Tipos Compostos
Tipos compostos em Go são tipos criados pela combinação de tipos básicos e tipos compostos.
Array
Array é uma sequência de elementos do mesmo tipo de dados. Um array tem um tamanho fixo, o qual é definido em sua declaração, e não pode ser mais alterado.
A declaração de um array segue o seguinte formato:
[]
Exemplo:
varlinhaTempo [10]int
Arrays também podem ser multidimensionais:
varmult [3][3]int
Iniciando um array com valores:
varlinhaTempo = [3]int{0, 5, 19}
Você pode usar ...(reticências) na definição de capacidade e deixar o compilador definir a capacidade com base na quantidade de elementos na declaração.
Slice é wrap flexível e robusto que abstrai um array. Em resumo, um slice não detém nenhum dado nele. Ele apenas referencia arrays existentes.
A declaração de um slice é parecida com a de um array, mas sem a capacidade definida.
// slice01.go
...
// declaracao com varvars1 []int
fmt.Println("Slice 1:", s1)
// declaração curtas2:= []int{}
fmt.Println("Slice 2:", s2)
// tamanho de um slice
fmt.Println("Tamanho do slice 1:", len(s1))
fmt.Println("Tamanho do slice 2:", len(s2))
...
O código anterior criou um slice sem capacidade inicial e sem nenhum elemento.
Também é possível criar um um slice a partir de um array:
// slice02.go
...
1// Naves do jogo "Star Wars: Battlefront"2naves:= [...]string{
31: "X-Wing",
42: "A-Wing",
53: "Millenium Falcon",
64: "TIE Fighter",
75: "TIE Interceptor",
86: "Imperial Shuttle",
97: "Slave I",
10 }
11// cria um slice de naves[1] até naves[3]12rebeldes:= naves[1:4]
13 fmt.Println(rebeldes)
...
A sintaxe s[i:j] cria um slice a partir do arraynaves iniciando do índice i até o índice j - 1. Então, na linha 12 do código, naves[1:4] cria uma representação do arraynaves iniciando do índice 1 até o 3. Sendo assim, o slice rebeldes tem os valores ["X-Wing" "A-Wing" "Millenium Falcon"].
Um slice pode ser criado usando a função make(), uma função nativa que cria um array e retorna um slice referenciando o mesmo.
A sintaxe da função é a seguinte: func make([]T, len, cap) []T.
Neste caso, é passando como parâmetro o tipo (T), o tamanho (len) e a capacidade (cap). A capacidade é opcional, e caso não seja informada, seu valor padrão será o tamanho (len), que é um campo obrigatório.
Como sabemos, arrays são limitados em seu tamanho e não podem ser aumentados. Já Slices, tem seu tamanho dinâmico e podem receber novos elementos em tempo de execução por meio da função nativa append.
A definição da função append é a seguinte: func append(s []T, x ...T) []T.
A sintaxe, x ...T significa que a função aceita um número variável de elementos no parâmetro x, desde que respeitem o tipo do slice.
Uma questão que pode ter ficado no ar: Se um slice é um wrap de um array, como ela tem esta flexibilidade?
Bem, o que acontece por debaixo dos panos quando um novo elemento é adicionado a um slice é o seguinte:
Um novo array é criado
Os elementos do array atual são copiados
O elemento ou elementos adicionados ao slice são incluido no array
É retornado um slice, que é uma referência para o novo array
Map
Um Map é uma estrutura de dados que mantém uma coleção de pares chave/valor. Também conhecido como hash table (tabela de dispersão ou tabela hash).
A declaração de um map segue o seguinte formato:
map[k]v
Onde k é o tipo da chave e v o tipo dos valores.
Exemplos de uso de map:
// map01.go
...
naves:=make(map[string]string)
naves["YT-1300"] = "Millennium Falcon"
naves["T-65"] = "X-Wing"
naves["RZ-1"] = "A-Wing"
naves["999"] = "Tunder Tanque"
fmt.Println("Quantidade de naves:", len(naves))
fmt.Println(naves)
fmt.Printf("Nave do Han Solo: %s\n", naves["YT-1300"])
fmt.Println("999 não é uma nave. Removendo...")
delete(naves, "999")
fmt.Println("Quantidade de naves atualizada:", len(naves))
fmt.Println(naves)
...
Dia 02
Estruturas de controle
Estruturas de controle são ultilizadas para alterar a forma como o nosso código é executado. Podemos, por exemplo, fazer com que uma parte do nosso código seja repitido várias vezes, ou que seja executado caso uma condição seja satisfeita.
if
O if é uma instrução que avalia uma condição booleana. Para entender melhor como ele funciona, vamos analisar o seguinte problema:
Em uma soma de dois números, onde a = 2 e b = 4, avalie o valor de c. Se c for igual a 6, imprima na tela "Sua soma está correta!", caso contrário, imprima "Sua soma está errada!".
package main
funcmain() {
vara, b = 2, 4c:= (a + b)
if c == 6 {
fmt.Println("Sua soma está correta.")
return
}
//uma forma d fazer se não
fmt.Println("Sua soma está errada.")
}
Outro Exemplo:
package main
funcmain() {
if2%2 == 0 {
fmt.Println("É par.")
} else {
fmt.Println("É impar.")
}
ifnum:=2 num < 0 {
fmt.Println(num, "É negativo.")
} elseif num < 10 {
fmt.Println(num, "Tem um dígito.")
} else {
fmt.Println(num, "Tem vários dígitos.")
}
}
Switch
A instrução switch é uma maneira mais fácil de evitar longas instruções if-else. Com ela é possível realiza ações diferentes com base nos possíveis valores de uma expressão.
Exemplo 1
package main
funcmain() {
i:=2switch i {
case1:
fmt.Println("Valor de ", i, " por extenso é: um")
case2:
fmt.Println("Valor de ", i, " por extenso é: dois")
case3:
fmt.Println("Valor de ", i, " por extenso é: três")
}
}
O switch pode testar valores de qualquer tipo, além de podermos usar vírgula para separar várias expreções em uma mesma condição case.
Exemplo 2
package main
funcmain() {
switch time.Now().Weekday() {
case time.Saturday, time.Sunday:
fmt.Println("É fim de semana.")
default:
fmt.Println("É dia de semana.")
}
}
O switch sem uma expressão é uma maneira alternativa para expressar uma lógica if-else.
Exemplo 3
package main
funcmain() {
j:=3switch {
case1 == j:
fmt.Println("Valor por extenso é: um")
case2 == j:
fmt.Println("Valor por extenso é: dois")
default:
fmt.Println("Valor não encontrado.")
}
}
for
Em outras linguagens de programação temos várias formas de fazer laços de repetição, porém, em Go só temos uma forma, e é usando a palavra reservada for.
Exemplo 1
A forma tradicional, que já conhecemos, e que no exemplo vai imprimir números de 1 a 10.
package main
funcmain(){
fori:=1; i <=10; i++ {
fmt.Println("O número é: ", i)
}
}
Exemplo 2
package main
funcmain(){
i:=5for i <= 5 {
fmt.Println("O número é: ", i)
i = i + 1
}
}
Exemplo 3 loop infinito
package main
funcmain(){
for {
fmt.Println("Olá sou o infinito")
break
}
}
for range
Já vimos as outras formas de usar o for, agora falta o range. Essa expressão espera receber uma lista (array ou slice).
Stuct é um tipo de dado agregado que agrupa zero ou mais valores nomeados de tipo quaisquer como uma única entidade. Cada valor é chamado de campo.
Struct Nomeada
Uma struct nomeada recebe um nome em sua declaração. Para exemplificar, criaremos uma strutc para representar um cadastro de funcionário em uma empresa. Seus campos pode ser acessados través da expressão variaval.Name, exemplo:
package main
typeEmployeestruct {
IDintNamestringAge *time.TimeSalaryfloat64Companystring
}
funcmain() {
cl:= Employee{}
//forma de acesso
cl.ID = 1
cl.Name = "Diego dos Santos"
cl.Age = nil
cl.Salary = 100.55
cl.Company = "Fliper"
fmt.Println("o nome é:", cl.Name, " trabalha na empresa: ", cl.Company)
//oura forma de popular structscl1:= Employee{
ID: 1,
Name: "Francisco Oliveira",
Age: nil,
Salary: 2000.50,
Company: "Iron Mountain",
}
fmt.Println("o nome é:", cl1.Name, " trabalha na empresa: ", cl1.Company)
}
Struct anônima
Uma struct anônima é tipo sem um nome como referência. Sua declaração é semelhante a uma declaração rapída de variável.
Só devemos usar uma struct anônima quando não há necessida de criar um objeto para o dado que será transportado por ela.
Funções são pequenas unidades de códigos que podem abistrair ações, retornar e/ou receber valores.
Como declarar uma função?
Declarar uma função é algo bem simples, utilizando a palavra reservada func seguida do identificador.
packagemainfuncnomeDaFuncao(){}
Essa é a declaração mais simples de função que temos. No exemplo acima criamos uma função que não recebe nenhum parametro e não retorna nada, o nome dela poderia ser fazNada.
Uma função em Go também é um tipo e pode ser declarada da segunte forma:
package main
typemyFunc=func(l, bint) intfuncmain() {
soma(func(l, bint) int {
returnl+b
})
}
funcsoma(fnmyFunc) {
res:=fn(1, 3)
fmt.Println(res)
}
Declaração de função que recebe parâmetros
Podemos declarar uma função que recebe dois números e faz uma multiplicação.
package main
funcmain() {
fmt.Println("Resultado é:", multiplica(3, 8))
}
funcmultiplica(a, bint) int {
return (a*b)
}
Veja que na declaração da func multiplica(a, b int) os parametros foram passados um seguido do outro, isso por que eles são do mesmo tipo (int). Caso fossem de tipos diferentes seria nessário declarar cada tipo separadamente, exemplo func minhaFunc(str string, i int).
Funções anônimas
Go também suporta declaração de funções anônimas. Funções anônimas são úteis quando você deseja definir uma função em linha sem ter que nomeá-la.
package main
funcmain() {
fn:=exemploAnonimo()
fmt.Println("Resultado é:", fn)
fmt.Println("Resultado é:", fn)
fmt.Println("Resultado é:", fn)
}
funcexemploAnonimo() func() int {
i:=0returnfunc() int {
i+=1returni
}
}
Função com retorno nomeado
Podemos criar uma função e nomear o retorno da mesma. veja o exemplo:
Função variádica é uma função que pode receber qualquer número de argumentos à direita e de um mesmo tipo. Um bom exemplo de função variádica é a função fmt.Println. A função pode ser chamada de forma usual, com argumentos individuais ou uma lista (Slice).
Erros são um assunto muito complexo em Go, pois não existe um tratamento de exeção como em outras linguagens. A única forma de se tratar erros em Go é usando a condição if ou então podemos criar uma função para realizar o tratamento. veja os exemplos:
Como mostrado no exemplo acima, uma função pode retornar algum resultado e/ou erro.
Métodos
Métodos em Go são uma variação da declaração de função. No método, um parâmetro extra aparece antes do nome da função e é chamado de receptor (receiver).
Métodos podem ser definidos para qualquer tipo de receptor, até mesmo ponteiros, exemplo:
package main
typeareastruct {
LarguraintAlturaint
}
func (r*area) CalculaArea() int {
res:=r.Largura*r.Alturareturnres
}
func (rarea) CalculaPerimetro() int {
res:=2*r.Largura*2*r.Alturareturnres
}
funcmain() {
a:=area{Largura: 10, Altura: 5}
resultArea:=a.CalculaArea()
fmt.Println("area: ", resultArea)
perim:=&a//repassando os valoresresultPerim:=perim.CalculaPerimetro()
fmt.Println("perim: ", resultPerim)
}
Interfaces
Uma interface é uma coleção de metódos que um tipo concreto deve implementar para ser considerado uma instância dessa interface. Portanto, uma interface define, mas, não declara o comportamento do tipo.
Para exemplificar vamos usar os mesmos exemplos que usamos para criar métodos.
packagemainimport"fmt"// Geo interface base para figuras geométricastypeGeointerface {
Area() float64
}
// Retangulo representa um retângulotypeRetangulostruct {
Largurafloat64Alturafloat64
}
// Area calcula a are de um retângulofunc (r*Retangulo) Area() float64 {
res:=r.Largura*r.Alturareturnres
}
// Triangulo representa um triângulotypeTriangulostruct {
Basefloat64Alturafloat64
}
// Area calcula a are de um triângulofunc (t*Triangulo) Area() float64 {
res:= (t.Base*t.Altura) /2returnres
}
funcimprimeArea(gGeo) {
fmt.Println(g)
fmt.Println(fmt.Sprintf("Área : %0.2f", g.Area()))
}
funcmain() {
r:=Retangulo{
Altura: 10,
Largura: 5,
}
t:=Triangulo{
Base: 10,
Altura: 5,
}
imprimeArea(&r)
imprimeArea(&t)
}
API
Se você já está na área de TI (tecnologia da informação) há algum tempo, provavelmente já deve ter ouvido o termo API pelo menos uma vez. Mas, o que é essa API?
"API (do Inglês Application Programming Interface) é um conjunto de rotinas e padrões estabelecidos por um software para a utilização das suas funcionalidades por aplicativos que não pretendem envolver-se em detalhes da implementação do software, mas apenas usar seus serviços"
Atualmente, boa parte das APIs escritas são APIs web e tendem a seguir o estilo Rest.
O que é REST?
REST é acrônimo para REpresentational State Transfer. É um estilo arquitetural para sistemas de hipermídia distribuídos e foi apresentado pela primeira vez por Roy Fielding em 2000 em sua famosa dissertação.
Projeto
Como projeto final, vamos desenvolver uma API que vai funcionar como um proxy para alguns serviços de CEP.
A ideia é utilizar a concorrência do Go para realizar diversas requisições simultâneas para cada um dos serviços de CEP e pegar a resposta do serviço que responder mais rapidamente.
API em Go com net/HTTP
O suporte HTTP em Go é fornecido pelo pacote da biblioteca padrão net/http. Dito isso, vamos fazer a primeira iteração da nossa API.
Começaremos com os três itens essenciais:
O primeiro item que precisamos é de um manipulador (ou handler). Se você tem experiência com MVC, pode pensar em manipuladores (handlers) como sendo os controladores. Eles são responsáveis pela execução da lógica da aplicação e pela criação de cabeçalhos e do corpo da resposta HTTP.
O segundo item é um roteador (ou servermux na terminologia do Go). Ele armazenará o mapeamento entre os padrões de URL da aplicação e os manipuladores (handlers) correspondentes. Normalmente temos um servermux para a aplicação contendo todas as rotas.
O último item que precisamos é um servidor web. Uma das grandes vantagens do Go é que você pode estabelecer um servidor Web e tratar solicitações recebidas como parte da própria aplicação. Você não precisa de um servidor de terceiros como o Nginx ou o Apache.
Vamos juntar esses itens, os conceitos vistos até aqui e criar uma aplicação didática e funcional.
Primeiramente, acesse o diretório do projeto configurado anteriormente e crie um arquivo chamado main.go:
$ cd$HOME/workshop/buscacep
# a criação do arquivo pode ser realizada dentro da prória IDE / Editor de texto
$ touch main.go
E digite o código a seguir:
// server01.go -> Referência para o arquivo no diretório exemplospackage main
import (
"log""net/http"
)
// Defina uma função manipuladora (handler) chamada "home" que escreve// um slice de bytes contendo "Bem vindo a API de CEPs" no o corpo da resposta.funchome(w http.ResponseWriter, r*http.Request) {
w.Write([]byte("Bem vindo a API de CEPs"))
}
funcmain() {
// Use a função http.NewServeMux() para inicializar um novo servermux,// depois registre a função "home" como manipulador do padrão de URL "/".mux:=http.NewServeMux()
mux.HandleFunc("/", home)
// Use a função http.ListenAndServe() para iniciar um novo servidor web.// Passamos dois parâmetros: o endereço de rede TCP que será escutado// (neste caso ":4000") e o servermux que acabamos de criar.// Se http.ListenAndServe() retornar um erro, usamos a função// log.Fatal() para registrar a mensagem de erro e sair.log.Println("Iniciando o servidor na porta: 4000")
err:=http.ListenAndServe(":4000", mux)
log.Fatal(err)
}
Considerando que você está no diretório onde está o arquivo main.go, para executar o código anterior, execute:
$ go run main.go
E para testar, abra o navegador e digite a URL http://localhost:4000 ou execute o seguinte comando:
$ curl localhost:4000
Rotas parametrizadas
Quando acessamos a URL /cep/04167001, queremos obter informações sobre o CEP 04167001. A primeira coisa a ser feita é obter o CEP a partir da URL e isso pode ser feito da seguinte maneira:
// server02.go -> Referência para o arquivo no diretório exemplos...// novo - função manipuladora (hanlder)funccepHandler(w http.ResponseWriter, r*http.Request) {
cep:=r.URL.Path[len("/cep/"):]
w.Write([]byte(cep))
}
funcmain() {
mux:=http.NewServeMux()
mux.HandleFunc("/", home)
// novo padrãomux.HandleFunc("/cep/", cepHandler)
log.Println("Iniciando o servidor na porta: 4000")
err:=http.ListenAndServe(":4000", mux)
log.Fatal(err)
}
...
Nota sobre rotas parametrizadas: Go não suporta roteamento baseado em método ou URLs semânticos com variáveis (/cep/{cep}). Idealmente, não devemos verificar o caminho da URL dentro do nosso manipulador (handler), devemos usar um roteador (router).
JSON
JSON (JavaScript Object Notation) é uma notação padrão para o envio e recebimento de informações estruturadas.
Sua simplicidade, legibilidade e suporte universal o tornam, atualmente, a notação mais amplamente utilizada.
Go tem um suporte excelente para codificação e decodificação de JSON oferecidos pelo pacote da biblioteca padrão encoding/json.
// server03.go -> Referência para o arquivo no diretório exemplos...typecepstruct {
Cepstring`json:"cep"`Cidadestring`json:"cidade"`Bairrostring`json:"bairro"`Logradourostring`json:"logradouro"`UFstring`json:"uf"`
}
...funccepHandler(w http.ResponseWriter, r*http.Request) {
rCep:=r.URL.Path[len("/cep/"):]
c:=cep{Cep: rCep}
ret, err:=json.Marshal(c)
iferr!=nil {
log.Printf("Ops! ocorreu um erro: %s", err.Error())
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
w.Write([]byte(ret))
}
...
O processo de converter uma estrutura (struct) Go para JSON chama-se marshaling e , como visto, é feito por json.Marshal.
O resultado de uma chamada a nossa API pode ser algo semelhante ao JSON a seguir:
Como pode ser percebido, nosso resultado apresenta campos vazios.
Caso seja necessário, isso pode ser contornado por meio do uso da opção adicional omitempty:
// server04.go -> Referência para o arquivo no diretório exemplos...typecepstruct {
Cepstring`json:"cep"`Cidadestring`json:"cidade,omitempty"`Bairrostring`json:"bairro,omitempty"`Logradourostring`json:"logradouro,omitempty"`UFstring`json:"uf,omitempty"`
}
...
Cliente HTTP
Um cliente HTTP também pode ser criado com Go para consumir outros serviços com o mínimo de esforço. Como é mostrado no seguinte trecho de código, o código do cliente usa o tipo http.Client para se comunicar com o servidor:
Ao enviar uma resposta, o Go definirá automaticamente três cabeçalhos gerados pelo sistema para você: Date, Content-Length e Content-Type.
O cabeçalho Content-Type é particularmente interessante. O Go tentará definí-lo de maneira correta, analisando o corpo da resposta com a função http.DetectContentType().
Se essa função não conseguir detectar o tipo de conteúdo, o cabeçalho será definido como Content-Type: application/octet-stream.
A função http.DetectContentType() geralmente funciona muito bem, mas uma dica para desenvolvedores Web novos no Go é que ela não consegue distinguir JSON de texto sem formatação. E, por padrão, as respostas JSON serão enviadas com um cabeçalho Content-Type: text/plain; charset=utf-8. Para impedir que isso aconteça, é necessário definir o cabeçalho correto manualmente da seguinte maneira:
// server07.go -> Referência para o arquivo no diretório exemplos...funccepHandler(w http.ResponseWriter, r*http.Request) {
...// Acertando o cabeçalhow.Header().Set("Content-Type", "application/json")
w.Write([]byte(ret))
}
...
Gran finale
Para finalizar, vamos adicionar um pouco de concorrência em nossa aplicação:
// server08.go -> Referência para o arquivo no diretório exemplospackage main
import (
..."regexp"// Novo"time"
)
typecepstruct {
...
}
// novofunc (ccep) exist() bool {
returnlen(c.UF) !=0
}
...// Função cepHandler foi refatorada e dela extraímos a função request funccepHandler(w http.ResponseWriter, r*http.Request) {
// Restrigindo o acesso apenas pelo método GETifr.Method!=http.MethodGet {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
rCep:=r.URL.Path[len("/cep/"):]
rCep, err:=sanitizeCEP(rCep)
iferr!=nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
ch:=make(chan []byte, 1)
for_, url:=rangeendpoints {
endpoint:=fmt.Sprintf(url, rCep)
gorequest(endpoint, ch)
}
w.Header().Set("Content-Type", "application/json")
forindex:=0; index<3; index++ {
cepInfo, err:=parseResponse(<- span="">ch)
iferr!=nil {
continue
}
ifcepInfo.exist() {
cepInfo.Cep=rCepjson.NewEncoder(w).Encode(cepInfo)
return
}
}
http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent)
}
// novofuncrequest(endpointstring, chchan []byte) {
start:=time.Now()
c:= http.Client{Timeout: time.Duration(time.Millisecond*300)}
resp, err:=c.Get(endpoint)
iferr!=nil {
log.Printf("Ops! ocorreu um erro: %s", err.Error())
ch<- span=""> nilreturn
}
deferresp.Body.Close()
requestContent, err:=ioutil.ReadAll(resp.Body)
iferr!=nil {
log.Printf("Ops! ocorreu um erro: %s", err.Error())
ch<- span=""> nilreturn
}
iflen(requestContent) !=0&&resp.StatusCode==http.StatusOK {
log.Printf("O endpoint respondeu com sucesso - source: %s, Duração: %s", endpoint, time.Since(start).String())
ch<- span=""> requestContent
}
}
...// Função para validar o CEPfuncsanitizeCEP(cepstring) (string, error) {
re:=regexp.MustCompile(`[^0-9]`)
sanitizedCEP:=re.ReplaceAllString(cep, `$1`)
iflen(sanitizedCEP) <8 {
return"", errors.New("O CEP deve conter apenas números e no minimo 8 digitos")
}
returnsanitizedCEP[:8], nil
}
funcmain() {
...
}->->->->
Go Modules: Gerenciamento de Dependências
Nos últimos anos houve muita turbulência em torno do gerenciamento de dependências do Go. Surgiram diversas ferramentas, como dep, godep, govendor e um monte de outras, que entraram em cena para tentar resolver esse problema de uma vez por todas.
O que é o Go Modules?
É o novo sistema de gerenciamento de dependências do Go que torna explícita e fácil o gerenciamento das informações sobre versões de dependências
Em poucas palavras, Go Modules é a resposta oficial para lidarmos com o Gerenciamento de Dependências em Go.
GOPATH, um pouco de história
O lançamento da versão 1.13 possibilitou a criação do diretório do projeto em qualquer lugar no computador, inclusive no diretório GOPATH. Em versões pré-1.13 e pós-1.11, já era possível criar o diretório em qualquer lugar, porém o recomendado era criá-lo fora do diretório GOPATH.
Esta é uma grande mudança em relação as versões anteriores do Go (pré-1.11), onde a prática recomendada era criar o diretório dos projetos dentro de uma pasta src sob o diretório GOPATH, conforme mostrado a seguir:
Nessa estrutura, os diretórios possuem as seguintes funções:
bin: Guardar os executáveis de nossos programas;
pkg: Guardar nossas bibliotecas e bibliotecas de terceiros;
src: Guardar todo o código dos nossos projetos.
De forma resumida:
Versões pré-1.11: A recomendação é criar o diretório do projeto sob o diretório GOPATH;
Versões pós-1.11 e pré-1.13: A recomendção é criar o diretório do projeto fora do GOPATH;
Versão 1.13: O diretório do projeto pode ser criado em qualquer lugar no computador.
Configuração do projeto e ativação do Go Modules
Para utilizar módulos no seu projeto, abra seu terminal e crie um novo diretório para o projeto chamado buscacep em qualquer lugar em seu computador.
Dica: Crie o diretório do projeto em $HOME/workshop, mas você pode escolher um local diferente, se desejar.
$ mkdir -p $HOME/workshop/buscacep
A próxima coisa que precisamos fazer é informar ao Go que queremos usar a nova funcionalidade de módulos para ajudar a gerenciar e controlar a versão de quaisquer pacotes de terceiros que o nosso projeto importe.
Para fazer isso, primeiro precisamos decidir qual deve ser o caminho do módulo para o nosso projeto.
O importante aqui é a singularidade. Para evitar possíveis conflitos de importação com os pacotes de outras pessoas ou com a Standard Library (biblioteca padrão) no futuro, escolha um caminho de módulo que seja globalmente exclusivo e improvável de ser usado por qualquer outra coisa. Na comunidade Go, uma convenção comum é criar o caminho do módulo com base em uma URL que você possui.
Se você estiver criando um pacote ou aplicativo que possa ser baixado e usado por outras pessoas e programas, é recomendável que o caminho do módulo seja igual ao local do qual o código pode ser baixado. Por exemplo, se o seu pacote estiver hospedado em https://github.com/foo/bar, o caminho do módulo para o projeto deverá ser github.com/foo/bar.
Supondo que estamos usando o github, vamos iniciar os módulos da seguinte forma:
$ cd$HOME/workshop/buscacep
$ go mod init github.com/[SEU_USARIO_GITHUB]/buscacep
// Saída no console
go: creating new go.mod: module github.com/[SEU_USARIO_GITHUB]/buscacep
Neste ponto, o diretório do projeto já deve possuir o aquivo go.mod criado.
Não há muita coisa nesse arquivo e se você abrí-lo em seu editor de texto, ele deve ficar assim (mas de preferência com seu próprio caminho de módulo exclusivo):
module github.com/[SEU_USARIO_GITHUB]/buscacep
go 1.13
Basicamente é isso! Nosso projeto já está configurado e com o Go Modules habilitado.