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