[. Net] Some Thoughts on exception and its use in projects (1)

Time:2021-4-24

Link to this article: https://www.cnblogs.com/hubaijia/p/about-exceptions-1.html

Article series:

As for the basic syntax and function of exception, I won’t repeat it here. Let’s record some thoughts about exception in my project.

1、 Basic documents

The basic document is the official or God’s explanation and elaboration on this issue. This paper is based on these documents, no more details, can refer to and record.

2、 Use exception instead of error code

In the initial design of a project, sometimes we define the following structure in order to clarify the error type:

enum ErrCode
{
    Ok,
    ArgumentErr,
    OtherErr1,
    OtherErr2
}

class SomeClass
{
    public ErrCode DealSomething()
    {
        //.....
        return ErrCode.Ok;
    }
}

class CallerClass
{
    public void CallSome(SomeClass some)
    {
		ErrCode err = some.DealSomething();
        
		if(err == ErrCode.Ok)
        {
            //....
        }
        else
        {
            //.....
        }
    }
}

Using errCode doesn’t mean that it’s totally impossible. For example, returning errCode in Web API call is a good choice.

1. Abnormal eating

However, in other cases, using errCode will make users miserable, because each call should be handled and judged carefully, otherwise it will be very easyAbnormal eating

Compare the following program fragments:

Using errCode Using exception
class SomeClass
{
    public ErrCode DealSomething()
    {
        //.....
        return ErrCode.SomeErr;
    }
}
class CallerClass
{
    public void CallSome(SomeClass some)
    {
		some.DealSomething();
		giveMoney();
	}
}
class SomeClass
{
    public void DealSomething()
    {
        //.....
        if(somthingWrong)
        {
        	throw new SomeException(...);
        }
    }
}
class CallerClass
{
    public void CallSome(SomeClass some)
    {
		some.DealSomething();
		giveMoney();
	}
}

The errCode is used on the left side, because the errCode is not handled carelessly, resulting in subsequent code execution;

With exception, even if it is not processed, the subsequent code will not be executed, and it will be thrown to wait for the upper user to process.

In addition, exception has many other benefits, such as stack trace information, cross process and so on.

So the essence of exception is a ready-made error handling mechanism, so don’t use custom errCode any more.

2、 The use of exception in project

Since we explicitly use exception as our error handling solution. How to use it?

1. Use refined exception

First, avoid the following writing:

class Dal
{
    public void Add(Entity entity)
    {
        //....
        if(somthingWrong)
        {
            throw new Exception("add wrong");
        }
        //...
    }
}

The problem with this way of writing is that the caller can’t make clear the reason and type of exception, and only rely on documents or messages to understand it. When more and more projects are built up, the same exception flies everywhere, and the log is full of all kinds of magical messages.

We can take multidimensional refinement exception: type, error type, specific reasons.

Use concrete typed exceptions, such asConnectionException, TimeoutException, that is, define an exception type for each error,

This method must be very clear to everyone, but if there are more than a dozen errors in a module, and you have more than a dozen modules?

There are two ways to deal with this situation.

  • By internal and external

    If an exception needs to be received and handled by external callers of the module, a specific exception type is defined, such as connectionexception and timeoutexception;

    If an exception is self processed by the module memory, only the same innerexception (or other name) plus other information is used to distinguish it( Did you smell errCode again, yes).

    This method is often seen in many open source class libraries.

  • A module defines an exception type, which is combined with errCode

    I just said that errCode can’t be used, but I also mentioned here. Don’t get me wrong. Look down carefully.

    For example, the following code:

    class ErrCode
    {
        public int Code { get; set; }
        public string? Name { get; set; }
        public string? Message { get; set; }
    }
    
    class DalException : Exception
    {
        public ErrCode Code { get; }
        
        public DalException(ErrCode errCode, Exception? innerException = null):base(errCode.Message, innerException)
        {
        	Code = errCode;    
        }
    }

    ErrCode is no longer simply an enum, but more useful.

    In this way, each module has its own exception type, and each error type can be effectively classified.

    This method is often used in business module and basic framework module of company project.

2. Use Exception.Data

Are you a little surprised by the above code segment? Man, are you a bit radical in fixing message directly into errCode.

In fact, this is also the experience summed up in the actual development. Try not to let programmers write all kinds of exception messages.

But we can use it Exception.Data To achieve more scenarios.

For example, the following code snippet:

static class ExceptionFactory
{
    public static DalException OnMigrateError(int oldVersion, int newVersion, string sql, string cause)
    {
        DalException ex = new DalException(ErrCodes.MigrationErr);
        
        ex.Data["OldVersion"] = oldVersion;
        ex.Data["NewVersion"] = newVersion;
        ex.Data["Cause"] = cause;
    }
}

class SomeCls
{
    void Migration()
    {
        //.....
        throw ExceptionFactory.OnMigrationErr(oldVersion,)
    }
}

In this way, the specific information that needs to be recorded is fixed for each error, and cause is the reason why the programmer records the current situation. Of course, you can rewrite onmigrateerror to record another scenario.

The advantage of recording the specific information required by exception in the dictionary of data rather than in message is that it is helpful for further processing in the future, such as structured log.

Of course, this brings a problem that needs to be paid attention to, that is, how to deal with it Exception.Data Because Exception.ToString () method, does not print data.

In terms of logging, if you useSerilogIt is recommended that you useSerilog.ExceptionsHe’s right Exception.Data Very friendly.

So far, we use the type — > errorcode — > specific cause in refining exception

3. Centralized management

All kinds of exceptions and unknown solutions need to be solved by rules, but rules need to be kept in mind, so the most reliable solution is to use code structure.

First set upErrCodesStatic class, and then createExceptionFactoryClass, so that all exceptions thrown in business code havethrow ExceptionFactory.XXException()Such a form.

//All error codes
internal static class ErrCodes
{
    public static ErrCode MigrationErr { get; } = new ErrCode(1, nameof(MigrationErr), "Error happens in Migration.");
    //........ other errors
}

//All exceptions are generated from this
internal static class ExceptionFactory
{
    public static DalException OnMigrateError(int oldVersion, int newVersion, string sql, string cause)
    {
        DalException ex = new DalException(ErrCodes.MigrationErr);
        
        ex.Data["OldVersion"] = oldVersion;
        ex.Data["NewVersion"] = newVersion;
        ex.Data["Cause"] = cause;
    }
    //Other scenes
}

In this way, when reviewing the code, you only need to see the programmer in the codenew XXExceptionThen urge him to be thereExceptionFactoryFind the appropriate exception scene or add it yourself.

With the accumulation of projects, even if there are many kinds of exceptions and errors, you only need toErrCodesandExcepionFactoryIn the two classes, summarize and reconstruct.

4. Reference code

Specific code, upload inGithubWelcome to discuss and correct.

3、 Notice

In the next article, I’ll explore some other practical project issues.