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

Postado em 08. out, 2010 por em Agile, Arquitetura

É 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.

Tags: , , , , , , ,

15 Respostas para “Em busca do nome adequado: métodos, variáveis e classes”

  1. Tiago Farias

    08. out, 2010

    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. out, 2010

    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. out, 2010

    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. out, 2010

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

  5. Guilherme Silveira

    10. out, 2010

    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. out, 2010

    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. out, 2010

    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. out, 2010

    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. out, 2010

    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. out, 2010

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

  11. André Thiago

    14. nov, 2010

    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. nov, 2010

    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. jul, 2011

    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 :)

Trackbacks/Pingbacks

  1. Para um bom programador, meia palavra basta? | Breno Martinusso - maio 12, 2011

    [...] Referente a esse problema, indico um excelente post do Guilherme Silveira: http://blog.caelum.com.br/em-busca-do-nome-adequado-metodos-variaveis-e-classes/ [...]

  2. Em busca do nome adequado: métodos, variáveis e classes | Leonardo Cotta - julho 29, 2011

    [...] de : http://blog.caelum.com.br/em-busca-do-nome-adequado-metodos-variaveis-e-classes/ Share on Facebook Tweet by leonardopcs | Leave a comment | Uncategorized ← The Social [...]

Deixar uma Resposta