Um bom código é aquele em que a manutenção é feita sem dispender muito tempo do desenvolvedor e sem gerar efeitos colaterais em outras partes do projeto.

Dessa forma, devemos evitar códigos que são difíceis de ler/entender, complicados de darem manutenção e muito acoplados, os chamados Code Smells denominado por Martin Fowler. Pode parecer simples ao falar, mas quando estamos escrevendo o código a diferença é muito sutil.

Como Robert Martin (o Uncle Bob) diz no livro O Código Limpo, programação é arte, é como escrever poesia e como tal a escrita requer cuidados, encaixe perfeito das peças e, principalmente, fluidez na leitura.

Origem do acrônimo SOLID

Uncle Bob levou 20 anos para formular o catálogo que originou o SOLID, com base na experiência dele e de seus companheiros em diversas linguagens de programação. Não foi uma suposição, foi experimentação conjunta ou referenciada dos trabalhos de vários nomes da Engenharia de Software e Ciência da Computação: Bárbara Liskov, Bertrand Meyer, David L. Parnas, Dave Thomas, Edsger Dijkstra, Kent Beck, Larry L. Constantine, Alistair Cockburn, Martin Fowler, Andrew Hunt, Michael Feathers e vários outros.

Uncle Bob criou e reuniu os conceitos desses vários profissionais, mas quem nomeou com o acrônimo foi Michael Feathers, dando origem ao “SOLID”, numa tentativa de remeter também ao desenvolvimento de um software “sólido”.

Esta é uma série de artigos publicados semanalmente com as definições propostas pelo Uncle Bob de cada uma das letras compondo o SOLID como um todo.

Princípios SOLID - Boas Práticas de Programação com C#: Parte 1 - SRP - Single Responsability Principle

SRP – Single Responsability Principle

Princípio da Responsabilidade Única: Uma classe/método/função deve ter somente uma única responsabilidade.

Entretanto classes, métodos e funções possuem abrangências diferentes, logo, podemos interpretar que uma classe deve ter somente um motivo para ser alterada; um método/função deve realizar somente uma ação.

Observe a seguinte classe como exemplo:

public class Funcionario
    {
        public int FuncionarioId { get; set; }
        public string Nome { get; set; }
        public string Email { get; set; }
        public string CPF { get; set; }
        public DateTime DataAdmissao { get; set; }
        public double Salario { get; set; }
        public string Cargo { get; set; }

        public void AtualizarSalario()
        {
            if (Cargo == "Junior")
                this.Salario = this.Salario + this.Salario * 0.2;
            else if (Cargo == "Pleno")
                this.Salario = this.Salario + this.Salario * 0.3;
            else if (Cargo == "Senior")
                this.Salario = this.Salario + this.Salario * 0.4;
        }

        public string AdicionarFuncionario()
        {
            if (!Email.Contains("@"))
                return "Funcionario com e-mail inválido";

            if (CPF.Length != 11)
                return "Funcionário com CPF inválido";

            using (var cn = new SqlConnection())
            {
                var cmd = new SqlCommand();

                cn.ConnectionString = "DescricaoConnectionString";
                cmd.Connection = cn;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "INSERT INTO FUNCIONARIO(NOME, EMAIL CPF, DATAADMISSAO) VALUES (@nome, @email, @cpf, @dataCad))";

                cmd.Parameters.AddWithValue("nome", Nome);
                cmd.Parameters.AddWithValue("email", Email);
                cmd.Parameters.AddWithValue("cpf", CPF);
                cmd.Parameters.AddWithValue("dataAdm", DataAdmissao);

                cn.Open();
                cmd.ExecuteNonQuery();
            }

            var mail = new MailMessage("[email protected]", Email);
            var client = new SmtpClient
            {
                Port = 25,
                DeliveryMethod = SmtpDeliveryMethod.Network,
                UseDefaultCredentials = false,
                Host = "smtp.google.com"
            };

            mail.Subject = "Bem Vindo(a).";
            mail.Body = "Parabéns! Agora você faz parte do nosso time!";
            client.Send(mail);

            return "Funcionario cadastrado com sucesso!";
        }
    }

Perceba que esta classe enorme possui ao menos três responsabilidades explícitas: calcular o salário, cadastrar um novo funcionário na base de dados e enviar um email informativo.

O princípio da responsabilidade única é justamente diminuir a quantidade de coisas que uma classe realiza. “Ah, mas eu vou precisar ter vários arquivos, vários métodos etc.” Tudo bem! Porque desta forma você está organizando seu código em blocos que facilitam a manutenibilidade e entendimento.

O grande problema aqui é que nós desenvolvedores tememos que escrever várias classes com pequenas responsabilidades, pelo receio de ter que abrir uma por uma para entender o fluxo. O que precisamos nos perguntar é: será que é melhor ter as ferramentas organizadas em caixas de ferramentas com muitas gavetas pequenas, cada um com objetos bem classificados e rotulados, ou poucas gavetas nas quais coloca-se tudo?

A dica aqui é:

  • Escreva classes e métodos que fortaleçam a coesão;
  • Classes menores e mais simples são menos suscetíveis a problemas futuros, o reuso fica mais fácil e tem menor chance de propagar problemas para outras classes;
  • Utilize o encapsulamento; separe métodos grandes em pequenos blocos; utilize padrões arquiteturais que facilitem a quebra de responsabilidades.

Refatorando a classe ‘Funcionario’ inicial teríamos algo como:

public class Funcionario
    {
        public int FuncionarioId { get; set; }
        public string Nome { get; set; }
        public Email Email { get; set; }
        public Cpf Cpf { get; set; }
        public DateTime DataAdmissao { get; set; }
                public Salario Salario { get; set; }
                public string Cargo { get; set; }

        public bool Validar()
        {
            return Email.Validar() && Cpf.Validar();
        }
    }
public class Salario
    {
                public double Valor { get; set; }

                public void AtualizarSalario(decimal aumento){
                        return Valor + (Valor * aumento);
                }
    }
public class FuncionarioService
    {
        public string AdicionarFuncionario(Funcionario funcionario)
        {
            if (!funcionario.Validar())
                return "Dados inválidos";

            var repo = new FuncionarioRepository();
            repo.AdicionarFuncionario(funcionario);

            EmailServices.Enviar("[email protected]", funcionario.Email.Endereco, "Bem Vindo(a).", "Parabéns! Agora você faz parte do nosso time!");

            return "Funcionário cadastrado com sucesso";
        }
    }
public class FuncionarioRepository
    {
        public void AdicionarFuncionario(Funcionario funcionario)
        {
            using (var cn = new SqlConnection())
            {
                var cmd = new SqlCommand();

                cn.ConnectionString = "DescricaoConnectionString";
                cmd.Connection = cn;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "INSERT INTO FUNCIONARIO (NOME, EMAIL CPF, DATAADMISSAO) VALUES (@nome, @email, @cpf, @dataCad))";

                cmd.Parameters.AddWithValue("nome", funcionario.Nome);
                cmd.Parameters.AddWithValue("email", funcionario.Email);
                cmd.Parameters.AddWithValue("cpf", funcionario.Cpf);
                cmd.Parameters.AddWithValue("dataAdm", funcionario.DataAdmissao);

                cn.Open();
                cmd.ExecuteNonQuery();
            }
        }
    }
public class Cpf
    {
        public string Numero { get; set; }

        public bool Validar()
        {
            return Numero.Length == 11;
        }
    }
public class Email
    {
        public string Endereco { get; set; }

        public bool Validar()
        {
            return Endereco.Contains("@");
        }
    }
public static class EmailServices
    {
        public static void Enviar(string de, string para, string assunto, string mensagem)
        {
            var mail = new MailMessage(de, para);
            var client = new SmtpClient
            {
                Port = 25,
                DeliveryMethod = SmtpDeliveryMethod.Network,
                UseDefaultCredentials = false,
                Host = "smtp.google.com"
            };

            mail.Subject = assunto;
            mail.Body = mensagem;
            client.Send(mail);
        }
    }

Desta forma estaríamos isolando as responsabilidades para realizar o fluxo como um todo.

Cabe destacar que os princípios SOLID são muito utilizados nos padrões de projeto para melhorar a escrita e manutenção. Para o padrão de projeto estrutural o Adapter, Bridge e Decorator; para o padrão de projeto comportamental o Chain of Responsability e State; e para o padrão de projeto criacional o Factory Method, Abstract Factory e Builder utilizam o SRP como diretriz para implementação.

Ainda há o que melhorar na classe ‘Funcionario’, porém o foco deste artigo é frisar a separação de responsabilidades.

Os Code Smells que ainda existem nessa implementação, serão ajustados nas demais definições do SOLID à medida que formos avançando.

No próximo artigo, vamos falar sobre o Princípio do Aberto e Fechado (OCP) e entender melhor como um código bem estruturado pode ser visto como peças que se encaixam.

Referências

O princípio da responsabilidade única – SRP

Solid Relevance

The Single Responsibility Principle

PIRES, E. SOLID: Teoria e Prática

ANICHE, M. Orientação a Objetos e SOLID para ninjas. São Paulo – Casa do Código

Samyla Dutra

Mineira, atuo na área de desenvolvimento desde 2017 e sou graduada em Engenharia de Computação pelo CEFET-MG. Gosto de entender a profundidade do backend e como melhorar a escrita de código. E isso se torna melhor em equipe! Amo estar em contato com a natureza, trilhar montanhas, ler livros e cozinhar.