Otimização e desempenho

quinta-feira, 29 de outubro de 2009

Como eu já disse anteriormente, sou monitor da disciplina "Computação 2" (Orientação a Objetos com Java) na graduação em Ciência da Computação da UFRJ.

Uma pergunta bastante recorrente ao longo dos períodos tem sido "Como é mais rápido?" geralmente acompanhada de "O que consome mais memória?".

Estas perguntas podem ser facilmente respondidas em Java com System.nanoTime() e System.currentTimeMillis() para a velocidade e ferramentas de profiling para a memória e outras informações. Embora isto responda a pergunta, ataca um sintoma e não a raiz do problema:

"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil." - Donald Knuth

O mal ("evil") a que ele se refere é consequência de algo bem explicado por outro grande autor:

"There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult." - C. A. R. Hoare

Quem já percebeu a relação entre estas duas frases, já sabe onde eu vou chegar. A questão é que muitos tendem a se preocupar primariamente com otimização e deixam a legibilidade do código em segundo plano.
Ao fazer isso, o código pode ficar otimizado onde não precisa, prejudicando a legibilidade sem trazer ganho real.

Em via de regra, seu foco deve ser fazer um código legível. O máximo possível. SE vc tiver problema de desempenho (o que você só sabe depois que rodar com a carga esperada de usuários e objetos criados simultaneamente) é que você deve alterar o código de forma a ganhar desempenho prejudicando a legibilidade o mínimo possível, até atingir o desempenho desejado.


“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”-Martin Fowler

Já falei de legibilidade aqui antes: "Você comenta seu código?"

A grande questão é que você não trabalha sozinho. Cedo ou tarde alguém vai por a mão no seu código, assim como você vai mudar algo que alguém fez. E ainda que você esteja começando a desenvolver agora e esteja só fazendo pequenos exercícios, é de cedo que se deve treinar bons hábitos.
É importante que você deixe tudo muito bem claro para o seu colega, assim como ele deve deixar tudo claro para você. É mais um fator que pode contribuir para que você tenha no seu currículo um projeto de sucesso, um reconhecimento no ambiente de trabalho e consequentemente uma boa carreira profissional.
Mesmo que seja um projeto seu, particular, você mesmo pode querer mudá-lo alguns meses depois de começado.
Clareza é fundamental e deve vir em primeiro lugar, principalmente porque quando se escreve código, não se sabe, a priori, o que será suficientemente rápido ou precisará de otimização.

Isto só se descobre quando a funcionalidade foi implementada, para que possa passar por teste de carga (também conhecido como teste de estresse ou de performance).

Testes de carga são aqueles em que o sistema é forçado a executar em condições acima do projetado, para ver como se comporta. Por exemplo, se a sua previsão é para 50 usuários simultâneos, teste com 200, para ver como o sistema reagiria num caso de pico de utilização.

Estes testes são feitos geralmente através de ferramentas automatizadas que simulam execuções por usuários virtuais extremamente leves (para que uma máquina sozinha possa simular muitos). Uma ferramenta para Java free e muito popular é o JMeter e há outras para outras linguagens, inclusive algumas independentes de linguagem, já que basta simular um "agente" (termo que usam para um usuário ou sistema externo).

Estes testes, associados a ferramentas de profiling já mencionadas anteriormente fornecem informações sobre quais são os métodos mais lentos, que consomem mais memória, que executam maior quantidade de vezes, etc.

É só a partir destas informações que se pode detectar onde as otimizações devem ser feitas CASO precisem ser feitas. E mesmo assim, o código deve ser modificado para a otimização que menos prejudica a legibilidade e a portabilidade. Depois de um novo teste é que se decide se foi suficiente ou se mais alguma ação deve ser tomada.

Um ótimo exemplo de otimização sem perda de legibilidade está no Blog C++ do Zimbrão. Este é sobre Java e este sobre C++, lembrando que otimizações que realmente funcionam variam de linguagem para linguagem e de compilador para compilador em uma mesma linguagem. O post de otimizações em C++ é muito elucidativo a este respeito.

Além disso, deve-se lembrar neste momento que há diversos mecanismos para melhorar a performance como clusters de aplicações (felizmente para quem trabalha com JEE, isto vem de graça), configurações de datasource, pool de conexões com banco, cache de banco que não mudam a aplicação em si, caso tenha sido desenvolvida corretamente, isolada de acoplamentos com outros sistemas, como o sistema gerenciador de banco de dados ou o servidor onde será executada.

Vou só lembrar ainda que é importante também seguir as convenções de codificação da sua organização. Mas este é assunto para outro post. ;-)

P.S.: Pra quem gostou dos quotes, aqui tem mais: http://en.wikipedia.org/wiki/Program_optimization#Quotes

Abraço a todos!

Bookmark and Share

Exceções são como são

sábado, 10 de outubro de 2009

O mecanismo de exceções comum em linguagens OO modernas como Java, C# e outras é talvez um dos mais incompreendidos.
Tamanho é o incômodo que traz aos desenvolvedores que em C# nenhuma exceção precisa obrigatoriamente ser tratada e frameworks são escritos para lidar com exceções de forma mais "transparente" possível.
Por transparente entende-se muitas vezes "não tratá-las" ou jogá-las a um limbo onde todas são "tratadas" da mesma forma (que geralmente é o mesmo que não tratar).
Mas se o desenvolvedor já ia ignorá-la de qualquer forma (deixando o bloco de captura em branco ou simplesmente imprimindo o trace na console onde nunca será lido), que diferença faz?

Exceções são como são para que possamos tratá-las onde soubermos como fazê-lo. É por isso que nos é dada a opção de relançar ou tratar.

É comum, entre as pessoas que estão aprendendo sobre este mecanismo a dúvida sobre o que fazer em que situação ou "onde tratar".

Tipicamente em um sistema OO, o usuário realiza uma ação que dispara um método buttonSaveAction() que chama um saveUserData(), depois um writeFormattedContentFile() e assim por diante, até que chegamos naquele método saveFile() que grava o conteúdo que o usuário forneceu a um determinado arquivo que o usuário indicou.

E aí entra o problema: o arquivo não existe no local indicado.

Há então algumas opções:
  1. Tratamento em branco / imprimir um erro na console:
  2. Embora seja muito fácil e muito comum, o usuário nunca saberá o que aconteceu. Claro, um erro na console é bom, se estamos olhando para a console. No caso de uma aplicação com interface gráfica ou rodando em um servidor, este erro passará despercebido. Em uma aplicação de linha de comando os desenvolvedores saberão o que aconteceu, mas o usuário nunca saberá o que o atingiu. Bastante desagradável.
  3. Registrar o ocorrido:
    Registre se precisar corrigi-lo posteriormente, mas o faça em um arquivo ao menos, para que as informações fiquem a salvo. E com informação que você julgue suficiente para corrigir. Infelizmente esta opção não ajuda nosso usuário, mas pelo menos ele não recebeu um golpe certeiro de um desenvolvedor descuidado, como um stack trace. Stack Trace real (4.40Mb, quase 46 MIL linhas - 45750 suprimidas - quem quiser ver tudo, me mande um e-mail):



  4. Avisar o usuário:
    Avisar o usuário parece ser uma boa idéia nesta situação. Se o arquivo não está no local indicado, pode ser que ele tenha informado o caminho errado, por exemplo. Uma forma de resolver o problema seria pedir o caminho novamente, informando o erro e ver se ele corrige. Claro, dependendo da situação, pode ser que sua aplicação resolva criar o arquivo ou salve em um arquivo padrão ou simplesmente não realize o salvamento. De qualquer forma, é sempre bom informar o usuário de que o que ele pediu não foi feito ou foi feito de forma diferente.
    Uma vez tomada a decisão de informar o usuário, que método você espera que fique responsável por isso?
    writeBytes(), o método onde o erro se originou é um método genérico demais. Pode ser usado para qualquer situação em qualquer aplicação. Resolver o que fazer com um erro irá acoplá-lo a uma regra daquela aplicação especificamente, impedindo seu reuso.
    writeFormattedContentFile(), o método que chamou writeBytes() não parece ser responsável por lidar com o usuário, assim como saveUserData(). Ainda que fossem, estando fora da camada de apresentação, como fazê-lo? É uma aplicação desktop em linha de comando, com interface gráfica ou Web? E mesmo que fosse, este era o caso de uso em que o usuário opta por um arquivo existente necessariamente ou é o caso de uso em que o arquivo pode ser criado caso não exista? Em outras palavras, qual era a ação presente no método buttonSaveAction()?
    Como se pode notar, conforme os métodos se aproximam do usuário, se tornam mais específicos da aplicação, com mais informação sobre o contexto de sua utilização. Conforme se afastam, se tornam mais genéricos e mais reutilizáveis.

Então, finalmente respondendo a pergunta:
As informações técnicas sobre o erro devem ser escondidas do usuário comum, mas salvas para que possam ser utilizadas para corrigir. O tratamento pode envolver tentar de outra forma, tentar novamente ou perguntar ao usuário o que fazer, mas de qualquer forma, o usuário deve ser avisado de que seu pedido não foi realizado da forma que se desejava. E sempre que a solução envolver o usuário (avisar do erro e/ou fornecer alternativas), o tratamento deve ser feito no método que tiver informação de contexto suficiente para não só saber quais são as alternativas viáveis, mas como avisar ao usuário.

Não raro, em aplicações com a camada de negócio e apresentação separadas, uma exceção técnica (como ArquivoNaoEncontrado) pode ser capturada na camada de negócio unicamente para que seja lançada outra em seu lugar, determinando apenas o curso de ação (como PerguntarCaminhoArquivo ou AvisarErroArquivoNaoEncontrado). Na camada de apresentação, então, determina-se como avisar ao usuário, de acordo com a ação decidida pelo lançamento da exceção específica na camada de negócio.

Se for bem utilizado, o mecanismo de exceções é seu amigo.

Abraço a todos!

Bookmark and Share

Agile, "tradicional" ou ambos?

terça-feira, 6 de outubro de 2009

Muitos não sabem mas antes de enveredar pelo MPS.BR*/CMMI* eu já estudava muito XP*. E antes do XP, muito RUP*. Agora, acabo de regressar de São Paulo onde fiz o curso oficial Certified ScrumMaster* com o Boris Gloger.

O curso foi ótimo para aprofundar meu aprendizado no Scrum. A experiência do Boris com certeza foi um fator importante neste processo. Acabei acumulando ao conhecimento que tive durante o treinamento de coach de XP com o Vinícius Teles e relembrando muitas práticas Ágeis que há muito haviam ficado esquecidas.

O curso também foi muito bom pela contribuição da Ana Rouiller com relação a integração do MPS.BR/CMMI e Scrum. É uma integração já com bastantes artigos publicados em congressos nacionais e internacionais e até onde eu pude perceber, bastante benéfica para as empresas. Espero que as comunidades de ambos os "flancos" ergam bandeiras brancas e percebam que há o que se aprender com a perspectiva do outro.
Esta integração, que eu sempre soube ser não só possível mas também benéfica, é um dos assuntos que mais desperta meu interesse desde que comecei a estudar Engenharia de Software.

*Para quem não está familiarizado com a sopa de letrinhas do primeiro parágrafo, aqui vão alguns links.
Estes são exemplos típicos de assuntos que pretendo abordar neste blog. Na verdade, cada um destes temas merece um blog, mas pretendo abordar todos estes assuntos aqui mesmo em futuras postagens.

Por enquanto, ficam as referências:

MPS.Br:
http://www.softex.br/mpsbr (Oficial)
http://pt.wikipedia.org/wiki/Mps.br

CMMI:
http://www.blogcmmi.com.br/
http://pt.wikipedia.org/wiki/Cmmi
http://www.sei.cmu.edu/cmmi (Oficial)
http://en.wikipedia.org/wiki/CMMI

Scrum:
http://en.wikipedia.org/wiki/Scrum_(development)
http://www.scrumalliance.org/ (Oficial)
http://pt.wikipedia.org/wiki/Scrum

eXtreme Programming:
http://www.improveit.com.br/xp
http://pt.wikipedia.org/wiki/Programação_Extrema
http://www.extremeprogramming.org/
http://en.wikipedia.org/wiki/Extreme_Programming

Rational Unified Process:
http://pt.wikipedia.org/wiki/Rup
http://www-306.ibm.com/software/awdtools/rup/?S_TACT=105AGY59&S_CMP=WIKI&ca=dtl-08rupsite (Oficial)
http://en.wikipedia.org/wiki/Rup

Abraço a todos!

Bookmark and Share

 
addthis_config = { data_ga_tracker: pageTracker }