Em busca do nome adequado: métodos, variáveis e classes

É muito comum após alguns dias de trabalho em um projeto perceber que as escolhas de nomes de classes e métodos não condizem com o que cada um representa. Isso acontece pois, com o passar do tempo, aumenta o nosso conhecimento sobre o domínio do problema.

Também é natural surgir o desejo de mudança: a medida que nos especializamos nesse domínio, o modelo do negócio em nossa mente passa a ser diferente da visão inicial.

Um exemplo simples dessa mudança acontece quando em OO criamos um relacionamento bidirecional entre duas classes através de listas, como no exemplo a seguir:

// a representação de um cliente, funcionário etc
public class Pessoa {
  List<Conta> contas;
}

// a representação de uma conta bancária (poupança, corrente, investimento etc)
public class Conta {
  List<Pessoa> pessoas;
}

A primeira vista esse é o relacionamento entre as duas entidades mas em determinado momento notamos que na verdade a relação entre pessoa e conta é um Titular, que possue características próprias, como o contrato que ele como titular da conta assinou:

public class Pessoa {
  List<Titular> titularidades;
}
public class Conta {
  List<Titular> titulares;
}
public class Titular {
  Conta conta;
  Pessoa pessoa;
  // outras características de uma titularidade
}

Nesse caso o processo de refatoração envolve extrair uma classe que representa tal relação, um Titular: um exemplo simples que demonstra como em OO é comum a existência de classes para representar informações intangíveis.

Outras questões simples mas fundamentais para o bom design e legibilidade da aplicação estão ligados aos nomes que escolhemos, não só em relação ao nome da classe e de seus métodos. No exemplo acima, por uma escolha fraca no nome da variável membro conta e pessoa acabamos com um código que parece repetitivo e pouco descritivo:

  Pessoa pessoa;

Se em nosso sistema funcionários, gerentes, titulares e caixas são representados através de instâncias do tipo Pessoa, o mais adequado seria nomeá-los de acordo:

public class Titular {
  Conta conta;
  Pessoa cliente;
}

A tendência natural do desenvolvedor em linguagens que obrigam a declaração do tipo é criar variáveis com nome identicos ao tipo declarado, por ser um atalho das IDEs e mais simples de escrever. Mas tais alternativas, como visto, dizem pouco sobre a variável que está sendo acessada. Por esses motivos, é muito comum encontrar código como:

Cliente cliente = clienteDao.busca(13);
Pagamento pagamento = pagamentoDao.busca(15);
ProcessadorDePagamento processador = 
                                new ProcessadorDePagamento();
processador.processa(cliente, pagamento);

Cliente, pagamento, processador: é somente desses três elementos que nosso domínio é composto? E nem mesmo Ruby foge disso, sendo comum encontrar:

cliente = Cliente.find_by_id 13
pagamento = Pagamento.find_by_id 15
processador = Processador.new
processador.processa cliente, pagamento

Três aparições de cliente, três de pagamento, quatro de processador. Aqui existe uma visão distorcida de que variáveis são somente aquilo que o tipo descreve: um cliente, um pagamento, um processador. Na verdade elas referenciam instâncias específicas e especializadas de um tipo determinado. Só faria sentido chamar o cliente 13 de cliente se ele é o único cliente, um singleton. Especialize o nome de suas variáveis e crie um código mais legível:

Cliente devedor = clientes.busca(13);
Pagamento fatura = pagamentos.busca(15);
ProcessadorDePagamento mensalidade =
                            new ProcessadorDePagamento();
mensalidade.cobra(devedor, fatura);

O mesmo pode ser refletido no exemplo de Ruby. E como trabalharemos nossos repositórios para permitir uma maior legibilidade do código?

Pessoa pessoa = ...;
List<Conta> contas = dao.contasDe(pessoa);

Note como por causa da escolha do nome da variável pessoa fomos obrigados a colocar algo mais descritivo no método. Caso a variável possua um nome mais adequado, teríamos então:

Pessoa cliente = ...;
List<Conta> contas = dao.contasDe(cliente);

Modificando nossa classe Conta para adicionar o gerente e o subgerente temos novamente o sentido das variáveis descrito não só pelo seu tipo, mas também pelo seu nome:

public class Conta {
  List<Titular> titulares;
  Pessoa gerente;
  Pessoa subgerente;
}

Nesse instante a query ficaria mais complexa pois existem dois relacionamentos distintos com pessoa, requerendo a criação de uma DSL interna para query. No exemplo a seguir usamos um builder para facilitar a construção da query:

Pessoa gerente = ...;
Pessoa subgerente = ...;
List<Conta> contas = 
               dao.comGerenteESubgerente
                            (gerente, subgerente):

Essa é a abordagem que o jbehave 3.2 e o selenium utilizam, onde acabamos por ter uma sintaxe muito longa e repetitiva.

Retomando o problema do nome das variáveis, que pode ser resolvido com outra refatoração de rename: o código da terceira linha é mais descritivo que o anterior:

Pessoa gerente = ...;
Pessoa subgerente = ...;
List<Conta> contas = 
               dao.comGerente(gerente)
                    .comSubgerente(subgerente):

Em linguagens onde mapas podem ser representados através de literais, o ruído sintático é baixo e permite que seja escrito código como a seguir em Ruby:

gerente = ...
subgerente = ...
contas = Conta.com
              :gerente => gerente, :subgerente => subgerente

Novamente, a refatoração aqui é fundamental:

principal = ...
secundario = ...
contas = Conta.com
              :gerente => principal, :subgerente => secundario

A escolha dos nomes de nossas classes influencia o design e legibilidade de nosso código direta e indiretamente, primeiro pela declaração das variáveis e a nossa visão do domínio. Depois pelas consequências que o mesmo possui na escolha dos nomes das variáveis e métodos, que permitem um design mais ou menos legível.

Essa escolha é tão importante quanto o resto de seu design para facilitar a compreensão do que seu domínio é e qual o problema que está tentando atacar. Na próxima vez que for declarar uma variável com o mesmo nome que o tipo, pense duas vezes.

15 Comentários

  1. Tiago Farias 08/10/2010 at 12:13 #

    Excelente post! Estava lendo sobre essa assunto no tectura essa semana… Essa coisa de declarar a variável com o mesmo nome do tipo parece mesmo uma doença! Esses posts e discussões são a cura pra isso =]
    Cada dia mais penso em como deixar o meu código o mais fluente possível. Keep it up!

  2. Rafael Ponte 08/10/2010 at 14:18 #

    Excelente post, Guilherme.

    Dar nome à classes, métodos e variáveis nem sempre é fácil, principalmente quando se trabalha com um domínio complexo e há muitos ruídos na comunicação.

    Porém mais importante que dar nomes apropriados aos artefatos no software é mante-los atualizados (em sincronia) com o entendimento do negócio e a linguagem ubíqua utilizada pela equipe. O que tenho notato é que muitos desenvolvedores preferem ter o desgate e ruído na comunicação a ter que alterar (refatorar) código para manter a sincronia com o negócio, e esquecem, ou não sabem, que este problema na comunicação só tende a piorar com o tempo.

    Enfim, este é um assunto que sempre me interessou muito e este post é altamente recomendado.

  3. Felipe Affonso 08/10/2010 at 14:41 #

    Muito legal, parabéns.
    Legal desse tipo de post, por não ser mais um “TUTORIAL DE FRAMEWORK X”.
    Impressionante o jeito como vocês conseguem evoluir os códigos, práticas internas da Caelum, e por consequência levam toda a comunidade a oportunidade de evoluir junto. Seja pelos treinamentos pagos, seja pelas iniciativas gratuitas como por exemplo esse blog, entre outras.

    Li as apostilas do FJ-11, FJ-28, e outras… muito me ajudaram no início de minha jornada. Fiz curso de arquitetura e design na Caelum, e sigo aprendendo com vocês.

    Obrigado pela iniciativa!

  4. Daniel 09/10/2010 at 21:57 #

    Excelente post!!
    Parabéns pra Caelum por compartilhar conhecimento, já evolui muito como
    profissional com aprendizado que adquiri neste blog

  5. Guilherme Silveira 10/10/2010 at 09:15 #

    Obrigado Felipe, Tiago e Daniel!

    Pois é Rafael, essa sincronia que é fundamental. Sabemos que o nosso conhecimento do dominio melhora com o tempo, e a refatoração eterna garante a melhoria do código de acordo com essa melhora.

    Abraços

  6. Christian 19/10/2010 at 09:45 #

    Só uma observação Guilherme:

    A refatoração:

    List contas = dao.comGerente(gerente).comSubgerente(subgerente):

    Acaba por criar um problema: a chamada a dao.comGerente(gerente deveria retornar um List. Esta interface não permite a chamada a comSubgerente(subgerente).

    Para fazer isso, eu deveria decorar o List com algum outro Objeto, certo?

    (Estou assumindo que comGerente(gerente) e comSubgerente(subgerente) podem ser chamados independente da ordem, evitando o Sequential coupling).

    []´s

  7. Guilherme Silveira 20/10/2010 at 14:26 #

    Oi Christian,

    Perfeito: se a abordagem citada utilizar o retorno de comGerente de uma List de Java padrão, não temos a definição de comSubgerente. Por isso a criação de DSLs em linguagens com tipagem definida como no Java (forte e estática), precisamos tomar alguns cuidados.

    Uma solução é fazer dao.comXpto() devolva a própria interface do dao (que deve ter um nome mais educado, como Contas). Então eu teria Contas.comGerente(x).comSubgerente(y) e o encadeamento é livre. Como Contas implementa a interface Iterable, também é possível iterar nela…
    Outra opção é devolver uma List com métodos parciais, ou ainda um Set.

    Apesar de devolver algo com a mesma interface, internamente os objetos são imutáveis e são sempre devolvidos novos objetos com a query parcialmente construida. Um exemplo disso em ruby é o relata: http://github.com/caelum/relata

    O que acha?

    Abraço!

  8. Christian 21/10/2010 at 16:31 #

    Oi Guilherme.

    Realmente Ruby é fantástico, vou analisar o relata com calma.

    No caso do método comGerente(x) devolver devolver a interface do dao (Fluent Interfaces) eu acabo por misturar uma Interface de um Dao (afim de promover o encadeamento) com uma interface Iterable, para percorrer as contas. Me veio a mente ValueListHandler, mesmo não sendo bem isso.

    Cada encadeamento acaba por ser um filtro da chamada anterior, sendo que a primeira chamada faz a busca no repositório de dados e as demais vão filtrando o resultado (e sendo encadeados), e então não tenho dependência na ordem em que eu chamo os métodos e mantenho o encadeamento.

    É isso mesmo ou viajei?

    []´s

  9. Guilherme Silveira 22/10/2010 at 08:23 #

    Tudo bem Christian?
    É isso mesmo, um builder de filtros para fazer a busca! Olha o código do Relata que você vai ver isso acontecendo.

    Abraço!

  10. Vinicius Viana 27/10/2010 at 15:20 #

    Ótimo post, é muito comum pegarmos códigos como o exemplificado no cotidiano ;D

  11. André Thiago 14/11/2010 at 10:59 #

    Belo post!

    No caso de dao.comGerente(gerente).comSubgerente(subgerente), essa implementação seria possível se o dao for stateless?

    Imagine ainda se o dao for um bean gerenciado pelo Spring Framework, e este fornecer, via DI, a session do Hibernate. Se no retorno de comGerente(gerente) eu retornar uma nova instância do dao (dando um new, por exemplo) eu perderia o gerenciamento do Spring – e, por consequência, a DI.

    Além do mais, eu não sei se o uso de fluent interface é adequado pada dao’s. Fazendo dao.comGerente(gerente).comSubgerente(subgerente) – corrija-me se eu estiver enganado – eu faço uma consulta em comGerente e depois outra consulta em comSubgerente. Ou seja, faço 2 acessos ao banco de dados onde apenas um resolveria.

  12. Guilherme Silveira 16/11/2010 at 15:55 #

    Tudo bem André.

    Você está correto. Em implementações mais simples de DAO, isso poderia ficar sujinho ou até mesmo virar um caos. Mas isso é questão de bom design de DSLs e não do DAO em si:

    Os dois exemplos que você citou:

    bom design 1: seu objeto de busca (chamamos ele de DAO, poderia ser outra coisa) ser imutável.
    Isso implica que a cada chamada de comXpto eu devolveria um novo objeto e não alteraria valores internos do atual

    bom design 2: a busca não é feita até o dado ser usado
    Isso implica que chamar comXpto não faz a query, so cria um novo objeto com o pedaço da criteria concatenado. Depois na hora de chamar size ou iterator, ele executa a query.

    Abraço!

  13. Escola de Poker 16/07/2011 at 09:29 #

    Boa tarde:), o meu nome é Sofia estudo Engenharia Civil e adorei imenso da tua página! Muito bonito sim senhora!
    Adequa-se muito bem tudo aquilo que aqui li.Hoje sempre muito para expressar nos blogues!Nada melhor do que implementar a nossa marca na net!
    Até à próxima 🙂

Deixe uma resposta