Article image

RS

Rodrigo Soares23/02/2024 16:11
Compartilhe

Funções em C#

  • #C#

Aplicação de Funções como modelo de boas práticas em C#

A maioria dos desenvolvedores conhece o conceito base de uma função: um bloco de código, que ao ser invocado, lhe retorna uma informação, ou realiza uma ação desejada.

Porém, poucos sabem utilizá-la de maneira coesa, e de forma que auxilie e agilize o desenvolvimento, de maneira clara e compreensível para qualquer profissional da área.

Um dos princípios base da programação, é a nomenclatura. Afinal, nada melhor do que um código onde as variáveis, classes e funções, possuem nomes coerentes, não é mesmo? Então fica o questionamento, como é possível melhorar a definição de nomenclatura de uma função, para que esta seja bem aplicada?

Bem, fica como primeira dica, nunca economizar nos caracteres! Observem o exemplo abaixo (que não devemos seguir):

// PRÁTICA INADEQUADA

public bool TemIddMin(int x)
{
  if (x >= 18)
     return true;
  else
     return false;
}

No exemplo citado, temos uma função chamada "TemIddMin", que recebe um parâmetro inteiro chamado "x" e retorna verdadeiro ou falso.

Não é muito intuitivo, concordam? Uma vez que sua nomenclatura não deixa claro o que a função faz, pois é composta por abreviações não padronizadas, e seu parâmetro "x" também não esclarece o tipo de informação que o mesmo carrega, ou seja, este é um exemplo de uma função descontextualizada.

Além disso tudo, ainda observa-se a presença de um número mágico "18" que pode não ser claro para todos. Um desenvolvedor que não é brasileiro, por exemplo, teria dificuldades em interpretar que o valor corresponde à idade mínima legal na legislação brasileira para considerar alguém maior de idade.

Agora fica o seguinte questionamento, como poderíamos reconstruir essa função baseando-se nas boas práticas, e aplicando-a em um cenário real?

Segue o exemplo de como a função deveria ser construída:

// PRÁTICA RECOMENDADA

private const int IdadeMinimaLegal = 18;


public bool PossuiIdadeMinima(int idade)
{
  if (idade >= this.IdadeMinimaLegal)
     return true;
  else
     return false;
}

Nesse novo exemplo, a função deixa claro o seu intuito, informar se, baseado no parâmetro "idade" a pessoa é maior de idade, ou não. E conseguimos alcançar o modelo ideal realizando pequenas alterações, que fazem grande diferença na leitura do código para o desenvolvedor da demanda, e futuros desenvolvedores que venham a realizar a manutenção da mesma.

Lembre-se que um código bem escrito, não necessariamente é um código pequeno, mas sim, um código claro naquilo que se propõe a fazer.

Afinal, quais foram as alterações?

  • Agora há uma constante chamada "IdadeMinimaLegal" que deixa claro a informação que ela carrega, removendo o famoso "número mágico" do bloco de código.
  • O nome da função foi substituido por "PossuiIdadeMinima", esclarecendo a validação que a mesma realiza, retornando verdadeiro ou falso, caso a condição citada seja cumprida.
  • O nome do parâmetro que a função solicita, foi substituido de "x" para "idade", dando mais contexto ao que se espera receber de informação através deste campo.

Ótimo! Mas, como utilizar a função, também beneficiando-se das boas práticas?

Neste caso, nada melhor do que demonstrarmos na prática, o uso esperado de uma função que valida se alguém é maior de idade, ou não.

Vamos começar com o exemplo de como NÃO deveríamos fazer:

// PRÁTICA INADEQUADA

Console.WriteLine("Insira sua idade:");


int idade = Console.ReadLine();


var resultado = this.PossuiIdadeMinima(idade);


if (resultado)
Console.WriteLine("Você é maior de idade! Parabéns!");
else
Console.WriteLine("Você ainda é menor de idade, aguarde mais um pouco!");

 

No exemplo exibido acima, atribuímos o resultado da função "PossuiIdadeMinima" à uma variável chamada "resultado", e logo em seguida, aplicamos a mesma em um condicional if. Logo, se "resultado" for verdadeiro, executamos um bloco de código, senão, executamos outro. Porém, o que significa a frase "Resultado é verdadeiro." ? De imediato, não é possível observar a clareza dessa condição.

Logo, o recomendado, quando possível, é utilizar o modelo abaixo, onde atribuímos de forma direta, a função à condição, possibilitando uma intepretação quase instantânea do contexto daquele bloco de código. Segue o exemplo:

// PRÁTICA RECOMENDADA	

Console.WriteLine("Insira sua idade:");

int idade = Console.ReadLine();

if (this.PossuiIdadeMinima(idade))
Console.WriteLine("Você é maior de idade! Parabéns!");
else
Console.WriteLine("Você ainda é menor de idade, aguarde mais um pouco!");

No modelo acima, transforma-se a condição em uma leitura bem mais similar à "humana", onde basicamente interpreta-se como: "se possui idade minima, o bloco abaixo é executado, senão, executa-se o bloco seguinte."

Excelente! Aprendi muitos conceitos de boas práticas utilizando funções, mas minhas funções têm muitos parâmetros, deveria ser assim mesmo?

Não mesmo! Funções com muitos parâmetros, prejudicam a legibilidade do código, e são passíveis de erro ao utilizá-las. Mas como melhorar isso?

Segue o exemplo de uma função que não segue os padrões de boas práticas quanto à quantidade de parâmetros:

// PRÁTICA INADEQUADA

public void CadastrarAluno(int idade, string nome, string cidade, string curso, DateTime dataMatricula,  bool pagamentoEfetuado)
      {
          Aluno aluno = new Aluno()
          {
              idade = idade,
              nome = nome,
              cidade = cidade,
              curso = curso,
              dataMatricula = dataMatricula,
              pagamentoEfetuado = pagamentoEfetuado
          };


          this.dbContext.Alunos.Add(aluno);


          this.dbContext.SaveChanges();
}

     

Nota-se que a função solicita 6 parâmetros distintos e não nulos, dificultando o uso da mesma ao invocá-la, além de possíveis manutenções na mesma.

Como seria então, um modelo adequado e coerente com as boas práticas neste caso?

O ideal nessas situações, é sempre criarmos uma classe (um DTO por exemplo), e transformarmos cada parâmetro, em uma propriedade pública da classe com getters e setters. Segue o exemplo:

Primeiro criaremos a classe DTOAluno, utilizando o recurso nativo do C# ImplicitOperator, a fim de converter a classe para a classe Aluno.

 public class DTOAluno
      {
          public string Nome { get; set; }


          public int Idade { get; set; }


          public string Cidade { get; set; }


          public string Curso { get; set; }


          public DateTime DataMatricula { get; set; }


          public bool PagamentoEfetuado { get; set; }


          public static implicit operator Aluno(DTOAluno dtoAluno)
          {
              return new Aluno
              {
                  Nome = dtoAluno.Nome,
                  Idade = dtoAluno.Idade,
                  Cidade = dtoAluno.Cidade,
                  Curso = dtoAluno.Curso,
                  DataMatricula = dtoAluno.DataMatricula,
                  PagamentoEfetuado = dtoAluno.PagamentoEfetuado
              };
          }
      }

Com a nossa classe DTO construída, vamos agora montar a nova função, e utilizar o nosso conversor, para obter a classe Aluno, que precisamos para realizar a inserção dos dados no banco.

// PRÁTICA RECOMENDADA

public void CadastrarAluno(DTOAluno dtoAluno)
      {
          //O implicit operator permite a conversão direta de classes como no exemplo abaixo:
          Aluno aluno = dtoAluno;


          this.dbContext.Alunos.Add(aluno);


          this.dbContext.SaveChanges();
      }

Após todos esses exemplos, não restam dúvidas quanto à importância de aplicarmos as boas práticas ao utilizar funções em C#, não é mesmo?

Referências:

  • https://learn.microsoft.com/pt-br/dotnet/csharp/language-reference/operators/user-defined-conversion-operators

Compartilhe
Comentários (0)