Clean Code (Código limpo) – Objetos e estruturas de dados

Porque tantos programadores adicionam getters e setters automaticamente a seus objetos, expondo suas variáveis privadas como se fossem públicas?

Há uma razão para mantermos nossas variáveis privadas. Não queremos que ninguém mais dependa deles. Queremos manter a liberdade de mudar seu tipo ou implementação por um capricho, impulso ou necessidade.

Abstração de dados

Os métodos impõem uma política de acesso. Veja o exemplo abaixo.

    public class Point
    {
        private int CoordinateX { get; set; }
        private int CoordinateY { get; set; }

        public int GetCoordinateX()
        {
            return CoordinateX;
        }
        public int GetCoordinateY()
        {
            return CoordinateY;
        }
        public void SetCoordinates(int x, int y)
        {
            CoordinateX = x;
            CoordinateY = y;
        }
    }

Você pode ler as coordenadas individuais de forma independente, mas deve definir as coordenadas juntas como uma operação atômica.

Ocultar a implementação não é apenas uma questão de colocar uma camada de funções entre as variáveis. Ocultar a implementação tem a ver com abstrações! Uma classe não simplesmente envia suas variáveis por meio de getters e setters. Em vez disso, ela expõe interfaces abstratas que permitem que seus usuários manipulem a essência dos dados, sem precisar saber sua implementação.

Queremos expressar nossos dados em termos abstratos. Isso não é feito apenas usando interfaces e/ou getters e setters. É preciso pensar seriamente na melhor maneira de representar os dados contidos em um objeto. A pior opção é adicionar getters e setters cegamente na sua implementação.

Objetos e Estrutura de dados

Os objetos ocultam seus dados por trás de abstrações e expõem funções que operam nesses dados. A estrutura de dados expõe seus dados e não tem funções significativas. Essa diferença pode parecer trivial, mas tem implicações profundas.

Considere, o exemplo de forma procedural abaixo. A classe Geometry opera nas três classes de formas. As classes de forma são estruturas de dados simples sem nenhum comportamento. Todo o comportamento está na classe Geometry.

    public class Square
    {
        public double side;
    }
    public class Rectangle
    {
        public double height;
        public double width;
    }
    public class Circle
    {
        public double radius;
    }
    public class Geometry
    {
        public double area(Object shape)
        {
            if (shape is Square)
            {
                // code
            }
            else if (shape is Rectangle)
            {
                // code
            }
            else if (shape is Circle)
            {
                // code
            }
            throw new Exception();
        }
    }

Agora considere a solução orientada a objetos abaixo. O método area() é polimórfico. Nenhuma classe Geometry é necessária. Portanto, se eu adicionar uma nova forma, nenhuma das funções existentes será afetada, mas se eu adicionar uma nova função, todas as formas devem ser alteradas!

    public class Square : Shape
    {
        public double area()
        {
            // code
        }
    }

    public class Rectangle : Shape
    {
        public double area()
        {
            // code
        }
    }
    public class Circle : Shape
    {
        public double area()
        {
            // code
        }
    }

Em qualquer sistema complexo, haverá momentos em que adicionaremos novos tipos de dados em vez de novas funções. Para esses casos, objetos e OO são os mais apropriados. Por outro lado, também haverá momentos em que desejaremos adicionar novas funções em vez de tipos de dados. Nesse caso, o código procedural e as estruturas de dados serão mais apropriados.

A Lei de Deméter

A Lei de Deméter é uma diretriz para o desenvolvimento de software, particularmente em programas orientados a objeto. Ela pode ser sucintamente resumido nas seguintes formas
– Cada unidade deve ter conhecimento limitado sobre outras unidades: apenas unidades próximas se relacionam.
– Cada unidade deve apenas conversar com seus amigos; Não fale com estranhos.
– Apenas fale com seus amigos imediatos.

Como vimos na última seção, os objetos ocultam seus dados e expõem operações. Isso significa que um objeto não deve expor sua estrutura interna por meio de métodos de acesso, porque fazer isso é expor, em vez de ocultar, sua estrutura interna.

Descarrilamento

Esse tipo de código costuma ser chamado de descarrilamento porque se parece com um monte de vagões acoplados atrapalhando a leitura.

String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

Normalmente, é melhor dividi-los da seguinte maneira:

Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
String outputDir = scratchDir.getAbsolutePath();

Híbridos

Estruturas híbridas são metade objeto e metade estrutura de dados. Eles têm funções que fazem coisas significativas e também têm variáveis ou acessadores públicos e modificadores que, para todos os efeitos, tornam as variáveis que são privadas em públicas. Isso deixa disponivel para que outras aplicações externas use estas variáveis da mesma forma que um programa procedural usa uma estrutura de dados.

Esses híbridos não só tornam difícil adicionar novas funções, mas também tornam difícil adicionar novas estruturas de dados. Eles são o pior dos dois mundos. Evite criá-los. Eles são indicativos de um design confuso, cujos autores não têm certeza, ou ignoram, se precisam de proteção contra funções.

Objetos de transferência de dados

A forma mais pura de uma estrutura de dados é uma classe com variáveis públicas e sem funções. Isso às vezes é chamado de objeto de transferência de dados ou DTO. Os DTOs são estruturas muito úteis, especialmente ao se comunicar com bancos de dados ou analisar mensagens de soquetes e assim por diante.

Frequentemente, eles se tornam os primeiros de uma série de estágios de tradução que convertem dados brutos de um banco de dados em objetos no código.

Active Record

Active Record são formas especiais de DTOs. Eles são estruturas de dados com variáveis públicas, mas geralmente possuem métodos de operação como save e find. Normalmente, esses Active Records são traduções diretas de tabelas de banco de dados ou outras fontes de dados.

Infelizmente, frequentemente descobrimos que os desenvolvedores tentam tratar essas estruturas de dados como se fossem objetos, inserindo nelas métodos de regras de negócios. Isso é estranho porque cria um híbrido entre uma estrutura de dados e um objeto.

A solução, é claro, é tratar o Active Record como uma estrutura de dados e criar objetos separados que contêm as regras de negócios e que ocultam seus dados internos (que provavelmente são apenas instâncias do Active Record).

Conclusão

Os objetos expõem o comportamento e ocultam dados. Isso torna mais fácil adicionar novos tipos de objetos sem alterar os comportamentos existentes. Também torna difícil adicionar novos comportamentos a objetos existentes.

As estruturas de dados expõem os dados e não têm comportamento significativo. Isso torna mais fácil adicionar novos comportamentos às estruturas de dados existentes, mas torna difícil adicionar novas estruturas de dados às funções existentes.

Bons desenvolvedores entendem essa diferença e escolher a melhor solução para o trabalho que tem a frente.

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 6

Leave a Reply

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