Forçando Chamadas de Funções Requeridas em C#
O Problema: Chamadas de Funções Não Verificadas
Em C#, é bastante comum criar funções que retornam um status, ajudando os desenvolvedores a monitorar o sucesso ou a falha das operações. No entanto, uma armadilha comum é que alguns desenvolvedores podem ignorar esses status retornados completamente. Isso pode levar a consequências indesejadas se o tratamento de erro adequado não for executado.
Por exemplo, considere uma classe Status
, que pode conter informações sobre se uma operação foi bem-sucedida ou se houve problemas durante a execução:
Status MinhaFuncao()
{
if(...) // algo errado
return new Status(false, "Algo deu errado");
else
return new Status(true, "OK");
}
A expectativa é que todos os chamadores de MinhaFuncao
verifiquem o status retornado:
Status meuStatus = MinhaFuncao();
if (!meuStatus.IsOK())
// trate-o, mostre uma mensagem,...
No entanto, alguns chamadores podem preguiçosamente ignorar a verificação de status:
// Ignorando o status retornado
MinhaFuncao(); // chama a função e ignora o Status retornado
Isso Pode Ser Evitado?
Isso levanta uma pergunta importante: Como podemos garantir que cada chamador verifique o status retornado? Em C++, poderia-se utilizar um destrutor para verificar o status no final do ciclo de vida do objeto. No entanto, em C#, destrutores geralmente são desencorajados devido à natureza não determinística do coletor de lixo.
A Solução: Usando Delegates para Chamadas de Funções Requeridas
Embora C# não tenha a capacidade de forçar que um valor de retorno de método seja chamado ou verificado, uma solução inovadora envolve o uso de delegates. Esta técnica não apenas retorna um valor; em vez disso, requer que o chamador processe o status retornado explicitamente através de um mecanismo de callback.
Implementação de Exemplo
Vejamos uma implementação simples usando delegates:
using System;
public class Exemplo
{
public class Brinquedo
{
private bool emArmario = false;
public void Brincar() { Console.WriteLine("Brincando."); }
public void Guardar() { emArmario = true; }
public bool EstaEmArmario { get { return emArmario; } }
}
public delegate void CallbackUsoBrinquedo(Brinquedo brinquedo);
public class Mae
{
public static void SolicitarBrinquedo(CallbackUsoBrinquedo callback)
{
Brinquedo brinquedo = new Brinquedo();
callback(brinquedo);
if (!brinquedo.EstaEmArmario)
{
throw new Exception("Você não guardou seu brinquedo no armário!");
}
}
}
public class Filho
{
public static void Brincar()
{
Mae.SolicitarBrinquedo(delegate(Brinquedo brinquedo)
{
brinquedo.Brincar();
// Oops! Esqueceu de guardar o brinquedo!
});
}
}
public static void Main()
{
Filho.Brincar();
Console.ReadLine();
}
}
Explicação do Código
Neste exemplo, quando Mae.SolicitarBrinquedo
é chamado, espera-se uma função de callback que executará operações com o objeto Brinquedo
. Após executar o callback, verifica-se se o brinquedo foi guardado:
- Se o callback não chamar
brinquedo.Guardar()
, uma exceção é lançada. Isso garante que o chamador deve lidar com oBrinquedo
ao final de seu uso, forçando efetivamente que operações de acompanhamento necessárias ocorram. - Este método não garante que o chamador se lembre de verificar o status, mas impõe uma estrutura de uma maneira que eleva a importância da operação.
Conclusão
Embora possa não ser possível em C# forçar verificações em valores retornados tão rigorosamente quanto em C++, podemos aproveitar os delegates para criar um padrão que efetivamente comunica a necessidade de uma ação adicional, aumentando a confiabilidade do nosso código. Utilizar este padrão ajuda a garantir o tratamento adequado dos resultados das funções, ao mesmo tempo em que mantém suas práticas de programação limpas e organizadas.
Ao implementar essa abordagem, garantimos que os desenvolvedores que utilizam seu código não possam facilmente contornar verificações essenciais. Embora possa parecer um pouco não convencional, serve como uma maneira útil de criar uma cultura de responsabilidade no tratamento de erros.