debugcsharpMinor
"Better" Exception with autogenerated message
Viewed 0 times
autogeneratedexceptionwithmessagebetter
Problem
Because I don't like writing the same useless exeption messages all the time ;-) I thought I create a better exception that would make this for me. This is what I came up with:
```
[Serializable]
public class SmartException : Exception
{
private const string PropertyNamePrefix = "$";
private const string InnerExceptionSeparator = " >>> ";
protected SmartException() { }
protected SmartException(Exception innerException) : base(null, innerException) { }
public override string Message => FormatMessage();
protected T GetValue([CallerMemberName] string propertyName = "")
{
return (T)Data[PropertyNamePrefix + propertyName];
}
protected void SetValue(T value, [CallerMemberName] string propertyName = "")
{
Data[PropertyNamePrefix + propertyName] = value;
}
private string FormatMessage()
{
var ex = (Exception)this;
var message = new StringBuilder(1024);
while (ex != null)
{
var exceptionSeparator = message.Length > 0 ? InnerExceptionSeparator : string.Empty;
var exceptionName = Regex.Replace(ex.GetType().Name, "Exception$", string.Empty);
message.Append($"{exceptionSeparator}{exceptionName}");
var properties = FormatProperties(ex);
message.Append(string.IsNullOrEmpty(properties) ? null : $": {properties}");
ex = ex.InnerException;
}
return message.ToString();
}
private static string FormatProperties(Exception ex)
{
return
ex is SmartException
? FormatPropertiesFromData(ex)
: FormatPropertiesFromType(ex);
}
private static string FormatPropertiesFromData(Exception ex)
{
var entries = ex.Data
.Cast()
.Where(de =>
de.Key is string &&
((string)de.Key).StartsWith("$") &&
de.Value != null);
var properties = new StringBuilder(1024
```
[Serializable]
public class SmartException : Exception
{
private const string PropertyNamePrefix = "$";
private const string InnerExceptionSeparator = " >>> ";
protected SmartException() { }
protected SmartException(Exception innerException) : base(null, innerException) { }
public override string Message => FormatMessage();
protected T GetValue([CallerMemberName] string propertyName = "")
{
return (T)Data[PropertyNamePrefix + propertyName];
}
protected void SetValue(T value, [CallerMemberName] string propertyName = "")
{
Data[PropertyNamePrefix + propertyName] = value;
}
private string FormatMessage()
{
var ex = (Exception)this;
var message = new StringBuilder(1024);
while (ex != null)
{
var exceptionSeparator = message.Length > 0 ? InnerExceptionSeparator : string.Empty;
var exceptionName = Regex.Replace(ex.GetType().Name, "Exception$", string.Empty);
message.Append($"{exceptionSeparator}{exceptionName}");
var properties = FormatProperties(ex);
message.Append(string.IsNullOrEmpty(properties) ? null : $": {properties}");
ex = ex.InnerException;
}
return message.ToString();
}
private static string FormatProperties(Exception ex)
{
return
ex is SmartException
? FormatPropertiesFromData(ex)
: FormatPropertiesFromType(ex);
}
private static string FormatPropertiesFromData(Exception ex)
{
var entries = ex.Data
.Cast()
.Where(de =>
de.Key is string &&
((string)de.Key).StartsWith("$") &&
de.Value != null);
var properties = new StringBuilder(1024
Solution
Whilst your code is a clever little trick to add useful details to an exception, there is one vital flaw as I see it, and that is you don't know where the exception was thrown. In the best code bases I've seen and worked with, there is always an unique exception message which you later on can use to locate where the specific exception was thrown.
In mostly stable production code, exceptions should not occur very often, but if they occur two things are important when looking into it:
Addendum: Unique exception messages
One way to generate unique message is to repeat something related to the method/function you are executing and the failing situation. Lets say that your missing file was within creation of a user account, then I would have preferred a message like:
Now when you, as a developer or support, gets a hold of the exception you should be able to locate where this exception occured in your code base by a single search for "Failed creating user account". This message is not dynamic, but unique. The properties are dynamic, and should add details to the exception.
With such an exception you can locate the offending code even though the stack trace is not available, which it wouldn't be if the exception is found is shown in a dialog window or status bar, or in some cases in a log file, or if it being copy-pasted from somewhere into an email.
The unique message is also something you can train users to respond to. They can now search in a given list where they can follow some procedures, whilst for other (and unknown exceptions) they contact support.
One final note on unique messages, even a unique number, would be better than nothing at all. Just don't let it be autogenerated which renders it useless when you rebuild your code. Some code bases I've seen has numbering schemes like Exxyyzzz where xx can denote module, yy file in module, and zzz could be a running number within that file.
In mostly stable production code, exceptions should not occur very often, but if they occur two things are important when looking into it:
- Details as to why the exception occured, and this your code does good. You make it easy to provide good details to the exception.
- Where the exception occured, and this you seem to bypass. You only have a generic reference to which exception was thrown and possibly a somewhat unique combination of parameters. This will make it hard for you to locate your exception when your code base is getting larger
Addendum: Unique exception messages
One way to generate unique message is to repeat something related to the method/function you are executing and the failing situation. Lets say that your missing file was within creation of a user account, then I would have preferred a message like:
UserNotFound: Failed creating user account - CallerName: "Foo", UserName: "TestUser" >>> FileNotFound: Some message - FileName: "helloworld.txt"
Now when you, as a developer or support, gets a hold of the exception you should be able to locate where this exception occured in your code base by a single search for "Failed creating user account". This message is not dynamic, but unique. The properties are dynamic, and should add details to the exception.
With such an exception you can locate the offending code even though the stack trace is not available, which it wouldn't be if the exception is found is shown in a dialog window or status bar, or in some cases in a log file, or if it being copy-pasted from somewhere into an email.
The unique message is also something you can train users to respond to. They can now search in a given list where they can follow some procedures, whilst for other (and unknown exceptions) they contact support.
One final note on unique messages, even a unique number, would be better than nothing at all. Just don't let it be autogenerated which renders it useless when you rebuild your code. Some code bases I've seen has numbering schemes like Exxyyzzz where xx can denote module, yy file in module, and zzz could be a running number within that file.
Context
StackExchange Code Review Q#109685, answer score: 4
Revisions (0)
No revisions yet.