Por que o Zolo avisa em 4 níveis de aninhamento (e a ideia de 60 anos por trás disso)
Um warning de lint sobre profundidade de aninhamento, rastreado por Linus Torvalds, a complexidade ciclomática de McCabe (1976), "Go To Considered Harmful" de Dijkstra (1968) e os limites de memória de trabalho de Miller (1956).
Por que o Zolo avisa em 4 níveis de aninhamento (e a ideia de 60 anos por trás disso)
🇺🇸 English version: Why Zolo Warns at 4 Levels of Nesting · 🇪🇸 Versión en español: Por qué Zolo avisa con 4 niveles de anidamiento
Você escreve um código em Zolo. O compilador devolve:
warning[max-nesting-depth]: nesting depth 5 exceeds max recommended (4)
warning[max-nesting-depth]: nesting depth 6 exceeds max recommended (4)
warning[max-nesting-depth]: nesting depth 7 exceeds max recommended (4)
Você pensa: "Quem decidiu que é 4? Por que não 5? Por que não 8?"
A resposta honesta é que o número exato é arbitrário, mas a ideia por trás dele é um dos princípios mais antigos e mais validados da engenharia de software — remonta a um artigo de psicologia cognitiva de 1956, uma carta de 1968 que praticamente deu à luz a programação estruturada, e uma métrica de 1976 ainda usada hoje por todo analisador estático sério.
Este post passa pelo lint, pela história, e pelo que isso significa para a forma como você escreve Zolo.
O que o lint realmente faz #
A regra max-nesting-depth mora em crates/zolo-compiler/src/lint.rs. A implementação cabe em uma tela:
fn enter_nesting(&mut self, span: Span) {
self.nesting_depth += 1;
let max = self.config.settings.max_nesting_depth;
if self.nesting_depth > max {
self.emit(
"max-nesting-depth",
format!(
"nesting depth {} exceeds max recommended ({})",
self.nesting_depth, max
),
span,
LintLevel::Warn,
);
}
}
Um visitor percorre a AST. Cada uma dessas construções incrementa o contador:
if/elsematch(e cada arm)forwhileloop- expressões de controle usadas como valor (
let x = if ...,let x = match ...)
O corpo da função em si não conta. Então fn foo() { if a { if b { if c { if d { if e { ... } } } } } } produz depth 5 e dispara o warning.
O limite padrão é 4, configurável no .lint.toml do projeto. Por que 4? Vamos voltar 70 anos.
1956 — Miller e os limites da memória de trabalho #
Em The Magical Number Seven, Plus or Minus Two: Some Limits on Our Capacity for Processing Information (Miller, 1956), o psicólogo George A. Miller observou que humanos conseguem segurar, em média, 7 ± 2 itens discretos na memória de curto prazo ao mesmo tempo.
O artigo original não é sobre código — Miller estudava percepção de tons, sabores, e recall de sequências de dígitos. Mas a descoberta foi adotada por todo campo posterior que se importa com processamento humano de informação. Pesquisas seguintes em carga cognitiva inclusive abaixaram o número — o trabalho de Nelson Cowan no início dos anos 2000 sugere que a capacidade real da memória de trabalho está mais perto de 4 ± 1 chunks (Cowan, 2001).
Por que isso importa para código aninhado? Porque ler um bloco profundamente aninhado não é um único pensamento. Para entender a linha N dentro de if A { for B { match C { if D { ... } } } }, você precisa segurar A, B, C e D simultaneamente na cabeça como contexto ativo — e lembrar o que cada um significa na lógica em volta. Cada nível é um chunk. Em 4–5 níveis, você já está na borda do que um humano consegue rastrear sem perder o fio.
🔖 Miller, G. A. (1956). The magical number seven, plus or minus two: some limits on our capacity for processing information. Psychological Review, 63(2), 81–97. DOI: 10.1037/h0043158
1968 — Dijkstra e a programação estruturada #
Em março de 1968, Edsger Dijkstra publicou uma carta de uma página e meia na Communications of the ACM intitulada Go To Statement Considered Harmful (Dijkstra, 1968). É talvez o artigo mais citado da computação.
"O uso desenfreado do go to tem como consequência imediata que se torna terrivelmente difícil encontrar um conjunto significativo de coordenadas para descrever o progresso do processo."
O argumento central: à medida que o código cresce em complexidade dinâmica, humanos perdem a capacidade de mapear mentalmente código-fonte para estado do programa. Ele defende restringir o fluxo de controle a sequência, seleção (if) e iteração (while) — o que hoje chamamos de programação estruturada.
A carta do Dijkstra matou o goto na prática mainstream, mas o insight mais profundo se aplica igualmente ao aninhamento profundo: cada nível adicional de estrutura de controle é mais uma coordenada que você precisa rastrear para saber onde está no programa. Cinco níveis de if, nesse sentido, é só marginalmente melhor que goto — ambos dificultam responder "o que é verdade aqui?"
🔖 Dijkstra, E. W. (1968). Go to statement considered harmful. Communications of the ACM, 11(3), 147–148. DOI: 10.1145/362929.362947 — também arquivado como EWD215.
1976 — McCabe dá um número à complexidade #
Em A Complexity Measure (McCabe, 1976), Thomas J. McCabe propôs uma métrica teórica-de-grafos: conte o número de caminhos linearmente independentes pelo grafo de fluxo de controle de uma função. Ele chamou de complexidade ciclomática.
A fórmula é simples: V(G) = E − N + 2P onde E é arestas, N é nós, P é componentes conectados. Para código típico, cada if, while, for, case soma 1.
McCabe não mediu aninhamento diretamente — mas a métrica dele e aninhamento são profundamente correlacionados, porque cada branch aninhada multiplica a contagem de caminhos. Ele propôs 10 como limite superior suave para complexidade ciclomática por função. Em 5 níveis aninhados de if, você já queimou esse orçamento só em estrutura, sem espaço para a lógica em si.
Essa é a métrica que vive, quatro décadas depois, dentro do SonarQube, Code Climate, regra complexity do ESLint, e cognitive_complexity do Clippy.
🔖 McCabe, T. J. (1976). A complexity measure. IEEE Transactions on Software Engineering, SE-2(4), 308–320. DOI: 10.1109/TSE.1976.233837
2001 — Torvalds, direto como sempre #
Pulando para o Linux Kernel Coding Style (Torvalds, atual). Seção 1, sobre indentação:
"A resposta para isso é que se você precisa de mais de 3 níveis de indentação, você está ferrado de qualquer jeito, e deveria consertar seu programa."
Linus não cita McCabe nem Miller. Ele oferece a versão field-tested e prática da mesma descoberta: código profundamente aninhado é um sintoma — quase sempre sintoma de que uma função está fazendo coisas demais e deveria ser dividida.
Vale notar que ele diz 3, não 4. O kernel usa tabs de 8 caracteres em parte para tornar isso fisicamente doloroso: em 3 níveis você já está 24 colunas para dentro.
O que o resto do mundo escolheu #
| Linter / Ferramenta | Regra | Padrão | Notas |
|---|---|---|---|
| ESLint | max-depth |
4 | "Profundidade máxima que blocos podem ser aninhados" |
| Clippy (Rust) | excessive_nesting |
opt-in (threshold padrão 0) |
Lint de restrição; usuários tipicamente colocam 5 |
| SonarQube | S134 | 3–4 (varia por linguagem) | Java/C++ padrão 4, vários outros 3 |
| Linux Kernel | coding-style §1 | 3 | "Se você precisa de mais, você está ferrado" |
| Zolo | max-nesting-depth |
4 | Configurável por projeto |
Não há consenso sobre o número exato, mas a faixa é estreita: 3 a 5. Zolo escolheu 4 porque casa com o ESLint (o linter mais difundido do planeta) e porque, na prática, traça a linha exatamente onde o código começa a ficar mais difícil de ler em voz alta.
Como isso fica em Zolo #
Uma função de 5 níveis (warning):
fn process(items) {
if items != nil { \ depth 1
for item in items { \ depth 2
if item.valid { \ depth 3
match item.kind { \ depth 4
"x" => if item.urgent { \ depth 5 → warning
do_urgent_x(item)
},
_ => skip(item),
}
}
}
}
}
A mesma lógica, refatorada com guard clauses e uma função extraída:
fn process(items) {
if items == nil { return }
for item in items {
handle(item)
}
}
fn handle(item) {
if !item.valid { return }
match item.kind {
"x" if item.urgent => do_urgent_x(item),
"x" => {},
_ => skip(item),
}
}
Duas coisas aconteceram:
- Guard clauses inverteram casos negativos em early returns, removendo um nível de
if. Esse padrão é às vezes chamado de "return early, return often" e remonta ao Object-Oriented Software Construction de Bertrand Meyer (1988). - Extração moveu o trabalho interno para
handle, que nunca passa de depth 2. O nome da função agora documenta o que antes era estrutura enterrada.
O resultado lê de cima para baixo. Você pode parar em qualquer linha e saber o que é verdade acima.
Quando a regra está errada #
Lints codificam padrões, não absolutos. Casos em que aninhamento profundo é genuinamente a expressão mais clara:
- Parsers e tokenizers — gramáticas sensíveis a contexto naturalmente produzem árvores profundas de
match. - Máquinas de estado — estado aninhado explícito pode ser mais claro do que uma tabela de dispatch plana quando transições são esparsas.
- Kernels numéricos — loops apertados sobre arrays multi-dimensionais (processamento de imagem, kernels de ML) frequentemente precisam de 4+ níveis só de
for.
Para esses, ajuste por projeto no .lint.toml:
[settings]
max-nesting-depth = 6
[rules]
\ ou desabilite completamente
max-nesting-depth = "off"
A intenção do warning não é dizer que 5 é ilegal. É que o padrão deve ser conservador, e ultrapassá-lo deveria ser uma escolha deliberada que você consegue explicar.
Por que o Zolo se preocupa com isso #
Um lint não é uma preferência de estilo. É um pequeno pedaço de revisão de pares automatizada que roda toda vez que você compila. O efeito cumulativo, ao longo de milhares de edits, é que o código permanece dentro da faixa onde humanos ainda conseguem raciocinar sobre ele sem dedicar um café para cada função.
Miller nos mostrou o teto cognitivo. Dijkstra nos mostrou por que estrutura importa. McCabe nos deu um jeito de medir. Linus traduziu para algo que um mantenedor diria de verdade.
O padrão 4 do Zolo é um número nessa longa linhagem. Se você sentir vontade de silenciar o warning, faça primeiro a pergunta do Linus: eu estou ferrado, e deveria consertar meu programa?
Na maioria das vezes, a resposta é sim.
Leitura adicional #
- Miller, G. A. (1956). The magical number seven, plus or minus two. PDF · DOI
- Dijkstra, E. W. (1968). Go to statement considered harmful. PDF · EWD215
- McCabe, T. J. (1976). A complexity measure. PDF
- Cowan, N. (2001). The magical number 4 in short-term memory. DOI
- Torvalds, L. Linux kernel coding style. kernel.org
- ESLint. Regra
max-depth - Rust Clippy. Lint
excessive_nesting - SonarSource. Regra S134