Como um código simples produz um sistema complexo (ou, aprendizados de uma visita ao cartório)

André Guimarães Sakata
3 min readAug 19, 2023

Recentemente eu tive uma experiência peculiar. Eu vou casar em algumas semanas, e fui ao cartório iniciar o processo de casamento civil. Eu e minha noiva nos informamos sobre os documentos necessários, e descobrimos que era preciso apresentar a certidão de nascimento de ambos. Entretanto, como eu sou uma pessoa divorciada, minha certidão de nascimento ficou retida nesse mesmo cartório quando eu casei a primeira vez.

Entramos em contato com o cartório para entender como proceder, e eles responderam que eu precisava ir antes ao local, pegar uma senha, e solicitar uma segunda via da certidão de nascimento porque o documento era obrigatório. Sim, eu precisaria me dirigir ao cartório, pagar por uma cópia de um documento, para entregar para eles mesmos no dia seguinte.

Bom, esse é uma típica situação absurda que vez ou outra nos deparamos em processos burocráticos, mas é uma situação simples de explicar. Esse cartório está voltado à produtividade — o processo se organizou de maneira a otimizar o throughput de solicitações. Ao invés de criar uma exceção no processo de casamento para pessoas que tem sua certidão de nascimento retida no local, o que tornaria o processo interno mais complexo, lento, e suscetível a erros, eles isolaram esse problema encaminhando essas pessoas para um processo já existente, de solicitação de segunda via da certidão. Dessa forma, eles conseguem atender mais solicitações de casamento por dia com menor chance de falhas.

Evidentemente, essa otimização localizada acontece em detrimento de um processo como um todo menos eficaz. Vamos dizer que, hipoteticamente, o cartório levou quinze minutos para emitir a segunda via de minha certidão de nascimento, e uma hora para dar entrada no casamento ao tratar os dois processos separadamente. Portanto, eles tiveram um montante de 1 hora e 15 minutos de trabalho. Porém, do ponto de vista do cliente, eu tive que me encaminhar ao local duas vezes, o que envolve: mudar minha agenda pessoal, pegar o carro, pagar um estacionamento, pegar fila, e fazer o caminho de volta. Além disso, precisei guardar o documento para entregá-lo no dia seguinte. Além do meu próprio tempo despendido, havia maior chance de algo sair errado — imprevistos poderiam acontecer no deslocamento. Portanto, para a real resolução do problema, o tempo de trabalho em andamento total foi de dois dias, e não apenas uma hora e quinze minutos.

O que eu percebo é que muitas vezes essa é exatamente a nossa maneira de pensar quando desenhamos APIs e serviços. Tendemos a pensar sobre simplicidade do ponto de vista da implementação, e não de quem vai usar aquele pedaço de software. Dessa forma, debatemos muito sobre “qual é o valor máximo de complexidade ciclomática que uma função deve ter?” ou “isso ou aquilo viola o princípio de responsabilidade única?”, e discutimos menos sobre o que é uma boa interface para resolver determinado problema — ou seja, como um serviço pode absorver ao invés de introduzir complexidade. É mais fácil focarmos no nosso pequeno escopo do que olhar para fora e entender como ele se integra para servir a um propósito. Assim, criamos nosso pequeno cartório dentro do ecossistema de um software, forçando sobrecarga de comunicação e jogando complexidade para o sistema como um todo.

Quando eu vejo padrões como BFF (Backend For Frontend), eu os interpreto como tentativas de dar coerência entre a API disponibilizada e o problema que se quer resolver — ou seja, servir melhor a UI especializada do produto, que foi construída para resolver bem um conjunto específico de casos de uso. Eu acho uma saída justa, com a ressalva de que talvez isso já seja um sintoma de um conjunto de microserviços que não atendem de maneira satisfatória a si mesmos.

Portanto, entre ter uma implementação interna mais “simples” (seja lá o que isso significa) ou uma interface melhor (que resolve um problema de maneira mais eficaz), a prioridade está na segunda. Pode haver casos em que outras forças te joguem para o sentido contrário — questões técnicas ou prazos, por exemplo — mas pense nisso como um mal necessário, ou um débito técnico. No fim do dia, é sempre bom lembrar que o propósito das nossas aplicações é resolver problemas persistindo e recuperando dados enquanto se aplicam regras de negócio. Se nossa arquitetura não está contribuindo para esse objetivo na maneira em que as partes dele se comunicam, precisamos reconsiderar.

--

--

André Guimarães Sakata

I write about software development, project management, and other stuff.