2
\$\begingroup\$

(Note: this is originally posted as this SO question )

I'm trying to refactor the error logging and exception throwing into a method.

Original code:

private Box retrieveBox() {
    BoxParameter param = new BoxParameter("red", 10, 5, 3);
    List<Long> boxIds = boxDao.retrieveByParameter(param);

    if (boxIds.isEmpty()) {
        // A
        logger.warn("box not found");
        throw new BoxNotFoundException();
    }
    if (boxIds.size() > 1) {
        // B
        logger.warn("multiple boxes found");
        throw new MultipleBoxesFoundException();
    }

    Optional<Box> maybe = boxDao.retrieve(boxIds.get(0));
    if (maybe.isPresent()) {
        return maybe.get();
    }

    // C
    logger.warn("box not found");
    throw new BoxNotFoundException();
} 

Refactored code:

private Box retrieveBox() {
    BoxParameter param = new BoxParameter("red", 10, 5, 3);
    List<Long> boxIds = boxDao.retrieveByParameter(param);

    if (boxIds.isEmpty()) {
        // A
        logErrorAndThrowBoxNotFoundException();
    }
    if (boxIds.size() > 1) {
        // B
        logErrorAndThrowMultipleBoxesFoundException();
    }

    Long boxId = boxIds.get(0);
    Optional<Box> maybe = boxDao.retrieve(boxIds.get(0));
    if (maybe.isPresent()) {
        return maybe.get();
    }

    // C-1
//  logger.warn("box not found");
//  throw new BoxNotFoundException();

    // C-2
    logErrorAndThrowBoxNotFoundException();
    return null;  // needs to be here to make it compile
}

private void logWarningAndThrowMultipleBoxesFoundException() throws MultipleBoxesFoundException {
    logger.warn("multiple boxes found");
    throw new MultipleBoxesFoundException();
}

private void logWarningAndThrowBoxNotFoundException() throws BoxNotFoundException {
    logger.warn("box not found");
    throw new BoxNotFoundException();
} 

I really dislike the return null part in C-2, but without it, I'll have to go back to C-1, which defeats the purpose of refactoring.

Is there a better way to do it?

\$\endgroup\$
  • \$\begingroup\$ It should just be throw logErrorAndThrowBoxNotFoundException which sounds redundant and maybe you should log the error and then do that in here. \$\endgroup\$ – Legato Jun 2 '16 at 20:16
  • \$\begingroup\$ Either throw an exception or log. Doing both would be redundant, because the exception may be caught and the user of your code may just log the exception message. If you can handle the error; log it. If you can't: throw an exception. Returning null or other special should be avoided if you can. \$\endgroup\$ – Jochem Kuijpers Jun 4 '16 at 22:13
1
\$\begingroup\$

Java 8

Optional<Box> maybe = boxDao.retrieve(boxIds.get(0));
if (maybe.isPresent()) {
    return maybe.get();
}

// C
logger.warn("box not found");
throw new BoxNotFoundException();

If this is the Java 8 Optional we are talking about, you can use Optional.orElseThrow(Supplier) instead:

return boxDao.retrieve(boxIds.get(0)).orElseThrow(() -> {
    logger.warn("Box not found.");
    return new BoxNotFoundException();
});

Guava

If this is Guava we are talking about, then between the options you presented, I think it's still fine to stick with the original. Method extraction in only two places - just to 'substitute' two lines with the added complication of throw-ing different Exception types altogether - is unnecessary here. And as you have observed, having to return null does look out of place too.

Alternative

I understand you are trying to unify the exception throwing when you have either no results, or no box from the given ID. In that case, perhaps you can reorder your checks accordingly:

private Box retrieveBox() {
    BoxParameter param = new BoxParameter("red", 10, 5, 3);
    List<Long> boxIds = boxDao.retrieveByParameter(param);
    if (boxIds.size() == 1) {
        Optional<Box> maybe = boxDao.retrieve(boxIds.get(0));
        if (maybe.isPresent()) {
            return maybe.get();
        }
    }
    boolean isMultiple = boxIds.size() > 1;
    logger.warn(isMultiple ? "Multiple boxes found." : "Box not found.");
    throw isMultiple ? new MultipleBoxesFoundException() : new BoxNotFoundException();
} 
\$\endgroup\$
  • 1
    \$\begingroup\$ thanks for the answer. your alternative fits my needs best. \$\endgroup\$ – Jeff Xiao Jun 6 '16 at 14:33
3
\$\begingroup\$

I'd suggest you just get rid of the throwing method and throw it in the retrieveBox method like this:

throw new BoxNotFoundException("box not found");

You should catch it outside this method and handle it, do the logging etc when you catch it. Logging is part of exception handling and should be done when the exception is being handled, not before.

\$\endgroup\$
3
\$\begingroup\$

I'll have to go back to C-1, which defeats the purpose of refactoring.

But, what was the purpose of that refactoring anyway? Extracting a method that has And in its name is just exposing the problem, not solving it: the method is responsible for too many things!

Let the caller catch these exceptions and handle them as they see fit!

private Box retrieveBox() {
    BoxParameter param = new BoxParameter("red", 10, 5, 3);
    List<Long> boxIds = boxDao.retrieveByParameter(param);

    if (boxIds.isEmpty()) {
        throw new BoxNotFoundException();
    }
    if (boxIds.size() > 1) {
        throw new MultipleBoxesFoundException();
    }

    Optional<Box> maybe = boxDao.retrieve(boxIds.get(0));
    if (maybe.isPresent()) {
        return maybe.get();
    }

    throw new BoxNotFoundException();
} 

The method is now responsible for one thing: retrieving a box. And it's throwing meaningful exceptions whenever something goes wrong!

It's the client code's job to know what to do with these exceptions. Side note, I don't know what logging framework you're using, but you should be able to simply do something like this:

try {
    box = retrieveBox();
} catch(Exception e) {
    logger.exception(e);
}

No need for a redundant "box not found!" message, when the exception type itself already conveys all the necessary information.

\$\endgroup\$
  • \$\begingroup\$ thanks for your answer. it's a good advice to separate throwing exception and logging it. i've accepted the other answer since it fits my needs better. \$\endgroup\$ – Jeff Xiao Jun 6 '16 at 14:48
1
\$\begingroup\$

Your original code is the best to start with. There is only one little issue. I would not expect to have the logging before the throw clause. And that is because you may introduce unneccessary redundant logging in the code that will catch the exception. So the right point to log is when the exception is caught.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.