Desvendando o mundo mágico dos acrônimos: SOLID, KISS, YAGNI, DRY, DDD, TDD

thiagomr

Thiago Moraes

Posted on September 3, 2020

Desvendando o mundo mágico dos acrônimos: SOLID, KISS, YAGNI, DRY, DDD, TDD

Introdução

No artigo de hoje vou esclarecer um pouco sobre essas siglas que ainda deixam muita gente confusa. De maneira geral, eu diria que a intenção é proporcionar o desenvolvimento de software de maior qualidade. Vamos começar então abordando o que seria um software de qualidade e qual a importância do mesmo.

O que é um software de qualidade?

Definir a qualidade de um software pode ser algo extremamente complexo, então vou resumir dois principais pontos dentro do assunto que iremos abordar.
Um software é um programa que visa resolver determinado problema. No mundo empresarial, diríamos que é uma solução que gera um determinado valor e satisfaz um cliente. Então esse é nosso primeiro conceito: a qualidade de um software pode ser definida pela sua capacidade em satisfazer as necessidades de um determinado usuário. E aí entram questões como usabilidade, aplicação correta de regras de negócio, performance, etc.
O segundo conceito é em relação ao processo de desenvolvimento, onde podemos levar em consideração a clareza do código, consistência, manutenibilidade, capacidade de expansão, dentre outros. No fim das contas, tudo está interligado, pois um software com boas práticas de design, claro e consistente, geralmente vai estar mais próximo de atender as necessidades para a qual foi especificado.

Por que precisamos de software de qualidade?

O primeiro ponto a destacar é que nós desenvolvemos software para pessoas. Mais especificamente para necessidades de pessoas. E as necessidades, inevitavelmente, mudam. Se as necessidades mudam, os softwares também deveriam ter a capacidade de mudar. Mas não basta apenas mudar, tem que ser fácil, rápido e livre de riscos.
O segundo ponto é que nós trabalhamos em times. E esses times possuem pessoas diferentes, com habilidades diferentes e eles também mudam. Todas essas mudanças, de softwares e de pessoas, geram uma série de problemas que todo desenvolvedor já passou (ou ainda vai passar), como demorar dias lendo um código completamente sem sentido ou desistir de implementar uma feature por medo de quebrar algo que funcionava anteriormente.
Então o importante aqui é que nós escrevemos código para pessoas, mas principalmente, para desenvolvedores. E ter um código limpo e claro, ou seja, documentado, testável, com padrões consistentes e boas práticas, vai trazer maior agilidade e segurança no desenvolvimento de novas funcionalidades. É sobre isso que se tratam os acrônimos citados no título, trazer maior qualidade para o código escrito. Alguns são conceitos, outros metodologias, mas todos com o mesmo propósito. Então, vamos aos conceitos.

Nota: Vale lembrar que a ideia aqui é dar uma visão geral sobre cada um dos conceitos. Alguns dos assuntos são extremamente profundos e merecem um (ou mais) artigos dedicados.

SOLID

O famoso S.O.L.I.D. é um conjunto dos 5 princípios essenciais para o Desenvolvimento Orientado a Objetos, visando construir código mais compreensível, flexível e sustentável. Alguns dos conceitos são mais fáceis de entender analisando exemplos, portanto, este será o único tópico em que vou colocar alguns códigos:

[S]ingle Responsibility Principle (Princípio da Responsabilidade Única)

A class should have one, and only one, reason to change.

Simples assim. Uma classe deve ter uma e somente uma responsabilidade. Apesar de parecer um dos princípios mais simples, é também um dos mais importantes, já que é essencial para o funcionamento dos outros. O Seguem dois exemplos:

//without solid
class EmailService {
    validateEmailAndSend(string emailAddress, string subject, string content): boolean;
}

//with solid
class EmailService {
    sendMail(emailAddress: string, subject: string, content: string): void {
        //
    };
}

class Validator {
    validateEmail(email: string): boolean {
        //
    };
}

[O]pen/Closed Principle (Princípio do Aberto/Fechado)

You should be able to extend a classes behavior, without modifying it.

Este princípio diz que você deve ser capaz de expandir seu código, ou seja, criar novas funcionalidades, sem modificar o código existente. O grande problema ao mudar um código existente é a chance de criar um possível bug. O exemplo a seguir é interessante pois ao mesmo tempo que reflete esse princípio, também está bastante atrelado com o anterior, uma vez que o cálculo do custo de cada tipo de fornecedor pode ter uma lógica diferente (e consequentemente são responsabilidades diferentes). Para cada novo tipo de fornecedor, o código seria alterado em uma parte crítica.

//without solid
class Provider {
    calculateCost(): number {
        if (this.type == 'email') {
        //
        } else if (this.type == 'internet') {
        //
        } else {
        //
        }
    }
}

//with solid
interface Provider {
    calculateCost(): number;
}

class EmailProvider implements Provider {
    calculateCost(): number {

    }
}

class InternetProvider implements Provider {
    calculateCost(): number {

    }
}

[L]iskov Substitution Principle (Princípio da Substituição de Liskov)

Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.

Olhando pela primeira vez, esse princípio assusta, mas o conceito é simples. Significa que classes derivadas de uma mesma classe devem poder ser substituídas entre si. No exemplo a seguir, as classes não são intercambiáveis pois não têm a capacidade de implementar os mesmo métodos:

//without solid
class Bird {
    walk(): void {
        //
    }

    fly(): void {
        //
    }

}

class Duck extends Bird {}

class Owl extends Bird {}

//with solid
class Bird {
     walk(): void {
        //
     }
}

class FlyingBird extends Bird {
    fly(): void {
        //
    }
}

class AquaticBird extends Bird {
    swim(): void {
        //
    }
}

class Duck extends AquaticBird {}

class Owl extends FlyingBird {}

[I]nterface Segregation Principle (Princípio da Segregação de Interfaces)

Make fine grained interfaces that are client specific.

Este princípio defende que uma classe deve depender de interfaces que contenham apenas o essencial para seu uso, sendo assim, priorizando interfaces específicas ao invés de genéricas. Segue exemplo:

//without solid
interface Stream {
    read(): void;
    write(): void;
}

class Reader implements Stream {}
class Writer implements Stream {}

//with solid
interface ReadableStream {
    read(): void;
}
interface WritableStream {
    write(): void;
}

class Reader implements ReadableStream {}
class Writer implements WritebleStream {}
class DuplexStream implements ReadableStream, WritableStream {}

[D]ependency Inversion Principle (Princípio da Inversão de Dependências)

Depend on abstractions, not on concretions.

Abstrações nunca deveriam depender de classes concretas. Entende-se por classes concretas, as que realmente têm a implementação completa de algo. O conceito da inversão de dependência tem como objetivo tornar as classes mais desacopladas, fazendo com que se tornem mais sustentáveis e testáveis. No exemplo a seguir, a classe está completamente dependente de um outro serviço. Com as alterações, ela vai depender apenas de uma interface, sendo possivel trocar facilmente a dependencia (para escrever um teste ou alterar ou utilizar um outro serviço de comunicação, por exemplo):

//without solid
class CloudNotificationService {
    sendMessage(): void {
        //
    }
}

class Transaction {
    private notificationService: CloudNotificationService;

    constructor(notificationService: CloudNotificationService) {
        this.notificationService = notificationService;
    }

    save(): void {
    //...
        this.notificationService.sendMessage();
    }
}

//with solid
interface NotificationService {
    sendMessage(): void;
}

class CloudNotificationService implements NotificationService {
    sendMessage(): void {
        //
    }
}

class Transaction {
    private notificationService: NotificationService;

    constructor(notificationService: NotificationService) {
        this.notificationService = notificationService;
    }

    save(): void {
        //...
        this.notificationService.sendMessage();
    }
}

KISS

Keep it simple, stupid

Esse é certamente o meu preferido. É sobre manter as coisas o mais simples possível e pode ser aplicado em tudo. Manter algo simples na prática significa dar bons nomes para metódos e variáveis, aplicar o conceito da responsabilidade única, organização do código e tudo que seja benéfico nesse sentido. Eu recomendo que você aplique esse conceito na sua vida como um todo, não só no desenvolvimento de software.

YAGNI

You Aren't Gonna Need It

Esse é um princípio da metodologia ágil XP (Extreme Programming) que sugere que você não deve adicionar coisas que não vai utilizar no seu código. No dia-a-dia isso vejo isso muito relacionado à Over Engineering, ou seja, criar uma solução muito mais complexa do que o necessário pensando em uma situação futura que pode chegar a nunca existir.

DRY

Don't repeat yourself

DRY é sobre não repetir código desnecessariamente. Você deve reutilizar código sempre que possível, de uma maneira inteligente, fazendo assim reuso de código sempre que possível e tornando alterações mais fáceis.

DDD

A sigla significa Domain Driven Development (Desenvolvimento Orientado a Domínio). O domínio de um software é a área de conhecimento a qual o mesmo pertence. Esse é um conceito um pouco mais abrangente que os anteriores, pois se trata de um modelo estrutural de software que propõe a organização em camadas separadas por subdomínios, visando isolar responsabilidades, desacoplamento e um criar softwares que refletem de forma clara o domínio para o qual foram criados. Mais do que um modelo de arquitetura, esse método tem alguns conceitos muito interessantes como o de manter uma linguagem única entre todos os envolvidos em um projeto.

TDD

Test Driven Development (Desenvolvimento Orientado a Testes) é uma metodologia que propõe iniciar o desenvolvimento criando testes para as funcionalidades propostas e somente depois partir para as implementações. O método consiste em escrever testes bastante específicos, refatorando o código até chegar na melhor versão possível. Esse tópico gera muita polêmica, pois muitos desenvolvedores acham que isso acarreta em mais código a ser desenvolvido e maior tempo de manutenção além de duvidarem da prática. Sim, de fato, os testes são código, e como qualquer outro código, precisam de manutenção, refatoração, etc. Porém, se aplicado da forma correta, com disciplina, pode trazer excelentes ganhos a médio / longo prazo.

Conclusão

A ideia por trás de todos os conceitos é a mesma: produzir um código de qualidade. Isso não significa que você precisa usar tudo isso ao mesmo tempo para produzir um bom software, mas sim que você precisa conhecer as ferramentas para que seja possível escolher as melhores opções de acordo com a situação em que você se encontra. Muitos dos conceitos tem um aspecto comportamental e podem trazer benefícios para sua vida além do trabalho como desenvolvedor.
Isso é tudo pessoal. Espero que tenham curtido e sintam-se a vontade para enviar dúvidas, sugestões, feedbacks e o que mais desejarem.

Referências:

https://khalilstemmler.com/articles/solid-principles/solid-typescript/

https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design

https://khalilstemmler.com/wiki

💖 💪 🙅 🚩
thiagomr
Thiago Moraes

Posted on September 3, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related