검색어를 입력해 주세요.
Programming / 08 7월 2020
.Net Exceptions Best Practices

Exception is one of those constructs that is easy to misuse. This might include not throwing exception when one should or catching the exception without a good reason. Also there is the problem of throwing the wrong exception which not only doesn’t help us, but can confuse us. On the other hand there is the problem of properly handling the exception. Exception handling is even worse when it comes to improper use. So in this post I’m going to discuss some best practices regarding throwing and handling exceptions. I’ll show you how throwing the proper exception can save us a lot of headaches in terms of debugging. I also going to discuss how bad exception handling can be misleading when we want find bugs.

Throwing Exception

When To Throw Exception

static void FindWinner(int[] winners)
{
    if (winners == null)
    {
        throw new System.ArgumentNullException($"Parameter {nameof(winners)} cannot be null", nameof(winners));
    }
    
    OtherMethodThatUsesTheArray(winner);
}
void WriteLog(FileStream logFile)
{
    if (!logFile.CanWrite)
    {
        throw new System.InvalidOperationException("Logfile cannot be read-only");
    }
    // Else write data to the log and return.
}
static int GetValueFromArray(int[] array, int index)
{
    try
    {
        return array[index];
    }
    catch (System.IndexOutOfRangeException ex)
    {
       throw new ArgumentOutOfRangeException("Index is out of range", "index", ex);
    }
}

Do Not Use Exceptions To Change The Flow Of The Program

[HttpPost]
public ViewResult CreateProduct(CreateProductViewModel viewModel)
{
    try
    {
        ValidateProductViewModel(viewModel);
        CreateProduct(viewModel);
    }
    catch (ValidationException ex)
    {
        return View(viewModel);
    }
}
[HttpPost]
public ViewResult CreateProduct(CreateProduct viewModel)
{
    bool viewModelIsValid = ValidateProductViewModel(viewModel);
        
    if(!viewModelIsValid) return View(viewModel); 
        
    CreateProduct(viewModel);
    
     return View(viewModel); 
}

Do Not Return Error Code, Throw Exception Instead

Make Sure To Clean Up Any Side Effect If Exception Thrown

private static void MakeDeposit(Account account,decimal amount)
{
    try
    {
        account.Deposit(amount);
    }
    catch
    {
        account.RollbackDeposit(amount);
        throw;
    }
}
try
{
    DBConnection.Save();
}
catch
{
    // Roll back the DB changes so they aren't corrupted on ANY exception
    DBConnection.Rollback();

    // Re-throw the exception, it's critical that the user knows that it failed to save
    throw;
}

If You Catch An Exception And Cannot Handle It Properly, Make Sure To Re-Throw It

try
{
    conn.Close();
}
catch (InvalidOperationException ex)
{
    _logger.LogError(ex.Message, ex);
    
    //bad practice, stack trace is lost
    //throw ex;
    
    //good practice, keepi'n the stack trace
    throw;
}

Do Not Use Exceptions As Parameter Or Return Value

Exception AnalyzeHttpError(int errorCode) {
    if (errorCode < 400) {
         throw new NotAnErrorException();
    }
    switch (errorCode) {
        case 403:
             return new ForbiddenException();
        case 404:
             return new NotFoundException();
        case 500:
             return new InternalServerErrorException();
        …
        default:
             throw new UnknownHttpErrorCodeException(errorCode);
     }
}

Prevent The Program From Throwing Exception If Possible, Exceptions Are Expensive

try
{
    conn.Close();
}
catch (InvalidOperationException ex)
{
    Console.WriteLine(ex.GetType().FullName);
    Console.WriteLine(ex.Message);
}
if (conn.State != ConnectionState.Closed)
{
    conn.Close();
}

Create Your Own Exception Class

[Serializable()]
public class InvalidDepartmentException : System.Exception
{
    public InvalidDepartmentException() : base() { }
    public InvalidDepartmentException(string message) : base(message) { }
    public InvalidDepartmentException(string message, System.Exception inner) : base(message, inner) { }

    // A constructor is needed for serialization when an exception propagates from a remoting server to the client. 
    protected InvalidDepartmentException(SerializationInfo info, StreamingContext context) { }
}

Handling (Catching) Exception

When To Catch Exception

public void OpenFile(string filePath)
{
  try
  {
     File.Open(path);
  }
  catch (FileNotFoundException ex)
  {
     Console.WriteLine("The specified file path not found, please enter another path.");
     PromptUserForAnotherFilePath();
  }
}
private static void Filter()
{
    try
    {
        A();
    }
    catch (OperationCanceledException exception) when (string.Equals(nameof(ExceptionFilter), exception.Message, StringComparison.Ordinal))
    {
    }
}
int GetInt(int[] array, int index)
{
    try
    {
        return array[index];
    }
    catch(System.IndexOutOfRangeException e)
    {
        throw new System.ArgumentOutOfRangeException("Parameter index is out of range.", e);
    }
}
try
{
    // Open File
}
catch (FileNotFoundException e)
{
    _logger.LogError(e);
    // Re-throw the error.
    throw;     
}
[HttpPost]
public async Task<IActionResult> UpdatePost(PostViewModel viewModel)
{
    try
    {
         _mediator.Send(new UpdatePostCommand{ PostViewModel = viewModel});
         return View(viewModel);
    }
    catch (Exception e)
    {
        _logger.LogError(e);
       return View(viewModel);
    }
}

Finally The Finally Block

FileStream file = null;
var fileinfo = new FileInfo("C:\\file.txt");
try
{
    file = fileinfo.OpenWrite();
    file.WriteByte(0xF);
}
finally
{
    // Check for null because OpenWrite might have failed.
    if (file != null)
    {
        file.Close();
    }
}

Summary

 

https://medium.com/@mincasoft/net-exceptions-best-practices-2dc8487d043f

Copyright © 2020 eloicube inc. All rights reserved.