Executando verificação de segurança...
10

Você quer ser um programador que faz códigos ou um que faz bons códigos?

O que é essa publicação e o porque do título?

O que é ser um programador? De forma leviana, é ser uma pessoa da qual tem capacidade de através da programação ("código") criar programas. Certo? Ok, até aqui beleza, até aqui você se encaixa. Mas será que você faz isso da forma mais eficiente? Com as melhores estruturas, patterns, reaproveitamentos e todas essas coisas com nome difícil? Você é um bom programador? Olha, você até pode ser mas EU não sou tão bom quanto eu quero ser. Por isso essa publicação existe. Vou realizar uma série de estudos publicados aqui do qual compartilho meus aprendizados e experiências sobre minha jornada em ser um programador melhor. Estudando mais o tão aclamado CLEAN CODE, design patterns, essas coisas. Coisas que não são voltados a conhecimentos específicos, como linguagens, libs ou bancos de dados, mas coisas que melhoram o seu código em geral, te tornam um programador melhor, afinal a linguagem é só a ferramenta.

ESTUDO NÚMERO 1: Você conhece o S.O.L.I.D?

Imagem da Ugonna Thelma sobre SOLID

Todos os desenhos são de autoria da Ugonna Thelma, link para o artigo dela a baixo

Se você é um programador mais experiente com certeza conhece, e até pode deixar mais detalhes, correções e dicas a baixo. Com certeza são bem-vindas!

SOLID é acrônimo (sigla que forma uma palavra) de uma metodologia para linguagens com POO (Programação orientada a objeto) do qual permite criar códigos de forma mais eficiente e agil. São princípios que prometem fazer você escrever códigos mais limpos, que separam responsabilidas, diminuem o acoplamento, facilita refatoração, estímula o reaproveitamento do código. Promete mais que coach vendedor de curso.

S - Single Resposibility Principle: SRP

Single Resposibility Principle, ou no bom e velho pt-br, Princípio da Responsabilidade Única.
Este princípio diz que cada classe deve ter uma única função, especializada em único assunto, mesmo que se tratem da mesma "entidade". Por exemplo, ao invés de ter uma única classe "Pedido" você ter uma classe para gerir cada responsabilidade de um pedido:

Exemplo em código:

Sem SRP:

class Pedido {
    constructor(id, cliente, itens) {
        this.id = id;
        this.cliente = cliente;
        this.itens = itens;
    }

    calcularTotal() {
        return this.itens.reduce((total, item) => total + item.preco, 0);
    }

    salvarNoBanco() {
        console.log(`Pedido ${this.id} salvo no banco de dados.`);
    }

    gerarRelatorio() {
        console.log(`Relatório do pedido ${this.id}.`);
    }
}

Com SRP:

class Pedido {
    constructor(id, cliente, itens) {
        this.id = id;
        this.cliente = cliente;
        this.itens = itens;
    }

    calcularTotal() {
        return this.itens.reduce((total, item) => total + item.preco, 0);
    }
}

class RepositorioDePedidos {
    salvar(pedido) {
        console.log(`Pedido ${pedido.id} salvo no banco de dados.`);
    }
}

class GeradorDeRelatorios {
    gerarRelatorio(pedido) {
        console.log(`Relatório do pedido ${pedido.id}.`);
    }
}

Imagem sobre SRP

O - Open and Close Principle: OCP

Open and Close Principle, ou, Princípio do Aberto e fechado. Ele diz que objetos e entidades estão abertos a extensões e fechados para modificações. Deve ser genérico o suficienta para aumentar sem se modificar. De forma leviana, você pode colocar mais coisas nele mas o que já estava antes tem que permancer igual. Falando em metáfora, (já deve ter percebido que gosto de ser simplório para explicar né 🙃), imagine que você vai compras pneus para seu carro, você não compra qualquer pneu, não faz isso de qualquer jeito, e depois tenta encaixar na roda, ficar mudando ela até funcionar, você tem que ter o pneu do tamanho certo, do jeito certo.

Código de exemplo:

Sem OCP:

Código incial: Método que aplica descontos para clientes regulares

class Desconto {
  calcularDesconto(tipoCliente, valor) {
      if(tipoCliente === "regular") return valor * 0.1;
      
      return valor; 
  }
}

Agora se quisermos adicionar um desconto para clientes vip temos a necessidade de mudar o método:

class Desconto {
  calcularDesconto(tipoCliente, valor) {
    if (tipoCliente === 'regular') {
      return valor * 0.1; 
    } else if (tipoCliente === 'vip') {
      return valor * 0.2;
    } else {
      return 0;
    }
  }
}

Imagine agora que temos dez tipos de clientes com diferentes tipos de desconto, esse if/else se tornaria provavelmente um enorme switch case alterado toda vez que tiver mais um tipo

Com OCP:

Veja como não precisamos alterar o método de cálculo mesmo adicionando mais um tipo de cliente

interface tipoCliente{
  calcularDesconto(valor: number): number;
}

class ClienteRegular implements TipoCliente{
  calcularDesconto(valor: number): number {
    return valor * 0.1; 
  }
}

class ClienteVip implements TipoCliente {
  calcularDesconto(valor: number): number {
    return valor * 0.2; 
  }
}

class ClientePremium implements TipoCliente {
  calcularDesconto(valor: number): number {
    return valor * 0.3; 
  }
}

class Desconto {
  calcular(valor: number, tipoCliente: TipoCliente): number {
    return tipoCliente.calcularDesconto(valor);
  }
}

Imagem sobre OCP

L - Liskov Substitution Principle: LSP

Liskov Substitution Principle, ou Princípio da Substituição de Liskov. O princípio diz que se B é um subtipo de A ele deve manter as características (métodos e propriedades) de A e é capaz de funcionar em seu lugar. Agora você pensa, por quê? Porque se você precisar desconstruir uma abstração, logo, você abstraiu errado.

Exemplo de quebra do LSP:

class Animal {
    cacar() {
        console.log("caçando animais...");
    }
    
    comer() {
        console.log("comendo");
    }
}

class Leao extends Animal{
    rugir() {
        console.log("rugindo");
    }
}

class Vaca extends Animal {
    cacar() {
        throw new Error("Vacas não caçam");
    }
}

Nesse caso, a classe Vaca não pode realizar todas as ações da classe Animal, portanto a classe Animal tem abstrações ruins.

imagem sobre LSP

I - Interface Segregation Principle: ISP

Interface Segregation Principle ou Princípio de Segregação de Interface. Este diz que uma classe não pode ser forçada a ter classes que não usam, então, devemos segregar (separar) as interfaces da class.

Código de exemplo:

sem ISP:

interface Animal {
    cacar(): void
    comer(): void
}

class Leao implements Animal{
    cacar() {
        console.log("caçando animais...");
    }
    
    comer() {
        console.log("comendo");
    }
}

class Vaca implements Animal {
    cacar() {
        // Não faz nada
    }
    
    comer() {
        console.log("comendo");
    }
}

Com ISP:

interface Animal {
    comer(): void
}

interface AnimalQueCaca {
    cacar(): void
}

class Leao implements AnimalQueCaca{
    cacar() {
        console.log("caçando animais...");
    }
    
    comer() {
        console.log("comendo");
    }
}

class Vaca implements Animal {    
    comer() {
        console.log("comendo");
    }
}

Nesse caso a classe vaca não precisa ter um método da qual ela não vai utilizar, você separa uma nova interface para a finalidade de caçar.

Imagem sobre ISP

D - Dependency Invernsion Principle: DIP

Dependency Invernsion Principle ou Princípio da Inversão de Dependência. O DIP diz que:

  1. Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.
  2. Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.

Vamos para a forma leviana, um pedreiro deve ser capaz de realizar uma tarefa sem depender de uma ferramenta específica, utilizando uma qualquer ferramenta que se encaixe, de modo que a lógica do trabalho não precise se preocupar com os detalhes de cada ferramenta específica.

Código de exemplo:

sem o DIP:

class ChaveDeFenda {
  usarChave() {
    console.log("Usando chave de fenda para apertar parafuso.");
  }
}

class Pedreiro {
  constructor() {
    this.chaveDeFenda = new ChaveDeFenda(); 
  }

  apertarParafuso() {
    this.chaveDeFenda.usarChave(); 
  }
}

Com DIP:

interface Chave {
  usarChave(): void;
}

class ChaveDeFenda implements Chave {
  usarChave() {
    console.log("Usando chave de fenda para apertar parafuso.");
  }
}

class ChavePhillips implements Chave {
  usarChave() {
    console.log("Usando chave Phillips para apertar parafuso.");
  }
}

class Pedreiro {
  constructor(chave) {
    this.chave = chave; 
  }

  apertarParafuso() {
    this.chave.usarChave(); 
  }
}

Imagem sobre DIP

Conclusão

Aplicar os princípios do SOLID não significa apenas escrever códigos que funcionem, mas também escrever códigos melhores. Se aprofundar nesses conceitos pode realmente te tornar um programador de códigos bons. E é isso que tentarei trazer nos próximos posts. Obrigado pela leitura e agradeço por dicas, correções e sugestões.

Links para você ver mais

Carregando publicação patrocinada...
8

Só lembrando que usar algo que "todo mundo" diz que é bom em todo lugar não é fazer bons códigos. Só porque tem um livro que fala sobre um assunto e um monte de gente repete aquilo sem questionamento, não quer dizer que o código ficará bom, pode até ficar bem pior. O exemplo clássico é o https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition. E não pense que é algo caricatural, grande parte da pessoas programa desse jeito, ou seja, elas são caricaturas de programdores.

Entender bem a computação, ter pensamento questionador, boa capacidade de interpretação de textos, domínio da matemática, especialmente da lógica apurada, entendimento da ciência e pragmatismo é que permitem um programador fazer bons códsigos, não receitas de bolo.

O primeiro vídeo do meu canal será justamente questionando as tais "boas práticas" que mais prejudicam as pessoas do que ajudam (ajudam também, para quem sabe usar) e que ajuda muito a IA substituir essas pessoas.

Quem usa SOLID ou outra técnica sem uma bagagem completa faz códigos ruins, ou bons por coincidência. SOLID mal usado cria complexidade sem trazer benefícios. Exemplos artificiais costumam mais desensinar (ocorre muito com OOP) porque mostra oi mecanismo, mas aplicando do jeito errado, e e assim que as pessoas vão aprender. QUando aprende e treina o erro é ele que sempre fará. E um dia brigará pelo erro. Só faça algo se você puder provar que aquilo mais ajuda que atrapalha, se m a prova, não use.


Farei algo que muitos pedem para aprender a programar corretamente, gratuitamente (não vendo nada, é retribuição na minha aposentadoria) (links aqui no perfil também).

2

Espero que essa mensagem lhe encontre bem!
Obrigado pelo comentário! Concordo com você, seguir fielmente um conjuto de convenções não é o que irá te fazer um bom programador, inclusive pode até de atrapalhar em alguns casos. Ser um programador bom envolve diversas coisas, seria a mesma coisa que dizer que um médico é bom só sabendo alguns protocolos. A ideia era apresentar o SOLID a pessoas que não conhecem com a tentativa de deixar o código dessas pessoas melhores, não tentei dizer que SOLID era como a bíblia, que deve ser seguida a risca e é a solução para todos seus problemas.

2
2

Que perda se tempo. Basicamente pelo artigo, quem não usa princípios SOLiD nao faz bons códigos. Sejam bem vindos a vida real fora do palco dos cursinhos online.

2

Espero que essa mensagem lhe encontre bem!
Peço perdão se o artigo deixou aberto a essa interpretação. A ideia do artigo era apresentar as ideias do SOLID para pessoas que não conhecem. O que eu acredito que não seja uma perda de tempo, afinal é um conhecimento útil em certos casos. O artigo não tem a intenção de impor a ninguém a utilização dos mesmos, afinal não existe regras ou algo muito bem definido sobre um jeito certo ou errado. A proposta toda está em conhecer algo novo que PODE, não necessariamente vai, te ajudar a escrever código melhores.

Obrigado pelo comentário, das próximas vezes tentarei ser um pouco mais delicado com as palavras para tentar não deixar nenhuma má interpretação.