Recentemente começei a trabalhar em um projeto usando a linguagem Go do Google. Minha intenção é expor aqui minhas impressões sobre a linguagem enquanto plataforma de desenvolvimento.
A primeira coisa que percebi é que abordei a linguagem inicialmente com uma visão equivocada. Olhei para Go do ponto de vista de Python e Node.js. O resultado foi uma total decepção. Quando você tenta usar uma linguagem
que oferece ferramentas mais poderosas no uso de recursos do sistema com
mentalidade de linguagens poderosas no sentido de facilitar
A partir do momento que chaveei meu pensamento para C++ e Objective-C tive uma epifania sobre Go: de repente ela se tornou um linguagem interessante, até divertida de programar.
Inclusive um amigo meu de muito boa vontade tentou me explicar ponteiros com analogias e relações ro vs rw – bastava dizer “são ponteiros“.
A única crítica que tenho nesse ponto a Go é que o Google poderia ter tentado menos reinventar a roda e aproveitado mais a sintaxe de C++, já amplamente experimentada e readaptada, como Vala fez com C#. Teria atraído mais bons programadores devido a uma menor curva de aprendizado (apesar de Linus Torvalds discordar).
Interfaces se parecem parte com interfaces de Java e parte com interfaces de Objective-C – na verdade struct
s se
parecem com interfaces de Objetive-C. Porém, ao contrário de Java, as
interfaces de Go seguem a filosofia goose typing, que é um aprimoramento do duck typing.
Ou seja, se seu objeto implementa todos os métodos descritos pela interface, ele é de fato uma instância daquela interface.
Por exemplo:
type Person interface {
FirstName() string
LastName() string
FullName() string
Birth() time.Time
}
type personType struct {
firstName, lastName string
birth time.Time
}
func NewPerson(firstName, lastName string, birth time.Time) Person {
return personType{firstName, lastName, birth}
}
func (p personType) FirstName() string {
return p.firstName
}
func (p personType) LastName() string {
return p.lastName
}
func (p personType) FullName() string {
return strings.Trim(fmt.Sprintf("%v %v", p.firstName, p.lastName), " ")
}
func (p personType) Birth() time.Time {
return p.birth
}
No exemplo acima, instâncias da struct
personType
são automaticamente instâncias de Person
, uma vez que
personType
implemente todos os métodos da interface, podendo ser
retornadas por NewPerson
como instâncias reais de Person
.
A biblioteca padrão de Go oferece recursos simples, porém poderosíssimos para testes unitários. Trata-se da lib testing
.
Por exemplo, para testarmos nossa struct
(sendo bastante prolixo):
func TestPerson(t *testing.T) {
timeForm := "2000-01-31"
birth, _ := time.Parse(timeForm, "2017-06-12")
p := NewPerson("John", "Doe", birth)
t.Run("primary methods", func(t *testing.T) {
t.Run("#FirstName", func(t *test.T) {
if got := p.FirstName(); got != "John" {
t.Fatalf("expected John, got %v", got)
}
})
t.Run("#LastName", func(t *test.T) {
if got := p.LastName(); got != "Doe" {
t.Fatalf("expected Doe, got %v", got)
}
})
t.Run("#Birth", func(t *test.T) {
expected, _ := time.Parse(timeForm, "2017-02-12")
if got := p.Birth(); got != expected {
t.Fatalf("expected %v, got %v", expected, got)
}
})
})
t.Run("secondary methods", func(t *test.T) {
t.Run("#FullName", func(t *test.T) {
if got := p.FullName(); got != "John Doe" {
t.Fatalf("expected John Doe, got %v", got)
}
})
})
}
Mais do que suficiente para os testes unitários do dia-a-dia.
A forma padrão de tratar exceções em Go é retornando a exceção como última resposta.
Por exemplo, você deve ter reparado no _
na linha que cria a data de nascimento:
birth, _ := time.Parse(timeForm, "2017-06-12")
O _
diz para Go descartar o retorno daquela posição. Um código mais honesto seria:
birth, err := time.Parse(timeForm, "2017-06-12")
if err != nil {
// Faça alguma coisa com err
...
}
O segundo valor retornado é o erro, caso o parsing da data tenha falhado, ou nil
se tudo deu certo e o objeto time.Time
pode
ser criado corretamente.
Porém há casos em que a exceção não pode ser tratada (ou não se deseja que seja). Nesses casos é levantado um panic
, que é uma exceção
destrutiva que derruba a gorrotina atual e cada uma que esteja
conectada a ela.
A forma de tratar um panic
é através de um defer
.
defer
é um bloco de código (não uma função!) que é executado incondicionalmente na saída da função atual.
Dentro do bloco defer
é possível chamar recover()
, que para a propagação do panic
. Se houver algum panic
sendo propagado,
sua exceção será retornada pelo recover()
, caso não haja, será
retornado nil
.
Por exemplo:
func DoSomethingDangerous(res chan<- error) {
defer res <- recover()
// Faz algo que possa levar a um panic
...
}
Podem ser registrados quantos defer
s forem necessários.
Esta foi uma avaliação bem superficial da linguagem, não entrei em detalhes importantes, como gorrotinas, ponteiros, chan
s,
sync.WaitGroup
s, slices, loops, etc. Fica para uma
próxima.
Quem estiver interessado em aventurar-se no mundo do Go, recomendo o tour pela linguagem, o livro