Pequenos objetos imutáveis e Tiny Types - Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum
Pequenos objetos imutáveis e Tiny Types
Postado dia 20/07/2009 por Jonas Abreu em Programação 20
Uma das grandes preocupações que temos quando estamos desenvolvendo aqui na Caelum e nas nossas consultorias é como manter o código o mais expressivo possível. Expressividade está muito ligada a uma manutenabilidade maior do código, porque código mais fácil de entender costuma ter menos bugs. Uma das técnicas que usamos pra atingir esse objetivo são os Tiny Types.
Dêem uma olhada no código do seguinte método:
public Projeto criaProjeto(String nomeProjeto,
String descricaoProjeto) {
// Executa a lógica de criação do projeto
Digamos que esse seja um factory method para criação de Projeto. Ele seria usado da seguinte forma:
new FabricaDeProjeto().criaProjeto("nome", "descrição");
Mas nada impede que esse método seja chamado dessa forma:
new FabricaDeProjeto().criaProjeto("descrição", "nome");
Notaram a diferença sutil na ordem dos parâmetros? Isso vai acontecer? Provável. Imaginem o tempo gasto com debug para corrigir esse problema? Não seria melhor aproveitarmos a tipagem explícita do Java para pegarmos esse problema em tempo de compilação? Ficaria assim:
public Projeto criaProjeto(Nome nomeProjeto,
Descricao descricaoProjeto) {
Ganhamos checagem em tempo de compilação. Mas apenas isso? Vamos olhar como esse método seria usado:
new FabricaDeProjeto()
.criaProjeto(new Nome("nome"), new Descricao("descrição"));
O código ficou mais expressivo. Fica bem claro que estamos passando o Nome do projeto e não algo genérico. Com isso, fica muito mais difícil confundir o que passar em cada parâmetro. Estamos fazendo uso do sistema de tipos explicitos do java para evitar problemas. Além disso existem mais vantagens, embora um pouco mais sutis.
Com esse novo design do código, temos uma melhor divisão de responsabilidade. Por exemplo, não queremos permitir que o nome do projeto possua números. Como faríamos isso na primeira forma? Colocaríamos a lógica de validação dentro do método criaProjeto. Agora não precisamos fazer isso. Podemos colocar a lógica de validação dentro do objeto Nome, que é o objeto responsável por tudo relativo à Nome. Colocamos a funcionalidade no lugar onde ela deve ficar. Dividimos melhor a responsabilidade porque podemos adicionar métodos em Nome.
Mas temos um pouco mais de trabalho pra fazer isso, correto? Será que o código não vai ficar lento porque estamos criando vários objetos apenas para encapsular Strings? Não. A forma como o Garbage Colector trabalha, coletando os objetos que devem ser mantidos em memória (e não o contrário) simplesmente não vai ser afetada pela criação dos novos objetos, que possuem vida bem curta.
Podemos ir ainda mais longe. Podemos fazer com que esses pequenos objetos sejam imutáveis. Se eles forem imutáveis, teremos diversos ganhos. Por exemplo, quem estiver manipulando esses pequenos objetos não precisará se preocupar com concorrência. Para objetos imutáveis,
não faz diferença se eles estão em um ambiente concorrente ou não: são thread safe por natureza. Esse ganho é tão importante, que existem linguagens de programação onde você não pode criar "objetos" mutáveis, como Erlang. Além da thread safety, a outra principal vantagem é não termos de nos preocupar com que código de outras pessoas modifiquem nossos objetos, sofrendo efeitos colaterais por causa da invocação de um método e passagem deste objeto como parâmetro. Também não há como nossos objetos ficarem fora de um estado consistente.
Esses pequenos objetos imutáveis podem facilitar muito o desenvolvimento, prevenindo problemas e evitando que por preguiça separemos de forma errada as responsabilidades dos objetos. No começo pode parecer que estamos criando um monte de objetos a mais, mas na verdade não é bem assim. Como esses objetos pequenos possuem responsabilidades bem definidas, fica muito fácil reaproveitar. Imaginem quantos lugares precisam ter descrição? Usaremos o mesmo pequeno tipo. O gasto inicial que temos é bem recompensante conforme o tempo passa.
Tags: design, imutabilidade, Java, oo, tiny types
Alberto 21/07/2009 at 10:21 #
eu acho bem divertido fazer assim, recentemente coloquei um projetinho no github para acessar o delicious e usei Tiny Types para a criação dos objetos de pesquisa e tal… Ficou bem mais fácil de ler e como vc falou, cada um sabe como deve ser, não jogando toda a responsabilidade para apenas um lugar. Além disso, em relação a escrever muitas classes e tal, a pessoa pode ir declarando os tipos dos objetos e ir pedindo para a ide criar a classe… salve o programador preguiçoso!!!
Daniel Tamiosso 21/07/2009 at 10:25 #
Muito bom o post. Realmente o uso de Tiny Types nos leva a um design muito mais coeso e expressivo.
Cássio Oliveira 21/07/2009 at 10:35 #
Até onde o uso dos Tiny Types é saudável? Estou imaginando aqui se para cada parâmetro passado tivéssemos um novo tipo. Type Hell? Onde fica escrito este tipo? Uma inner class na classe correspondente? Tem uma estrutura de diretórios sugerida para guardar estes tipos?
Marcelo Madeira 21/07/2009 at 10:58 #
Me fez lembrar a frase de um amigo:
"Tem gente que ainda programa orientado a String!!!!"
Cássio, acho que criar Tiny Types para todos os parâmetros não é legal. Cada caso é um caso, deve-se estudar a necessidade.
Eu também sou bem favorável a está idéia. Em alguns casos, quando a construção dos objetos envolve a passagem de vários parâmetros, prefiro usar um Builder.
Rodrigo C. A. 21/07/2009 at 12:21 #
Interessante, mas isso se aplicaria apenas com parâmetros dos métodos?
Por exemplo, nessa classe Projeto, imagino que ela tenha os fields nome e descricao com seus getters e setters, esses fields seriam String ou usariam os Tiny Types? No caso de usar os Tiny Types, como ficaria o mapeamento ORM?
Rodnei 21/07/2009 at 15:00 #
O que impede um desenvolvedor de fazer o que segue…?
.criaProjeto(new Nome("descrição"), new Descricao("nome"));
E se Nome tivesse dois parâmetros? Devo aplicar o Tiny e aumentar a quantidade de classes? Não diminuiria muito a legibilidade?
Jonas Abreu 21/07/2009 at 15:06 #
@Alberto e @Daniel
Realmente. A principal razão que me levou a usar Tiny Types foi a melhor separação de responsabilidades. Isso melhorou muito a qualidade do código que eu escrevo.
Em geral costumo criar uma classe pública mesmo. Não tem um estrutura muito definida. Em geral coloco eles junto com meus objetos de domínio (afinal, eles são objetos do meu domínio 🙂 ).
Não sei o que seria o Type Hell. O que acontece é que como esses tipos são muito específicos e bem definidos, é muito fácil reaproveitá-los. Minha classe projeto e produto tem descrição, que pode ser representado pelo mesmo tipo Descrição. Sem contar que se você não criar novos tipos, você já está no String Hell.
Além disso a IDE costuma ajudar muito quando você tem os tipos definidos (coisa que não acontece com string, porque é muito genérica)
Germano 21/07/2009 at 15:33 #
Muito interessante Jonas, eu não conhecia este pattern. Mas me surgiu uma dúvida, a sua classe Projeto possui os atributos com os Tiny Types okay? se você usa Hibernate/JPA como fica o mapeamento? Obrigado.
Jonas Abreu 21/07/2009 at 17:25 #
Não apenas. Você pode aplicar aos atributos da classe e valor de retorno de métodos (ou qualquer outro lugar em que você possa encapsular Strings).
@Rodrigo e @Germano
Para trabalhar com JPA, você pode criar seus user types (https://www.hibernate.org/hib_docs/v3/api/org/hibernate/usertype/UserType.html)
ou anotar os seus tiny types com @Embedable.
Realmente nada impede que o código seja escrito dessa forma, mas fica muito mais fácil encontrar o erro.
Quanto à quantidade de tiny types, tem que pesar bem isso. Dois dos principais objetivos do uso de Tiny Types são melhorar a legibilidade e evitar erros de wiring. Se está prejudicando a legibilidade, talvez tiny types não esteja ajudando muito.
Julien Renaut 21/07/2009 at 19:49 #
Eu não acho que o simples de fato de tornar o código mais legível faz com que ele tenha menos bugs. Mas certamente fica mais fácil corrigí-los.
Quanto aos Tiny Types achei a ideia bem interessante mas como foi colocado pelo Marcelo Madeira o "Builder Pattern" talvez fosse mais adequado.
Na minha humilde opinião por mais que seja possível compartilhar o Tiny Type "Nome" acho pouco provável que as validações sejam uniformes pelo sistema. O que quero dizer com isso é que posso ter restrições diferentes em algo que pode ser encapsulado como "Nome" em diferentes partes do sistema. Para este cenário e da forma que foi colocado o TinyType não poderá ser reaproveitado. Se for, a validação terá que ficar no objeto que recebe o nome novamente.
Por outro lado, usando Builder Pattern podemos garantir as validações específicas de cada objeto embora não fique evidente quais são as chamadas que obrigariamente devem ser feitas antes de construir o objeto de fato. Por exemplo:
Projeto p = ProjetoBuilder.comNome("nome").comDescricao("desc").build();
Não há nada que programaticamente garanta que o nome e a descrição sejam obrigatoriamente informados.
Em suma, cada caso é um caso e devem ser medidas as vantagens e desvantagens. Para um sistema de pequeno e médio porte talvez Tiny Types sejam uma boa ideia. Mas para um sistema muito grande onde facilmente validações podem variar bastante de objeto para objeto me parece inviável.
Jonas Abreu 26/07/2009 at 00:31 #
Concordo com você em várias partes. Realmente existem diversos casos onde não podemos reaproveitar tiny types. Mas eles são objetos bem simples, então não vejo problema na criação de tipos mais específicos deles (como NomeDoProjeto).
O Builder não permite nenhum tipo de reaproveitamento de código. Você sempre terá que colocar todas as regras de validação em todos os builder, mesmo que elas sejam iguais. Builders ainda tem outro problema. Em geral não são implementados levando em consideração ambientes multi-thread (embora isso não seja muito difícil de ser feito), o que pode facilmente causar problemas.
Mas como tudo em computação, devemos pesar bem antes de decidir o que empregar. Tiny Types tem sua aplicação em determinados momentos assim como Builders. Mas não acredito que isso esteja relacionado ao tamanho do projeto (se é que podemos comparar projetos por tamanho). Essas técnicas não são mutalmente exclusivas. Pelo contrário. Elas podem muito bem ser utilizadas juntas.
Alberto 28/07/2009 at 00:22 #
Eu acho que os Tiny Types são tão "bestas" de usar, no bom sentido, que vale a pena na maioria dos projetos. O trabalho extra é muito pequeno e o ganho em legibilidade é muito grande, sem contar os outros já citados… Mas a legibilidade fica tão legal que já se paga… pelo menos na minha opinião.
Julien Renaut 18/08/2009 at 10:05 #
Concordo com você e de fato builders não irão proporcionar reaproveitamento de código. No caso em que a validação do input tenha que ficar no objeto sendo instanciado acho ambos os "idiomas" (builders versus tiny) comparáveis.
Criando tipos mais específicos voltamos a tirar a validação do objeto mas também diminuimos o compartilhamento de código.
Gostaria de deixar um alerta. Até que ponto recursos deste tipo facilitam a vida do programador e até que ponto tratam o mesmo como incapaz de tomar cuidado ao programar?
Acho muito válido pensar em construções e idiomas que nos protejam de erros corriqueiros só que isso não pode ser levado ao extremo. Se não em breve o programador não vai passar de um cara fazendo "content-assist" de tudo que vê pela frente. Não é essa a ideia que eu tenho de nossa profissão e não gostaria de vê-la reduzida à isso.