Go para desenvolvedores PHP: o que muda na prática

1. Paradigmas e Filosofia de Linguagem

A primeira diferença que um desenvolvedor PHP encontra ao migrar para Go é o sistema de tipos. Enquanto PHP é dinamicamente tipado (você pode fazer $x = "texto" e depois $x = 42 sem problemas), Go exige que você declare o tipo de cada variável. Isso parece restritivo no início, mas elimina uma classe inteira de bugs em tempo de compilação.

// PHP — tipagem dinâmica
$nome = "João";
$nome = 123; // Funciona, mas pode causar bugs

// Go — tipagem estática
var nome string = "João"
nome = 123 // Erro de compilação: cannot use 123 (type int) as type string

Em vez de classes e herança, Go usa structs e interfaces. A composição substitui a herança:

// PHP — orientação a objetos clássica
class Animal {
    public function falar() { return "som"; }
}
class Cachorro extends Animal {
    public function falar() { return "latido"; }
}

// Go — composição com structs e interfaces
type Animal interface {
    Falar() string
}

type Cachorro struct{}
func (c Cachorro) Falar() string { return "latido" }

Go não possui exceptions. O tratamento de erros é explícito com o tipo error:

// PHP — try-catch
try {
    $arquivo = fopen("dados.txt", "r");
} catch (Exception $e) {
    echo "Erro: " . $e->getMessage();
}

// Go — tratamento explícito
arquivo, err := os.Open("dados.txt")
if err != nil {
    fmt.Println("Erro:", err)
    return
}

2. Sintaxe e Estruturas de Controle

A declaração de variáveis em Go usa := para inferência de tipo, enquanto PHP usa $:

// PHP
$nome = "Maria";
$idade = 30;

// Go
nome := "Maria"
idade := 30
// Ou explicitamente:
var nome string = "Maria"

Go tem apenas uma estrutura de repetição: o for. Mas ele se adapta a diferentes usos:

// PHP — foreach, while, for
foreach ($itens as $item) { }
while ($condicao) { }
for ($i = 0; $i < 10; $i++) { }

// Go — apenas for
for _, item := range itens { }
for condicao { } // equivalente ao while
for i := 0; i < 10; i++ { }

Funções em Go podem retornar múltiplos valores, algo que no PHP exigiria arrays ou objetos:

// PHP — retorno único
function dividir($a, $b) {
    if ($b == 0) return ["erro" => "divisão por zero"];
    return ["resultado" => $a / $b];
}

// Go — múltiplos retornos
func dividir(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("divisão por zero")
    }
    return a / b, nil
}

3. Gerenciamento de Dependências e Módulos

Go usa Go Modules (go.mod e go.sum), que funcionam de forma similar ao Composer do PHP, mas com diferenças importantes:

// PHP — composer.json
{
    "require": {
        "monolog/monolog": "^2.0"
    }
}

// Go — go.mod
module meu-projeto

go 1.21

require github.com/gorilla/mux v1.8.0

Enquanto PHP usa use com namespaces relativos, Go importa pacotes com caminhos absolutos baseados em URL:

// PHP
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Go
import (
    "log"
    "net/http"
    "github.com/gorilla/mux"
)

Go não tem autoloading. O código é compilado estaticamente em um único binário, o que elimina a necessidade de carregar classes sob demanda.

4. Concorrência e Paralelismo

Esta é uma das maiores diferenças. PHP tradicionalmente executa código de forma síncrona (cada requisição é um processo separado). Go tem goroutines e canais como cidadãos de primeira classe:

// PHP — sem concorrência real (a menos que use pthreads)
$dados = [];
foreach ($urls as $url) {
    $dados[] = file_get_contents($url); // bloqueante
}

// Go — concorrência com goroutines e canais
func buscar(url string, ch chan<- string) {
    resp, _ := http.Get(url)
    ch <- resp.Status
}

func main() {
    ch := make(chan string)
    urls := []string{"https://exemplo1.com", "https://exemplo2.com"}

    for _, url := range urls {
        go buscar(url, ch) // executa em paralelo
    }

    for range urls {
        fmt.Println(<-ch) // recebe resultados
    }
}

Para sincronização, Go oferece sync.WaitGroup e o poderoso select:

var wg sync.WaitGroup
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        fmt.Println("Trabalhando:", id)
    }(i)
}
wg.Wait() // aguarda todas as goroutines

5. Tratamento de Erros e Logging

O padrão if err != nil é onipresente em Go. Diferente do PHP, onde exceptions podem ser capturadas em qualquer nível, Go exige que cada erro seja tratado explicitamente:

// PHP — exception pode ser capturada longe do local de origem
function conectar() {
    throw new Exception("Falha na conexão");
}

try {
    conectar();
} catch (Exception $e) {
    log($e->getMessage());
}

// Go — erro tratado imediatamente
func conectar() (string, error) {
    return "", errors.New("falha na conexão")
}

resultado, err := conectar()
if err != nil {
    log.Printf("Erro: %v", err)
    return
}

Go permite wrapping de erros para adicionar contexto, sem hierarquia de classes:

if err != nil {
    return fmt.Errorf("falha ao processar usuário %d: %w", id, err)
}

Para logging, o pacote nativo log é simples, mas funcional. Não há equivalente ao Monolog com múltiplos handlers, mas bibliotecas como logrus ou zap preenchem essa lacuna.

6. Performance e Compilação

Go compila para binário nativo. Um programa Go é um único executável que não depende de runtime externo:

# PHP — precisa de servidor + interpretador
php -S localhost:8080 index.php

# Go — compila e executa diretamente
go build -o meuapp main.go
./meuapp

O garbage collector de Go é mais agressivo que o do PHP, mas com pausas muito curtas. Ferramentas de profiling são nativas:

// Ativar profiling no Go
import _ "net/http/pprof"

// Acessar: http://localhost:6060/debug/pprof/

Enquanto PHP depende de Xdebug ou Blackfire, Go oferece pprof integrado, flame graphs e análise de alocação.

7. Ecossistema e Ferramentas para Web

Go tem um servidor HTTP nativo no pacote net/http, sem necessidade de SAPI como PHP-FPM:

// PHP — precisa de Nginx + PHP-FPM
// index.php
echo "Olá, mundo!";

// Go — servidor nativo
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Olá, mundo!")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

Para roteamento, bibliotecas como chi ou gorilla/mux substituem frameworks PHP:

// Go com chi
r := chi.NewRouter()
r.Get("/usuarios/{id}", buscarUsuario)
r.Post("/usuarios", criarUsuario)
http.ListenAndServe(":8080", r)

Em vez de Eloquent ou Doctrine, Go usa database/sql com drivers SQL puros. Bibliotecas como sqlx adicionam conveniência:

// Go com database/sql
db, _ := sql.Open("postgres", "conn_string")
rows, _ := db.Query("SELECT id, nome FROM usuarios")
for rows.Next() {
    var u Usuario
    rows.Scan(&u.ID, &u.Nome)
}

8. Migração Prática: Projeto Real

Vamos replicar um CRUD simples de PHP para Go:

Estrutura de pastas em Go:

meu-crud/
├── main.go
├── handlers/
│   └── usuario.go
├── models/
│   └── usuario.go
├── database/
│   └── conexao.go
└── go.mod

Testes unitários em Go vs PHP:

// PHP — PHPUnit
class UsuarioTest extends TestCase {
    public function testCriar() {
        $this->assertTrue(true);
    }
}

// Go — testing package
func TestCriarUsuario(t *testing.T) {
    resultado := criarUsuario("João")
    if resultado == nil {
        t.Error("Esperava um usuário, recebi nil")
    }
}

Deploy:
- PHP: Container + PHP-FPM + Nginx (múltiplos componentes)
- Go: Binário único + container mínimo (scratch image)

# Dockerfile para Go
FROM golang:1.21 AS build
WORKDIR /app
COPY . .
RUN go build -o meuapp

FROM scratch
COPY --from=build /app/meuapp /meuapp
CMD ["/meuapp"]

Conclusão

Migrar de PHP para Go exige repensar a forma como você estrutura código, trata erros e gerencia concorrência. A tipagem estática e o tratamento explícito de erros trazem mais segurança em tempo de compilação, enquanto goroutines e canais abrem possibilidades de concorrência que o PHP tradicional não oferece. O ecossistema Go é mais enxuto, mas extremamente poderoso para aplicações que exigem performance e simplicidade operacional.

Referências