O título desse post parece estranho, mas vamos lá. De fato, eu sou um anti-identação, não gosto de ver meu código identado.

Não estou dizendo que eu não idento meu código-longe disso. Apenas que eu prefiro evitar a identação sempre que for possível. Mas vamos por partes…

Primeira coisa, isso começou há algum tempo quando uma pessoa propôs o seguinte problema: montar uma lista encadeada (Linked List) com as seguintes regras:

1) A lista deve validar se ela está vazia, e lançar um erro caso tente-se retirar um item da lista vazia
2) Cada método pode ter, no máximo, uma linha
3) Não se pode usar “if” em momento algum

Claro que é uma bela loucura, mas depois que eu consegui resolver esse código, pensei: se eu consegui resolver um problema desses com apenas uma linha por método, será que meus códigos não tem coisa demais?

Aí, comecei a experimentar isso em código de produção. Isso meio que criou um estilo de codificar bem interessante, e vou tentar mostrar aqui com alguns exemplos:

Digamos, por exemplo, que temos um código que valida determinados atributos. Depois, um método que, se os atributos forem válidos, salva. Caso contrário, exibe algo na tela. Somos tentados a escrever um código assim:

def save
  if valid?
    save_product_without_validations
    return true
  else
    puts "Invalid record!"
    return false
  end
end

Não é difícil de ver que esse método faz coisas demais… ele tem a lógica do que deve ser feito se o produto for válido e se ele for inválido. Pensando numa descrição breve do que esse método faz, “save deve salvar o produto se ele for válido” já mostra que tem algum código a mais… e se retirarmos a identação?

def save
  return invalid_product unless valid?
  save_product_without_validations
  return true
end

def invalid_product
  puts "Invalid record!"
  return false
end
private :invalid_product

E essa é uma das táticas que eu uso. A outra, basicamente, é em relação aos “case”. Eu, particularmente, não idento o “when” no mesmo nível do “case”, e isso é por apenas um motivo: cada “case” tem apenas um comando.

case age
  when 18 then just_completed_18
  when 21 then buy_a_car_already
  else uninteresting_age_found
end

Isso auxilia, absurdamente, a idéias das boas-práticas de qualquer sistema. Claro que, como sempre, isso não é uma regra-de fato, se existe uma regra sobre código limpo é que não existem regras absolutas sobre o código limpo… simplesmente porque, seguir regras cegamente não garante um bom “design”, caso isso fosse possível já teríamos encontrado uma regra matemática para um bom design e todo o processo de refatoração poderia ser feito automaticamente-já que já existem “ofuscadores de código”, porque não um “refatorador automatizado”?

Claro que esse tipo de prática não serve para algumas linguagens. Por exemplo, qual o uso do exemplo acima no caso de Java:

switch(age) {
  case 18: justCompleted18(); break;
  case 21: buyACarAlready(); break;
  default: uninterestingAgeFound();
}

Não faz muito sentido quando se precisa forçar o “break” no código, na verdade. Mas voltando às identações, isso vale não só para condicionais, como para loops também:

metadata.each { |m| create_record(m) }

(que, nesse caso, funciona ainda melhor na versão em Scala)

metadata foreach createRecord

Finalizando esse curto post: a idéia não é evitar identar o código-simplesmente, a idéia é evitar aqueles códigos que parecem um sinal de maior (>) aonde se tem diversos “ifs” encadeados, ou coisas parecidas… sempre que for abrir um IF, ver se não faz mais sentido criar um novo método e usar o “if” com uma linha só. Sempre que for abrir “case”, deixar cada condição do case num método separado. E, se isso estiver dando muito trabalho, se em cada um dos novos métodos estiver chamando outro “if”, talvez seja hora de pensar numa mudança no design, talvez extraindo uma classe separada (ou uma Factory) para as condições.

E, vale a pena dizer: se um código tem mais de quatro níveis de identação, ele tem um sério problema…


2 Comments

Renato Zannon (@renato_bill) · 2011-10-19 at 06:12

Bom post! Acho que é uma ‘regra’ relativamente fácil de seguir, e que aparentemente dá bons resultados (vou tentar, e depois comento os resultados!).

Uma outra “rule of thumb”, um pouco mais nebulosa, que eu tento seguir é a de “não envolva vários níveis de abstração num mesmo método”. Acho que ter vários “if”s aninhados no mesmo método é um belo indicador de uma ‘violação’ na abstração.

Esse estilo de codificar que começa tentando descrever o que o método faz, e depois ‘documenta’ ele em termos de métodos privados é algo que tanto o Kent Beck quanto o Uncle Bob defendem muito, e que eu tento aplicar no meu código. Quando eu de fato consigo, o resultado é muito bom!

Alan Justino (@alanjds) · 2011-10-19 at 17:24

Certa vez tentei dar manutenção em código Perl que basicamente era como você descreve, Maurício. Precisávamos de algumas poucas funcionalidades extras. Acontece que o código chamava funções definidas bem longe do trecho que essas anti-identações estavam, às vezes em outro arquivo. Código esse que chamava ainda outro arquivo… Desistimos de adaptar a ferramenta.

E, caso se esteja usando um debugger interativo, como se sabe em qual ponto está a execução, ou qual a próxima chamada? Uma coisa é resolver variáveis “na mão”, outra é debugar construções formadas por funções.

Sei lá… tem que ter bastante discernimento pra programar assim…

Comments are closed.