Tomada de decisões

Recentemente me deparei – de novo – com o ubíquo problema dos if’s aninhados.

If aninhado na minha visão é um dos principais responsáveis pela “complicação” em códigos de programação e é muito comum ver esta abordagem em ensaios de iniciantes. “Aprende-se” usar o if e em seguida ocorre a gênese do código “espaguete”. Ou seja, se só tivermos um martelo, todos os problemas passarão a ser pregos.

Esta situação costuma acontecer também com programadores mais “calejados” em situações de pressão e cobrança por resultados imediatos, que nem sempre parte da chefia, mas sim da própria ansiedade inerente de ter o “treco rodando”.

No meu caso, fiz um protótipo em bash para analisar um extrato bancário que me chegou em papel, o qual digitalizei e apliquei reconhecimento de caracteres (manualmente), para obter a massa de dados para trabalhar. Esta situação ocorreu há mais ou menos um ano e meio, depois há cerca de uns oito meses e agora, há uns três meses. Como se pode notar aqui, a probabilidade de um problema se repetir e ter que ser atacado “do zero”, é muito mais frequente do que se pode imaginar.

Ora, da primeira vez fiz um código – só para variar, sem comentar ou guardar em local seguro – logo sei que existe, mas tive que “baixar da memória” novamente…

A forma de processamento deste extrato (assim como de outros tantos relatórios que vemos por aí) é simples:

  1. Ler cada linha.
  2. Identificar suas partes.
  3. Selecionar as que interessam.
  4. Formatar a saida para uso (normalmente importação de dados ou confecção de relatórios).

O maior problema reside no segundo passo: identificar as partes!

Tentei diversas abordagens nos ciclos de criação, codificação, otimização que foram de arrays, estruturas, objetos. Cada uma com suas nuances, mas que acabavam sempre na velha “macarronada de if’s”.

Pensando um pouco, deduzi que deveria existir um jeito melhor de se fazer isto sem tanta confusão e de modo acessível aos “cérebros menos brilhantes”, como o meu. Ao pesquisar percebi que o problema não é muito relatado ou não é a ele dada a devida importância que parece merecer. Será que todo mundo faz código elegante? Ninguém esbarra nesta confusão de if’s? Ou será que este problema é um tabu, onde cada um o resolve sem divulgar os resultados? Segredo de família? Pulo do gato? …

Na verdade, a solução que encontrei para o meu caso, foi deduzida de um comentário encontrado sob uma pergunta neste sentido feita no forum do StackOverflow. O comentário, na minha tradução particular, foi: quando seus ifs passam de três níveis de aninhamento, você com certeza está errando na concepção do problema… Deve existir jeito melhor!

E o jeito melhor, pelo que entendi, é se valer da boa e confiável Tabela de Controle ou Tabela de Decisões. Eu também encontrei boas referências no meu antigo Livro do Dragão, só que também implementado com árvores binárias. Estava lá no Cap. 8, junto com os tópicos sobre alocação de memória.

Estas técnicas utilizam diversas ferramenta para serem implementadas e as mais primárias são as Tabelas da Verdade, mapas de Karnaugh e a boa técnica de Quine-McCluskey.

Resumindo o que fiz (e suponho que é o que se deve fazer):

  1. Identifique as condições (aquilo que vai dentro dos parenteses dos if’s)
  2. Defina as ações
  3. Crie seus if’s de modo atômico, colocando as condições entre parênteses, para que se possa consultá-los na tabaela de condições e movê-los com facilidade durante a edição
  4. Crie suas ações também de modo conciso para inserl-las como blocos na condicional dos if’s
  5. Terminado o processo utilize as técnicas citadas – Tab. Verdade, K-maps, Quine, etc – para aplicar as otimizações possíveis que seriam as reduções de termos via lógica. Essencialmente ** De Morgan **.

Não quero criar aqui a solução definitiva ou dizer que este ou aquele são os melhores métodos de se programar ou otimizar, mas sim apontar uma solução viável para um problema que entendo ser muito comum, mas que acaba sempre sendo tratado pela intuição ou tentativa-e-erro.

Sei também que existem alguns padrões de desenvolvimento que minimizam estes desgastes – como vi em algum lugar algo do tipo do padrão Template ou Specification. Claro, isto tudo é muito lindo e acadêmico, mas no calor da batalha, a nossa amiga Tabela da Verdade terá seu lugar e estes “padrões” ultra-modernos ficariam para os analistas “pós-guerra” reescreverem nosso código se, para isto, tiverem tempo.

A conclusão que tirei da minha experiência foi na verdade uma “escutada” à minha consciência, sempre ignorada nestas horas:

  • Esforce-se, i.e. gaste muito tempo, para fazer certo da primeira vez.
  • Sempre use C, não perca tempo procurando um jeito fácil e bonito com os objetos ou scripts “da hora”.
  • Se for para sentar e escrever código, dedique o tempo que não perdeu analizando, a fazer a coisa bem comentada e bem testada.
  • Não confie ou libere o que não terminou: caso tenha lido o código com honestidade e chegado à conclusão que ficou uma merda ou muito complicado, refaça.

E por fim apresento uma idéia da técnica que usei

  • Coloquei as condições em macros #define e chamei de Cxx, onde xx é um número
  • Coloquei as ações também em defines e chamei de Axx, onde xx é um número
  • Li todo o arquivo na memória, via alocação dinãmica
  • Varri “o imenso array” de linhas usando o encadeamento de if’s o mais linear possível: com profundidade 1.


#define C1 (  foo == 1 ) /* condição 1: foo deu, certo ? :-) */
#define C2 ( bar != 51 ) /* condição 2: bar não tem 51 ? */
#define A1 bar = close
#define A2 bar = 69

if ( C1 && C2 ) { A1; }

if ( !C1 && !C2 ) { A2; }

Sei que este modo irá criar muitas repetições no código devido a inserção duplicada das macros das condições, que podem vir a ser bem complexas. Mas o objetivo não é otimizar o código num primeiro momento, mas sim fazer com que funcione gerando um método padronizado. A otimização virá depois com a aplicação das tabelas de controle/decisão.

Otimizações mais apuradas – dependentes da linguagem, processador, ambiente, etc. Ex.: funções inline, constantes, matrizes de funções, etc. – poderiam então ser feitas, mas estariam sobre a pré-otimização “lógico-matemática” obtidas com o método sugerido.

O fato é que numa primeira abordagem, normalmente usando a tentativa-e-erro, cometemos erros que acabam se infiltrando permanentemente na origem do código (defective by-design) e isto, nestes casos, assim como na minha opinião, ocorre por negligenciarmos a base primordial da programação: a lógica booleana.

Ao detectar esta condição insólita no seu produto, um programador de bom senso deveria rever o código e reescrevê⁻lo. Também deveria haver a firmeza do conhecimento de que, se não utilizarmos técnicas adequadas nesta nova revisão, fatalmente o problema ressurgiá, muitas vezes com novas faces: numa síndrome da Hidra de Lerna.

Entretanto se o programador já tiver se valido das técnicas de algébra booleana, com certeza a revisão será mais “macia” e permitirá um manejo fácil das linhas de código e das idéias implementadas por elas.

Anúncios

2 comentários sobre “Tomada de decisões

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s