Tell, don’t ask (ou… fique longe das minhas propriedades)

Email

O "Tell, don’t ask" (diga, não peça) é um dos princípios da orientação a objetos que considero mais importantes, e também um dos mais difíceis de ver aplicado. Ele define claramente a diferença entre um sistema procedural e um sistema orientado a objetos. Vou explicar porque.

Uma linguagem é orientada a objetos porque, além de suportar todos os requerimentos formais, possui objetos como conceitos de primeira importância. Programar com uma linguagem orientada a objetos nos permite usar estes conceitos. Mas isso não significa que faremos isso. É absolutamente possível programar uma linguagem orientada a objetos de forma procedural, basta que você ignore o fato de que um objeto é, além de dados, também comportamento. Tratando os objetos como meros pacotes de dados você está efetivamente programando orientado a procedimentos. É aí que entra o "tell, don’t ask".

Quando você diz a um objeto que deve fazer algo, é o objeto, dentro de suas regras internas e do seu encapsulamento, que vai fazer este algo. A outra opção é não dizer, mas pedir uma informação, e então decidir o que deve ser feito com ela.

Por exemplo, suponha um procedimento de emissão de notas fiscais:

public class NF
{
    public IList<Item> Itens {get; set; }
}

Você está acrescentando itens à nota:

var nf = ObterUmaNF();
var item = ObterItem();
nf.Itens.Add(item);

Esse é um código muito comum, certo? Mas ele viola diretamente o encapsulamento da classe de nota fiscal. Em vez de dizer à nota fiscal que deve acrescentar um item, estamos criando o item diretamente, e acrescentando ele. Estamos pedindo sua lista de itens, e acrescentando nós mesmos o item à lista. Esse código é procedural, não OO.

Há algumas alternativas para resolver isso. Mas antes de mais nada primeiro vamos deixar a lista de itens oculta na classe de NF:

public class NF
{
    protected IList<Item> ItensInterno {get; set; }
}

Dessa forma ninguém vai poder acrescentar itens à nossa lista de itens. "Mas como vamos poder acessar os itens da NF?", você deve estar se perguntando. Bom, se você realmente precisar acessar esses itens (e essa questão vai dar argumento para outro post), você pode expô-los via um enumerador. Um enumerador não permite inclusões:

public class NF
{
    protected IList<Item> ItensInterno {get; set; }
    public IEnumerable<Item> Itens {get { return this.ItensInterno; } }
}

Para incluir um item é fácil. Nós dizemos à classe de NF que queremos criar um novo item, ou passando ele diretamente (estou ocultando o resto):

public class NF
{
    //lista e enumerador ocultos
    public void AdicionarItem(Item item)
    {
        this.ItensInterno.Add(item);
    }
}

Ou passando os dados necessários à inclusão do item e deixando a criação do item para a classe de NF.

public class NF
{
    //lista e enumerador ocultos
    public Item AdicionarItem(Produto produto, int quantidade, decimal preco)
    {
        var item = Item.Criar(produto, quantidade, preco); //algum factory method
        this.ItensInterno.Add(item);
        return item;
    }
}

Há cenários onde a primeira abordagem é melhor (o item já vem criado), principalmente se você está trabalhando com uma ligação mais simples, e uma camada de façade não baseada em DTOs. No segundo caso você está indo ainda mais a fundo, ao dizer que à classe de NF que quer incluir um item, mas sequer sabe como criá-lo. Ela ainda é gentil e te devolve o item criado, caso você queira atuar sobre ele.

Nesse cenário temos claramento dados encapsulados, e comportamento (incluir itens de nota fiscal). Nesse método está toda a regra de negócio necessária à inclusão de um item. Regras como a verificação do preço, por exemplo, poderiam ser acionadas de imediato.

Pedir a um objeto que revele detalhes de seu estado interno acaba muitas vezes levando a uma abordagem procedural. Não estou dizendo que essa é uma regra, mas é algo para se atentar. Sempre que acessar uma propriedade, verifique se não está tomando uma responsabilidade que deveria estar em outro lugar. Veja se não está violando o encapsulamento de uma classe.

Para deixá-los um pouco mais com a pulga atrás da orelha, vou apresentar um caso onde praticamente todos os desenvolvedores ASP.Net já violaram o princípio "tell, don’t ask": validadores do webforms. A não ser que você tenha começado a usar ASP.Net agora, e caiu diretamente no ASP.Net MVC, vou já usou um validador. E validadores são um exemplo clássico de violação de encapsulamento. Quem deve validar o estado interno de uma entidade é ela mesma, não um formulário na interface gráfica (?!). Um validador viola tão profundamente o conceito de OO, que ele sequer pede à entidade por seu estado, ele primeiro determina se o valor é valido ou não, e só depois envia o dado à entidade. Mais procedural impossível.

E por fim, gostaria de lembrar porque sou tão contra trabalhar banco de dados antes de trabalhar o domínio: como o foco é o dado, você acaba pedindo dados o tempo tudo, muito mais do que dizendo a um objeto o que fazer.

Email
Esse post foi publicado em Sem categoria e marcado por Giovanni Bassi. Marcar link permanente.

Sobre Giovanni Bassi

Arquiteto e desenvolvedor, agilista, pai, filho, namorado, escalador, provocador.
Programa porque gosta, e começou a trabalhar com isso porque acha que trabalhar como administrador é meio chato. Por esse motivo sempre diz que nunca mais vai virar gerente de ninguém. E também porque acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é melhor que hierarquia. Mas isso é outro assunto.
Foi reconhecido  Microsoft MVP depois que alguém notou que ele não dormia a noite pra ficar escrevendo artigos, cuidando e participando do .Net Architects, gravando o podcast Tecnoretórica, escrevendo posts no blog e falando o que bem entende no twitter @giovannibassi. E por falar nisso é no twitter que conta pra todos que gerencia de projetos deve ser feita pelo time e não por um gerentes, que greves em TI são coisas sem sentido e que stored procedure com regras de negócio são malígnas.
Você já deve ter percebido (até porque está lá na primeira frase) que Giovanni é agilista. De tanto gostar disso ele trouxe os programas de certificação e treinamento  PSD e PSM da Scrum.org pro Brasil, e por causa deles, do MVP e de algum trabalho que aparece tem que ficar indo pros EUA de vez enquando, coisa que prefere não fazer. (É bem comum você ouvir ele perguntando porque a Scrum.org e a Microsoft não estão na Itália, por exemplo.)
Junto com alguns Jedis criou a Lambda3, que, apesar de ser pequena e de não ser muito comum no Brasil, insiste em fazer projetos e consultoria direito. Por causa da Lambda3 ele tem trabalhado mais do que quando era consultor independente, mas menos do que a maioria das pessoas. Quer dizer, isso se você considerar que os trabalhos junto à comunidade não são trabalho, caso contrário ele trabalha mais que a maioria das pessoas.
Recentemente ele resolveu que merecia viver melhor e ganhar uns anos de vida e desistiu de ser sedentário, fazendo algum barulho de vez em quando com os amigos no twitter com a hashtag #DotNetEmForma. Por causa do convite recente de amigos do lado Open Source (que ele respeita e admira), começou a escalar, e agora está sempre com as mãos machucadas. Mas ainda dá pra programar. Você encontra ele sempre em algum evento, como o TechEd, e o DNAD, mas também outros menos comuns para o pessoal do .NET, como a RubyConf. Nesses eventos, ou ele está vendo palestras, ou batendo papo com alguém, ou codando alguma aplicação que alguém achou que dava pra fazer durante o evento.
  • http://www.mgrtconsultoria.com/ fabiomargarito

    Muito bom o post parabéns. Todo mundo diz que programa orientado a objetos, mas a verdade é o que você falou, programas basicamente procedurais e lotados das malditas classes utiliy. Com relação a parte de banco de dados, acho que é possível trabalhar e concordo contigo, só é um pouco mais complicado, pois o um conceito acaba poluindo o outro, mas defendo, em empresas com dezenas de equipes e que tenha bases de dados comuns, que exista o papel do Analista de Dados, para evitar redundância de informação, e ter padrões.

    []‘s

  • escosta

    legal o post, dicas interessantes e importantes, muitos acham que implementa OO puro, falta muito para saber do "paradigma OO". Legal também com relação ao "mas código" nos post. Parabéns.

  • Marco Souza

    Show de post, isso é pura verdade, muitos acham que estão programando OO e na verdade o codigo não passa de procedural e macarrônico.

  • http://www.perlink.com.br/ Alexandre Valente

    Oi Giovanni,

    Apesar de concordar com vc em teoria, na prática eu não acho que seja possível ser assim. O maior problema é de ordem prática, já que a modelagem puramente OO é extremamente complexa de ser realizada no sentido que vc não sabe quem é responsável pelo que. Imagine um cenário fictício, vc tem um carro e um pneu modelado. Vc quer fazer um método de CalibrarPneu()… Onde ele ficaria, no pneu ou no carro? Vc pode argumentar pelos dois lados! E assim por diante… Outro problema é a questão de serviços, em DDD vc deve isolar muito bem a camada de serviços… assim suas entidades são POCO, sem métodos. Ou seja, na teoria ótimo, na prática é muito dificil programar OO. Na minha visão, regras de negócio são procedurais por natureza, e a camada de serviços é um local muito mais tranquilo de se colocá-las do que tentar encaixar nos objetos… Pelo menos na tecnologia atual! :-) . Abs!

  • http://unplugged.giggio.net/ Giovanni Bassi

    Oi Alexandre,
    Respostas:
    Colocaria o método na classe "Frentista". Quem calibra não é o pneu nem o carro. Talvez trabalharia com double dispatch para completar a ação. Simples e sem nenhuma dúvida. Segue o mundo real, como deveria.
    POCO não são classes sem métodos. São classes que não devem implementar nenhuma interface ou mudar o design para se adaptar a nenhum requerimento técnico, geralmente o ORM.
    Uma classe sem métodos é a base de um modelo anêmico, que por sua vez é a base da programação procedural, e não OO.
    Os serviços são ações de domínio, e só contém regras que não cabem nas entidades. Em nada são isolados do resto do domínio. São parte dele, só que são ações sem entidades. E se só fizermos ações sem entidades, não é OO, ou seja, muito cuidado com os serviços.

  • http://www.alcateiadigital.blogspot.com/ Lobo Jr

    Ótimo post. Eu também concordo que um modelo de domínio rico é a melhor solução pra modelar regras de negócios complexas. Entretanto, só pra complementar: Os validators apenas são uma maneira mais rápida e leve de validar as coisas, mas estas regras devem estar no mínimo duplicadas no domínio, pois estes Jamais podem ficar inconsistentes, ou exceptions irão rolar! :-P

  • Régis Soares

    Olá, Giovanni, primeiramente, excelente resposta (ao argumento do Alexandre). Nesse caso, entendo que a classe Frentista nada mais é do que um Serviço (caso Frentista não seja uma entidade do domínio), certo?

    Tenho uma dúvida, quanto a expor a propriedade Itens do tipo IEnumerable. Nesse caso, ela não funciona com o NH, pois internamente ele precisa ter acesso (set) a essa propriedade para popular Itens. O que sugere nesse caso?

  • http://unplugged.giggio.net/ Giovanni Bassi

    Régis, é isso mesmo. Mas se frentista não for uma entidade, então o serviço deveria ser uma classe, geralmente com nome de algum verbo, para deixar mais explícito.
    O NH consegue acessar propriedades protected numa boa. Resolvido. :)

  • http://prodis.pro.br/ Prodis a.k.a. Fernando Hamasaki de Amorim

    Esse é um pequeno exemplo de Aggregates em DDD, onde a classe NF é o Aggregate Root. Muito bom.

    Concordo plenamente que os ASP.NET Validators violam o príncipio. Agora quem os criou e nos incentivou a usá-los? Quem criou os WebControls para fazer ligação direta com banco de dados? Quem tem cursos oficiais para certificação que ensinam a acessar banco de dados diretamente da UI?

    É por isso que precisamos cada vez mais de iniciativas como as do .NET Architects para mudar esse cenário.

  • Márcio Rezende

    Caro Giovanni,

    Achei excelente o post, pois tocou num ponto muito interessante da OO. Fiquei com uma dúvida quando você fala do modelo anêmico.
    Por exemplo, se eu tenho uma classe que representa uma nota fiscal e uma outra classe que é responsável por criar novas instâncias da NF e recuperar ou gravar os dados dessa nota numa base de dados qualquer, esse seria um modelo anêmico?

  • http://unplugged.giggio.net/ Giovanni Bassi

    Marcio, não. Um modelo anêmico seria se você separasse o comportamento da NF da classe de NF, colocasse ele em outra classe.
    O que você está dizendo é um trabalho normal de persistência de uma entidade.

  • http://blog.ricardoserradas.net/ Ricardo Serradas

    Fala Giggio!

    Post nota 10! Assunto interessantíssimo. Além do que, estou num trabalho de fortalecimento dos conceitos de OO com o time aqui, não sabe o quanto seu post vai ajudar! :-)

    []‘s