Fantochada

Dom Quixote lutava contra moinhos de vento. Muitos programadores estão lutando contra fantoches.  Generalizou-se a ideia de que objetos sem métodos outros que não modificadores (set) e acessores (get) são ruins porque são sempre manipulados por outros objetos: são fantoches.

Ora, esta ideia, em si mesma é que é a grande fantochada. Alguns objetos são simplesmente um saco de propriedades e não há nada que possamos fazer contra isso. Embutir artificialmente métodos nesses objetos só para que pareça que eles fazem alguma coisa é simplesmente errado. Ao tentar fugir dos fantoches criam-se monstros piores.

O Principio de Separação de Responsabilidade pede que cada objeto tenha apenas uma responsabilidade. Se a responsabilidade do objeto é apenas ser um saco de propriedades, então que seja. Exigir dele mais do que isso é violar o Principio de Separação de Responsabilidade por querer colocar no objeto coisas que ele não sabe fazer ou não pode fazer.

Um  exemplo clássico do que estou falando é o objeto Usuario. Muitos gostam de colocar um mudarSenha(String senhaAntiga,  String senhaNova). Já vi isto várias vezes. Dois erros:

  1. Porque precisa da senha antiga? O usuário já tem um getSenha que é esse valor. O argumento é que o usuário real tem que saber a senha atual e portanto senhaAntiga tem que bater com o valor em getSenha(). E o que acontece quando não base certo? Lança exception? E se eu não quiser fazer isso?
    Conclusão: é uma decisão que pode ter várias estratégias conforme quem decide, quando e onde. Não ha uma resposta possivel única. Logo, isso deve ser isolado em outro objeto.
  2. Quem muda a senha do usuário? A aplicação. Certo? Não é o próprio usuário que realmente muda a senha. É sempre alguém que o faz por ele. Esse alguem não é o usuário, logo, isso não é responsabilidade da classe usuário. É responsabilidade de um outro agente do sistema, outro objeto.

Outro é o uso do pseudo-ActiveRecord. Por alguma razão que me escapa ao entendimento muitos acham que  ter um método save() no proprio objeto que está sendo guardado, é  uma excelente ideia.  Pode até ser, se for bem implementado. Isso é a essência do padrão ActiveRecord. Só que o ActiveRecord tem contra indicações. Ele não é recomendado para sistemas grandes. Certo. Agora vai dizer que o seu sistema não é grande e portanto você pode usar o ActiveRecord. O sistema pode parecer pequeno agora, mas deixe passar um tempo e verá. ActiveRecord é bom para protótipos que vão – com certeza – ser jogados no lixo. Mas aceitemos que para o seu caso ActiveRecord é uma boa ideia; ai ,muita gente faz assim :


public class ObjectQualquer {

DAO daoObjectoQqualquer;

public void save(){
daoObjectoQualquer.save(this);
}
}

Não vou nem entrar no mérito se deve usar DAO ou Repositorio (para ActiveRecord é DAO mesmo) mas esse código é o exemplo de como é fácil associar responsabilidades aos objetos que eles não têm. Como sei isso? Porque o objeto simplesmente delega a outro. Sem alterar nada, sem produzir nenhum outro efeito. Depois vêm perguntar: “Como injeto o DAO neste objeto?” a resposta é “Porque raios você tem esse objeto ai para começo de conversa?!” É muito difícil chama o DAO apenas quando ele é necessário? Que raio de preguiça é essa?

Não é perguiça. É a falsa noção de que os objetos têm que ter métodos diferentes de get/set. E ai as coisas mais absurdas são feitas para atingir esse objetivo.

Um objecto é composto por três coisas: estado, comportamento e responsabilidade. Responsabilidade é o que os outros objetos esperam que ele faça. Comportamento é como esses objetos pedem que isso seja feito e estado é a forma que o objeto tem de “memorizar” o que fez.

A responsabilidade deve ser apenas uma. Isso é obvio. Quanto mesmos responsabilidade mais simples é o objeto. Lembre-se disto. O comportamento é um contrato publico. É algo do tipo : “quando chamarem por  desta maneira farei este trabalho, desta forma.” Isto não necessariamente significa que existe um método especifico que traduz o comportamento, mas sim que ha um contrato que é acessível por meio de um método. Por exemplo, ArrayList e CopyOnWriteArrayList têm comportamentos diferentes, embora as sua interface (o conjunto de métodos) seja a mesma.

Ao definir um objeto primeiro você define a sua responsabilidade: “Este é o objeto que é responsável por … “, depois como ele vai possibilitar essa responsabildiade. Só quando você o implementa é que você precisa de pensar no estado do objeto.

Mas e os objetos cuja responsabilidade é apenas portar dados? O comportamento deles é dar acesso aos dados e o estado deles são os dados (ou representações dos dados).

Tomemos como exemplo o objeto String. Ele porta dados (caracteres) e tem estado (um array de char) que representa esses dados.  Pronto. Preciso colocar um save() nele só porque ele vai ser gravado em banco de dados? Não. A responsabilidade de String é muito simples: portar caracteres. String é talvez o objeto mais manipulado em Java. Isso, com certeza, não faz dele um objeto fantoche. Então porque o meu TipoProduto que só contém uma String com o nome do tipo e um inteiro com um código para o tipo é um fantoche ? Não é.

Todo este delirio quixotesco de ver objetos fantoche onde só existem objetos simples e incólumes advém de um má interpretação daquilo que significa “comportamento” em um objeto. A mensagem original é muito simples e se prende com outra caracteristica do design orientado a objetos : inversão de controle.  A ideia é o seguinte : não deixe para os outros o que você pode fazer. Um objeto string pode informar quantos caracteres guarda. Isso é trivial, porquê delegar isso a outro objeto? Um objeto String pode informar qual é o caracter que está na posição n da cadeia. Por que delegar isso a outro objeto?

Existem comportamentos que são naturalmente associáveis com a responsabilidade do objeto e por isso o objeto ganha essas sub-responsabilidades. Isto é feito , não porque o Principio de Separação de Responsabilidade é fraco, mas sim porque ele é forte. Se deixassemos outro objeto nos informar de, por exemplo, o tamanho da String, teriamos um objeto com um metodo  objectoX.length(String s). Quando um objeto Y precisasses saber o tamnho da String ele teria que chamar X. Isso cria um acomplamento artificial entre X e Y. Então , atribuindo a responsabilidade ao String esse acoplamento não existe mais. Contudo, isto não significa que os métodos sempre devem ser transportados para o objeto que contém os dados.  O comportamento do método têm que ser natural à responsabilidade principal do objeto.

Um contra exemplo para mostrar isto é o objeto Date. Ele contém uma data. Se eu quiser saber a idade de uma pessoa eu preciso saber a data do seu nascimento. A primeira aproximação seria fazer calculaIdade(Date dataPessoa) e seguindo o exemplo atrás você poderia ser tentado a fazer dataPessoa.calculaIdade(). Ai você bateria de frente com o fato do Java não lhe permitir criar métodos novos em classes existentes e a sua conclusão seria que java é uma porcaria de linguagem e você migraria para ruby onde isso é possível.  Coitado do Java. Na realidade é você que é um péssimo designer. A idade é da Pessoa e não da data de nascimento. O método seria calculaIdade(Pessoa pessoa). E pessoa tem um atributo público que informa a data de nascimento. Claro que a idade é algo que depende de quando fazemos a pergunta. Então precisamos de mais uma informação que é a data atual: calculaIdade(Pessoa pessoa, Date atual)

Utilizando o método acima poderiamos pensar em fazer Pessoa.calculaIdade(Date atual). Seria isso certo? Pessoas podem calcular as suas idades?  Podem.

Animais não sabem calcular as suas idades. Significa isso que Cao.calculaIdade(Date atual) é errado? Não.
Por muito que você goste de abstrair o mundo em objetos e o princípio de separação de reponsabilidade tenha que ser cumprido, ele se aplica a Objectos e não a coisas do mundo. Então, embora um cão não saiba calcular a sua idade, o objecto Cao não É o cão. Ele é uma abstração humana daquilo que um cão É. Portanto, o objeto pode conter responsabilidades que um humano entenderia para o objeto e não as responsabilidades que o ente real, realmente tem. Ou seja, em termos simples: um objeto pode ter mais responsabilidades/comportamentos  que a sua contraparte real.

A mensagem é que diminua ao máximo o acoplamento atribuindo comportamentos naturais aos objetos que já tem no seu modelo. Sim, poderiamos pensar que um comportamento como “capitalize” (coloca todos os caracteres do principio das palavras em maiuscula) é um comportamento natural de um String. E ai você irá xingar o Java de novo por não lhe permitir adicionar esse comportamento. Mas será mesmo que é isso?

Não será que na realidade isso seria o comportamento esperado para um Texto? Ou mais especificamente ainda, para um Titulo? Qual é a diferença entre formatar um data para um String com certo padrão e formater um String para outra String com um certo padrão. Não será que na realidade Capitalize é a responsabilidade de um objeto de formatação?

Não existe uma única resposta para um design. É por isso que se chama design (desenho). Cada um descreve de uma forma. E sim, existe a corrente do Realismos (os objetos são as coisas), do Impressionismo (os objetos parecem as coisas) e até do Cubismo (todos os objetos são cúbicos). Da designer tem o seu estilo, e se funciona não está muito mal. Só que um design não “corre”. Ele funciona abstratamente e não no CPU. Um design que funciona (que é funcional) é aquele com baixa manutenção. E baixa manutenção envolve várias coisas além de gosto artistico por um estilo. Envolve decidir e fazer trade-off de muitas coisas simultaneamente. Se você não tem um estilo artistico de design, pelo menos conheça e use as tecnicas dos designers profissionais.

A proliferação de objetos do tipo bean (apenas como get/set) não é uma questão  derivada de mau design. Ela acontece porque esta é a forma padronizada que criar objectos que são sacos de propriedades (PropertyBag). Muitas outras formas existiriam: como usar Map ou criar uma classe PropertyBag. Contudo, classes genéricas como essas tirariam poder de abstração que é tão importante e pior que isso, tirariam tipagem forte. Repare que fazer bean.getNome() é muito mais forte e refactorável que propertyBag.get(“nome”). O uso de Strings é desaconselhado e por isso o uso de beans é tão comum. Contudo, sempre que o bean puder provêr comportamente extra que lhe seja natural, deve fazê-lo e não delegar isso para outro objeto (normalmente apenas com métodos estáticos). A palavra chave é puder. Às vezes ele quer, e até parece natural, mas ele não pode.

Não há problema nenhum em que um objeto só seja um bean com os seus get/set se isso é realmente o que ele deve ser. O problema real é você ter objetos (classes) apenas com métodos estáticos que fazem operações sobre objetos que um deles poderia fazer sozinho. Essa delegação excessiva é que é realmente o problema, ou colocar sempre os get/set sem pensar se é esse o comportamento esperado/necessário daquele objeto.

Não lutemos então contra fantoches  ou fantasmas anoréxicos que só existem na nossa cabeça.  Alguns objetos são realmente simples e com uma só responsabilidade e poucos comportamentos. Tão poucos que a única grande responsabilidade é guardar dados e prover acesso a essa memória.

Anúncios

13 opiniões sobre “Fantochada”

  1. Parabéns Sérgio, ótimo post!!
    Estou vivendo este dilema neste exato momento em meus projetos onde no passar do tempo fui criando meus “objetos burros” e seus famosos DAO´s, BO´s, VO´s, DTO´s, Services e cia… Hoje com um pouco mais de experiência procuro analisar se determinado objeto precisa realmente ter métodos sem sentido, o grande problema é quando usamos frameworks que nos convidam a satisfazer suas exigências (diga-se de passagem hibernate) que insiste que tenhamos getter´s e setter´s expostos para se valer da reflection. Pergunto: como/qual a maneira mais sensata de manter os objetos do domain model o mais limpo e transparente possível das camadas de infra-estrutura? Confesso que já pensei por diversas vezes usar o Spring que prega justamente isso (não misturar código de infra com código de negócio) mas não consigo me conformar em ficar escrevendo/parametrizando centenas de xml´s pra isso, sei lá, prefiro anotações! Claro que tem gente que não gosta por também de certa forma poluir o código com isso ou aquilo enfim.
    Pra falar a verdade eu tenho atualmente esse exato modelo que você descreveu onde todos os minhas Entity´s herdam uma classe abstrata que fornece os dao´s e métodos como save() e delete() direto na instância e uma porção de métodos estáticos para pesquisa na classe, não estou muito satisfeito com essa implementação por ter que carinhosamente forçar meus pojos herdarem outra classe por outro lado acho muito mais elegante e limpo tascar um pessoa.save() do que espalhar uns PessoaDao.save(pessoa) no sistema. Bom, é isso aí!! Vamos trocar figurinhas???

    1. Sobre exigencia de getters/setters… o JPA não te obriga a expor tudo…

      existem 2 abordagem, na 1° você mapeia por campo, e neste contexto vc é obrigado a deixar todos os seus campos pelomenos protected, os métodos você pode deicha-los private se quiser… nesta abordagem o mapeamento seria assim

      public class Pessoa … {
      @Id
      protected Integer id;
      protected String cpf;
      //…
      }

      Ps.: o campo protected é exigencia do JPA o hibernate consegue usar mesmo se você mapear o campo como private

      em uma segunda abordagem você mapeia as propriedade, e neste caso você é obrigado a manter os métodos pelomenos protected … nesta abordagem o mapeamento seria assim

      public class Pessoa {
      //… sua propriedades, com qualquer nome
      //… ou modificador de acesso
      @Id
      protected Integer getId() {
      //…
      }
      protected void setId(Integer id) {
      //…
      }
      }

      ou seja, existe formas sim, de você não criar os getters ou setters e mapear corretamente seu objeto, o hibernate ainda te da a vantagem de no mapeamento por campos ele não se preucupar com o modificador de acesso…

      1. @Tomaz o problema não é get/set nem o private/protected é a informação dos metadados do modelo. Como informas o JPA que o campo X é chave ? Com @Id Esta anotação (e as outras) cria uma dependencia da api do jpa. Se usares uma anotação que não é do JPA … @key , por exemplo, ele não entende. Como disse o hibernate até dá a opção de criares o modelo via objetos de modelo sem precisar de xml ou anotação, mas é uma feature bem avançada do hibernate.

  2. concordo com vc sergio,
    O desenvolvimento com EJBs principalmente meio que te obriga a nao colocar as responsas nos javabeans, claro q varias coisas simples podem ser delegadas neles.
    Ex: um bean qualquer com uma enum de estados, posso colocar metodos “semanticos”: Contrato.podeSerAprovado e esse metodo avalia os estados, com certeza esse metodo nao deveria estar em lugar nenhum a nao ser no javabean.

    tem os caras que leem sobre ddd e fazem 25 mil coisas pra injetar os Daos nos javabeans e poder invocar

    Contrato.geraFacturacao();

    e esse metodo soh chamar o DAo como vc mesmo falou

    encontrei teu blog agora mesmo

    muito interessante os post

    até

  3. Fala Sergio, post excelente! muito se tem falado sobre objetos sem comportamento.. você foi “contra” a maior parte dos posts que vejo, Ficou do lado da simplicidade e dos princípios.. De qualquer forma acho designer/arquitetura um assunto um tanto quanto complexo. Saber princípios, padrões e soluções ajudam mas não resolvem todos os problemas.. tem horas que falta o bom e velho bom senso.. estudo arquitetura e padrões, além de TDD e programação como um todo..de qualquer forma acho que é algo que pode ser conseguido com experiência e estudo.. Grande abraço

    1. @HigorCesar Saber princípios, padrões e soluções ajudam e resolvem todos os problemas. Se o seu problema não está sendo resolvido é porque vc não está usando os principios certos ou está perante uma situação que pede por um novo principio ( e isto é rarissimo hoje em dia, então o mais provável é que realmente não esteja usando os principios certos 🙂 ) . Não estou dizendo que é facil , mas estou dizendo que não pode desistir à partida. Muitas pessoas dirão que fazer contas de cabeça é dificil. Não para quem sabe fazer. E saber fazer é questão de aprender a fazer.

      @Roberto A forma mais limpa não é com certeza usar Spring. O spring é um agregador de frameworks, então, usando spring vai acabando usando hibernate na mesma. Manter o dominio isolado da infra é feito com um DomainStore. Se vc pensar que o Hibernate já é um domainstore, mas mesmo assim vc quiser isolá-lo mais então vc cria uma camada de DomainStore como se ele fosse um serviço que pudesser ser implementado de N formas sendo hibernate uma delas. Isso é correto do ponto de vista do design, o problema é mais tecnico porque o Hibernate precisa da definição do dominio em anotações ou xml que ficam nos objetos (sim tem uma forma especial para vc não usar isso, mas dá trabalho). Se vc decidir trocar a implementação HibernateDomainStore por outra isso não e´tecnicamente possivel de forma simples. Vc precisa criar um isolamento/abstração do hibernate para depois poder uma várias implementações. Ou seja, vc precisa ter um mecanismo de DomainStore universal e depois especificar para o hibernate. Isto não compensa do ponto de vista custo-beneficio. Vc vai acabar só usando o hibernate mas passou um tempo grande abstraindo-o. Então, o trade-off é : use o Hibernate diretamente. Só que detalhe, apenas o invoque de dentro de repositórios. Assim vc vai centralizar as chamadas e no futuro, lá longe, quando o custo/beneficio for outro, vc pode apenas criar o domainStore universal e modificar apenas os repositorios.
      Claro que vc pode tb optar por ser um visionário( ou louco , depende da sua opinião) e pensar que embora o seu custo e esforço para criar toda a abstração em cima do hibernate é imenso vc vai poder dividir esse custo pelo resto da sua vida em todo e qualquer projeto que vier a seguir. Não só isso, mas quando for criado um substituto do hibernate vc pode fazer as suas aplicações antigas usarem a nova tecnologia sem esforço algum. Ou seja, vc tem um maior custo de desevolvimento da primeira vez que vai diminuindo a cada projeto e um custo de manutenção igual para todas as aplicações novas ou velhas. Isso diminui o efeito legado e os custos a longo prazo.

      É tudo uma questão de equacionar quanto vc quer se esforça e quanto vc quer gastar agora e depois. Normalmente se foca tudo no agora e se esquece o depois. Se vc estiver em posição para pensar no depois, pense bem antes de escolher. Se isso é escolha de outrem , o máximo que vc pode fazer é instruir ( hoje diz-se evangelizar) mas espere nada.

      Ou você poderá usar o MiddleHeaven que provê exactamente um mecanismo de abstração de domainstore. O problema é que se vc considera o hibernate infra, então tb considerá o Storage ToolBox do MiddleHeaven como infra… 😉

  4. Sergio,

    Pelo que vi, em sua opinião, algo como um objeto conseguir fazer uma pesquisa em uma de suas propriedades 1-N por exemplo é algo desaconselhável ?

    Por exemplo

    public class Pessoa {
    private Set amigos;

    public Set getAmigos(Sexo sexo) {
    //….
    }
    }

    claro que isso é so um exemplo… e pesquisas mais complexas adicionadas ao objeto em si, seria um exagero ? O uso de daos ou repositorios por injeção de dependencia para prover essas pesquisas, seria um exagero ?

    1. Não. O problema não é injetar o Repositorio. O problema é colocar responsabilidade errada no objeto. Colocar save/insert no objeto é errado. Deixar o objeto manipular o seu agregado não é. Procurar os amigos de um certo género não é problema se isso é realmente a responsabilidade do objeto. Só que a implementação teria que ser algo como um for sobre o Set amigos. Ou ter um Map.
      Ao injetar o repositorio vc está dizendo que a entidade não sabe procurar os amigos do genero certo mesmo quando ele tem um Set com todos os amigos. Este refetching não é muito próprio. Mas se vc não tiver o Set amigos como atributo e sempre pedir ao repositorio esse tipo de procuras a sua interface principal será mais simples (mais fluente). Eu já usei essa tecnica e funciona para sistema com pouca concorrencia. Com muita concorrencia ha um problema transacional ao pegar a pessoa como um certo set de amigos e depois pegar de novo os de um certo genero. Estes podem não bater com os que foram pegos antes. É por isto que o hibernate usa o objeto session. Ele garante que para aquela “sessão de consulta” todas as pesquisas fazem sentido transacional.
      Usar ou não a injeção de repositorios para fazer pesquisas é um trade-off. Sendo que a responsabilidade por pesquisas é do repositorio não estamos violando nenhum principio. Só que pode ter problemas técnicos como a coerência dos dados.
      O ideal é criar as suas entidades como se eleas existissem sozinhas, da mesma forma que vc programaria um Integer ou um Money. Sem dependências explicitas de terceiros. O repositorio pode depois fazer algumas mágicas para se autoinjetar e modificar o comportamento dos métodos de pesquisa e/ou dos objetos de coleção (como faz o hibernate). Esta solução é melhor no sentido se ser mais facilmente testável e menos acoplada. Mas tecnicamente mais complexa porque precisa mexer com bytecode rewriting e proxy dinâmicos . Mas hoje em dia com a CGLIb isso é quase trivial.

  5. “Não. O problema não é injetar o Repositorio. O problema é colocar responsabilidade errada no objeto. Colocar save/insert no objeto é errado. Deixar o objeto manipular o seu agregado não é.”

    Pergunto,

    O que é Deixar o objeto manipular o seu agregado ?

    1. O aggregado é o conjunto de objetos que estão associados ao objeto principal e que não existem sem ele. Por exemplo Pedido. Pedido tem associados a ele, por exemplo, um cliente, um vendedor e um conjunto de itens. Cada item tem associado um produto.
      Itens só existem no Pedido. Não ha itens soltos que não sejam associados a Pedidos. Itens são objetos agregados a Pedido.
      Os outros objetos – cliente,vendedor, não são agregados, contudo no texto extendi o conceito para me referir a eles também. o tempo certo deveria ter sido “dependencias” ou “objetos relacionados”.
      O objeto pode naturalmente ter acesso aos objetos com quais está relacionado e por conseguinte pode manipulá-los (ler, editar , etc..)

      1. Falaa Sergiooo !!!

        Hummm, na verdade foi a palavra agregado, que me causou um transito de ideias, melhor agora de forma mais detalhada no que você observou ser um conjunto de objetos.

        Obrigadooo !!!

        + uma Vez !!!

Deixe uma Resposta

Please log in using one of these methods to post your comment:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão /  Alterar )

Google+ photo

Está a comentar usando a sua conta Google+ Terminar Sessão /  Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão /  Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão /  Alterar )

w

Connecting to %s