Clean Code (Código limpo) – Classes

Apesar de toda a atenção à expressividade dos códigos e das funções que o compreendem, ainda não temos um código limpo até que tenhamos prestado atenção aos níveis mais elevados de organização do código. Então agora vamos falar sobre Classes limpas.

Organização da Classe

Uma classe deve começar com uma lista de variáveis. As constantes estativas pulicas, se houver, devem vir primeiro. Em seguida, variáveis estáticas privadas, seguidas por variáveis privadas. Raramente há um bom motivo para ter uma variável publica.

As funções públicas devem seguir a lista de variáveis. Gostamos de colocar os serviços privados chamados por uma função pública logo após a própria função pública. Isso segue a regra de redução e ajuda a leitura do programa como um artigo de jornal.

Encapsulamento

Gostamos de manter nossas variáveis e funções de suporte privadas, mas não somos fanáticos por isso. Às vezes, precisamos proteger uma variável ou função de suporte para que possa ser acessada por um teste. Para nós, os testes são uma regra.

No entanto, primeiro procuraremos uma maneira de manter a privacidade. Afrouxar o encapsulamento é sempre o último recurso.

As classes devem ser pequenas!

A primeira regra das classes é que elas devem ser pequenas. A segunda regra das classes é que elas devem ser menores do que isso. Não, não vamos repetir exatamente o mesmo texto do artigo de Funções. Mas, como acontece com as funções, menor é a regra principal quando se trata de projetar classes. Tal como acontece com as funções, nossa pergunta imediata é sempre “Quão pequeno?”

Com funções, medimos o tamanho contando as linhas físicas. Com as classes, usamos uma medida diferente. Contamos responsabilidades.

Por exemplo, cinco métodos não são demais, não é? Em alguns casos sim, porque apesar de seu pequeno número de métodos, uma classe com cinco métodos pode te muitas responsabilidades.

O Princípio da Responsabilidade Única

O Princípio de Responsabilidade Única (SRP do inglês) afirma que uma classe ou módulo deve ter um, e apenas um, motivo para mudar. Este princípio nos dá uma definição de responsabilidade e diretrizes para o tamanho da classe

Tentar identificar responsabilidades (motivos para mudar) muitas vezes nos ajuda a reconhecer e criar melhores abstrações em nosso código e remover da classe informações que tratam de outras responsabilidades

SRP é um dos conceitos mais importantes em design OO. É também um dos conceitos mais simples de entender e seguir. Ainda assim, estranhamente, SRP é frequentemente o princípio de design de classe mais abusado. Regularmente encontramos classes que fazem muitas coisas.

Fazer o software funcionar e torná-lo limpo são duas atividades muito diferentes. A maioria de nós tem espaço limitado em nossas cabeças, então nos concentramos em fazer nosso código funcionar, mais do que organização e limpeza do mesmo. Mas manter uma separação de interesses é tão importante em nossas atividades de programação quanto em nossos programas.

O problema é que muitos de nós pensam que terminamos uma atividade assim que o programa funcionar. Deixamos de lado a preocupação de organização e limpeza. Passamos para o próximo problema, em vez de voltar e dividir as classes sobrecarregadas em unidades dissociadas com responsabilidades únicas.

No entanto, um sistema com muitas classes pequenas não possui mais partes móveis do que um sistema com algumas classes grandes. Há muito o que aprender no sistema com algumas classes grandes. Portanto, a questão é: Você deseja que suas ferramentas sejam organizadas em caixas de ferramentas com muitas gavetas pequenas, cada uma contendo componentes bem definidos e bem identificados? Ou você quer algumas gavetas em que apenas joga tudo dentro?

Cada sistema de tamanho considerável conterá uma grande quantidade de lógica e complexidade. O principal objetivo no gerenciamento de tal complexidade é organizá-la de forma que um desenvolvedor saiba onde procurar para encontrar as coisas e precise apenas entender a complexidade diretamente afetada em um determinado momento. Em contraste, um sistema com classes maiores e polivalentes sempre nos atrapalha, insistindo que devemos examinar muitas coisas que não precisamos saber agora.

Queremos que nossos sistemas sejam compostos de muitas classes pequenas, não algumas grandes. Cada pequena classe encapsula uma única responsabilidade, tem um único motivo para mudar e colabora com alguns outros para atingir os comportamentos de sistema desejados.

Coesão

As classes devem ter um pequeno número de variáveis de instância. Cada um dos métodos de uma classe deve manipular uma ou mais dessas variáveis. Em geral, quanto mais variáveis um método manipula, mais coeso ele é para sua classe. Uma classe em que cada variável é usada por cada método é maximamente coesa.

Quando a coesão é alta, isso significa que os métodos e variáveis da classe são co-dependentes e se unem como um todo lógico.

A estratégia de manter as funções pequenas e as listas de parâmetros curtas pode às vezes levar a uma proliferação de variáveis de instância que são usadas por um subconjunto de métodos. Quando isso acontece, quase sempre significa que há, pelo menos, uma outra classe tentando sair da classe maior. Você deve tentar separar as variáveis e métodos em duas ou mais classes, de forma que as novas classes sejam mais coesas.

Mantendo Resultados de Coesão em Muitas Classes Pequenas

Considere uma função grande com muitas variáveis declaradas dentro dela. Digamos que você queira extrair uma pequena parte dessa função em uma função separada. No entanto, o código que você deseja extrair usa quatro das variáveis declaradas na função. Você deve passar todas essas quatro variáveis para a nova função como argumentos?

De jeito nenhum! Se promovêssemos essas quatro variáveis a variáveis de instância da classe, poderíamos extrair o código sem passar nenhuma variável. Seria fácil quebrar a função em pequenos pedaços.

Infelizmente, isso também significa que nossas classes perdem coesão porque acumulam mais e mais variáveis de instância que existem apenas para permitir que algumas funções as compartilhem. Mas espere! Se houver algumas funções que desejam compartilhar certas variáveis, isso não as torna uma classe por si só? Claro que sim.

Quando as classes perderem coesão, divida-as!

Portanto, dividir uma função grande em muitas funções menores geralmente nos dá a oportunidade de dividir várias classes menores também. Isso dá ao nosso programa uma organização muito melhor e uma estrutura mais transparente.

Agora imagine uma classe longa, com muitas centenas de linhas.
– Primeiro, refatoramos usando nomes de variáveis mais longos e descritivos.
– Segundo, refatoramos usando declarações de função e classe como uma forma de adicionar comentários ao código.
– Terceiro, refatoramos usando espaços em branco e técnicas de formatação para manter o programa legível.

Esta não foi uma reescrita! Não começamos do zero e escrevemos o programa novamente. Na verdade, se você olhar atentamente para os dois programas, antes e depois, verá que eles usam o mesmo algoritmo e mecânica para realizar seu trabalho.

A mudança foi feita escrevendo um conjunto de testes que verificou o comportamento preciso do primeiro programa. Em seguida, uma miríade de pequenas mudanças foi feita, uma de cada vez. Após cada alteração, o programa foi executado para garantir que o comportamento não mudou. Um minúsculo passo após o outro, o primeiro programa foi limpo e transformado no segundo.

Organização para a mudança

Para a maioria dos sistemas, a mudança é contínua. Cada mudança, esta sujeita ao risco de que o restante do sistema não funcione mais como planejado. Em um sistema limpo, organizamos nossas classes de forma a reduzir o risco de mudança.

O problema de abrir uma classe é que isso traz riscos. Quaisquer modificações na classe têm o potencial de quebrar outros códigos da classe e deve ser novamente testado.

Queremos estruturar nossos sistemas de modo que mude com o mínimo possível quando os atualizamos com recursos novos ou alterados. Em um sistema ideal, incorporamos novos recursos estendendo o sistema, não fazendo modificações no código existente.

Isolando para a Mudança

As necessidades da aplicação mudarão, portanto o código mudará. Aprendemos em no básico de OO que existem classes concretas (contêm detalhes de implementação – código), e classes abstratas (representam apenas conceitos). Uma classe que depende de classes concretas está em risco quando esses detalhes mudam. Para isso podemos introduzir interfaces e classes abstratas e ajudar a isolar o impacto desses detalhes causam.

Dependências de detalhes concretos criam desafios para testar nosso sistema. Se estamos construindo uma classe de CurrentCoins e ela depende de uma API externa OneCryptExchange, nossos casos de teste são afetados pela volatilidade de tal pesquisa.

Em vez de projetar CurrentCoins de forma que dependa diretamente do OneCryptExchange, criamos uma interface, ICryptCoinExchange, que declara um único método:

    public interface ICryptCoinExchange
    {
        float CurrentCryptoPrice(strin cryptoCode);
    }

Projetamos o OneCryptExchange para implementar essa interface. Também nos certificamos de que o construtor do CurrentCoins leva uma referência ICryptCoinExchange como um argumento:

    public class CurrentCoins
    {
        private ICryptCoinExchange _cryptCoinExchange;
        public CurrentCoins(ICryptCoinExchange cryptCoinExchange)
        {
            _cryptCoinExchange = cryptCoinExchange;
        }
    }

Agora nosso teste pode criar uma implementação testável da interface ICryptCoinExchange que emula o OneCryptExchange. Esta implementação de teste usará um Mock para qualquer símbolo que usamos no teste.

Se um sistema for desacoplado o suficiente para ser testado dessa forma, ele também será mais flexível e promoverá mais reutilização. A falta de acoplamento significa que os elementos do nosso sistema estão melhores isolados uns dos outros e da mudança. Esse isolamento facilita a compreensão de cada elemento do sistema.

Ao minimizar o acoplamento dessa maneira, nossas classes aderem a outro princípio de design de classe conhecido como Princípio de Inversão de Dependência (DIP em inglês). Em essência, o DIP diz que nossas classes devem depender de abstrações, não de detalhes concretos.

Em vez de depender dos detalhes de implementação da classe OneCryptExchange, nossa classe CurrentCoins agora depende da interface ICryptCoinExchange. Esta, interface representa o conceito abstrato de solicitar o preço atual. Essa abstração isola todos os detalhes específicos de obtenção de tal preço, incluindo de onde esse preço é obtido.

Qualquer dúvida ou dicas, entre em contato: leandrolt@gmail.com

Referência
– Clean Code: A Handbook of Agile Software Craftsmanship (English Edition) – Robert C. Martin – Capítulo 10

Leave a Reply

Your email address will not be published. Required fields are marked *