Hoje em dia é comum ouvir dizer que a velocidade com que a aplicação executa as suas atividades – chamada empiricamente performance – não deve ser uma preocupação para a versão 1.0 de um software. Existe até um ditado “Primeiro faz funcionar, depois faz funcionar bem e depois faz funcionar depressa”.
Em pleno século XXI quando as práticas de teste preventivo se têm mostrado mais eficazes, não faz sentido que o “funcionar bem” seja um preocupação secundária e o mesmo pode ser defendido para a performance.
Acho que ninguém terá tido a experiência de ver um utilizador pedir para ter um software mais lento. Todos querem o mais rápido que o seu dinheiro pode comprar.
Isto significa que performance não é algo que se ajusta depois. É um requisitos canônico, sempre presente, que deve ser pensado em todos os momentos e pontos da aplicação ao lado da segurança, por exemplo.
Em paralelo, está se generalizando a ideia de que porque o desenvolvimento de software é um processo iterativo as primeira iterações podem ser completamente feitas à toa sem ter que pensar ou decidir à priori coisas relevantes como a arquitetura ou o design.
Isto nada mais é que uma pequena confusão entre processo iterativo e processo evolutivo.
A construção do software é feita à conta de processos iterativos. Só depois dele estar construído é que pode evoluir. O processo ser iterativo significa que defeitos imprevistos são ser corrigidos e requisitos imprevistos são ser equacionados à media que vão aparecendo, não singifica que ser parte de um monte de código , design e arquitetura meia-boca e se vai corrigindo todo um conjunto de problemas que foram incluídos no inicio.
Não considerar a performance um requisito canônico e pensar que tudo pode ser solucionado depois levam ao descaso pela preocupação em prevenir problemas de performance ou prevenir refactoring desnecessário. Não é porque você pode emendar algo, que você vai começar por sempre fazer errado.
É difícil definir o que “performance” significa, por significar simultaneamente várias coisas, mas normalmente sempre está associada à ausência de momentos de espera. Vários fatores externos podem afetar a performance ,causando necessidade de espera, tais como o uso da banda em uma conexão remota. Mas muitos dos fatores que influenciam a performance são internos.
Poderíamos dividir os fatores internos que influenciam a performance em três categorias: arquiteturais, de design e de codificação. Os fatores arquiteturais são normalmente chamados fatores de escalabilidade porque a este nível a velocidade só pode ser aumentada utilizando mais máquinas ou adicionar mais recursos na mesma máquina. Contudo, isso só pode ser feito se a aplicação está preparada para ser distribuída por várias máquinas. Este tipo de estrutura tem que ser avaliada e decidida antes de começar a codificar o software, pois esta distribuição se refletirá diretamente nas responsabilidades de cada nodo que, por sua vez, refletiram nas responsabilidades das classes e código escrito para cada nodo. Mudar a arquitetura tem um impacto profundo no sistema que pode ter um custo ou esforço proibitivo.
Fatores de design são também importantes. Se a aplicação trabalha com certo tipo de domínios ou tem certos tipos de necessidade (processamento em massa, por exemplo) então isso tem que ser equacionado na atribuição de responsabilidades de cada classe durante o design. Alterar responsabilidades de design é mais fácil que mudar as de arquitetura mas pode incluir várias rachaduras na arquitetura vigente. Por isso é bom que ambas as coisas sejam equacionadas juntas para resolverem todos os problemas já previstos antes de começar a codificar. É isto que se chama “solução por design”; o problema não vai aparecer porque o sistema foi desenhado para que ele nunca existisse.
Fatores de codificação são os mais difíceis de encontrar. Muitas vezes correspondem ao uso incorreto de algoritmos, ao uso de objetos caseiros em vez da API padrão, mas principalmente advém da realidade que o código é maior que a soma das suas partes e portanto difícil de reconhecer à partida onde estarão os problemas.
Fatores de código realmente escapam pelos dedos devido ao uso de API de terceiros e outros tipos de código que não dominamos. Entenda-se que estes fatores estão ocultos à primeira vista e por isso eles afetam a performance. Se tivessem sido identificados antes, não mais estariam lá para atrapalhar pois teriam sido eliminados por um design mais cuidadoso.
É preciso deixar bem claro que decisões como utilizar um mapa em vez de uma iteração sobre uma lista de objetos são decisões de design e válidas à partida. Não constituem um refinamento de performance. Constituem, isso sim, a opção correta para resolver o problema. A utilização do padrão Produtor-Consumidor é mandatória nos cenários onde foi identificado que o uso do padrão aumenta a performance. E tanto que é assim, que existe um mundo completo de API e produtos relacionado a este padrão. A API padrão é a Java Messaging Service (JMS) que, embora o nome sugira que se trata de uma tecnologia para envio de mensagens, nada mais é que a especificação robusta do padrão Produtor-Consumidor.
Mesmo aplicando todas as soluções conhecidas pelo estado-da-arte sempre é possível que alguma falha na integração de API ou nas decisões de design e arquitetura cause alguma entropia que acaba se manifestando em baixa performance. Para resolver estes fatores de código não resta muito mais que utilizar uma ferramenta de profiling e verificar onde estão os problemas.
Só que ferramentas de profilling apenas de lhe dirão quais métodos de quais classes estão demorando mais e quais objetos estão sendo construídos e destruídos mais, e quais estão ocupando a memória por mais tempo. Ferramentas de profiling não lhe dirão que o seu design é falho ou a sua arquitetura sustentada com palitos. A analise para um bom design e arquitetura é abstrata e mental, nenhum ferramenta poderá fazer isso por você.
O exemplo clássico de como não proceder é o clássico caso processamento em massa (em batch). Quem nunca escreveu um na mão que dê um passo em frente…
A ideia é processar um conjunto de dados o mais absurdamente volumoso possível no tempo mais curto possível. Sabemos que um simples for não vai dar conta e já pensamos em paralelizar o processamento. Isso é o que é esperado de qualquer estudante do segundo ano. Mas como implementar isso? Se não tivermos cuidado procurando a arquitetura e design corretos para este problema, acabaremos tendo um código caseiro de processamento em batch que – como todos sabem – nunca é rápido de primeira.
A primeira coisa que temos que fazer não é sair codificando e esperar que várias iterações resolvam o problema… A verdadeira solução advém de procurar se já foi identificada uma forma padronizada de resolver o problemas.
Estamos com sorte. Essa forma já foi identificada e se chama Padrão Produtor-Consumidor. Mas um padrão tem muitas formas de ser implementado. Vamos começar implementando a nossa forma? Não. Calma.
Primeiro vamos desenhar os intervenientes neste processo de forma abstrata (usando definição de interfaces, contratos e atribuindo responsabilidades) conforme o padrão especifica. Após isso pensamos em implementação. Como o nosso design é flexível várias implementações são possíveis. A primeira a tentar e mais interessante é aquela que já exista. Mais uma vez estamos com sorte. A JMS serve que nem uma luva.
Agora basta colar a JMS com as nossas interfaces (padrão Bridge) e voilá, temos um modulo de processamento em batch sem quase escrever nenhum código e logo na primeira iteração. Mas se não gostarmos de JMS ou não o podermos utilizar por alguma razão, podemos olhar outras opções como o Spring Batch, por exemplo. O ponto é que se escolher bem o seu design e arquitetura poderá facilmente usar o que já existe e até trocar depois para outra solução se a primeira não foi satisfatória. Essa flexibilidade não só o ajuda a saber mais sobre o problema mais o ajuda a se esforçar menos.
Otimização prematura de performance de fatores de código é impossível. Ela requer que o sistema esteja ponto e que seja possível executá-lo em vários cenários de forma a analisar o que acontece na memória e no processamento da JVM. Contudo a escolha certa de padrões de design e arquitetura não é evolutiva e deve ser feita à priori, porque escolher o design ou arquiteturas erradas levarão a pesados esforços e custos de remodelação do sistema como um todo. Especialmente, ela deve ser baseada em padrões que já tenham implementações e que já se provaram competentes. Para isso é vital que sejam conhecidas com antecedência as necessidades e expectativas de performance do cliente.
Conheça as expectativas do cliente quando a performance. Não aceite “isso não é importante agora”. Desenhe para performance (e segurança e qualquer outro requisito não-funcional) com a mesma astucia que desenha para requisitos de negócio. Desenha a aplicação para ter fácil manutenção e não para que funcione.
Se a aplicação tem fácil manutenção e não funciona é trivial corrigi-la. Se ela funciona, mas é difícil de manter, é inerente uma falha inesperada a qualquer momento.
Otimizar significa “tornar ótimo”. Tornar ótimo é realmente um detalhe, mas tornar fácil agora tornar ótimo depois é o desafio. Vai encarar? Ou vai continuar pensando que iterações são evoluções?
Boa Tarde,
Sergioooo !!!
Quero dizer que foi um excelente texto, mas gostaria de compartilhar alguma de umas duvidas.
A performance ela é reflexida ao contexto de tecnologias que vão abraçar determinado cenário, falando em J2EE/JEE o que seria uma visão para a Oracle na otica de seus produtos, a mesma é dominante e lider Mundial como vendor.O que ao meu ver atinge o mundo corporativo é a escolha de fazer algo barato mas com inteligencia ao ROI e sobre os recursos Open Source ou de licença planejada que façam elas terem econominas futuras à se encaixarem como partners de grandes players,e que possam se tornarem produtivos e desfrutar de uma infra-tecnologica aceitável, porque usar JBoss se Websphere é mais scalável e estável ou derrepente pra usar Webshpere se posso usar Bea Weblogic OSB para gerenciar melhor questões de SOA para ESB, e assim vai.
Abraçossss….