Design de software

O princípio da menor surpresa: por que getUser não deve enviar e-mails

Você já encontrou uma função assim durante uma revisão de código?

4 min de leitura
Imagem de capa de O princípio da menor surpresa: por que getUser não deve enviar e-mails

Você já encontrou uma função assim durante uma revisão de código?

Ela se chamava getUser(), mas, ao abrir o código, descobri que consultava o usuário, atualizava o horário do último acesso, atualizava o cache e até enviava um e-mail de notificação se a conta estivesse inativa.

Se você a chamasse dentro de um loop pensando, apenas pelo nome, “é só uma consulta, então deve ser segura”, não acabaria enviando uma bomba de e-mails para os usuários?

O princípio de hoje é o princípio da menor surpresa, que ajuda a identificar esse tipo de código. Ele faz parte da nossa série sobre princípios de desenvolvimento, que passou por KISS, DRY, YAGNI e SOLID — e o nome do princípio de hoje é especialmente divertido.

O princípio da menor surpresa (Principle of Least Astonishment, ou POLA) diz o seguinte:

O código deve se comportar como quem o lê espera. Se um design surpreende os usuários, repense esse design.

É um princípio antigo, surgido no design de sistemas das décadas de 1960 e 1970, mas aplicável a tudo, do design de UI e API ao código do dia a dia.


Por que a surpresa custa caro?

Na programação, “surpresa” não é uma questão emocional; é uma questão de custo.

Ao ler código, desenvolvedores criam hipóteses com base em nomes e convenções. getUser provavelmente só faz uma consulta, e isValid provavelmente retorna um booleano. Quando o código se comporta conforme a hipótese, a leitura flui.

Mas, no momento em que a hipótese é quebrada, você precisa parar de ler, entrar na função e verificar o que ela realmente faz. Mesmo que existam apenas dez funções assim na base de código, surge a desconfiança: “Não dá para confiar nos nomes deste código”. A partir daí, você passa a abrir todas as funções enquanto lê. Sua velocidade de leitura cai pela metade.

O pior caso é usar o código de acordo com a hipótese sem verificar e causar uma falha, como a bomba de e-mails acima.


Onde a surpresa aparece no código

Vou organizar isso com base nos padrões que já vivenciei na prática.

1. Funções cujo nome não corresponde ao comportamento

// O nome indica uma consulta, mas a função altera o estado
func getUser(id: Int) -> User {
    let user = db.find(id)
    user.lastSeenAt = Date()  // Surpresa 1: Efeito colateral
    db.save(user)
    if user.isDormant {
        mailer.send(to: user.email, message: "Aviso de reativação de conta inativa")  // Surpresa 2: Chamada externa
    }
    return user
}

Operações de escrita escondidas em funções de consulta são uma fonte clássica de surpresa. get deve apenas ler; se alterar o estado, use um nome como update ou touch.

2. Valores de retorno que traem as convenções

Se, na mesma base de código, uma função retorna nil quando não encontra algo, outra lança uma exceção e outra retorna um objeto vazio, quem chama precisa apostar todas as vezes. É importante padronizar em uma única abordagem.

3. Falhas silenciosas

func parseConfig(_ json: Data) -> Config {
    guard let config = try? JSONDecoder().decode(Config.self, from: json) else {
        return Config()  // Surpresa: a configuração está corrompida, mas uma configuração vazia é retornada como se nada tivesse acontecido
    }
    return config
}

Se o arquivo de configuração está corrompido e o sistema volta silenciosamente aos valores padrão, o usuário sofrerá mais tarde, perguntando: “Por que minha configuração não foi aplicada?”. As falhas surpreendem menos quando são comunicadas claramente.

Quando o nome e o comportamento divergem, quem lê paga o preço
Quando o nome e o comportamento divergem, quem lê paga o preço

Como escrever um código menos surpreendente

Estas são as práticas que tento seguir.

Prática Conteúdo
O nome é um contrato Faça apenas o que o nome promete; se fizer mais, mude o nome
Siga as convenções Não se afaste sem motivo do estilo existente da linguagem, do framework ou da equipe
Deixe os efeitos colaterais explícitos O nome deve deixar claro que a função altera o estado
Documente o que for surpreendente Se um comportamento incomum for inevitável, destaque-o nos comentários e na documentação

A segunda prática é a mais poderosa. Quando consideramos a equipe inteira, abordagens padrão e entediantes sempre vencem métodos pessoais e engenhosos. É exatamente a mesma ideia do episódio anterior sobre KISS: um loop comum é melhor do que uma “linha de código impressionante”.

No fim, o código entediante e previsível vence
No fim, o código entediante e previsível vence

Para concluir

Há três coisas para lembrar sobre o princípio da menor surpresa.

  • O código deve se comportar conforme as expectativas criadas por seus nomes e convenções. Código que quebra essas expectativas é um terreno fértil para bugs.
  • Não esconda efeitos colaterais em funções de consulta nem engula falhas silenciosamente.
  • Em uma equipe, código entediante e previsível vence código engenhoso.

Se eu tivesse de escolher um único critério para um bom código, seria este: código que não surpreende quem o lê.

Se você leu algum código recentemente e pensou “Ué, por que isso funciona assim?”, esse foi exatamente um momento em que este princípio foi violado.