Recommendations for adding exceptions messages to code [closed]
https://softwareengineering.stackexchange.com/questions/301735
-
21-10-2020 - |
Question
The "problem" that I am having right now its quite simple, I am creating a code with A LOT of validations, in case one of those fails it is supposed to throw an exception.
In order to make the messages explicit in favor of depicting the reason for the exception I need to write a "considerable" amount of text. This wouldn't be an issue if it were only a couple of exceptions, but now I am in direct course to end with a file with more text (for the exception messages) than code.
I would like to get some suggestions/alternatives to put all that text out of my code (I was thinking on having it in variables at the top of the file but that is not very smart "memoriwise-able" speaking).
Example
if (operator.isBitwiseOperator()) {
// >>=, <<=, &=, |=, ^=
if (!(IntegerStmt.isAnIntegerAsignableClass(fstOpClass)
&& IntegerStmt.isAnIntegerAsignableClass(sndOpClass))) {
if ((operator.equals(Operator.AND_ASSIGN)
|| operator.equals(Operator.OR_ASSIGN)
|| operator.equals(Operator.XOR_ASSIGN))
&& !(LogicalInfo.isBoolean(fstOpClass)
&& LogicalInfo.isBoolean(sndOpClass))) {
throw new UnsupportedOperationException(
"In order to use a bitwise logical assign "
+ "operator (&=, |=, ^=) it is "
+ "necessary booth operands to be "
+ "either an integer assignable type "
+ INTEGER_ASSIGNABLE_VALUES
+ " or a logical boolean value "
+ "(b/Boolean)");
} else {
throw new UnsupportedOperationException(
"In order to use a bitwise assign operator it is "
+ "necessary booth operands to be an "
+ "integer assignable type "
+ INTEGER_ASSIGNABLE_VALUES);
}
}
}
Now, like that example which shows that I have "some" exceptions that I consider as not terribly long, I have many, so from drop to drop I am filling the glass :/ ...
Solution
I will start with the recommendation to read "Clean Code" by Robert Martin. If we follow recommendations from that book, you could :
- break that huge if tree into smaller chunks of code
- put throwing exceptions into private methods at the end of files
And that would be the solution to your problem. That way, the text will not interfere with the logic.
Something like this :
if (operator.isBitwiseOperator()) {
// >>=, <<=, &=, |=, ^=
if (!(IntegerStmt.isAnIntegerAsignableClass(fstOpClass)
&& IntegerStmt.isAnIntegerAsignableClass(sndOpClass))) {
if ((operator.equals(Operator.AND_ASSIGN)
|| operator.equals(Operator.OR_ASSIGN)
|| operator.equals(Operator.XOR_ASSIGN))
&& !(LogicalInfo.isBoolean(fstOpClass)
&& LogicalInfo.isBoolean(sndOpClass))) {
ThrowExceptionForNotIntTypes();
} else {
ThrowExceptionForIncopatibleTypes();
}
}
}
private void ThrowExceptionForNotIntTypes() {
throw new UnsupportedOperationException(
"In order to use a bitwise logical assign "
+ "operator (&=, |=, ^=) it is "
+ "necessary booth operands to be "
+ "either an integer assignable type "
+ INTEGER_ASSIGNABLE_VALUES);
}
private void ThrowExceptionForIncopatibleTypes() {
throw new UnsupportedOperationException(
"In order to use a bitwise assign operator it is "
+ "necessary booth operands to be an "
+ "integer assignable type "
+ INTEGER_ASSIGNABLE_VALUES);
}
OTHER TIPS
Good exception messages are important. However, the exception messages generated by a piece of code might not always be suitable for an end user. You can therefore defer construction of an error message to an outer layer. Your exceptions should instead contain all the necessary data to construct a message. This implies that you create sufficient application-specific exception classes.
If you are creating a library rather than an application, fine-grained exception hierarchies are more useful than the ability to add a error message localization layer, but a bit of abstraction will still come in very useful. Especially, common code should be factored out. In your case, both messages have very similar syntactic structure. You could therefore create a message template and simply fill in the blanks when throwing an exception. Or you could create a helper function that creates an exception.
I would rewrite your code as
// making the exception format public makes testing easier
public static final String BITWISE_ASSIGN_ERROR_MESSAGE_FORMAT = "In order to use %1s it is necessary that both operators be %2s"
...
// >>=, <<=, &=, |=, ^=
if (operator.isBitwiseOperator()) {
checkBitwiseOperator(...);
}
...
private void checkBitwiseOperator(...) {
bool operandsAreIntegerAssignable =
IntegerStmt.isAnIntegerAsignableClass(fstOpClass)
&& IntegerStmt.isAnIntegerAsignableClass(sndOpClass);
bool operandsAreBoolean =
LogicalInfo.isBoolean(fstOpClass)
&& LogicalInfo.isBoolean(sndOpClass)
bool operatorIsLogicalAssign =
operator.equals(Operator.AND_ASSIGN)
|| operator.equals(Operator.OR_ASSIGN)
|| operator.equals(Operator.XOR_ASSIGN);
// special-case &=, |=, ^= – they can be integer or boolean
if (operatorIsLogicalAssign && !operandsAreBoolean) {
if (operandsAreInteger || operandsAreBoolean)
return;
throw new UnsupportedOperationException(
String.format(BITWISE_ASSIGN_ERROR_MESSAGE_FORMAT,
"a bitwise logical assignment operator (&=, |=, ^=)",
"either integer assignable " + INTEGER_ASSIGNABLE_VALUES + " or a boolean value"));
}
// the shift-assignment operators can only work on integers
if (operandsAreIntegerAssignable)
return;
throw new UnsupportedOperationException(
String.format(BITWISE_ASSIGN_ERROR_MESSAGE_FORMAT,
"bitwise assignment operator",
"integer assignable " + INTEGER_ASSIGNABLE_VALUES));
}
The next step would be to extract message construction. Note that your code is essentially doing type-checking, so we could create a special exception class for that. It could show the surrounding code, print the line number, and explain the types. This gets more powerful when you have a sufficient explicit model of the available operations. This could e.g. allow you to create an exception such as
throw new TypeError(expectedType, actualType, expression.sourceInfo());
and get an error message created by that exception type like
inputFile:32:6: TypeError
expected operator |= (Int left, Int right)
or operator |= (Bool left, Bool right)
but found "x" of type String
and "y + 2" of type Int
here:
foo(x |= y + 2)
~~^~~~~~~~
This is extremely user-friendly, but hard to get right (for starters, you need an actual model of the type system in your code, and a mechanism to connect types with snippets of source code).
Throw an error code, and then use it to lookup the lengthy text. That text could even be placed in a locale-specific resources file.
That said, I'd throw a short human-readable error message as well just so an admin reading logs would be able to understand which error it was without having to look it up.