7 vantagens da linguagem Go
Igor Melo
Posted on August 6, 2023
Antes de tudo
Primeiro, vou fazer uma confissão…
A primeira vez que vi um código em Go eu achei a linguagem esquisita e me desinteressei.
- Como assim não tem try/catch?
- Por que métodos têm essa sintaxe diferente?
- Por que não tem a feature XYZ que "toda" linguagem tem?
Essas foram algumas das perguntas que eu me fiz.
Só anos depois que eu resolvi dar uma segunda chance para a linguagem e resolvi aprender.
Então, antes de tudo, vai um conselho: não foque em detalhes bobos que nem eu fiz.
Uma linguagem tem muito mais a oferecer do que só uma sintaxe “bonita”.
É muito provável que em Go as coisas sejam diferentes de como você está acostumado a fazer, então não se assuste.
Introdução
Go é uma linguagem criada por desenvolvedores da Google e lançada a público em 2009.
A linguagem nasceu com foco em simplicidade, estabilidade e produtividade, pensada para desenvolver aplicações complexas e longevas.
Nesse artigo vamos ver em detalhes os pontos que eu considero os mais importantes da linguagem.
Tópicos:
- Retrocompatibilidade
- Compilação e portabilidade
- Concorrência
- Biblioteca padrão rica
- Simplicidade
- Ferramenta de profiling de performance nativa
- Ferramenta de testes nativas
1. Retrocompatibilidade
O Go lançou sua versão estável 1.0 em 2012, com a promessa de que novas versões 1.x.y sejam retrocompatíveis.
Em outras palavras, isso significa que um código em Go escrito em 2012 usando a versão 1.0 deverá compilar e rodar corretamente em 2023 utilizando a versão 1.20.5, por exemplo.
A Google e muitas outras empresas testam a versão de pré-release do Go nos projetos internos delas para assegurar que a linguagem se manteve compatível e que nada quebrou. A vantagem disso é que se um projeto com milhões de linhas de código de produção e testes não quebraram, é bem improvável que projetos menores vão ter quebrado.
Porém a promessa de compatibilidade pode ser quebrada em alguns casos, como:
- Correção de falhas de segurança
- Correção de bugs na linguagem
E alguns outros cenários específicos.
O mais importante da retrocompatibilidade da linguagem é permitir que desenvolvamos códigos longevos sem precisar estarmos constantemente corrigindo e adaptando o código para as novas mudanças da linguagem.
2. Compilação e portabilidade
Na implementação oficial do Go, o código é compilado para um executável com tudo que ele precisa para rodar, o que reduz uma série de dores de cabeça.
O Go compila e otimiza o código em tempo de build, então o programa não vai utilizar memória nem CPU otimizando seu código em tempo de execução, diferente de como faz o Python, Java, JavaScript, PHP, etc.
Um detalhe importante é que no Go também é possível compilar para outras arquiteturas. Isso significa que você no Linux num processador ARM pode compilar seu código para rodar em um Windows 64 bits.
Pode não parecer muito, mas em C por exemplo isso não só não é trivial de fazer, como também é desencorajado.
Exemplo de cross-compiling de um programa Go:
GOOS=windows GOARCH=386 go build main.go # windows 32 bits
GOOS=linux GOARCH=arm go build main.go # linux arm
3. Concorrência
O Go conta com as goroutines, que são similares a threads, porém são mais leves e gerenciadas pelo runtime do Go.
O Go distribui as goroutines pelos núcleos da CPU o que possibilita processarmos coisas em paralelo, e também faz bastante uso de I/O assíncrono, o que possibilita um ganho considerável de performance em aplicações que façam muito uso de I/O, como aplicações web.
A linguagem também conta com canais (channels), que são uma ferramenta poderosa para comunicar e sincronizar goroutines.
Channels é um tópico tão importante da linguagem que eu fiz uma série de 5 artigos dedicada a eles.
4. Biblioteca padrão rica
O Go vem com uma biblioteca bem rica de pacotes que você pode utilizar em suas aplicações.
Esses são apenas alguns exemplos:
-
io
: para lidar com leitura, escrita, etc de bytes -
os
: para lidar com o OS (arquivos, env, processos, etc) -
time
: para obter, converter e formatar tempo -
testing
: para testes automatizados, incluindo benchmark e fuzzing -
crypto
: para algoritmos e constantes criptográficas -
net
: para lidar com TCP, UDP, DNS -
net/http
: para clientes e servidores HTTP 1 e 2 -
html/template
: template engine para HTML
5. Simplicidade
Muita gente se assusta quando ouve alguém falar que "C é uma linguagem muito simples".
Mas quando alguém fala que "C é simples" não significa que programar em C é moleza, mas sim que a linguagem em si é mais minimalista, não possui muitas formas diferentes de fazer a mesma coisa, não tem muito comportamento implícito, etc.
Alguns exemplos de como esse princípio é aplicado no Go:
- Idealmente só há uma forma de fazer algo
- O equivalente a "constructors" e "destructors" são explícitos
- A linguagem possui apenas 25 palavras reservadas (vs 67 do Java, 95 do C++)
- A especificação tem cerca de 100 páginas (vs +800 do Java, +1800 do C++)
- A stdlib evita o uso de abstrações e indireções quando não é necessário
Obs.: Java e C++ foram citados só para fins de comparação
A vantagem de uma linguagem simples é que você se dedica menos tempo aprendendo a linguagem em si, e mais tempo desenvolvendo e aprendendo conceitos mais importantes.
6. Ferramenta de profiling de performance nativa
Não deixei esse tópico como um dos primeiros porque a maioria das soluções tem uma performance "boa o suficiente" para maioria dos cenários, e otimizações costumam ser bem pontuais.
Porém, quando sua aplicação tem milhões de usuários diariamente batendo em endpoints, uma linguagem que usa pouca memória e consegue gerenciar milhares de requisições por segundo é um diferencial.
O Go conta com uma ferramenta nativa para medir uso de memória, CPU, goroutines, e muito mais, o que te permite otimizar performance com base em métricas em vez de suposições.
Além disso, desde a versão 1.20, o Go suporta Profile-guided optimization (PGO), que é utilizar dados obtidos num profile de performance como parâmetro para otimizar o código durante a compilação.
7. Ferramenta de testes nativas
O Go vem com suporte nativo a testes automatizados, fuzzing e benchmarking.
Com testes automatizados você pode validar que seu código está sempre se comportando como esperado.
func TestFormatarNomeDeCartao(t *testing.T) {
nome := "Zezinho da Silva Freitas Junior"
esperado := "Zezinho da S F Junior"
resultado := FormatarNomeDeCartao(nome)
if resultado != esperado {
t.Errorf("esperado: %s, obtido: %s", esperado, resultado)
}
}
Com testes de fuzzing você alimenta o teste com um valor inicial, que no nosso exemplo vai ser um nome, e o fuzzer vai começar a criar variações desse valor e chamar sua função com eles.
func FuzzFormatarNomeDeCartao(f *testing.F) {
f.Add("Zezinho da Silva")
f.Fuzz(func (t *testing.T, nome string) {
resultado := FormatarNomeDeCartao(nome)
if len(resultado) > len(nome) {
t.Errorf("O nome abreviado '%s' está maior que o original '%s'", resultado, nome)
}
})
}
Com testes de benchmark você pode verificar o tempo médio que uma função leva para rodar, uso de memória, alocações etc.
func BenchmarkFormatarNomeDeCartao(b *testing.B) {
for i := 0; i < b.N; i++ {
FormatarNomeDeCartao("Zezinho da Silva Freitas Junior")
}
}
Posted on August 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 9, 2023