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 :/ ...

Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
scroll top