terça-feira, 11 de dezembro de 2012

Complexidade acidental


Complexidade acidental é um dos antipatterns mais ativos do momento, e um dos grandes males atuais dos projetos de software. Vamos ver o que é a complexidade acidental, como surge e o que podemos fazer para evitá-la.





Complexidade acidental é aquela complexidade não essencial, causada pela abordagem escolhida para resolver um problema. Em termos de software, pode ser definida como a complexidade acrescentada ao projeto, que não é essencial para resolver o problema, mas é decorrente da solução adotada. Temos uma boa definição no guia de Antipatterns:
It stems from the complex code resulting from ugly designed interfaces of system routines.
(Surge do código complexo, resultante de interfaces mal projetadas para as rotinas do sistema). 
Em contraponto à complexidade acidental, existe a Complexidade essencial, que é aquela acrescentada especificamente para resolver o problema.

E por que isto acontece? 


Geralmente, as soluções de maior complexidade acidental ferem algumas boas práticas do desenvolvimento ágil, sendo representadas por antipatterns como:

  • YAGNI: You ain't gonna need it - "você não vai precisar disto". É um vício de acrescentar funcionalidade (e complexidade) antes dela se tornar necessária;
  • GoldenHammer: Você está tentando resolver todos os problemas de uma vez;
  • Junkyard coding: Acoplar sistemas e componentes à força.

E acontece por vários motivos, muitas vezes (aparentemente) inocentes.

Certa vez, eu estava ensinando a criar Web Services em Java para um grupo de colegas, quando um deles me perguntou: "mas precisa disso tudo mesmo para resolver esse problema?" E foi então que eu reparei na quantidade de código, configuração e frameworks que eu estava usando... Tudo para dar meia dúzia de bytes de informação. Certamente, existem maneiras mais simples.

Embalado neste caso, eu sugeri mudar a abordagem e criar um Web Service simples, baseado em REST, e em PHP mesmo, já que era só para consultar status. Algumas pessoas vieram com "pedras" na mão, argumentando desde a fragilidade do PHP até a dificuldade futura que o uso de REST implicaria. Ou seja, só motivos que não estavam relacionados com o problema em si. É o contrário do YAGNI!

Vamos ver mais motivos de ocorrência de complexidade acidental nos projetos de software.

Herança maldita



As vezes, a Empresa possui uma "arquitetura tecnológica" que deve ser seguida por todos, independentemente da complexidade do problema. Isto implica em alto custo de desenvolvimento e manutenção, que poderiam ser evitados. Eu chamo esta atitude de "herança maldita", que já contamina até mesmo os mais novos projetos, forçando os desenvolvedores a implementarem soluções altamente complexas, sem nenhum motivo lógico.

Outras vezes, somos obrigados a adotar determinado componente ou framework, só porque a diretoria tem que justificar o gasto. Realmente, é uma situação lamentável, algo que só uma gestão profissional de portfólio de TI poderia resolver.

Planejamento por exceção 



Certa vez, um gerente que eu tive usou este termo para descrever o comportamento de algumas pessoas, que ficam pensando mais nas dificuldades criadas pelas situações de exceção, do que em resolver o problema em si. O planejamento por exceção leva a atrasos e a tendência de "GoldenHammer" (ou "Goldplating" que é folear a ouro).

Nos últimos anos (talvez devido à velhice), passei a ter menos tolerância com essas coisas. Geralmente, quando vejo complexidade acidental em uma atividade, começo a perguntar "por que?" e, quando vejo subjetividade na resposta, eu sugiro não seguir adiante.

Como não conseguem planejar tudo, os "panejadores por exceção" acabam criando situações como a da foto...

Gambiarras



Geralmente ocorrem quando o processo de planejamento e projeto é "ad-hoc", ou seja, tudo feito "just-in-time". Falta de planejamento do projeto, falta de estudo arquitetural, seleção de soluções de forma artesanal e codificação "macarrônica" podem levar a situações inesperadas, normalmente às vésperas da homologação do sistema. Neste caso, começam a surgir as "gambiarras" (http://desciclopedia.ws/wiki/Gambi_Design_Patterns) para fazer o sistema ser aprovado de qualquer maneira.

As Gambiarras são também produto de uma prática condenável, que eu chamo de "Results-driven programming". O nome é legal, não? Parece até coisa boa... Mas não é! Sabe aquele cara ou aquela equipe que "apaga incêndios"? Aqueles que resolvem qualquer problema, o mais rapidamente possível? Pois é... Results-driven programming é focar apenas na entrega (ou seja: no resultado), não importando os meios para conseguir cumprir o prazo.

Soluções "Cabeça de pudim"



Lembram dos "Três patetas" (http://pt.wikipedia.org/wiki/Three_Stooges)? O Moe (o mais bravo) sempre chamava os outros de "cabeça de pudim", quando algo saia errado. Pois é, eu acho que tem muita gente com "cabeça de pudim" desenvolvendo software!

Todo desastre em projeto de software começa com uma solução ruim, mal pensada e decidida sem critérios efetivos. É o que eu chamo de solução "cabeça de pudim". Geralmente, são provocadas pelo antipattern "ReinventingTheWheel" (reinventar a roda), quando o desenvolvedor resolve escrever código para resolver problemas que já foram resolvidos, ao invés de utilizar um componente pronto.

Alguns exemplos (que eu não me canso de citar) são:

  • Parsing de XML por substring. Quando o programador lê e analisa um arquivo XML manualmente. E pode vir com um "plus", que é o uso de textos e números mágicos (para indicar posição e conteúdo). Já existem zilhões de soluções testadas para de-serializar um XML;
  • Sincronização por file system: Quando é necessário processar transações de maneira assíncrona, e ficamos analisando o conteúdo de um diretório, buscando por arquivos de requisições. Esta solução tem uma prima mais nova, que é a "Sincronização por banco de dados". Já existem várias soluções de processamento de mensgens assíncronas, muito mais eficientes e testadas;
  • Mamãe, criei meu primeiro servidor: Quando alguém cria um servidor Socket para processar requisições, ao invés de utilizar outros mecanismos existentes (Web, Web Services etc);
Ao adotar uma solução destas, você aumenta alguns indicadores do seu projeto de software: complexidade, risco, custo e prazo. Vale a pena ver o que já existe, antes de partir para uma solução digna dos "Três patetas". 


Ok, e como evitar isso?



Bem, para começar, você deve sempre trabalhar numericamente. Se você costuma utilizar métricas em seu dia a dia, fica fácil comprovar os problemas nas reuniões de "status". Seja mais objetivo em seu trabalho e procure agir com menos emoção. Você também deve procurar evitar a "síndrome do comprometimento", que é caracterizada por seguir adiante MESMO que esteja errado, por se considerar "comprometido demais" com determinada solução. Não tenha medo de "rasgar" coisas e recomeçar.

Outro conselho que dou é sempre decidir com base em critérios. Eleja os critérios para solução, atribua pesos a eles e compare cada solução de acordo com esta lista. Justifique suas escolhas.

Se você passar a questionar objetivamente, vai notar que as soluções ruins tendem a desaparecer, diminuindo, consequentemente, o índice de complexidade acidental dos projetos. Alguns vão alegar "mas e quando a diretoria interferir no projeto?" Se você trabalha quantitativamente, pode demonstrar de forma clara e objetiva o erro que eles estão cometendo. Nenhum diretor quer se expor quando existem provas objetivas de que a decisão é errada.


Autor: Cleuton Sampaio.