O principio da responsabilidade única é o primeiro do acrônimo S.O.L.I.D., um conjunto de princípios que auxiliam na criação de um software de qualidade. Single responsability é o princípio mais fácil de entender, mas o mais difícil de aplicar.
Mostrando postagens com marcador princípios. Mostrar todas as postagens
Mostrando postagens com marcador princípios. Mostrar todas as postagens
quarta-feira, 4 de abril de 2012
sábado, 24 de julho de 2010
Reflexões sobre a Qualidade de Software
Há alguns meses, surgiu uma questão a respeito da qualidade. Enquanto pensava na afirmação, "Com certeza a qualidade interessa a todos, sempre", e fazendo um paralelo com o nosso dia a dia, percebi que esta frase não é verdadeira. Nem sempre queremos qualidade, ou estamos com possibilidades de pagar por ela.
Marcadores:
princípios,
qualidade de software
quarta-feira, 29 de julho de 2009
Princípios de projeto - Parte IX - Relacionamento entre classes
Aproveitando o meu post nesta thread do JavaFree.org, vou desenvolver aqui um pouco mais o relacionamento entre classes.
As decisões quanto a esse aspecto, podem levar a sua aplicação a ter um bom ou um mau design. Alguns conceitos, como os que abordam a questão da coesão, são decisivos para uma boa escolha para o relacionamento entre classes.
As decisões quanto a esse aspecto, podem levar a sua aplicação a ter um bom ou um mau design. Alguns conceitos, como os que abordam a questão da coesão, são decisivos para uma boa escolha para o relacionamento entre classes.
Marcadores:
acoplamento,
coesão,
design,
modelagem,
princípios,
relacionamento
sexta-feira, 17 de julho de 2009
Princípios de projeto - Parte VIII - Usabilidade
O blog Portal do Arquiteto publicou recentemente, uma série de cinco artigos que dizem respeito a problemas de usabilidade.
Segundo a Wikipedia:
E, no nosso contexto, nossos sistemas são as ferramentas. Assim, trazendo para o nossa área:
Nosso objetivo maior, quando estamos participando de um processo de desenvolvimento, é facilitar a vida do usuário. Precisamos que nosso sistema disponha de uma boa usabilidade, do ponto de vista do usuário, para que possamos cumprir esse objetivo.
O termo usabilidade se fundiu com o termo navegabilidade, mais ainda com o advento da WEB.
Seu usuário precisa sentir-se confortável durante os acessos ao seu sistema. Se o acesso aos pontos de interesse for dificultado, o sistema perde a credibilidade.
Quando estamos desenvolvendo sistemas específicos para uma empresa ou para um grupo de usuários que nos contratou, nossa busca pela excelência na usabilidade fica facilitada. Entretanto, quando nosso foco é geral, como uma loja online, por exemplo, o trabalho fica um pouco mais difícil. Nesses casos, a busca por uma boa usabilidade depende de uma eficiente pesquisa de opinião, pesquisa essa que o usuário nem sempre está disposto a responder.
Na maioria dos casos, se o usuário não se sente confortável em seu site, ele simplesmente o abandona e procura outro que o atenda perfeitamente em suas necessidades. Assim, essa pesquisa de opinião deve ser realizada, também, por pessoas de nosso conhecimento, capacitadas a testar nosso trabalho.
Em grandes empresas, normalmente, existe uma equipe de testes específica para tratar de casos como a usabilidade, baseando-se nos requisitos dos clientes.
Um princípio que aprendi nesses meus anos de experiência como desenvolvedor é: quem desenvolve não está apto a testar a usabilidade.
Por que?
Tenhamos sempre em mente a usabilidade, pois se um sistema é complicado de ser usado pelo usuário, para que existir sistema?
Parte I - Introdução
Parte II - Correção
Parte III - Design por contrato
Parte IV - Flexibilidade
Parte V - A Lei de Demeter
Parte VI - Eficiência
Parte VII - Coesão
Parte IX - Relacionamento entre classes
Segundo a Wikipedia:
Usabilidade é um termo usado para definir a facilidade com que as pessoas podem empregar uma ferramenta ou objeto a fim de realizar uma tarefa específica e importante.
E, no nosso contexto, nossos sistemas são as ferramentas. Assim, trazendo para o nossa área:
A usabilidade está diretamente ligada ao diálogo na interface e a capacidade do software em permitir que o usuário alcance suas metas de interação com o sistema. Ser de fácil aprendizagem, permitir uma utilização eficiente e apresentar poucos erros, são os aspectos fundamentais para a percepção da boa usabilidade por parte do usuário. Mas a usabilidade pode ainda estar relacionada com a facilidade de ser memorizada e ao nível de satisfação do usuário.
Nosso objetivo maior, quando estamos participando de um processo de desenvolvimento, é facilitar a vida do usuário. Precisamos que nosso sistema disponha de uma boa usabilidade, do ponto de vista do usuário, para que possamos cumprir esse objetivo.
O termo usabilidade se fundiu com o termo navegabilidade, mais ainda com o advento da WEB.
Seu usuário precisa sentir-se confortável durante os acessos ao seu sistema. Se o acesso aos pontos de interesse for dificultado, o sistema perde a credibilidade.
Quando estamos desenvolvendo sistemas específicos para uma empresa ou para um grupo de usuários que nos contratou, nossa busca pela excelência na usabilidade fica facilitada. Entretanto, quando nosso foco é geral, como uma loja online, por exemplo, o trabalho fica um pouco mais difícil. Nesses casos, a busca por uma boa usabilidade depende de uma eficiente pesquisa de opinião, pesquisa essa que o usuário nem sempre está disposto a responder.
Na maioria dos casos, se o usuário não se sente confortável em seu site, ele simplesmente o abandona e procura outro que o atenda perfeitamente em suas necessidades. Assim, essa pesquisa de opinião deve ser realizada, também, por pessoas de nosso conhecimento, capacitadas a testar nosso trabalho.
Em grandes empresas, normalmente, existe uma equipe de testes específica para tratar de casos como a usabilidade, baseando-se nos requisitos dos clientes.
Um princípio que aprendi nesses meus anos de experiência como desenvolvedor é: quem desenvolve não está apto a testar a usabilidade.
Por que?
- Em primeiro lugar, porque a equipe que desenvolveu o sistema, normalmente, conhece todos os caminhos (e atalhos) do sistema e está viciado em uma navegação que sempre funciona.
- Em segundo lugar, não desenvolvemos sistemas para nós e sim para o usuário/cliente. Então quem precisa avaliar se o sistema é utilizável de forma eficiente ou não é o usuário e não a equipe de desenvolvimento.
Tenhamos sempre em mente a usabilidade, pois se um sistema é complicado de ser usado pelo usuário, para que existir sistema?
Parte I - Introdução
Parte II - Correção
Parte III - Design por contrato
Parte IV - Flexibilidade
Parte V - A Lei de Demeter
Parte VI - Eficiência
Parte VII - Coesão
Parte IX - Relacionamento entre classes
Marcadores:
princípios,
usabilidade
segunda-feira, 8 de junho de 2009
Princípios de projeto - Parte VII - Coesão
Já conversei com algumas pessoas que utilizaram esta frase, "receita de bolo", quando eu perguntei o que significava, um bom design OO:
Este artigo é totalmente baseado no livro Fundamentals of Object-Oriented Design in UML, do Page-Jones.
Coesão de classe é a medida da inter-relação dos recursos (atributos e operações) localizadas na interface externa de uma classe. Page-Jones, diz que o termo coesão de tipo seria uma definição melhor, já que o conceito que está sendo passado aqui é: quão bem uma classe funciona como uma implementação de algum tipo de dado abstrato.
Ainda segundo Page-Jones, uma classe com baixa (ruim) coesão tem um conjunto de recursos que não estão em harmonia entre si. De outro modo, uma classe com alta (boa) coesão, tem um conjunto de recursos onde todos contribuem para a abstração de tipo implementada pela classe.
Algumas pessoas tem tentado definir coesão de classe considerando como os métodos usam os atributos internos. Entretanto, Page-Jones não gosta dessa definição por dois motivos: O primeiro é que a coesão de classe deve ser percebido de fora de uma unidade de software; o segundo é porque a coesão pode mudar de acordo com mudanças na classe durante o seu ciclo de vida, isto é, uma classe imatura pode parecer ter menor coesão que uma classe mais madura.
Page-Jones observou 3 problemas principais na alocação de recursos numa classe: mixed-instance, mixed-domain e mixed-role. Os problemas estão listados em ordem de relevância, sendo a mixed-instance o maior problema e mixed-role o menor.
Uma classe sem esses três problemas de coesão é inteiramente coesiva e pode-se dizer que possui uma coesão ideal.
Vamos as definições.
Parte I - Introdução
Parte II - Correção
Parte III - Design por contrato
Parte IV - Flexibilidade
Parte V - A Lei de Demeter
Parte VI - Eficiência
Parte VIII - Usabilidade
Parte IX - Relacionamento entre classes
Baixo acoplamento e alta coesão.Mas, o que é coesão de classe?
Este artigo é totalmente baseado no livro Fundamentals of Object-Oriented Design in UML, do Page-Jones.
Coesão de classe é a medida da inter-relação dos recursos (atributos e operações) localizadas na interface externa de uma classe. Page-Jones, diz que o termo coesão de tipo seria uma definição melhor, já que o conceito que está sendo passado aqui é: quão bem uma classe funciona como uma implementação de algum tipo de dado abstrato.
Ainda segundo Page-Jones, uma classe com baixa (ruim) coesão tem um conjunto de recursos que não estão em harmonia entre si. De outro modo, uma classe com alta (boa) coesão, tem um conjunto de recursos onde todos contribuem para a abstração de tipo implementada pela classe.
Algumas pessoas tem tentado definir coesão de classe considerando como os métodos usam os atributos internos. Entretanto, Page-Jones não gosta dessa definição por dois motivos: O primeiro é que a coesão de classe deve ser percebido de fora de uma unidade de software; o segundo é porque a coesão pode mudar de acordo com mudanças na classe durante o seu ciclo de vida, isto é, uma classe imatura pode parecer ter menor coesão que uma classe mais madura.
Page-Jones observou 3 problemas principais na alocação de recursos numa classe: mixed-instance, mixed-domain e mixed-role. Os problemas estão listados em ordem de relevância, sendo a mixed-instance o maior problema e mixed-role o menor.
Uma classe sem esses três problemas de coesão é inteiramente coesiva e pode-se dizer que possui uma coesão ideal.
Vamos as definições.
- Mixed-instance: Uma classe com mixed-instance cohesion possui alguns recursos que não são definidos para alguns objetos da classe. Ex: em um departamento de vendas, existem vendedores comissionados e não comissionados. Uma classe Vendedor possui o método pagarComissao(). José é comissionado e Maria não. Poderíamos até setar a comissão de Maria para zero. Entretanto, isso não seria verdade, já que Maria não tem zero de comissão. Poderíamos criar um atributo boolean que indicaria se o vendedor é comissionado, mas isso seria um péssimo design. A solução mais elegante seria criar uma classe VendedorComissionado e VendedorNaoComissionado;
- Mixed-domain: Uma classe com mixed-domain cohesion contém um elemento que sobrecarrega a classe com uma classe extrínseca de um domínio diferente. Uma classe A é extrínseca de B se A puder ser totalmente definida sem noção de B. Ex: Uma classe Real possui um método arcTan. Mas arcTan não é um recurso de Real e sim de Angle. Quando você está modelando uma classe de um dado domínio, você só poderá incluir classes dos domínios inferiores. É o que define reusabilidade.
- Mixed-role: Uma classe contém um elemento que faz parte do domínio, mas não faz parte da abstração dessa classe. Ex: Uma classe Pessoa possui um método qtdCachorros(). Só que Cachorro, na verdade, não faz parte de Pessoa. Como você reusaria Pessoa se não houver Cachorro na nova aplicação? E se continuássemos com essa filosofia de design? Onde pararíamos? qtdBarcos(), qtdGatos(), qtdCarros(), etc. Todos esses atributos sobrecarregam a classe Pessoa. É muito simples cair nesse tipo de problema de coesão, pois: 1- É muito fácil escrever uma mensagem para descobrir quantos cachorros uma pessoa tem, simplesmente fazendo jose.qtdCachorros(); 2- Muitas abordagens de design indicam que qtdCachorros() é um método de Pessoa; 3- Na vida real, se você quiser saber quantos cachorros José tem, você perguntaria a ele. Uma solução para esse problema seria criar uma classe intermediaria, entre Pessoa e Cachorro, que mapaearia a quantidade de cachorros que uma pessoa possui. Problemas de mixed-role cohesion reduzem a reusabilidade de uma classe, fique atento.
Parte I - Introdução
Parte II - Correção
Parte III - Design por contrato
Parte IV - Flexibilidade
Parte V - A Lei de Demeter
Parte VI - Eficiência
Parte VIII - Usabilidade
Parte IX - Relacionamento entre classes
Marcadores:
coesão,
princípios,
qualidade de design,
qualidade de software
segunda-feira, 1 de junho de 2009
Princípios de projeto - Parte VI - Eficiência
Algumas pessoas preferem o termo eficácia para definir o que vou escrever agora. Não pretendo entrar nessa discussão. O objetivo é falar de código que cumpra os requisitos usando menos recursos (processador, memória, etc.) possível. Dessa forma, usarei a palavra eficiência para expressar esse sentido. O próprio Aurélio trata os termos como sinônimos e a grande maioria dos sites que encontrei diferenciando-os, estão relacionados com administração de empresas.
Este é o princípio mais importante quando desenvolvemos Real-Time Applications. Quando não estamos trabalhando com este tipo de aplicação, este é o primeiro a sofrer degradação para que o código atenda a outros princípios. Entretanto, existem códigos eficientes que não precisam ser degradados.
A matemática nos fornece meios para tornarmos algumas soluções mais eficientes. Um exemplo de código eficiente é o usado para somar os números de 1 a n. Uma técnica matemática.
Alguns podem pensar: "Ué, é só fazer um for de 1 a n, somando um contadorzinho idiota!". Quem está pensando nisso não conhece Mr. Carl Friedrich Gauss.
Gauss encontrou, durante uma aula de aritmética, a propriedade da simetria das progressões aritméticas, derivando a fórmula da soma para uma progressão aritmética arbitrária – fórmula que, provavelmente, Gauss descobriu por si próprio.
Chega de papo e voltemos a eficiência de código.
Imagine que no seu sistema exista uma funcionalidade que some os números de 1 a 1.000.000.000.000.
Já é possível notar que um laço for seria dispendioso. Para essa situação, Gauss vem em nosso socorro, com sua fórmula da soma para uma progressão aritmética: Sn = (n * (A1 + An)) / 2.
Utilizando a fórmula, seu código estará mais eficiente que um código utilizando um laço for. Estaremos consumindo menos recursos de hardware e nosso sistema responderá muito mais depressa.
Este foi um exemplo de eficiência que não precisa ser degradado para o atendimento de qualquer outro princípio, como a legibilidade.
Outras técnicas que necessitam de uma preocupação maior com a eficiência são as técnicas de classificação (Bubble Sort, Tree Sort, etc). Quando usamos estas técnicas, buscamos eficiência. Não é raro encontrar milhares e até milhões de dados precisando de ordenação. Um algoritmo ineficiente, comprometerá demais a perfomance da aplicação. Nestes casos, a eficiência sobe na escala da prioridade, ficando abaixo apenas da correção e da robustez.
Quando estamos usando um framework, devemos conhece-lo profundamente, a fim de não degradarmos a eficiência da nossa aplicação por motivos de falta de conhecimento. Se a eficiência é uma preocupação, devemos conhecer profundamente como configurar o(s) framework(s) escolhido(s), assim como conhecer as classes que nos darão ganho de eficiência. No último Falando em Java, técnicas de otimização do Hibernate foram apresentadas. Veja aqui.
Sites Relacionados: http://pt.wikipedia.org/wiki/Teoria_dos_n%C3%BAmeros
http://www.educ.fc.ul.pt/docentes/opombo/seminario/gauss/gauss.htm
http://wapedia.mobi/pt/Teoria_dos_n%C3%BAmeros
http://hypescience.com/10-simples-truques-de-aritmetica/
Parte I - Introdução
Parte II - Correção
Parte III - Design por contrato
Parte IV - Flexibilidade
Parte V - A Lei de Demeter
Parte VII - Coesão
Parte VIII - Usabilidade
Parte IX - Relacionamento entre classes
Este é o princípio mais importante quando desenvolvemos Real-Time Applications. Quando não estamos trabalhando com este tipo de aplicação, este é o primeiro a sofrer degradação para que o código atenda a outros princípios. Entretanto, existem códigos eficientes que não precisam ser degradados.
A matemática nos fornece meios para tornarmos algumas soluções mais eficientes. Um exemplo de código eficiente é o usado para somar os números de 1 a n. Uma técnica matemática.
Alguns podem pensar: "Ué, é só fazer um for de 1 a n, somando um contadorzinho idiota!". Quem está pensando nisso não conhece Mr. Carl Friedrich Gauss.
Gauss encontrou, durante uma aula de aritmética, a propriedade da simetria das progressões aritméticas, derivando a fórmula da soma para uma progressão aritmética arbitrária – fórmula que, provavelmente, Gauss descobriu por si próprio.
Chega de papo e voltemos a eficiência de código.
Imagine que no seu sistema exista uma funcionalidade que some os números de 1 a 1.000.000.000.000.
Já é possível notar que um laço for seria dispendioso. Para essa situação, Gauss vem em nosso socorro, com sua fórmula da soma para uma progressão aritmética: Sn = (n * (A1 + An)) / 2.
Utilizando a fórmula, seu código estará mais eficiente que um código utilizando um laço for. Estaremos consumindo menos recursos de hardware e nosso sistema responderá muito mais depressa.
Este foi um exemplo de eficiência que não precisa ser degradado para o atendimento de qualquer outro princípio, como a legibilidade.
Outras técnicas que necessitam de uma preocupação maior com a eficiência são as técnicas de classificação (Bubble Sort, Tree Sort, etc). Quando usamos estas técnicas, buscamos eficiência. Não é raro encontrar milhares e até milhões de dados precisando de ordenação. Um algoritmo ineficiente, comprometerá demais a perfomance da aplicação. Nestes casos, a eficiência sobe na escala da prioridade, ficando abaixo apenas da correção e da robustez.
Quando estamos usando um framework, devemos conhece-lo profundamente, a fim de não degradarmos a eficiência da nossa aplicação por motivos de falta de conhecimento. Se a eficiência é uma preocupação, devemos conhecer profundamente como configurar o(s) framework(s) escolhido(s), assim como conhecer as classes que nos darão ganho de eficiência. No último Falando em Java, técnicas de otimização do Hibernate foram apresentadas. Veja aqui.
Sites Relacionados: http://pt.wikipedia.org/wiki/Teoria_dos_n%C3%BAmeros
http://www.educ.fc.ul.pt/docentes/opombo/seminario/gauss/gauss.htm
http://wapedia.mobi/pt/Teoria_dos_n%C3%BAmeros
http://hypescience.com/10-simples-truques-de-aritmetica/
Parte I - Introdução
Parte II - Correção
Parte III - Design por contrato
Parte IV - Flexibilidade
Parte V - A Lei de Demeter
Parte VII - Coesão
Parte VIII - Usabilidade
Parte IX - Relacionamento entre classes
Marcadores:
eficiência,
gauss,
princípios
quarta-feira, 20 de maio de 2009
Princípios de projeto - Parte V - A Lei de Demeter
A Lei de Demeter foi concebida por volta de 1987, definindo uma forma de desenvolver, também conhecida por Princípio do Mínimo Conhecimento (Principle of Least Knowledge). A lei tem origem em bla bla bla, e seu nome deriva de bla bla bla
Formalmente falando, um método M de um objeto O só pode consumir serviços dos seguintes tipos de objetos:
A grande maioria do material sobre a lei na Internet, usa o exemplo do cachorro:
Vamos demonstrar essa teoria:
O que a Lei de Deméter diz é para não enviarmos mensagens para Pata e sim para Cachorro, que fará a delegação para as patas e estas para as classes que forem necessárias para fazer o cachorro andar.
A diferença é clara:
disso:
para isso:
Dá para notar a facilidade de manutenção nesse exemplo? Posso alterar o comportamento das patas e o cão continua andando, sem afetar o cliente da classe
A complexidade da ação está encapsulada. Não é preciso ninguém saber quais músculos, ossos, tendões, etc estão envolvidos na ação. Isso não é problema de quem está dando a ordem para o cão. Com a complexidade encapsulada, reduzimos dependências desnecessárias, facilitando a manutenção, aumentando a flexibilidade do nosso sistema.
Outro exemplo prático podemos encontrar nesse artigo.
Até a próxima.
Parte I - Introdução
Parte II - Correção
Parte III - Design por contrato
Parte IV - Flexibilidade
Parte VI - Eficiência
Parte VII - Coesão
Parte VIII - Usabilidade
Parte IX - Relacionamento entre classes
Formalmente falando, um método M de um objeto O só pode consumir serviços dos seguintes tipos de objetos:
- Do próprio objeto O;
- Parâmetros de M;
- Qualquer objeto criado/instanciado por M;
- Componentes diretos de O.
A grande maioria do material sobre a lei na Internet, usa o exemplo do cachorro:
Quando você precisa que um cachorro ande, você dá a ordem para as pernas diretamente, ou para o cachorro? Obviamente que para o cachorro e este sabe o que precisa ser acionado para andar.
Vamos demonstrar essa teoria:
package br.com.celsomartins.cachorro;
import java.util.ArrayList;
import java.util.List;
public class Cachorro {
private List <Pata> patas = new ArrayList <Pata>();
public boolean andar() {
for (Pata pata: patas) {
if (!pata.andar()) {
return false;
}
}
return true;
}
}
package br.com.celsomartins.cachorro;
public class Pata {
public boolean andar() {
return true;
}
}
O que a Lei de Deméter diz é para não enviarmos mensagens para Pata e sim para Cachorro, que fará a delegação para as patas e estas para as classes que forem necessárias para fazer o cachorro andar.
A diferença é clara:
disso:
...
for (Pata pata: cachorro.getPatas()){
pata.andar();
}para isso:
cachorro.andar();
Dá para notar a facilidade de manutenção nesse exemplo? Posso alterar o comportamento das patas e o cão continua andando, sem afetar o cliente da classe
Cachorro. O cachorro pode ter perdido uma das patas e andar com apenas três, isso não é importante para o cliente da classe. A complexidade da ação está encapsulada. Não é preciso ninguém saber quais músculos, ossos, tendões, etc estão envolvidos na ação. Isso não é problema de quem está dando a ordem para o cão. Com a complexidade encapsulada, reduzimos dependências desnecessárias, facilitando a manutenção, aumentando a flexibilidade do nosso sistema.
Outro exemplo prático podemos encontrar nesse artigo.
Até a próxima.
Parte I - Introdução
Parte II - Correção
Parte III - Design por contrato
Parte IV - Flexibilidade
Parte VI - Eficiência
Parte VII - Coesão
Parte VIII - Usabilidade
Parte IX - Relacionamento entre classes
Marcadores:
dependência,
encapsulamento,
Lei de Demeter,
princípios
sexta-feira, 15 de maio de 2009
Princípios de projeto - Parte IV
Agora falaremos da flexibilidade. O princípio da flexibilidade diz respeito a como sua aplicação é capaz de se adequar às mudanças solicitadas na aplicação.
Mudanças acontecem a todo momento durante o processo de software. Sua aplicação deve estar preparada para isso. A ausência deste princípio gera, naturalmente, um receio por mudanças. É a ausência desse princípio que produz as tão famosas reclamações da equipe com relação ao cliente.
Basicamente, existem dois tipos de Change Requests: o que é ocasionado pela deficiência no levantamento de requistos e o que é ocasionado por mudanças no negócio do cliente.
Como fazer com que essas alterações não transformem nossas vidas em um inferno?
Um desenvolvedor preocupado com as boas práticas produzirá códigos mais flexíveis. Usaremos, para exemplificarmos, o primeiro exemplo de qualidade na prática.
Cenário: o cliente nos informa que, a partir de agora, para solicitação de compras de produto de um pedido, primeiramente, será preciso verificar se o mesmo está confirmado.
Solução com o código primitivo: Descobrir qual valor corresponde ao status confirmado. Verificar se o status do pedido é igual ao valor de confirmado.
Solução com o código refatorado: Perguntar se o status é confirmado, através do método estahConfirmado(short status).
É válido notar que, numa solução OO, o método estahConfirmado(), é uma operação (comportamento) de um objeto de uma classe, Pedido por exemplo. Dessa forma, sempre que precisássemos confirmar se o status de um pedido está em conformidade com a operação que desejamos realizar, pergutamos para quem sabe, isto é, para o objeto.
Onde a flexibilidade do código refatorado? Bem, logo de cara, percebemos que não precisamos duplicar código para verificar o status. Dessa forma, estaremos reusando um código que já foi testado diversas vezes. O analista de negócios solicitou uma alteração e a fizemos somente realizando mais uma chamada a um método já existente.
Outro exemplo interessante, surgiu nessa thread do GUJ:
Cenário: O desenvolvedor precisa de uma pilha limitada.
A primeira solução que forneci é, na verdade, uma gambiarra. Na segunda, aproveitei a flexibilidade do Java para fornecer uma solução mais elegante.
Outro exemplo clássico que prova a flexibilidade do código refatorado é a mudança da lógica de negócios para o status confirmado. Nenhum cliente do objeto da classe Pedido precisa saber que o chato do cliente mudou a lógica. Faremos a alteração na classe Pedido e nenhuma outra alteração será necessária. Essa flexibilidade é um dos motivos para o Shoes e Fowler serem contras a separação de dados e comportamento em VO e BO, produzindo um modelo anêmico.
Outras formas de conseguir flexibilidade é eliminando dependências desnecessárias e melhorando a legibilidade. Por consistirem em assuntos vastos, falaremos mais adiante.
A flexibilidade é subjetiva, difícil de perceber. Normalmente, só a percebemos quando precisamos. Portanto poderíamos escrever milhares de linhas e ainda não cobriríamos todo assunto. Espero que a idéia central tenha sido passada.
No próximo post, falarei da Lei de Deméter. Até lá.
Mudanças acontecem a todo momento durante o processo de software. Sua aplicação deve estar preparada para isso. A ausência deste princípio gera, naturalmente, um receio por mudanças. É a ausência desse princípio que produz as tão famosas reclamações da equipe com relação ao cliente.
Basicamente, existem dois tipos de Change Requests: o que é ocasionado pela deficiência no levantamento de requistos e o que é ocasionado por mudanças no negócio do cliente.
Como fazer com que essas alterações não transformem nossas vidas em um inferno?
Um desenvolvedor preocupado com as boas práticas produzirá códigos mais flexíveis. Usaremos, para exemplificarmos, o primeiro exemplo de qualidade na prática.
Cenário: o cliente nos informa que, a partir de agora, para solicitação de compras de produto de um pedido, primeiramente, será preciso verificar se o mesmo está confirmado.
Solução com o código primitivo: Descobrir qual valor corresponde ao status confirmado. Verificar se o status do pedido é igual ao valor de confirmado.
Solução com o código refatorado: Perguntar se o status é confirmado, através do método estahConfirmado(short status).
É válido notar que, numa solução OO, o método estahConfirmado(), é uma operação (comportamento) de um objeto de uma classe, Pedido por exemplo. Dessa forma, sempre que precisássemos confirmar se o status de um pedido está em conformidade com a operação que desejamos realizar, pergutamos para quem sabe, isto é, para o objeto.
Onde a flexibilidade do código refatorado? Bem, logo de cara, percebemos que não precisamos duplicar código para verificar o status. Dessa forma, estaremos reusando um código que já foi testado diversas vezes. O analista de negócios solicitou uma alteração e a fizemos somente realizando mais uma chamada a um método já existente.
Outro exemplo interessante, surgiu nessa thread do GUJ:
Cenário: O desenvolvedor precisa de uma pilha limitada.
A primeira solução que forneci é, na verdade, uma gambiarra. Na segunda, aproveitei a flexibilidade do Java para fornecer uma solução mais elegante.
Outro exemplo clássico que prova a flexibilidade do código refatorado é a mudança da lógica de negócios para o status confirmado. Nenhum cliente do objeto da classe Pedido precisa saber que o chato do cliente mudou a lógica. Faremos a alteração na classe Pedido e nenhuma outra alteração será necessária. Essa flexibilidade é um dos motivos para o Shoes e Fowler serem contras a separação de dados e comportamento em VO e BO, produzindo um modelo anêmico.
Outras formas de conseguir flexibilidade é eliminando dependências desnecessárias e melhorando a legibilidade. Por consistirem em assuntos vastos, falaremos mais adiante.
A flexibilidade é subjetiva, difícil de perceber. Normalmente, só a percebemos quando precisamos. Portanto poderíamos escrever milhares de linhas e ainda não cobriríamos todo assunto. Espero que a idéia central tenha sido passada.
No próximo post, falarei da Lei de Deméter. Até lá.
Parte I - Introdução
Parte II - Correção
Parte III - Design por contrato
Parte V - A Lei de Demeter
Parte VI - Eficiência
Parte VII - Coesão
Parte VIII - Usabilidade
Parte IX - Relacionamento entre classes
Marcadores:
flexibilidade,
princípios
terça-feira, 12 de maio de 2009
Princípios de Projeto III - Design por contrato
Bertrand Meyer descreve as pré e pós-condições de uma operação como design por contrato. Invariantes são condições que todo objeto de uma determinada classe deve satisfazer em todo ciclo de vida, enquanto estiver em equilíbrio, isto é, não estiver numa mudança de estado.
Um exemplo de invariante, dado por Page-Jones é sobre um objeto da classe Triangulo.
Interfaces são outra forma de design por contrato. Quando uma determinada classe afirma que implementa uma interface, ela deve, obrigatoriamente, implementar todas as operações que estão definidas nesta interface. Existe um excelente artigo aqui sobre o assunto.
Enfim, invariantes, pré e pós-condições e interfaces formam uma abordagem de design conhecida como "Design por Contrato". Esta abordagem garante que uma operação de um dado objeto vai gerar a resposta correta, para o objeto cliente (objeto que está consumindo o serviço), ao mesmo tempo que obriga o objeto cliente a obedecer as pré-condições do serviço que está consumindo. Também obriga a implementação das operações que estão definidas na interface implemetada.
Referência: Fundamentals of Object-Oriented Design in UML by Meilir Page-Jones
Um exemplo de invariante, dado por Page-Jones é sobre um objeto da classe Triangulo.
Sabendo que os lados de um triângulo são Triangulo.a, Triangulo.b, Triangulo.c, então uma parte da invariante de classe de Triangulo é: a + b > c and b + c > a and c + a > b.A pré-condição é uma condição que deve ser verdadeira antes do início da execução da operação. Se não for, a operação pode recusar executar e lançar alguma excessão. Uma pós-condição é uma condição que deve ser verdadeira no fim da execução da operação.
Interfaces são outra forma de design por contrato. Quando uma determinada classe afirma que implementa uma interface, ela deve, obrigatoriamente, implementar todas as operações que estão definidas nesta interface. Existe um excelente artigo aqui sobre o assunto.
Enfim, invariantes, pré e pós-condições e interfaces formam uma abordagem de design conhecida como "Design por Contrato". Esta abordagem garante que uma operação de um dado objeto vai gerar a resposta correta, para o objeto cliente (objeto que está consumindo o serviço), ao mesmo tempo que obriga o objeto cliente a obedecer as pré-condições do serviço que está consumindo. Também obriga a implementação das operações que estão definidas na interface implemetada.
Referência: Fundamentals of Object-Oriented Design in UML by Meilir Page-Jones
Parte I - Introdução
Parte II - Correção
Parte IV - Flexibilidade
Parte V - A Lei de Demeter
Parte VI - Eficiência
Parte VII - Coesão
Parte VIII - Usabilidade
Parte IX - Relacionamento entre classes
segunda-feira, 11 de maio de 2009
Princípios de projeto - Parte II
Existem alguns motivos principais para um sistema não atender os requisitos para os quais foi contratado o projeto. Vamos analisa-los:
- Muitas pessoas entre o cliente e a equipe de desenvolvimento: Os requisitos podem chegar deturpados até os desenvolvedores. Não é culpa da equipe, nem do cliente. Simplesmente, o telefone sem fio não funciona com precisão em área alguma, ainda mais em engenharia de software. Em algumas empresas, a equipe de desenvolvimento, não chega a participar das reuniões de levantamento de requisitos. Assim, os problemas são propagados pelas camadas da empresa, "cascateando" o mal entendido do cliente para o analista, deste para o líder e deste para a equipe. Ainda não tive experiência com esse tipo de divisão de papéis. Com as empresas que trabalhei até hoje, o papel do analista esteve sempre vinculado ao papel do programador. Nos locais onde atuei, a vontade do cliente chega diretamente para a equipe e, assim, analisamos, fornecemos os prazos, desenvolvemos e realizamos os testes unitários
- A equipe de desenvolvimento não compreendeu a vontade do cliente: Em outras vezes, o desenvolvedor não conseguiu compreender bem os requisitos. Quanto mais cedo a falha na interpretação for detectada, menos custo vai gerar. A capacidade de interpretação deve ser uma preocupação do desenvolvedor. Se algo não foi bem entendido, não deixe o momento da reunião passar. Alguns minutos a mais utilizados durante o levantamento dos requisitos, podem significar menos horas ou até dias durante o processo de desenvolvimento. Obviamente, não estou falando de nenhuma metodologia ágil, como a XP, que prevê a participação efetiva de alguém do negócio durante o desenvolvimento;
- O cliente não sabe o que quer: Acontece com frequência. Entretanto, o nosso papel como consultor, na minha visão, também consiste em ajudar nesse sentido. Traduzir uma chuva de idéias em algo sistemático. Devemos conseguir enxergar os processos repetitivos e automatiza-los. Normalmente o cliente não sabe (e nem tem a obrigação de saber) como as coisas se comportarão quando os requisitos sairem do papel e virarem sistema. O cliente entende do negócio dele e sabe, melhor do que qualquer um, o que vai agregar valor. O desenvolvedor deve ter a perspicácia de encontrar a real necessidade daquele que o contrata. O conhecimento técnico de quem está solicitando o sistema é, normalmente, muito vago.
Salta aos olhos o motivo raiz (root cause) dos três motivos que citei acima: comunicação.
Nesse sentido, a ubiquitous language (linguagem ubíqua), trazida a tona pelo DDD (Domain Driven Design), exerce um papel fundamental.
A princípio, o que seria uma linguagem ubíqua? Ubíqua, segundo o Aurélio, significa:
Sem uma boa comunicação, é difícil conseguirmos um bom sistema. É comum em nossa área, termos profissionais com certa dificuldade para se expressar e para entender o que está sendo discutido. Esse problema é, normalmente, causado, pela timidez e dificuldade de concentração. Entretanto, existe uma luz no fim do túnel. Existem treinamentos para resolver essas dificuldades.
A timidez é oposta à segurança. Quanto mais seguro você está sobre um assunto, menos tímido você se sentirá para aborda-lo. Assim, alguns passos para acontecer a mágica da mudança de comportamento é:
Nesse sentido, a ubiquitous language (linguagem ubíqua), trazida a tona pelo DDD (Domain Driven Design), exerce um papel fundamental.
A princípio, o que seria uma linguagem ubíqua? Ubíqua, segundo o Aurélio, significa:
adj. Que está ao mesmo tempo em toda parte. (Sin.: onipresente.).Trazendo para a nossa área, é uma linguagem dominada tanto pelo cliente quanto pela equipe de desenvolvimento. Desenvolveremos a questão da ubiquitous language e a DDD em posts futuros. No momento, basta sabermos que existe uma forma de melhorar a comunicação com o cliente.
Sem uma boa comunicação, é difícil conseguirmos um bom sistema. É comum em nossa área, termos profissionais com certa dificuldade para se expressar e para entender o que está sendo discutido. Esse problema é, normalmente, causado, pela timidez e dificuldade de concentração. Entretanto, existe uma luz no fim do túnel. Existem treinamentos para resolver essas dificuldades.
A timidez é oposta à segurança. Quanto mais seguro você está sobre um assunto, menos tímido você se sentirá para aborda-lo. Assim, alguns passos para acontecer a mágica da mudança de comportamento é:
- Estude muito: quanto maior for o seu domínio sobre determinado assunto, maior será a sua segurança de falar sobre ele. Não estude apenas a teoria, mas também, crie cenários onde a teoria possa ser exercitada;
- Não falar de algo que você não entenda bem: Esse é outro ponto importante. Se você está inseguro sobre determinado assunto, não fale nada. Escute e tente absorver o máximo de conceitos que está sendo debatido por outras pessoas. Se a sua idéia sobre determinado assunto não estiver completamente formada, não opine;
- Aprenda a ser contrariado: quem nunca foi contrariado, nunca opinou. No nosso dia a dia, nos deparamos com situações que dependem da nossa avaliação e do nosso ponto de vista. Entretanto, outras pessoas podem ter um ponto de vista melhor devido a, por exemplo, ter mais experiência na área ou na empresa. Devemos nos preparar para sermos contrariados. Se não nos prepararmos quanto a isso, vamos correr o risco de gerar transtornos psicológicos que farão nos omitirmos cada vez mais.
Os pontos levantados ajudam na melhora da comunicação. Melhorando a comunicação, as reuniões com os clientes fluem com mais naturalidade e, consequentemente, mais fácil será o levantamento de requisitos.
Parte I - Introdução
Parte III - Design por contrato
Parte IV - Flexibilidade
Parte V - A Lei de Demeter
Parte VI - Eficiência
Parte VII - Coesão
Parte VIII - Usabilidade
Parte IX - Relacionamento entre classes
terça-feira, 5 de maio de 2009
Princípios de projeto - Parte I
Falaremos agora de correção e robustez.
Aqui, a palavra correção, é utilizada para traduzir correctness, representando a qualidade do que é correto.
Sintetizando em uma frase, correção é o atendimento dos requisitos pela sua aplicação; robustez é a capacidade do seu sistema em lidar, de forma correta, com as exceções.
Estes princípios são obrigatórios. Sem eles não existe aplicação. Na teoria, isso é simples de ver, enquanto, na prática, não é tão simples assim. Existem alguns motivos para o projeto falhar na correção, e serão mostrados no próximo post.
Quanto mais simples e direta a pergunta, mais simples e direta será a resposta. Entretanto, é com requisitos complexos e confusos que, normalmente, trabalhamos. O cliente, não nos chama para resolver dois mais dois.
Normalmente, para implementar um determinado requisito, dispomos de n formas. Assim, para sabermos qual a forma correta, precisamos recorrer aos outros princípios de projeto de software. Por hora, isso não nos interessa.
Podemos usar invariantes para garantir os valores de entrada. Essa é, inclusive, a razão de viver dos métodos de atribuição: setters.
Mas o que são invariantes? Segundo Page-Jones:
O tratamento de exceções é outra forma de garantia de robustez. E o primeiro aspecto que deve ser observado é: nunca lance uma Exception. Se você está tratanto de alguma exceção, saiba o que está sendo tratado. O tempo é curto, mas isso é ponto chave na aplicação. Saiba, ao certo, que tipo de exceção aquele trecho de código pode lançar e trate-a. Todos nós, desenvolvedores, seremos um pouco mais felizes.
É válido notar que, os testes podem passar e mesmo assim a robustez não estará totalmente garantida. A suíte não é responsável por testar determinadas situações, como queda do banco. Enquanto o banco está no ar, os testes rodam. E quando cair? Vai despejar um stack trace na tela do cliente? Claro que não.
A robustez é uma das variáveis que fornecerá os dados para medir a confiabilidade do seu sistema. Outras variáveis são a arquitetura de hardware, S.O. e a rede. A confiabilidade é obtida através de qtd de falhas / unidade de tempo.
Uma falha na rede pode, facilmente, ser tratada na sua aplicação, mostrando uma mensagem de erro agradável (se é que podemos unir erro e agradável na mesma frase) para o usuário. Falhas e/ou baxa perfomance de hardware ou S.O. é mais complicado de resolver no código.
Podemos prever problemas de perfomance, pensando na eficácia do código. Entretanto, estaremos sempre limitados ao(s) hardware(s), ao S.O. e à rede. Se uma base de dados tunada, estiver em um Pentium 100 MMX, com 64mb de memória, em uma rede de 10Mbits, sua aplicação CRUD pode estar em um supercomputador, com código eficaz e, mesmo assim, deixará a desejar na perfomance.
É necessário buscar o equilíbrio correto entre essas quatro variáveis principais, equilibrando, também, com uma variável importante para o cliente: o custo.
No próximo post, discutiremos alguns dos fatores que levam a falha do princípio da correção no processo de software.
Aqui, a palavra correção, é utilizada para traduzir correctness, representando a qualidade do que é correto.
Sintetizando em uma frase, correção é o atendimento dos requisitos pela sua aplicação; robustez é a capacidade do seu sistema em lidar, de forma correta, com as exceções.
Estes princípios são obrigatórios. Sem eles não existe aplicação. Na teoria, isso é simples de ver, enquanto, na prática, não é tão simples assim. Existem alguns motivos para o projeto falhar na correção, e serão mostrados no próximo post.
Quanto mais simples e direta a pergunta, mais simples e direta será a resposta. Entretanto, é com requisitos complexos e confusos que, normalmente, trabalhamos. O cliente, não nos chama para resolver dois mais dois.
Normalmente, para implementar um determinado requisito, dispomos de n formas. Assim, para sabermos qual a forma correta, precisamos recorrer aos outros princípios de projeto de software. Por hora, isso não nos interessa.
Podemos usar invariantes para garantir os valores de entrada. Essa é, inclusive, a razão de viver dos métodos de atribuição: setters.
Mas o que são invariantes? Segundo Page-Jones:
É uma condição que todo objeto de uma determinada classe deve satisfazer, por todo seu ciclo de vida (quando o objeto estiver em equilibrio).Também temos as pré e pós-condições, que atuam a nível das operações. Ex: MENOR_DATA <= nascimento <= MAIOR_DATA. Lembrando que eliminamos números mágicos, portanto não interessa, para quem estiver implementando, os valores das constantes MENOR_DATA e MAIOR_DATA. Em termos gerais, esses valores, fazem parte das regras de negócio do cliente. Mas e a correção no comportamento? Este tipo é bem coberto pelos Testes Unitários. Com eles, descobrimos se a lógica está fazendo o que deveria fazer. Existe um excelente artigo sobre o JUnit aqui. Outro excelente artigo, este sobre mock objects. Montando uma suíte de testes, teremos muito mais segurança ao executar refatorações e atender as Change Requests.
Objeto em equilíbrio é o objeto que não está em transição de estado.
O tratamento de exceções é outra forma de garantia de robustez. E o primeiro aspecto que deve ser observado é: nunca lance uma Exception. Se você está tratanto de alguma exceção, saiba o que está sendo tratado. O tempo é curto, mas isso é ponto chave na aplicação. Saiba, ao certo, que tipo de exceção aquele trecho de código pode lançar e trate-a. Todos nós, desenvolvedores, seremos um pouco mais felizes.
É válido notar que, os testes podem passar e mesmo assim a robustez não estará totalmente garantida. A suíte não é responsável por testar determinadas situações, como queda do banco. Enquanto o banco está no ar, os testes rodam. E quando cair? Vai despejar um stack trace na tela do cliente? Claro que não.
A robustez é uma das variáveis que fornecerá os dados para medir a confiabilidade do seu sistema. Outras variáveis são a arquitetura de hardware, S.O. e a rede. A confiabilidade é obtida através de qtd de falhas / unidade de tempo.
Uma falha na rede pode, facilmente, ser tratada na sua aplicação, mostrando uma mensagem de erro agradável (se é que podemos unir erro e agradável na mesma frase) para o usuário. Falhas e/ou baxa perfomance de hardware ou S.O. é mais complicado de resolver no código.
Podemos prever problemas de perfomance, pensando na eficácia do código. Entretanto, estaremos sempre limitados ao(s) hardware(s), ao S.O. e à rede. Se uma base de dados tunada, estiver em um Pentium 100 MMX, com 64mb de memória, em uma rede de 10Mbits, sua aplicação CRUD pode estar em um supercomputador, com código eficaz e, mesmo assim, deixará a desejar na perfomance.
É necessário buscar o equilíbrio correto entre essas quatro variáveis principais, equilibrando, também, com uma variável importante para o cliente: o custo.
No próximo post, discutiremos alguns dos fatores que levam a falha do princípio da correção no processo de software.
Parte II - Correção
Parte III - Design por contrato
Parte IV - Flexibilidade
Parte V - A Lei de Demeter
Parte VI - Eficiência
Parte VII - Coesão
Parte VIII - Usabilidade
Parte IX - Relacionamento entre classes
Marcadores:
correção,
invariantes,
princípios,
qualidade,
robustez,
testes
sexta-feira, 24 de abril de 2009
O que é qualidade de software? - Introdução
Esta é a pergunta que não quer calar: O que é qualidade de software?
Eric Braude, em Software Design: From Programming to Architecture, definiu que os princípios de um projeto de software são: correção, robustez, flexibilidade, reusabilidade, eficiência, usabilidade e confiabilidade. Acrescento também a legibilidade, pois como diz Martin Fowler:
A qualidade do código está diretamente ligada a todos estes princípios. Devem estar presentes na cabeça do desenvolvedor desde a primeira linha escrita, desde a modelagem da primeira classe. Tornar realidade nos primeiros esboços, seria o mundo perfeito. Mas não somos máquinas. Entretanto, isso não exime o desenvolvedor de estar com esses princípios em mente desde o nascimento dos primeiros requisitos.
Normalmente, desenvolvemos pensando apenas na correção e na robustez, preocupados em atender os requisitos e na confiabilidade da aplicação. Nesse momento entra a refatoração e, com ela, o ganho de experiência nas boas práticas. Refatoramos a arquitetura, movemos e criamos atributos e métodos, aplicamos padrões de projeto. Ao ficar inquieto com o que acabei de criar, quero aprender uma forma mais elegante de implementar aquela solução.
Gosto de comparar o desenvolvedor a um artista. Devemos ter tempo para olhar a obra e corrigir as imperfeições. E, infelizmente, tempo é uma peça chave nos processos de negócio. Não podemos menosprezar o problema de prazo do cliente.
Esta questão será peça chave também no blog e espero conseguir mostrar que com um tempo a mais para pensar nas melhores práticas, o cliente ganhará muito mais com o software em produção.
Eric Braude, em Software Design: From Programming to Architecture, definiu que os princípios de um projeto de software são: correção, robustez, flexibilidade, reusabilidade, eficiência, usabilidade e confiabilidade. Acrescento também a legibilidade, pois como diz Martin Fowler:
Any fool can write code that a computer can understand... But only good programmers write code that humans can understandEm alguns casos, precisamos degradar um dos princípios em prol de outro, como por exemplo a eficiência pela reusabilidade. Mas correção e robustez são princípios básicos, sendo assim, obrigatórios.
A qualidade do código está diretamente ligada a todos estes princípios. Devem estar presentes na cabeça do desenvolvedor desde a primeira linha escrita, desde a modelagem da primeira classe. Tornar realidade nos primeiros esboços, seria o mundo perfeito. Mas não somos máquinas. Entretanto, isso não exime o desenvolvedor de estar com esses princípios em mente desde o nascimento dos primeiros requisitos.
Normalmente, desenvolvemos pensando apenas na correção e na robustez, preocupados em atender os requisitos e na confiabilidade da aplicação. Nesse momento entra a refatoração e, com ela, o ganho de experiência nas boas práticas. Refatoramos a arquitetura, movemos e criamos atributos e métodos, aplicamos padrões de projeto. Ao ficar inquieto com o que acabei de criar, quero aprender uma forma mais elegante de implementar aquela solução.
Gosto de comparar o desenvolvedor a um artista. Devemos ter tempo para olhar a obra e corrigir as imperfeições. E, infelizmente, tempo é uma peça chave nos processos de negócio. Não podemos menosprezar o problema de prazo do cliente.
Esta questão será peça chave também no blog e espero conseguir mostrar que com um tempo a mais para pensar nas melhores práticas, o cliente ganhará muito mais com o software em produção.
Marcadores:
princípios,
qualidade
Assinar:
Postagens (Atom)