Convenções de codificação

quarta-feira, 23 de dezembro de 2009

Estudos revelam que apenas 20% a 40% do custo de um software está relacionado com a construção. 60% a 80% com manutenção (Livro "Facts and fallacies of software engineering", Robert L. Glass).
Isso não significa dizer que manutenção é o mais importante no ciclo de desenvolvimento de software, mas certamente requer uma atenção especial.
Levando em consideração que também é bastante comum fazer manutenção em código de outra pessoa é importante que a outra pessoa nos deixe um código limpo, claro, fácil de entender e sem armadilhas escondidas.

Assim é muito importante que bons padrões de codificação sejam seguidos. Bons padrões melhoram a legibilidade, deixam claras as intenções do autor do código e expõem erros, facilitando não só para quem faz a manutenção mas para quem escreve o código.

Infelizmente muita gente não dá a devida importância ao tópico porque é bastante comum que outra pessoa dê manutenção no código no final das contas. O grande problema é que alguns de vocês leitores precisam escrever um código bom para que outros de vocês sejam capazes de entender, corrigir e mudar. Além disso, você se pergunta em que condições você está entregando o produto do seu trabalho? Você se pergunta o qual o valor do que você está entregando para o seu empregador?

Agora estou escrevendo este post no Blog. E simplesmente não dá para diferenciar os que estão dando manutenção no código de outra pessoa dos que estão escrevendo código para outra manter. E isso se dá pelo simples fato de que todos nós trocamos os chapéus vez ou outra. Então melhor do que torcer para que o seu próximo código a ser mantido tenha sido bem escrito é escrever o seu código atual bem. Se o seu amigo estiver fazendo o mesmo, você terá um bom código para manter e ele também.

Eu já escrevi anteriormente sobre qualidade de código mas desta vez me refiro a padrões. Padrões como http://java.sun.com/docs/codeconv/ e outros podem ser encontrados pelo Google. O padrão definido na linguagem Java tem sido largamente aceito inclusive por comunidades de outras linguagens de programação por ser bastante geral, simples e manter o código limpo.

Para quem acha que estou exagerando na importância do tópico vale lembrar que o padrão ajuda outros desenvolvedores a entenderem o código por já estarem familiarizados com o padrão. Um padrão ajuda em tarefas de automação como contagem de linhas de código (não estou defendendo esta medida, apenas citando) e revisões por pares frequentemente envolvem leitura de código.


Veja o trecho de código abaixo com uma identação adequada e sem uma identação adequada:

    //RUIM
    if ((condition1 && condition2)
        || (condition3 && condition4)
        ||!(condition5 && condition6)) { //QUEBRA RUIM
        doSomethingAboutIt();            //FÁCIL DE PERDER ESTA LINHA
    }

    //BEM MELHOR
    if ((condition1 && condition2)
            || (condition3 && condition4)
            ||!(condition5 && condition6)) {
        doSomethingAboutIt();
    }

    //TAMBÉM SERVE
    if ((condition1 && condition2) || (condition3 && condition4)
            ||!(condition5 && condition6)) {
        doSomethingAboutIt();
    }


Exemplo retirado do padrão Java, onde se lê "Line wrapping for if statements should generally use the 8-space rule, since conventional (4 space) indentation makes seeing the body difficult."

E o principal é fazer com que o que esteja errado pareça estar errado. Às vezes encontramos um bug num trecho inocente:

    if (condicao)
        fazerAlgo();

E descobrimos que o autor esqueceu de fazerAlgoAntes();. Assim, vamos corrigir:

    if (condicao)
        fazerAlgoAntes();
        fazerAlgo();


E pronto, lá está o fazerAlgo(); fora do if simplesmente porque o desenvolvedor não abriu um bloco no if:

    if (condicao) {
        fazerAlgo();
    }


E não é importante? Quem quer criar um bug novo ao consertar outro? Ou pior, quando você achou que era só "fazerAlgoAntes();" e não percebeu que quebrou o "fazerAlgo();" vai ficar se perguntando porque não funcionou, se não era só isso, como uma coisa fez a outra quebrar, etc...

Viu a diferença que um bloco no if pode fazer?

Não fique parado! Aprenda o padrão da sua linguagem mais utilizada/preferida! :-)

E se você tem algum caso de bug causado por código mal formatado, comente!

Bookmark and Share

Interfaces e comportamento aumentando a coesão e reduzindo o acoplamento

segunda-feira, 7 de dezembro de 2009

Você já encontrou uma pessoa famosa na rua executando tarefas cotidianas? Eu já. Encontrei, há muitos anos, o Francisco Cuoco alugando um filme numa Blockbuster em Ipanema. Também numa ida ao Tanaka San da Lagoa encontrei, jantando, Helena Ranaldi, Malu Mader e outros.

Como todas as outras pessoas, estas também implementam interfaces. E estas implementam interfaces públicas! ;-)

Elas comem como todo mundo, assistem filmes, fazem tudo o que nós fazemos. E atuam. Quando estão atuando, estão no ambiente de trabalho. Lá, diferente da vida pessoal, eles são obrigados a obedecer certas regras, assim como o resto de nós.

Ninguém vai trabalhar de pijamas, apesar de ser compreensível que, no mesmo horário, no conforto do seu lar, alguém pudesse estar de pijamas.

Isto porque no trabalho precisamos apresentar uma outra interface, com outro comportamento. Este comportamento é definido por um conjunto coeso de tarefas que podemos executar. Estas tarefas são executadas de uma determinada forma, seguindo determinados passos que compõem nosso método de executá-las.

Assim é com as classes. Ao definir métodos públicos para uma classe, estamos definindo uma interface para ela. Esta interface exibe um comportamento que poderá ser exigido dela através da execução destes métodos.

Se a sua classe implementa mais de uma interface, ela exibe mais de um comportamento. O mecanismo de interfaces presente em muitas linguagens é geralmente usado para simular uma herança múltipla, muitas vezes ausente, pois as interfaces podem ser usados como tipos quando o comportamento que será exigido de um objeto está inteiramente contido nela (e é o mais recomendável pois deixa o código mais flexível e reutilizável).

No entanto, o significado de implementar uma interface é dizer que um determinado comportamento é atendido e criar uma interface não só mostra isso com mais clareza mas também agrupa os métodos de forma mais coesa e dá uma flexibilidade maior ao sistema permitindo que classes em outra hierarquia também possam implementar o mesmo comportamento.

Este é o ponto de vista interno, do desenho da classe.

Do ponto de vista das classes clientes, ou seja, das classes que irão exigir o comportamento chamando os métodos, utilizar através de uma interface significa não estar preso a uma implementação específica, que pode inclusive ser trocada em tempo de compilação ou de execução, reduzindo drasticamente o acoplamento.

Em tempo de compilação esta flexibilidade permite que os métodos possam ser chamados de classes diferentes e cada classe poderá realizar diferentes ações. Esta flexibilidade é usada em muitos frameworks para isolar a implementação real da funcionalidade sendo disponibilizada, permitindo que os desenvolvedores possam evoluir o framework sem impactar na API externa.

Em tempo de execução esta flexibilidade permite que um método seja chamado de instâncias de diferentes classes, por exemplo, para notificar o acontecimento de um evento. A partir daí, cada instância fará o que foi programada para fazer no acontecimento daquele evento. Assim, uma coleção de instâncias de diferentes classes observadoras pode ser notificada quando um evento observável ocorrer em uma instância de outra.
Este é o padrão de projetos Observer.

Ainda não sei se vou explicar padrões de projeto aqui. Tem muita informação disponível a respeito já, basta googlar. O que acham?


Bom, vão as dicas:


- Depois de identificadas as entidades do sistema, verifique comportamentos em comum que serão exigidos pelas funcionalidades já requisitadas e os defina em termos de interfaces. Isto permitirá a reutilização de funcionalidades já construídas para diferentes hierarquias, reduzindo tempo e custo de desenvolvimento e manutenção.

- Procure identificar funcionalidades que tratam classes de hierarquias diferentes de forma semelhante no seu sistema e verifique se uma interface não poderia eliminar a redundância.

Você cria interfaces no seu sistema?

Bookmark and Share

O CMMI e os processos de software

quarta-feira, 25 de novembro de 2009

O CMMI (Capability Maturity Model Integration) é, como o nome diz, um modelo de capacidade e maturidade. É um modelo de referência para a melhoria dos processos relacionados ao ciclo de desenvolvimento de software em organizações que desempenham esta atividade.

A melhor explicação que já ouvi até agora sobre capacidade e maturidade me foi contada pela professora Ana Regina C. da Rocha, verdadeira autoridade na área. Ela diz que uma criança adquire a capacidade de escrever ainda cedo, mas escreve de uma forma quando aprende, outra quando é adolescente, outra quando é adulta e outra no fim de uma carreira literária. O que muda é a sua maturidade.

Assim também, organizações adquirem a capacidade de fazer software de acordo com determinados processos e a cada ciclo de melhoria, deveria melhorar também a maturidade do que já era feito.

O CMMI organiza os processos de software em 5 níveis. Cada nível introduz novos processos e aumenta a exigência sobre os que já haviam sido introduzidos, obrigando a organização a aumentar sua maturidade.

A cada nível é possível fazer uma avaliação, onde pessoas credenciadas ao Software Engineering Institute, da Carnegie Mellon University atestam que a organização implementa os processos daquele nível de forma suficiente para as exigências do modelo.

Estes processos não foram tirados do bolso, obviamente. Foi muita pesquisa até se chegar na norma internacional ISO/IEC 12207 na qual o CMMI se baseou. Também se baseou na ISO/IEC 15504 para seu processo de avaliação.

Este modelo é considerado de difícil implantação nas pequenas empresas brasileiras, com avaliações caras. Mas temos o MPS.BR, totalmente compatível, com implementação facilitada para pequenas empresas e custos muito mais baixos. Falo dele num próximo post.

Os processos de cada nível são:
Nível 1: Ad hoc - Não há processos e atividades definidas. As pessoas trabalham de forma reativa, sem muito planejamento na maior parte das vezes.

Nível 2: Gerenciado - Processos de gerência, requisitos, medição e análise e outros necessários para um funcionamento controlado.
CM – Gerência de configuração
MA – Medição e análise
PP – Planejamento de projetos
PMC – Acompanhamento de projetos
PPQA – Garantia de qualidade do processo e produto
REQM – Gerência de requisitos
SAM – Gerência de acordos com fornecedores

Nível 3: Definido - Os processos, ferramentas e métodos são definidos a nível organizacional e padronizados em todos os projetos.
PI – Integração do produto
RD – Desenvolvimento de requisitos
TS – Solução técnica
VAL - Validação
VER – Verificação
OPD – Definição do processo organizacional
OPF – Foco no processo organizacional
OT – Treinamento organizacional
IPM – Gerência integrada de projetos
DAR – Análise de decisão e resolução
RSKM – Gerência de riscos

Nível 4: Gerenciado quantitativamente - Os processos são medidos e gerenciados através de ferramentas estatísticas. Considerado como de alta maturidade. Em geral, após um grande esforço de implementação do nível 3, este nível dificilmente é avaliado. Por só ter dois processos, as empresas costumam implementar este junto com o 5 e fazer uma avaliação só.
QPM – Gerência quantitativa de projetos
OPP – Desempenho do processo organizacional

Nível 5: Em otimização - Melhoria contínua implementada com uso do ferramental estatístico e dados coletados a partir de processos estabilizados graças a maturidade obtida pela implantação dos processos dos níveis anteriores.
CAR - Análise de causa e resolução
OID - Inovação organizacional e implantação

O CMMI também permite que uma empresa escolha quais processos quer avaliar por achar mais relevantes na sua situação e quais níveis de cada processo serão avaliados (como expliquei, os processos do nível 2 devem ser mais maduros no 3 do que eram no 2). Este tipo de avaliação é chamada de "contínua" em oposição à avaliação "em estágios". Como a avaliação contínua não dá um nível que a empresa possa estampar no seu material de divulgação, acaba sendo preterida à outra.

No futuro, pretendo falar a respeito de cada um destes processos ou até pedir a especialistas na área que escrevam algo para eu postar aqui. Tenho certeza que será bastante interessante.

Só a título de curiosidade, o MPS.Br facilita as coisas tendo mais níveis com menos processos em cada um, trazendo um retorno de investimento mais cedo. Os níveis G e F do MPS.Br correspondem ao 2 do CMMI. Os níveis C, D e E ao nível 3. O nível B ao 4 e o A ao 5. Outra facilidade também é a presença de avaliadores e implementadores em quase todos os estados e nas principais cidades brasileiras, tornando a implementação e a avaliação mais baratas, além de fontes de subsídio para que as organizações implementem suas melhorias. Mas o MPS.Br fica pra outro post. Merece um só para ele.

Links do 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

Abraço a todos!

Bookmark and Share

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

Conformidade de tipo

terça-feira, 22 de setembro de 2009

Eu já falei sobre isso em algumas aulas mas sempre é válido ressaltar: herança é um acoplamento forte.

Acoplamentos fortes são indesejáveis, pois criam dependências pelas quais o impacto de uma mudança se propaga.
Uma herança entre duas classes traz várias implicações. Uma delas é dizer que a subclasse se comporta como a superclasse (Princípio de Substituição de Liskov*).

Assim, um pequeno polimorfismo de classe pode causar grandes transtornos se este relacionamento não for bem observado. Vou falar um pouco de covariância e contravariância.

Imaginem uma Superclasse e uma Subclasse, ambas com um método m declarado, sendo redefinido na subclasse. Este método tem um retorno e uma lista de parâmetros.

A covariância é a determinação de que o retorno da subclasse deve ser do mesmo tipo do retorno da superclasse ou de um tipo mais específico (subtipo) que este.

Então suponha a classe Object e a classe String herdando de Object.

O método m na Superclasse pode retornar Object. O método m redefinido na Subclasse pode retornar Object ou String (ou qualquer outro subtipo de Object).

Isto porque um código como

Object objeto = instancia.m();

deveria funcionar com

Superclasse instancia = new Superclasse();
ou
Superclasse instancia = new Subclasse();

Reparem que o código cliente (que usa as classes) assume que os retornos serão pelo menos do tipo declarado na Superclasse (ou mais específico) e, mais ainda, a variação deste retorno deve ser igual ou menor. Por exemplo, fosse o retorno um inteiro entre 0 e 10, a subclasse poderia retornar quaisquer números entre 0 e 10 (não necessariamente todos) mas nunca um número fora destes limites, pois o código cliente não pode estar preparado para lidar com situações que não estão previstas para a execução daquele método através de uma referência para a Superclasse.

Notem que para os objetos retornados, o ideal é que o espaço-estado* também seja igual ou menor.

Assim, conforme o tipo vai ficando mais específico (descendo na hierarquia), o retorno também vai ficando mais específico, configurando-se a covariância.

A contravariância é o mesmo conceito aplicado aos parâmetros. Ela é "contra" pois conforme se desce na hierarquia (ficando mais específico), os parâmetros vão aumentando os limites.

Se passo para o método m na superclasse um valor entre 0 e 5 e ele funciona, na subclasse, ele deve funcionar, no mínimo, com valores entre 0 e 5. Nenhum problema se ele funcionar também com 314.1592. O problema é se ele só funcionar com valores entre 0 e 3 ou não funcionar com 4 ou qualquer outra restrição. Devemos lembrar que a declaração
Superclasse instancia = new Subclasse();
é sempre válida e quem determina se uma classe funciona como deveria é o código cliente (novamente Princípio de Substituição de Liskov).

Vocês se preocupam com o funcionamento correto das heranças nas aplicações que fazem?

Abraço a todos!

*Prometo que no futuro falo um pouco do Princípio de Substituição de Liskov e de espaço-estado.

Bookmark and Share

Você comenta seu código?

segunda-feira, 7 de setembro de 2009

Para quem não sabe, sou monitor honorário da disciplina "Computação II" no Departamento de Ciência da Computação da UFRJ (http://www.dcc.ufrj.br/~comp2/professor.html).
Como monitor honorário eu ajudo os outros monitores nas tarefas que me agradam. ;-) Isso significa dar aulas de apoio e tirar dúvidas, principalmente.
Na última semana um aluno perguntou sobre a importância de comentar o código dos exercícios a serem corrigidos. Adaptei a resposta para a pergunta "É importante comentar?".
É MUITO importante que seu código seja compreensível, não só por você mesmo (agora ou no futuro), por pessoas que trabalham com você ou trabalharão num projeto em que você desenvolveu um dia.
Como regra, eu adoto a política de tentar fazer um código tão claro que dispense comentários. Só quando isto não é possível por uma idiossincrasia da vida que eu comento e tento ser o mais explicativo possível.

“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.” Sir Charles Antony Richard Hoare

Reparem, por exemplo, neste trecho fictício:
if ((e1.comparaMaior(e2) || e1.vazio()) && (e2 != null)) {
    fazerAlgo();
}

em contraste com:
if (elemento1AtendeRestricaoComparandoElemento2(elemento1, elemento2)) {
    fazerAlgo();
}

boolean elemento1AtendeRestricaoComparandoElemento2(Elemento elemento1, Elemento elemento2) {
    return (elemento1.comparaMaior(elemento2) || elemento1.vazio()) && (elemento2 != null);
}


Notem que fica claro o que está sendo testado no if e fica clara também a intenção daquela comparação enorme graças ao nome método implementado em seguida. Não é necessário o comentário. Assim, evita-se, entre outras coisas, que o código seja atualizado, e o comentário esquecido, ficando desatualizado e informando uma condição diferente da que acontece, causando muitos transtornos.
Além disso, se a intenção é entender a finalidade do código, muitas vezes entender a comparação é desncessário.

Usar nomes de variáveis que signifiquem alguma coisa também é outra boa prática. Todas as boas IDEs completam os nomes quando digitamos os primeiros caracteres e pressionamos ctrl+space.
Notem que não poupei letras no nome do método. O importante é deixar claro e organizado.

Vale notar também que este código é agora mais testável, já que é possível testar apenas a comparação. ;-)

Uma situação válida para a utilização de comentários é quando entendemos algum código um pouco complicado, que demanda algum raciocínio de como o código funciona para entendermos seu objetivo. Neste caso, vale comentar o objetivo se não for possível (por restrições quaisquer) refatorá-lo para torná-lo adequado ao entendimento.

O Javadoc também é bem interessante, mas nele deve constar apenas "o que" o método faz (objetivo) e não "como" o método faz (lógica). Assim, a documentação se torna suscinta, simples e direta, atendendo aos objetivos de quem a lê. Quem quiser entender como irá ler o código e o deve encontrar devidamente organizado.

E vocês? O que comentam? Como comentam?

Abraço a todos!

Bookmark and Share

Um blog!

Então galera...
Muita gente tem me perguntado porque eu não tenho um blog sobre coisas relacionadas a Java ou orientação a objetos ou desenvolvimento de software em geral. A resposta é simples. Porque eu não tenho tempo pra ficar postando, atualizando. E ainda acho que isso é verdade.
De qualquer forma, vou fazer uma tentativa.
A princípio, este blog não é sobre Java. É sobre desenvolvimento de software em geral. Sendo de um assunto mais amplo, pode ser que eu tenha mais o que dizer. ;-) Também aumentam as chances de um assunto interessante aparecer por acaso e me animar para postar.
Enfim, pretendo abordar assuntos relacionados a qualidade de software em geral, de processos e metodologias a questões de design, boas práticas, padrões, etc.
É claro que como trabalho com Java, vez ou outra haverá aqui dicas de aplicações, componentes, frameworks e trechos de código interessantes. E os exemplos, em geral, provavelmente serão em Java quando envolverem código.

Vamos ver se esta empreitada segue em frente ou desisto depois de meia dúzia de posts.

Abraço a todos.

Bookmark and Share

 
addthis_config = { data_ga_tracker: pageTracker }