DEV Community

renanbastos93
renanbastos93

Posted on

Por que você deve repensar o uso de Regex em validações de strings em Go

Quando falamos de validação de strings no Go, uma das soluções mais comuns é o uso de expressões regulares (regex). No entanto, dependendo do contexto, o uso de regex pode ser menos eficiente do que alternativas mais simples. Em sistemas de alta performance, como os que lidam com grandes volumes de dados ou que precisam ser executados em ambientes com recursos limitados, é importante considerar o impacto de cada escolha de implementação.

Embora regex seja uma ferramenta poderosa, sua utilização indiscriminada pode resultar em impactos de performance, principalmente em Go, onde cada operação é otimizada ao máximo para garantir alta eficiência. Neste post, vamos abordar um exemplo simples de validação de strings alfanuméricas, comparando o uso de regex com uma abordagem baseada em Unicode e explicando como otimizar o uso de regex no Go para obter melhores resultados.

O que acontece por trás das cortinas: Regex vs Unicode
A biblioteca regexp é bastante útil, mas pode ser mais lenta do que validações feitas manualmente usando funções da biblioteca unicode. Isso ocorre porque o Go precisa compilar o regex e avaliar sua expressão toda vez que é usado dentro de um método. Isso consome mais tempo de CPU e memória, especialmente em validações simples, como a checagem, se uma string contém apenas caracteres alfanuméricos.

A alternativa, mais eficiente, é iterar sobre a string e validar cada caractere individualmente, utilizando funções como unicode.IsLetter e unicode.IsDigit. Isso evita a sobrecarga de compilar o regex toda vez que a função é chamada e pode ser muito mais rápido para cenários simples.

Exemplo prático: Comparando as abordagens

Aqui está um exemplo em Go para comparar as duas abordagens: uma usando regex e outra utilizando Unicode diretamente.

Implementação com Regex:

var re = regexp.MustCompile(`^[A-Za-z0-9_]+$`)

func isValidWithRegex(s string) bool {
    return re.MatchString(s)
}
Enter fullscreen mode Exit fullscreen mode

Implementação usando Unicode:

func isValid(s string) bool {
    for _, r := range s {
        if !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_') {
            return false
        }
    }
    return len(s) > 0 // Garante que a string não está vazia
}
Enter fullscreen mode Exit fullscreen mode

Comparação de Performance

Vamos rodar alguns benchmarks para comparar a performance das duas abordagens.

Benchmark com Unicode:

// BenchmarkTestIsValid-8       18216368            66.18 ns/op        0 B/op          0 allocs/op
func BenchmarkTestIsValid(b *testing.B) {
    for i := 0; i < b.N; i++ {
        isValid("Teste_123")
        isValid("Valido_ABC")
        isValid("Invalido!")
        isValid("12345")
        isValid("")
    }
}
Enter fullscreen mode Exit fullscreen mode

Benchmark com Regex:

// BenchmarkTestIsValidWithRegex-8       3069778           401.3 ns/op         0 B/op          0 allocs/op
func BenchmarkTestIsValidWithRegex(b *testing.B) {
    for i := 0; i < b.N; i++ {
        re.MatchString("Teste_123")
        re.MatchString("Valido_ABC")
        re.MatchString("12345")
        re.MatchString("")
    }
}
Enter fullscreen mode Exit fullscreen mode

Resultados

Como você pode observar nos benchmarks acima, a versão que usa Unicode é significativamente mais rápida do que a versão com regex. A versão com Unicode realiza cerca de 18 milhões de operações por segundo, enquanto a versão com regex realiza apenas cerca de 3 milhões.

Por que isso acontece?

Quando usamos regex, estamos criando e compilando uma expressão regular toda vez que chamamos a função. O processo de compilação e execução da expressão regular envolve mais passos do que simplesmente iterar sobre os caracteres da string com funções do unicode. Isso faz com que a execução com regex consuma mais tempo de CPU e memória.

Quando usar Regex?

Isso não significa que você deve parar de usar regex. Em casos mais complexos, onde você precisa de validações mais sofisticadas, regex pode ser a melhor escolha. No entanto, em validações simples, como a checagem de caracteres alfanuméricos, o uso de Unicode diretamente é muito mais eficiente.

Dica de Performance: Compile Regex fora do método
Se você optar por usar regex, uma boa prática é compilar a expressão regular fora do método, para que ela não precise ser recompilada a cada chamada. Isso pode ajudar a reduzir o custo de performance de usar regex.

var re = regexp.MustCompile(`^[A-Za-z0-9_]+$`)

func isValidWithRegex(s string) bool {
    return re.MatchString(s)
}
Enter fullscreen mode Exit fullscreen mode

Ao compilar a expressão regular uma vez e reutilizá-la, você reduz significativamente o impacto de performance.

Conclusão

Embora regex seja uma ferramenta poderosa e útil, seu uso em validações simples pode ser ineficiente, especialmente quando a performance é uma prioridade. Em Go, alternativas como a utilização da biblioteca unicode podem oferecer ganhos significativos de performance. Se for necessário usar regex, lembre-se de compilar a expressão regular fora do método para otimizar o desempenho.

Agora, repense como você está validando suas strings no seu projeto e escolha a abordagem mais eficiente para o seu caso!

Top comments (0)