C#
- Fail Fast (Falhando rápido)
![]() |
Hoje veremos o princípio 'Fail Fast' ou 'Falhe Rápido' e como usá-lo no C#. |
O termo 'Fail Fast' refere-se a um princípio de design de software que se aplica a muitas linguagens de programação, incluindo C#. O conceito do "Fail Fast" enfatiza a detecção e o tratamento precoce de erros e problemas no código, a fim de melhorar a confiabilidade e a capacidade de diagnóstico do software
Aqui estão algumas das ideias-chave associadas ao "Fail Fast" em C# e na plataforma .NET:
A seguir temos exemplos de aplicação deste princípio:
1- Validação de Parâmetros em Funções:
Uma maneira comum de aplicar o "Fail Fast" é validar os parâmetros de uma função. Se um parâmetro não atender aos critérios de validação, uma exceção é lançada imediatamente.
public
class
Calculadora { public int Dividir(int dividendo, int divisor) { if (divisor == 0) { throw new ArgumentException("Divisor não pode ser zero."); } return dividendo / divisor; } } |
Neste exemplo, se alguém tentar chamar o método Dividir com um divisor igual a zero, uma exceção será lançada imediatamente.
2- Validação de Entrada de Usuário:
Em um aplicativo da web, você pode aplicar o "Fail Fast" para validar a entrada do usuário antes de processá-la.
public
IActionResult UpdateProfile(string
username,
string email) { if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(email)) { // Falha imediatamente se os campos estiverem vazios. return BadRequest("Nome de usuário e email são obrigatórios."); } // Continue com a atualização do perfil. } |
Aqui, o aplicativo verifica se os campos de nome de usuário e email estão vazios e, se estiverem, retorna uma resposta de erro imediatamente.
3- Manipulação de Arquivos:
Quando você trabalha com arquivos, o "Fail Fast" pode ser aplicado para garantir que os arquivos necessários estejam presentes antes de prosseguir.
public
void
ProcessaArquivo(string
caminhoArquivo) { if (!File.Exists(caminhoArquivo)) { throw new FileNotFoundException("O arquivo não existe.", caminhoArquivo); } // Continue com o processamento do arquivo. } |
Neste caso, se o arquivo especificado não existir, uma exceção FileNotFoundException será lançada imediatamente.
4- Validação de Configurações:
No início da aplicação, é importante validar as configurações do aplicativo para garantir que tudo esteja configurado corretamente.
public
void
InitializeApp() { string apiKey = ConfigurationManager.AppSettings["ApiKey"]; if (string.IsNullOrWhiteSpace(apiKey)) { throw new ConfigurationException("A chave de API não foi configurada."); } // Continue com a inicialização da aplicação. } |
Se a chave de API não estiver configurada, a aplicação falhará na inicialização.
Agora vou mostrar um exemplo onde não escrevemos nenhuma validação e onde estamos obtendo uma string de conexão de nossa configuração e usando-a para adicionar um banco de dados Postgres à coleção de serviços de nosso aplicativo ASP.NET Core.
public
void
ConfigureServices(IServiceCollection services) { var connStr = Configuration.GetConnectionString("DefaultConnection"); services.AddDbContext<AppDbContext>(options => options.UseNpgsql(connStr)); } |
Usando este código em algum momento de nossa inicialização, garantimos que o banco de dados seja migrado usando:
context.Database.Migrate();
Agora, se você executar seu código sem configurar a string de conexão corretamente, receberá o seguinte erro:
System.ArgumentException: Host não pode ser nulo
Você pode se perguntar: “O que diabos isso significa? Qual argumento ? Qual Host ?”
A razão pela qual você está ficando confuso e a mensagem é tão enigmática é que não fizemos a devida diligência e validamos a entrada de uma fonte externa assim que a recebemos.
Neste caso, não validamos se a string de conexão retirada da Configuração realmente existe. Ao fazer isso, garantimos que o aplicativo falhará o mais tarde possível, em um momento imprevisível e em condições imprevisíveis.
Você vai perder um bom tempo para detectar este problema.
A solução
A solução é simples. NÃO codifique defensivamente.
Falhe RÁPIDO. Falhe
informativamente. Lançe exceções.
Ao lidar com qualquer entrada, pergunte-se:
'Essa entrada é crítica para o escopo com o
qual você está trabalhando (aplicativo, solicitação HTTP, manipulador de
barramento de eventos...)?'
Se sim, valide e, se
não for o esperado, lance uma exceção imediatamente
public
void
ConfigureServices(IServiceCollection services) { var connStr = Configuration.GetConnectionString("DefaultConnection"); if (string.IsNullOrEmpty(connStr)) { throw new ConfigurationMissingException("DefaultConnection"); } services.AddDbContext<AppDbContext>(options => options.UseNpgsql(connStr)); } |
Ou :
... builder.Services.AddDbContext<ProductServiceContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("ProductServiceContext") ?? throw new InvalidOperationException("Connection string 'ProductServiceContext' not found."))); ... |
Dificultando a depuração
Uma maneira ainda melhor de garantir que nosso aplicativo falhe de maneira imprevisível em momentos imprevisíveis é programar de forma excessivamente defensiva e introduzir valores padrão e alternativos para entradas críticas.
Programar de forma excessivamente defensiva e introduzir valores padrão e alternativos pode levar a problemas como:
Considere clássico que ilustra isso em ação pode ser visto no código a seguir:
public
class
UserAuthentication { public bool AuthenticateUser(string username, string password) { // Verificação defensiva if (username == null) { username = "defaultUser"; } if (password == null) { password = "defaultPassword"; } // Simulação de autenticação if (username == "admin" && password == "admin123") { return true; } return false; } } class Program{ static void Main() { UserAuthentication auth = new UserAuthentication(); // Tenta autenticar um usuário bool isAuthenticated = auth.AuthenticateUser(null, null); if (isAuthenticated) { Console.WriteLine("Usuário autenticado com sucesso."); } else { Console.WriteLine("Falha na autenticação."); } } } |
Neste exemplo, a classe UserAuthentication introduz verificações defensivas para verificar se os valores de entrada username e password são nulos. Se forem nulos, valores padrão são atribuídos. No entanto, isso introduz complexidade desnecessária e obscurece o motivo real da falha na autenticação.
Quando executamos o programa com auth.AuthenticateUser(null, null), o resultado é que o usuário é autenticado com sucesso, embora não tenhamos fornecido credenciais válidas. Isso é devido à lógica defensiva e à atribuição de valores padrão. Como resultado, a depuração se torna complicada, pois é difícil determinar por que o usuário foi autenticado quando as credenciais estavam ausentes.
Em vez de programar de forma excessivamente defensiva com valores padrão, é preferível falhar imediatamente quando as entradas críticas são nulas ou inválidas, seguindo o princípio "Fail Fast". Isso torna a depuração mais simples e ajuda a identificar e resolver problemas com mais eficácia.
E estamos conversados...
E Jesus, respondendo,
disse-lhes: Não necessitam de médico os que estão sãos, mas, sim, os que estão
enfermos;
Eu não vim chamar os justos, mas, sim, os pecadores, ao
arrependimento.
Lucas
5:31,32
Referências:
.NET MAUI - Lançamento da Release Candidate