Logotipo do ConstructiCat
CodeBust.
Explorar a seção ▾

Ação a distância.

Ação a distância é um antipadrão em que o código de uma parte do programa altera silenciosamente o comportamento de outra parte distante e sem relação.

Ação a distância (em inglês, action at a distance) é um antipadrão na ciência da computação no qual o comportamento de uma parte de um programa varia drasticamente com base em operações difíceis ou impossíveis de identificar em outra parte do programa.

A maneira de evitar os problemas associados à ação a distância é um bom design, que evita variáveis globais e altera dados apenas de maneira controlada e local, ou o uso de um estilo de programação puramente funcional com transparência referencial.

O termo vem da física, em que ação a distância designa a influência mútua entre objetos sem nenhum intermediário visível.

Bugs de software causados por ação a distância podem surgir porque um componente do programa está fazendo algo na hora errada ou afetando algo que não deveria. É muito difícil, no entanto, rastrear qual componente é o responsável. Efeitos colaterais de ações inofensivas podem deixar o programa em um estado desconhecido, de modo que dados locais não são necessariamente locais. A solução nesse cenário específico é definir quais componentes devem interagir com quais outros. Um bom design, que define com precisão a interface entre as partes de um programa e que evita estados compartilhados, pode eliminar em grande parte os problemas causados pela ação a distância.

Exemplo histórico: $[ em Perl

Este exemplo, da linguagem de programação Perl, demonstra um caso especialmente grave de ação a distância (observe que a variável $[ foi descontinuada em versões posteriores do Perl):

Os índices de arrays normalmente começam em 0 porque o valor de $[ normalmente é 0; se você definir $[ como 1, então os arrays começam em 1, o que deixa os programadores Fortran felizes, e assim vemos exemplos como este na man page do perl(3):

foreach $num ($[ .. $#entry) {
    print "  $num\t'",$entry[$num],"'\n";
}

E, claro, você poderia definir $[ como 17 para que os arrays começassem em algum número aleatório, como 17 ou 4, em vez de 0 ou 1. Essa era uma ótima maneira de sabotar os autores de módulos.

Felizmente, o bom senso prevaleceu. Hoje reconhece-se que esses recursos foram erros. A lista de discussão perl5-porters agora tem um bordão para tais recursos: eles são chamados de "action at a distance". O princípio é que uma declaração em uma parte do programa não deveria alterar drástica e invisivelmente o comportamento de alguma outra parte do programa.

— Mark Jason Dominus, Sins of Perl Revisited

Ação a distância entre objetos

A programação orientada a objetos bem feita envolve princípios de design que evitam a ação a distância.

A Lei de Deméter afirma que um objeto deve interagir apenas com outros objetos próximos a ele. Caso seja necessária uma ação em uma parte distante do sistema, ela deve ser implementada propagando uma mensagem. Um bom design limita severamente as ocorrências de ação a distância, contribuindo para programas fáceis de manter. A pressão para criar uma object orgy resulta de um design de interface ruim, talvez assumindo a forma de um God object, da não implementação de objetos de verdade ou da falta de atenção à Lei de Deméter.

Uma das vantagens da programação funcional é que a ação a distância é menos enfatizada, às vezes a ponto de ser totalmente impossível de expressar na linguagem de origem.

Estar ciente do perigo de permitir a ação a distância em um design, e ser capaz de reconhecer a presença da ação a distância, é útil para desenvolver programas corretos, confiáveis e fáceis de manter. Dado que a maior parte do custo de um programa pode estar na fase de manutenção, e que a ação a distância torna a manutenção difícil, cara e propensa a erros, vale o esforço de evitá-la durante o design.

Exemplo

Aqui o módulo de relatórios altera um dicionário de configurações compartilhado e mutável. Nada muda em invoices.py e, mesmo assim, depois da primeira exportação para CSV todas as faturas passam a ser impressas com quatro casas decimais.

# settings.py
DEFAULTS = {"currency": "USD", "precision": 2}

# invoices.py
from settings import DEFAULTS

def format_amount(amount):
    return f"{amount:.{DEFAULTS['precision']}f} {DEFAULTS['currency']}"

# reports.py
from invoices import format_amount
from settings import DEFAULTS

def export_csv(rows):
    DEFAULTS["precision"] = 4  # todas as faturas impressas depois também mudam
    return [format_amount(r) for r in rows]

Agora o formato é um valor imutável passado explicitamente:

from dataclasses import dataclass


@dataclass(frozen=True)
class Format:
    currency: str = "USD"
    precision: int = 2


def format_amount(amount, fmt=Format()):
    return f"{amount:.{fmt.precision}f} {fmt.currency}"


def export_csv(rows):
    fmt = Format(precision=4)  # afeta apenas esta exportação
    return [format_amount(r, fmt) for r in rows]

Uma mudança em um lugar não consegue mais vazar para outro; globais mutáveis compartilhadas como DEFAULTS são o que a teoria de acoplamento chama de acoplamento comum (veja Acoplamento forte).

Adaptado do artigo da Wikipédia “Action at a distance (computer programming)” (revisão 1329485461), disponível sob a licença CC BY-SA 4.0; o texto foi traduzido e editado, e exemplos foram adicionados.