← Voltar para o blog

Go Fix: A Evolução da Manutenção de Código com Inlining no Nível do Código-Fonte

O Go 1.26 introduziu uma implementação totalmente nova do subcomando `go fix`, projetado para auxiliar na modernização e manutenção do código Go. Uma...

O Go 1.26 introduziu uma implementação totalmente nova do subcomando go fix, projetado para auxiliar na modernização e manutenção do código Go. Uma das funcionalidades mais notáveis é o “inliner” no nível do código-fonte (source-level inliner), que permite que autores de pacotes expressem migrações e atualizações de API de forma direta e segura. Este artigo explora o que é o inliner, como usá-lo e alguns dos desafios e tecnologias por trás dele.

O que é Source-Level Inlining?

O “source-level inlining” é uma técnica que substitui uma chamada de função por uma cópia do corpo da função chamada, substituindo os argumentos pelos parâmetros correspondentes. Diferente dos inliners tradicionais encontrados em compiladores, que operam em representações intermediárias, o inliner do Go modifica o código-fonte diretamente.

Se você já utilizou a refatoração interativa “Inline call” do gopls, você já utilizou o inliner no nível do código-fonte. Essa ferramenta é crucial para várias transformações de código, como “Change signature” e “Remove unused parameter”, pois lida com sutilezas que podem surgir durante a refatoração de chamadas de função.

O mesmo inliner agora faz parte do comando go fix, habilitando migrações e atualizações de API “self-service” através de um novo comentário diretivo: //go:fix inline.

Exemplos Práticos de Uso

Renomeando ioutil.ReadFile

No Go 1.16, a função ioutil.ReadFile foi depreciada em favor de os.ReadFile. Para facilitar a transição, o inliner pode ser usado. Primeiro, anota-se a função antiga com //go:fix inline:

package ioutil

import "os"

// ReadFile reads the file named by filename…
// Deprecated: As of Go 1.16, this function simply calls [os.ReadFile].
//go:fix inline
func ReadFile(filename string) ([]byte, error) {
    return os.ReadFile(filename)
}

Ao executar go fix em um arquivo que chama ioutil.ReadFile, a chamada é substituída por os.ReadFile:

$ go fix -diff ./...
-import "io/ioutil"
+import "os"

-   data, err := ioutil.ReadFile("hello.txt")
+   data, err := os.ReadFile("hello.txt")

A principal vantagem dessa abordagem é que ela é intrinsecamente mais segura do que ferramentas de reescrita arbitrária como gofmt -r, pois substitui a chamada por uma cópia do corpo da função, preservando o comportamento do programa (exceto em casos raros que dependem da inspeção da pilha de chamadas).

Corrigindo Falhas de Design de API

O inliner também pode ser usado para corrigir falhas de design de API. Considere um pacote hipotético oldmath com os seguintes problemas:

  • A função Sub tem a ordem dos parâmetros invertida.
  • A função Inf implicitamente prefere um dos infinitos.
  • A função Neg é redundante.
// Package oldmath is the bad old math package.
package oldmath

// Sub returns x - y.
func Sub(y, x int) int

// Inf returns positive infinity.
func Inf() float64

// Neg returns -x.
func Neg(x int) int

Para migrar os usuários para um pacote newmath que corrige esses problemas, o primeiro passo é implementar a API antiga em termos da nova e depreciar as funções antigas. Em seguida, adicione as diretivas de inliner:

// Package oldmath is the bad old math package.
package oldmath

import "newmath"

// Sub returns x - y.
// Deprecated: the parameter order is confusing.
//go:fix inline
func Sub(y, x int) int {
    return newmath.Sub(x, y)
}

// Inf returns positive infinity.
// Deprecated: there are two infinite values; be explicit.
//go:fix inline
func Inf() float64 {
    return newmath.Inf(+1)
}

// Neg returns -x.
// Deprecated: this function is unnecessary.
//go:fix inline
func Neg(x int) int {
    return newmath.Sub(0, x)
}

Agora, ao executar go fix, as chamadas às funções antigas serão substituídas pelas novas:

import "oldmath"

var nine = oldmath.Sub(1, 10) // diagnostic: "call to oldmath.Sub should be inlined"

será transformado em:

import "newmath"

var nine = newmath.Sub(10, 1)

O inliner também funciona com tipos e constantes. Se o pacote oldmath originalmente declarasse um tipo de dados para números racionais e uma constante para π, poderíamos usar as seguintes declarações de encaminhamento para migrá-los para o pacote newmath, preservando o comportamento do código existente:

package oldmath

//go:fix inline
type Rational = newmath.Rational

//go:fix inline
const Pi = newmath.Pi

Implicações e Benefícios

O “source-level inlining” oferece vários benefícios:

  • Migrações de API Seguras: Garante que as mudanças de API sejam feitas de forma controlada e previsível, minimizando o risco de introduzir bugs.
  • Melhoria da Qualidade do Código: Permite a correção de falhas de design de API e a remoção de código obsoleto.
  • Automatização da Manutenção: Facilita a automatização de tarefas de manutenção de código em larga escala.
  • Integração com Ferramentas Existentes: O inliner está integrado com ferramentas como gopls, fornecendo feedback imediato aos desenvolvedores.

Em resumo, o “source-level inlining” no Go 1.26 é uma ferramenta poderosa para modernizar e manter o código Go. Ao permitir que os autores de pacotes expressem migrações de API de forma segura e automatizada, ele simplifica a vida dos desenvolvedores e melhora a qualidade geral do ecossistema Go. O go fix com o inliner representa um passo significativo em direção a um ecossistema Go mais robusto e sustentável.


Artigo Original

Este e um resumo em português do artigo original publicado no blog oficial do Go.

Titulo original: //go:fix inline and the source-level inliner

Leia o artigo completo em ingles no Go Blog

Autor original: Alan Donovan