106
David Robert [email protected] Programando em Go

Programando em Go

Embed Size (px)

Citation preview

Page 1: Programando em Go

David [email protected]

Programando em Go

Page 3: Programando em Go

Go é uma linguagem de programação open source que faz com que seja fácil construir software simples,

confiável, e eficiente https://golang.org

Page 4: Programando em Go

tem grandes, grandes problemas!

Page 5: Programando em Go

Quais (grandes) problemas?

❏ Tanto hardware quanto software são gigantes❏ Milhões de linhas de código❏ Servidores principalmente em C++ e uma grande

quantidade em Java e Python❏ Milhares de desenvolvedores trabalhando ❏ Softwares sendo executado em zilhões de maquinas❏ Sistemas distribuídos em larga escala

Page 6: Programando em Go

Software em larga escala

❏ Builds lentos ❏ Dependências não controladas ❏ Custo de updates ❏ Dificuldade para automatizar tarefas ❏ Builds entre várias linguagens de programação❏ Código difícil de compreender

Page 7: Programando em Go

No geral, o desenvolvimento no é grande, lento, e muitas vezes

desajeitado. Mas é efetivo

''''

https://talks.golang.org/2012/splash.article

Page 8: Programando em Go

❏ Ken Thompson (B, C, Unix, UTF-8) ❏ Rob Pike (Unix, UTF-8) ❏ Robert Griesemer (Hotspot, JVM) … e muitos outros engenheiros do Google

Page 9: Programando em Go

Muitas pessoas ajudam a transformar o

protótipo em realidade

Go se tornou um projeto Open

Source

https://golang.org/doc/faq#history

2008 2009 20102007

Começa a ter adoção por outros desenvolvedores

Iniciado e desenvolvido por Robert Griesemer,

Rob Pike e Ken Thompson em um projeto part-time

História

Page 10: Programando em Go

http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html

2008 2009 20102007

História

Programming Language Hall of Fame

Page 12: Programando em Go

Google Trends "golang"

Page 13: Programando em Go

❏ Eliminar lentidão❏ Melhorar a eficacia❏ Aumentar a produtividade ❏ Manutenção escalável

Go foi projetado para que pessoas possam escrever, ler, debugar e manter grandes sistemas de software.

O proposito do Go não é ser uma pesquisa sobre design de linguagens de programação.

O objetivo do Go é fazer com que os programadores vivam melhor.

Por que usar Go?

Page 14: Programando em Go

Performance

http://benchmarksgame.alioth.debian.org

Page 15: Programando em Go

fix, fmt, get, install, list, tool, version, vet.

build compila pacotes e dependencias

run compila e executa programas Go

clean remove arquivos objeto

env imprime informações do ambiente

test testa pacotes e benchmarks

Go contém um conjunto de ferramentas para gerir código fonte...

Principais ferramentas:

Outras ferramentas:

Page 16: Programando em Go

Mas quem usa Go ?

Page 17: Programando em Go

https://github.com/golang/go/wiki/GoUsers

Page 18: Programando em Go

❏ Compilado❏ Memoria gerenciada (garbage-collected)❏ Tem seu próprio runtime❏ Sintaxe simples❏ Excelente biblioteca padrão❏ Multi plataforma❏ Orientação a Objetos❏ Estaticamente e fortemente tipado❏ Concorrência (goroutines)❏ Dependências explicitas❏ Retorno multi valorado❏ Ponteirose mais...

O que você vai ver em Go

Page 19: Programando em Go

Estas características não foram implementadas em favor da

eficiência e simplicidade

❏ Tratamento de exceções❏ Herança❏ Generics❏ Assert❏ Sobrecarga de métodos

O que você não vai ver em Go

Page 20: Programando em Go

um pouco de código!

Page 21: Programando em Go

Pacotes

❏ Todo programa Go é composto por pacotes❏ Programas começam com o pacote main❏ Este exemplo usa os pacotes ftm e math

$ go run packages.goMy favorite number is 1

Page 22: Programando em Go

❏ A instrução var declara uma lista de variavéis❏ O tipo é informado no fim❏ A instrução var pode incluir inicialização, 1 por variavél. Neste

caso, o tipo pode ser omitido porque será inferido

Variavéis

$ go run variables.go0 false false false

$ go run variables-with-initiali1 2 true false no!

Page 23: Programando em Go

❏ Dentro de uma função, pode ser usada a instrução := para inicializar e declarar a variavél

Declaração de Variáveis

$ go run short-variable-declarations.go1 2 3 true false no!

Page 24: Programando em Go

$ go run constants.goHello world! Happy 3.14 Day! Go rules? true

❏ Constantes são declaradas com a keyword const❏ Não é possível usar :=

Constantes

Page 25: Programando em Go

Funções❏ Funções podem ter argumentos❏ O tipo fica após o nome do argumento

$ go run functions.go55

Page 26: Programando em Go

❏ Uma função pode ter múltiplos retornos

Retorno de valores múltiplos

$ go run multiple-results.goworld hello

Page 27: Programando em Go

❏ A única estrutura do laço que o Go tem é for❏ Muito similar com Java ou C, exceto pelos ( )❏ A parte inicial e final da declaração podem estar vazias

Laço For

$ go run for.go45

$ go run for-continu1024

Page 28: Programando em Go

$ go run for-is-go-while.go1024

❏ Ponto e vírgula pode ser removidos para simular um while ❏ for pode executar para "sempre"

Laço while

$ go run forever.goprocess took too long

Page 29: Programando em Go

❏ Muito similar com Java ou C, exceto pelos ( )

Condicional

$ go run if.go1.4142135623730951 2i

Page 30: Programando em Go

$ go run switch.goGo runs on nacl.

❏ Muito similar com Java ou C, exceto pelos ( )

Switch

Page 31: Programando em Go

$ go run defer.gohello world

Defer❏ A declaração defer adia a execução de uma função até o final do

retorno da função❏ Os argumentos das chamadas adiadas são avaliados imediatamente

Page 32: Programando em Go

Orientação a Objetos

Page 33: Programando em Go

Orientação a Objetos

type retangulo struct { largura, altura int}

Estruturas do Go (similares a classes)

Page 34: Programando em Go

Orientação a Objetos

type retangulo struct { largura, altura int}

Go suporta métodos definidos em tipos struct

func (r retangulo) area() int { return r.largura * r.altura}

Estruturas do Go (similares a classes)

Page 35: Programando em Go

Orientação a Objetos

type retangulo struct { largura, altura int}

Go suporta métodos definidos em tipos struct

func (r retangulo) area() int { return r.largura * r.altura}

Estruturas do Go (similares a classes)

retangulo{10, 20}retangulo{largura: 10, altura: 20}

Inicialização de estruturas

Page 36: Programando em Go

Orientação a Objetos

package vingadores

type vingador struct {nome string

}func NewVingador(nome string) *vingador {

v := new(vingador)v.nome = nomereturn v

}

func main() {capitao := vingadores.NewVingador("Capitão América")fmt.Println(capitao.nome)

}

Construtores

Page 37: Programando em Go

Orientação a Objetos

package vingadores

type vingador struct {nome string

}func NewVingador(nome string) *vingador {

v := new(vingador)v.nome = nomereturn v

}

func main() {capitao := vingadores.NewVingador("Capitão América")fmt.Println(capitao.nome)

}

Construtores ❏ Depois de importar um pacote, você pode consultar os nomes que exporta

❏ Um nome é exportado se começa com uma LETRA MAIÚSCULA

Page 38: Programando em Go

Orientação a Objetosstruct.go

12345678910111213141516171819

package main

import "fmt"

type pessoa struct {nome stringidade int

}

func main() {fmt.Println(pessoa{"Homem de Ferro", 40})fmt.Println(pessoa{nome: "Thor", idade: 100})

s := pessoa{nome: "Hulk", idade: 45}fmt.Println(s.nome)

s.idade = 42fmt.Println(s.idade)

}

Page 39: Programando em Go

Orientação a Objetosstruct.go

12345678910111213141516171819

package main

import "fmt"

type pessoa struct {nome stringidade int

}

func main() {fmt.Println(pessoa{"Homem de Ferro", 40})fmt.Println(pessoa{nome: "Thor", idade: 100})

s := pessoa{nome: "Hulk", idade: 45}fmt.Println(s.nome)

s.idade = 51fmt.Println(s.idade)

}

$ go run struct.go{Homem de Ferro 40}

{Thor 100}

Hulk

42

Page 40: Programando em Go

Orientação a Objetosmethods.go

12345678910111213141516171819

package mainimport "fmt"

type retangulo struct { largura, altura int}func (r retangulo) area() int { return r.largura * r.altura}

func (r retangulo) perimetro() int { return 2 * r.largura + 2 * r.altura}func main() { r := retangulo{largura: 10, altura: 5}

fmt.Println("area: ", r.area()) fmt.Println("perim:", r.perimetro())}

Page 41: Programando em Go

Orientação a Objetosmethods.go

12345678910111213141516171819

package mainimport "fmt"

type retangulo struct { largura, altura int}func (r retangulo) area() int { return r.largura * r.altura}

func (r retangulo) perimetro() int { return 2 * r.largura + 2 * r.altura}func main() { r := retangulo{largura: 10, altura: 5}

fmt.Println("area: ", r.area()) fmt.Println("perim:", r.perimetro())}

$ go run methods.goarea: 50perim: 30

Page 42: Programando em Go

HERANÇA

Page 43: Programando em Go

HERANÇA

Como assim não tem

herança !?

Page 44: Programando em Go

Por que usamos herança?

Herança

Page 45: Programando em Go

Por que usamos herança?

❏ Reuso de código

Herança

Page 46: Programando em Go

Por que usamos herança?

❏ Reuso de código

Existe efeito colateral em seu uso?

Herança

Page 47: Programando em Go

Por que usamos herança?

❏ Reuso de código

Existe efeito colateral em seu uso?

Acoplamos a implementação da classe mãe muito precocemente. A classe filha precisa

conhecer muito bem o código interno da mãe.

Quebra de encapsulamento

Herança

Page 48: Programando em Go

Alguns críticos afirmam que ela nunca deveria ser utilizada

Herança

Page 49: Programando em Go

Alguns críticos afirmam que ela nunca deveria ser utilizada

Go não tem Herança !

Herança

Page 50: Programando em Go

Alguns críticos afirmam que ela nunca deveria ser utilizada

Go não tem Herança !

Mas tem composição !

Um dos princípios do livro "Design Patterns": Evite herança, favoreça composição

Herança

Page 51: Programando em Go

Herança composition.go

12345678910111213141516171819

package mainimport "fmt"

type Car struct {wheelCount int

}func (car Car) numberOfWheels() int {

return car.wheelCount}type Ferrari struct {

Car //anonymous field Car}

func main() {f := Ferrari{Car{4}}

fmt.Println("A Ferrari tem ", f.numberOfWheels(), " rodas")

}

Page 52: Programando em Go

Herança composition.go

12345678910111213141516171819

package mainimport "fmt"

type Car struct {wheelCount int

}func (car Car) numberOfWheels() int {

return car.wheelCount}type Ferrari struct {

Car //anonymous field Car}

func main() {f := Ferrari{Car{4}}

fmt.Println("A Ferrari tem ", f.numberOfWheels(), " rodas")

}

$ go run composition.goA Ferrari tem 4 rodas

Page 53: Programando em Go

Herança (anonymous field) anonymous-field.go

12345678910111213141516171819

package mainimport "fmt"type Cozinha struct { lampadas int}

type Casa struct { Cozinha }

func main() { c := Casa{Cozinha{2}} fmt.Println("A casa tem ", c.lampadas , " lampadas") }

Page 54: Programando em Go

Herança (anonymous field) anonymous-field.go

12345678910111213141516171819

package mainimport "fmt"type Cozinha struct { lampadas int}

type Casa struct { Cozinha }

func main() { c := Casa{Cozinha{2}} fmt.Println("A casa tem ", c.lampadas , " lampadas") }

$ go run anonymous-field.goA casa tem 2 lampadas

Page 55: Programando em Go

Herança (anonymous field) anonymous-field.go

12345678910111213141516171819

package mainimport "fmt"type Cozinha struct { lampadas int}type Quarto struct { lampadas int}type Casa struct { Cozinha Quarto}

func main() { c := Casa{Cozinha{2}, Quarto{3}} fmt.Println("A casa tem ", c.lampadas , " lampadas") }

Page 56: Programando em Go

Herança (anonymous field) anonymous-field.go

12345678910111213141516171819

package mainimport "fmt"type Cozinha struct { lampadas int}type Quarto struct { lampadas int}type Casa struct { Cozinha Quarto}

func main() { c := Casa{Cozinha{2}, Quarto{3}} fmt.Println("A casa tem ", c.lampadas , " lampadas") }

$ go run anonymous-field.goambiguous selector c.lampadas

Page 57: Programando em Go

Herança (anonymous field) anonymous-field.go

12345678910111213141516171819

package mainimport "fmt"type Cozinha struct { lampadas int}type Quarto struct { lampadas int}type Casa struct { Cozinha Quarto}func (c Casa) lampadas() int { return c.Cozinha.lampadas + c.Quarto.lampadas}func main() { c := Casa{Cozinha{2}, Quarto{3}} fmt.Println("A casa tem ", c.lampadas(), " lampadas") }

Page 58: Programando em Go

Herança (anonymous field) anonymous-field.go

12345678910111213141516171819

package mainimport "fmt"type Cozinha struct { lampadas int}type Quarto struct { lampadas int}type Casa struct { Cozinha Quarto}func (c Casa) lampadas() int { return c.Cozinha.lampadas + c.Quarto.lampadas}func main() { c := Casa{Cozinha{2}, Quarto{3}} fmt.Println("A casa tem ", c.lampadas(), " lampadas") }

$ go run anonymous-field.goA casa tem 5 lampadas

Page 59: Programando em Go

Go

Tratando Erros

http://blog.golang.org/error-handling-and-go

Page 60: Programando em Go

Assinatura da função:func Open(name string) (file *File, err error)

Exemplo de uso com tratamento de erro:f, err := os.Open("filename.ext")

if err != nil {

log.Fatal(err)

}

// faça algo com o arquivo f

Estados inconsistentes ou anormais são indicados através do error. Por exemplo a função os.Open

Tratamento de Erros

Page 61: Programando em Go

Todo erro em Go é representado pela interface errortype error interface { Error() string}

A implementação mais comum é a errorString.type errorString struct {

s string}

func (e *errorString) Error() string { return e.s

}

Representação do erro

Page 62: Programando em Go

A forma mais simples de criar um errorString é a partir da função errors.New

func New(text string) error { return &errorString{text}}

Usando a função para disparar um erro:func Sqrt(f float64) (float64, error) { if f < 0 { return 0, errors.New("negative number") } // implementaçào caso f >= 0}

Disparando um errorString

Page 63: Programando em Go

Usando a função para disparar um erro:func Sqrt(f float64) (float64, error) { if f < 0 { return 0, errors.New("negative number") } // implementaçào caso f >= 0}

A forma mais simples de criar um errorString é a partir da função errors.New

func New(text string) error { return &errorString{text}}

Disparando um errorString

Não estamos informando qual o número passado para a função. Informação muito importante sendo perdida!

Page 64: Programando em Go

De forma similar como disparamos um errorString através da função errors.New ... podemos usar a função Errorf do pacote fmt para disparar erros mais completos.

Internamente a função Errorf se utiliza da função errors.New

Disparando erros com Errorf

if f < 0 { return 0, fmt.Errof("negative number %g", f) }

Page 65: Programando em Go

Esta é uma abordagem muito mais sofisticada do que usar apenas errorString pois permite type assertions.

type NegativeSqrtError float64

func (f NegativeSqrtError) Error() string { return fmt.Sprintf("negative number %g", float64(f))}

É possível criar suas próprias implementações de erros.

Como por exemplo deste caso do número negativo, poderíamos por exemplo:

Customizando seus erros

Page 66: Programando em Go

tratamento de erros é importante!

As convenções e design da linguagem nos encorajam a verificar explicitamente por erros onde eles possam ocorrer

indo em desencontro com muitas outras linguagens.

Em alguns casos, isto pode fazer Go um tanto verbosa, mas felizmente existem técnicas para lidar com isso.

Page 67: Programando em Go

Concorrência & Paralelismo

Page 68: Programando em Go

Concorrência & Paralelismo

O mundo moderno é paralelo

❏ Multicore❏ Networks❏ Cloud Computing❏ Grande quantidade de usuários.

Page 69: Programando em Go

Go suporta concorrência

❏ execução concorrente (goroutines)❏ sincronização e mensagens (channels)❏ controle concorrente (select)

Page 70: Programando em Go

Concorrência é legal !

Uhu paralelismo !

Isso é uma falacia!

Quando o Go anunciou, muitos ficaram confusos com a distinção

Page 71: Programando em Go

❏ A concorrência é sobre como lidar com um monte de coisas ao mesmo tempo.

❏ O paralelismo é sobre fazer muitas coisas ao mesmo tempo.

❏ A concorrência é sobre a estrutura, o paralelismo é sobre a execução.

❏ Concorrência fornece uma maneira de estruturar uma solução para resolver um problema que pode (mas não necessariamente) ser paralelizável.

Concorrência versus Paralelismo

Page 72: Programando em Go

Concorrência versus Paralelismo

❏ Com apenas um Gopher isso vai levar muito tempo

Page 73: Programando em Go

Concorrência versus Paralelismo

❏ Mais Gopher não são suficientes ❏ Eles precisam de mais carrinhos

Page 74: Programando em Go

Concorrência versus Paralelismo

❏ Vai ir mais rápido, mas haverá gargalos na pilha e no incinerador

❏ Precisa sincronizar os Gophers❏ Comunicação entre os esquilos sera necessária

Page 75: Programando em Go

Concorrência versus Paralelismo

❏ Remover os gargalos torna realmente independente❏ Vai consumir duas vezes mais rápido❏ Concorrência de dois processos Gopher

Page 76: Programando em Go

Concorrência versus Paralelismo

❏ Três Gophers em ação, mas com atrasos prováveis❏ Cada Gopher é um processo independente, acrescido de

coordenação (comunicação)

Page 77: Programando em Go

Concorrência versus Paralelismo

❏ Quatro Gophers em ação, cada com uma tarefa simples❏ Quatro vezes mais rápido que a versão original com um

Page 78: Programando em Go

Concorrência versus Paralelismo

Procedimentos concorrentes

Para cada Gopher um procedimento distinto:❏ Carregar o carrinho com livros❏ Mover o carrinho para incinerador❏ Descarregar o carrinho no incinerador❏ Retornar o carrinho vazio

Diferentes designs concorrentes permitem diferentes formas de paralelizar

Page 79: Programando em Go

Concorrência versus Paralelismo

❏ Mais paralelismo❏ Oito Gophers totalmente ocupados

Page 80: Programando em Go

Concorrência versus Paralelismo

❏ Mesmo que apenas um Gopher esteja ativo (sem paralelismo), ainda é uma solução correta e concorrente

Page 81: Programando em Go

Concorrência versus Paralelismo

❏ Outra maneira: Composição simultânea de procedimentos❏ Dois procedimentos e uma pilha de preparo

Page 82: Programando em Go

Concorrência versus Paralelismo

❏ Executar mais procedimentos concorrentes para obter mais throughput

Page 83: Programando em Go

Concorrência versus Paralelismo

❏ Utilizando a pilha no modelo multi gopher

Page 84: Programando em Go

Concorrência versus Paralelismo

❏ Otimização total❏ Usando todas as técnicas, dezesseis Gopher estão

trabalhando

Page 85: Programando em Go

Concorrência versus Paralelismo

❏ Há muitas maneiras de dividir o processamento

❏ Isso é design concorrente

❏ Uma vez que temos a divisão, a paralelização fica mais fácil

Page 86: Programando em Go

É uma função que executa no mesmo endereço de outras goroutines

❏ Como uma chamada de função no shell com &

Goroutines

Page 87: Programando em Go

Goroutinesgoroutines.go

12345678910111213141516171819

package main

import ("fmt""time"

)

func say(s string) {for i := 0; i < 5; i++ {

time.Sleep(100 * time.Millisecond)

fmt.Println(s)}

}

func main() {go say("world")say("hello")

}

$ go run goroutines.gohelloworldhelloworldhelloworldhelloworldworldhello

Page 88: Programando em Go

Apesar de serem parecidas com threads, elas são muito mais baratas

❏ Goroutines são multiplexados em threads no S.O. conforme necessário

❏ Quando uma goroutine fica bloqueada, aquela thread fica bloqueada, mas não impacta em nenhuma outra goroutine

Goroutines não são threads

Page 89: Programando em Go

❏ Channels são canais que conectam goroutines concorrentes. Você pode enviar valores para os canais de uma goroutine e receber esses valores em outra goroutine

❏ Cria um novo canal com make(chan tipo-val)

❏ A sintaxe <- server para enviar ou receber mensagens

Channels

Page 90: Programando em Go

Channelschannels.go

12345678910111213141516171819

package mainimport "fmt"

func sum(a []int, c chan int) {sum := 0for _, v := range a {

sum += v}c <- sum // send sum to c

}func main() {

a := []int{7, 2, 8, -9, 4, 0}

c := make(chan int)go sum(a[:len(a)/2], c)go sum(a[len(a)/2:], c)x, y := <-c, <-c // receive from cfmt.Println(x, y, x+y)

}

$ go run channels.go17 -5 12

Page 91: Programando em Go

http://www.goinggo.net/2014/02/the-nature-of-channels-in-go.html

c := make (chan int)

Channels

Page 92: Programando em Go

❏ Por padrão canais são não buferizados. Só aceitarão envios se houver um receptor pronto para receber o valor

❏ Canais buferizados aceitam um número limitado de valores sem um receptor correspondente para esses valores

Buffered Channels

Page 93: Programando em Go

Buffered Channelsbuffered-channels.go

12345678910111213

package main

import "fmt"

func main() { mensagens := make(chan string, 2) mensagens <- "buferizado" mensagens <- "canal" fmt.Println(<-mensagens) fmt.Println(<-mensagens)}

$ go run buffered-channels.go

buferizado

canal

Page 94: Programando em Go

http://www.goinggo.net/2014/02/the-nature-of-channels-in-go.html

c := make (chan int, 10)

Buffered Channels

Page 95: Programando em Go

❏ Para executar uma goroutine: go

❏ Para enviar ou receber informações entre goroutines: channels

❏ Use a variável de ambiente GOMAXPROCS para definir a quantidade de threads

Concorrência em Go

Page 96: Programando em Go

Go Runtime

Page 97: Programando em Go

https://www.quora.com/How-does-the-Go-runtime-workhttp://www.cs.columbia.edu/~aho/cs6998/reports/12-12-11_DeshpandeSponslerWeiss_GO.pdfhttp://blog.altoros.com/golang-internals-part-5-runtime-bootstrap-process.html

❏ Um pacote como qualquer outro em Go.

❏ É implementado em GO, C e Assembly

❏ É empacotado junto com o programa no momento do build

❏ Responsável pelo:❏ Gerencimento de mémória❏ Criação das GO Routines❏ Comunicação entre channels

Go Runtime

Page 98: Programando em Go

Go Runtime

Page 99: Programando em Go

Só isso ou tem mais?❏ Pointer❏ Struct❏ Matrix❏ Slice❏ Range❏ Map❏ Value function❏ Closures❏ Method❏ Interface❏ Stringer❏ Error

e muito mais!!!

http://go-tour-br.appspot.com

Page 100: Programando em Go

$ go run http.go

Servidor Web❏ Um servidor web com menos de 15 lines!!

Page 101: Programando em Go

Now you are ready to

!

Page 102: Programando em Go

Perguntas?

Page 103: Programando em Go

Obrigado!

David [email protected]

Page 104: Programando em Go

Bibliografia❏ http://golang.org❏ http://go-tour-br.appspot.com/❏ https://tour.golang.org❏ http://www.golangbr.org/❏ https://vimeo.com/49718712❏ http://talks.golang.org/2012/waza.slide❏ http://gophercon.com❏ http://www.infoq.com/br/news/2014/09/go-1-3❏ http://www.casadocodigo.com.br/products/livro-google-go❏ http://www.grokpodcast.com/series/a-linguagem-go/❏ https://pt.wikipedia.org/wiki/Go_(linguagem_de_programação)❏ https://gobyexample.com❏ http://goporexemplo.golangbr.org/❏ http://www.goinggo.net/2014/02/the-nature-of-channels-in-go.html❏ http://www.goinggo.net/2013/09/detecting-race-conditions-with-go.html❏ http://golangtutorials.blogspot.com.br/2011/06/inheritance-and-subclassing-in-go-or.html❏ http://golangtutorials.blogspot.com.br/2011/06/anonymous-fields-in-structs-like-object.html❏ http://commandcenter.blogspot.ca/2012/06/less-is-exponentially-more.html❏ http://www.toptal.com/go/go-programming-a-step-by-step-introductory-tutorial❏ http://www.slideshare.net/while42/programando-em-Go

Page 106: Programando em Go

Estamos contratando

[email protected]