Domingo, 10 de Agosto de 2008

Parece plágio, mas não é...

Descobri que já existia um site chamado linhadecodigo.com. É um site voltado à plataforma .net (por isso que nunca tinha ouvido falar hehe). Para evitar confusão, vou mover este blog para um novo endereço em breve.

Quinta-feira, 7 de Agosto de 2008

Quando as leis do software não se aplicam mais

A física Newtoniana especifica um conjunto de leis que descrevem muito bem o universo na escala de espaço e tempo que nós podemos perceber com nossos sentidos. Nesta escala, o universo é 'bem comportado' dentro do limite destas leis, o que significa que se pode prever muito bem as interações entre os corpos usando a física Newtoniana.

Na escala do muito grande ou do muito pequeno, porém, as Leis de Newton falham. No domínio das partículas subatômicas, nós precisamos contar com a física quântica. Quando tratamos de velocidades ou massas muito grandes, é necessário usar a física de Einstein para descrever e predizer o comportamento do universo.

Eu gosto de encarar o software da mesma maneira. Em condições bem controladas, softwares são bem comportados sob as regras tradicionais de engenharia de software. Nesta situação se pode seguir as recomendações da literatura ao projetar sua arquitetura. Você pode projetar sua base de dados usando as regras tradicionais de normalização. Você pode usar os mais populares frameworks, bibliotecas, ferramentas e metodologias (tanto as clássicas quanto as ágeis ou 'extreme'). Estas regras, ferramentas e metodologias existem e são largamente aceitas utilizadas com motivo.

Esta é a situação em que a VASTA maioria do software existente se encontra. Esta é a condição quando se está lidando com requisitos razoáveis para escalabilidade, disponibilidade, responsividade, volume de dados ou data de entrega. E a definição de 'razoável' se estende cada vais mais conforme o tempo passa.

Contudo, quando os requisitos para seu software são estendidos para o limite, e o que eu quero dizer como 'limite' é um caso realmente extremo, jão não é possível se utilizar das regras da literatura. É necessário se pensar em projetos e soluções alternativos. Na arquitetura, será necessário sacrificar a pureza do projeto. Será necessário fazer concessões para extrair a máxima performance. No lado da base de dados, será necessário desnormalizar e pensar em maneiras não tradicionais de armazenar distribuir os dados. No lado das ferramentas, você terá que desenvolver internamente suas próprias ferramentas, bibliotecas e frameworks, ou adaptar as existentes para suas próprias necessidades.

A princípio, estas medidas parecerão heresia. Você irá pensar que as pessoas que desenvolveram o software são um bando de débeis mentais que não têm a menor idéia do que é desenvolvimento de software. Você irá achar que trabalha no lugar perfeito para alimentar o The Daily WTF por anos. Mas se você der um passo atrás, você pode perceber que não há uma solução pronta para tudo. Às vezes você vai se encontrar tendo que se virar sozinho, e você ainda vai ter que entregar o software quando o deadline chegar. Não se agarre a uma regra só porque seu professor mandou.

Eu não estou sugerindo que desistamos de tudo que aprendemos sobre engenharia de software. Como eu falei, as regras de arquitetura se aplicam na vasta maioria dos casos. Elas existem e são largamente aceitas com motivo. Mas nós precisamos entender a razão por trás destas regras. Ou nós estaremos fazendo Cargo Cult Programming, ou talvez tomando decisões a partir de um vago senso de dever para com os fantasmas de Boyce-Codd.

Uma vez que as regras de engenharia de software se aplicam à grande maioria dos casos, provavelmente se você ver alguém quebrando estas regras é porque esta pessoa é realmente um hacker. Mas também pode ser porque esta pessoa é um projetista inteligente que está resolvendo um problema difícil de uma maneira que ninguém imaginou antes. Contudo, em geral é melhor se utilizar uma prjeto limpo e simples. Como Ted Dziuba diz, "Escalabilidade não é seu problema. Fazer com que as pessoas dêem a mínima é o seu problema.

Mas se você for julgar alguém, ou se algum dia você se encontrar na situação de ter que projetar software para lidar com volumes ridículos de transações ou dados, lembre-se de manter uma mente aberta.

Mais informações:

O blog High Availability é uma fonte de informações muito útil sobre a arquitetura de alguns dos sites de maior volume do mundo.

Martin Fowler fala sobre a Heresia Mor: ambiente sem transações no EBay. Além disso, as tabelas do EBay não têm chaves estrangeiras!

Domingo, 3 de Agosto de 2008

Classes Imutáveis e o padrão Flyweight.

Tim High (que é o arquiteto na empresa em que trabalho) destaca corretamente a relação entre classes imutáveis e o padrão Flyweight. Este padrão é outro exemplo de quando classes imutáveis devem ser utilizadas. Uma vez que objetos flyweight podem ser compartilhados entre muitas classes, as classes flyweight devem ser sempre imutáveis.

Compartilhamento de atributos

Sabemos que classes imutáveis ajudam a reduzir a utilização de memória porque elas permitem que instâncias sejam livremente compartilhadas. Mas classes imutáveis também podem compartilhar seus atributos entre instâncias da mesma classe, reduzindo ainda mais a utilização de memória. A classe String faz exatamente isso:


String a = new String("Hello world");
String b = new String(a);


No código acima, b e a apontam para o mesmo array de caracteres. O construtor que recebe outra String como parâmetro cuidade de não fazer uma cópida desnecessária do array.

Nota: Ambas as linhas acima são consideradas más práticas em Java. O certo seria o seguinte:


String a = "Hello world";
String b = a;


Considere utilizar factory methods estáticos

Se você tem um domínio limitado de possíveis valores para sua classe imutável, você deve considerar criar factory methods estáticos que retornam instâncias de um pool. Um exemplo clássico é a classe java.lang.Integer, que fornece o método valueOf(int i) para ser usado ao invés do construtor. Aqui está a implementação deste método:

public static Integer valueOf(int i) {
final int offset = 128;
if (i >= -128 && i <= 127) {
return IntegerCache.cache[i + offset];
}
return new Integer(i);
}

Este método retorna uma referência a partir de um pool se o valor do argumento está entre -128 e 127. Você deve não apenas utilizar este método sempre que possível, e os equivalentes nas outras classes wrapper do Java, como também deve considerar criar métodos similares para suas próprias classes imutáveis.

Uma nota final sobre o valor de classes imutáveis

Uma razão pela qual as pessoas acabam escolhando não fazer suas classes imutáveis é porque os benefícios da imutabilidade são menos tangíveis que os benefícios da mutabilidade.

Se você faz com que sua classe seja mutável, você pode 'pegar um atalho' em situações em que imutabilidade teriam adicionado algum overhead. Se a classe String fosse mutável, você nunca teria que criar uma instância de StringBuffer para fazer concatenação de Strings em um loop. E os projetistas da class String nem teriam tido que criar a classe StringBuffer pra começar! Mas imagine os milhares de bugs que teriam acontecido em todos os sistemas em Java quando alguém inadvertidamente mudasse uma String que era compartilhada por um outro objeto!

Portanto, este esforço extra é um pequeno preço a pagar por benefícios que não são tão imediatos, que são a eliminação de bugs complexos causados pelo acoplamento, e a redução da utiização de memória causada por mais compartilhamento.

Classes Imutáveis

Eu tive uma discussão recentemente com a minha equipe a respeito de classes imutáveis. Alguns membros concordaram que classes imutáveis simplificam o desenvolvimento e tornam o código mais seguro, mas outros argumentaram que suas desvantagens se sobrepõem às suas vantagens. Outra questão que surgiu foi sobre quando usá-las e quando não usá-las.

Visão Geral

Antes que eu possa abordar cada uma das questões acima em detalhas, eu vou começar com uma uma visão geral declasses imutáveis. Uma classe é dita imutável se o estado de um objeto dsta classe não pode ser alterado depois que o objeto é criado. Em Java, você consegue isso se não fornecer nenhum método "setter", ou, mais genericamente, nenhum método público que possa mudar o valor de um atributo. Você pode reforçar a condição de imutável se fizer com que todos os atributos sejam final. Normalmente você também vai querer fazer com que a própria classe também seja final, para evitar que um usuário mal intencionado ou descuidado não estenda a classe e a torne mutável.

Os campos de uma classe imutável devem ser ou eles próprios imutáveis, ou não deve ser expostos para o exterior da classe. O objetivo é evitar que alguém modifique um atributo privado de uma classe imutável. Se você quiser expor um campo não imutável de uma classe imutável através de um "getter", por exemplo, você deveria fazer uma cópia do objeto e retornar a cópia ao invés do objeto original. A mesma coisa é válida quando um objeto mutável é passado para o construtor de uma classe imutável. Neste caso, você deve se certiricar de fazer uma cópia do objeto antes de armazená-la.

Exemplos de classes imutáveis no Java são java.lang.String and as classes wrapper como java.lang.Double, java.lang.Integer, etc. Um exemplo de uma classe que não é imutável mas deveria ser é a classe java.util.Date.

Vantagens

A principal vantagem de classes imutáveis é que você não precisa fazer cópias de segurança de objetos. Digamos que você está escremento um método que recebe uma String como parâmetro e seta como atributo da classe que contém o método. Você não precisa fazer uma cópia de segurança da String. Uma vez que a classe String é imutável, não há risco de alguém mudar a String fora da sua classe. De maneira similar, você não precisa fzer uma cópia da String quando está retornando através de um getter.

Mas se você escrever um método que recebe um objeto Date como parâmetro, ou um getter que retorna um atributo Date, você provavelmente vai querer fazer uma cópia de segurança do objeto Date, ou você pode ter bugs complexos quando alguém muda inadvertidamente o objeto Date que seu objeto retornou.

Veja um exemplo de bug que pode acontecer neste caso (retirado daqui):

task1.setStartDate(new Date("1 Jan 98");
task2.setStartDate(task1.getTaskDate());
// em algum lugar da classe Task
void delay(int delayDays) {
_startDate.setDate(_startDate.getDate() + delayDays);
}

// em um lugar qualquer...
task2.delay(5);
E você vai descobrir que o campo _startDate do objeto task1 mudou.

Uma outra vantagem é que classes imutáveis são inerentemente thread safe. Uma vez que nenhum campo pode ser modificado, você pode compartilhar objetos entre várias threads sem ter que sincronizar os acessos ao objeto.

Mais uma vantagem de classes imutáveis é que elas automaticamente reduzem o acoplamento. Mesmo que uma instância da classe String seja compartilhada entre vários outros objetos, não há acoplamento entre eles porque não há estado ocmpartilhado. Um objeto jamais pode afetar o estado de outro objeto através desta String.

Desvantagens

A principal desvantagem de classes imutáveis é que podem ser chatas de usar em iterações onde seus valores devem ser constantemente atualizados. Neste caso, para cada iteração você tem que criar uma nova instância da classe com os valores atualizados. Isto foi citado na discussão que tive com minha equipe como uma razão para não usar classes imutáveis.

Este problema no entanto pode ser facilmente resolvido através da criação de uma classe mutável auxiliar. De volta ao exemplo da classe String, o Java fornece a classe StringBuffer que você deve usar quando tem que fazer atualizações freqüentes em uma String. Mas o bom senso dita que a utilização da classe mutável auxiliar deve ser tão localizada quanto possível. Você começa com uma String, aí você cria o StringBuffer para ser atualizado em um loop, e ao fim do loop você converte de volta para uma String e descarta o StringBuffer. Normalmente não se passa StringBuffers de um lado para o outro entre classes ou os seta como atributos de outras classes.

Quando utilizá-las

Uma situação em que se deve sempre usar classes imutáveis é quando a classe modela um value object. Value Objects são coisas como Strings, Dates, Numbers, etc. Você provavelmente tem outros Value Objects específicos dos seu domínio. Value Objects devem sempre ser imutáveis. Martin Fowler afirma aqui: "Se você está usando um Value Object que é mutável, trate-o como se ele fosse imutável. Você pode não perceber o porquê, mas você vai economizar muito tempo e dinheiro.". Value Objects são um tópico que merecem uma discussão à parte, então não vou entrar em detalhes aqui, mas você pode olhar os links acima para maiores informações.

Você não deve entrar em uma loucura de classes imutáveis. Classes que modelam entitades de negócio não devem ser imutáveis, uma vez que elas têm atributos que mudam através do tempo e há uma razão legítima para compartilhar a mesma instância de uma entidade de negócio entre classes.

Uma forma de identificar value objects é se perguntando se duas instâncias de uma mesma classe com os mesmos valores em todos os atributos são equivalentes. Se a resposta for sim, sua classe é provavelmente um Value Object. Duas entidades de negócio podem ter os mesmos valores nos seus atributos mas ainda assim representar diferentes entidades e ter chaves primárias diferentes.

Conclusão

Classes imutáveis melhoram muito a qualidade do seu código uma vez que evitam bugs complexos e reduzem acoplamento. Elas são recomendadas especialmente para value objects, uma vez que eles são passados de um lado para o outro com freqüencia. Entidades de negócio não devem ser imutáveis.

Sábado, 2 de Agosto de 2008

Versão PT-BR de www.codeinstructions.com