7
\$\begingroup\$

Introduction

In my Java application, I need to represent the result of an operation. This can either be OK or ERROR + an error message.


Background

I have a class to verify the integrity of a directory (e.g. to check that a few conditions hold). If it finds out the directory is corrupted, it should return a description of the error, such as:

  • Unexpected file - [filename].
  • Unexpected number of files - expected [X], actual [Y].

The callers of the class don't know anything about the directory and the conditions. They are only interested to know if the directory is OK or not, and in that case, to get an appropriate error message.

The Result class was intended as a way to transmit the results of the verification.

I chose not to throw exceptions as (to me) that seemed more convenient for reporting errors in the verification process. It seemed inappropriate to intermix those two situations (e.g. the directory was found invalid vs. an error occurred while verifying the directory).


The current solution

I have so far been able to come up with the following class.

public class Result
{
    private static enum State { OK, ERROR };

    private State state;
    private String errorMessage;

    private Result(State state, String errorMessage)
    {
        this.state = state;
        this.errorMessage = errorMessage;
    }

    public static Result ok()
    {
        return new Result(State.OK, null);
    }

    public static Result error(String errorMessage)
    {
        return new Result(State.ERROR, errorMessage);
    }

    public boolean isOK()
    {
        return (this.state == State.OK);
    }

    public String getErrorMessage()
    {
        return errorMessage;
    }
}

The client code then looks like this:

Result result = Result.ok();
Result result = Result.error("Could not ...");

if (result.isOK())
    ...
else
{
    String errorMessage = result.getErrorMessage();
    ...
}

This has the benefit that when creating a result, the client only has to deal with error messages in case of an error.

On the other hand, the API does not prevent the client code to ask for an error message in case of an OK result. That does not feel right.

I also don't like the way the information about valid states (OK/ERROR) gets dupplicated into:

  • the internal enumeration,
  • the public static methods.

What do you think about my implementation? How would you design the class?

\$\endgroup\$
3
  • 2
    \$\begingroup\$ Are you sure you even need such a class? Why not stick with exceptions? \$\endgroup\$
    – Adam
    Commented Jan 8, 2013 at 22:30
  • \$\begingroup\$ I added some background information and elaborated more on that decision. I'm not that sure about it anymore, though. Firstly, I'm not sure that the verification process itself might (in my specific case) fail. Secondly, I could use two different exception types to mark the distinction (and catch and process them separately). But I have a feeling that those two (e.g. verification failure vs. a negative response) are different concepts and should be handled differently. I mean, it is not an error in the verification process if a directory is found invalid. \$\endgroup\$ Commented Jan 9, 2013 at 9:33
  • \$\begingroup\$ Thanks for the clarifying edit, that makes a lot of sense to me. \$\endgroup\$
    – Adam
    Commented Jan 9, 2013 at 10:12

3 Answers 3

3
\$\begingroup\$

If you don't want to use exceptions you should know that in OOP there is a lot of ways to implement the fact that some object belongs to some specific class. It can be either some enum that enumerates all possible domains or in case of OOP languages it can be class.

So in case of Java (again if you don't want exceptions by some reason) you can use:

class Result
{
    public static final Result ok = new Result(); // I'm not sure this is correct in terms of Java
}

class Failure extends Result
{
    public final String errorMessage;
    public Faliure(String message)
    { this.errorMessage = message; }
}

if (result instanceof Failure)
{
    // do something
}
\$\endgroup\$
4
  • \$\begingroup\$ I like this idea. The only (subtle) drawback I see is that you have to explicitly cast the result to a Failure in order to obtain the error message. You also have to know the implementation details (e.g. that there is a class hierarchy) to be able to distinguish between an OK and an ERROR state. \$\endgroup\$ Commented Jan 9, 2013 at 9:22
  • \$\begingroup\$ @DušanRychnovský, you need to know that as well in case of enum and others. You can tear off need of hierarchy by using interfaces. You can add continuation behavior and make Result to provide Result RunWithSuccess(/* some action representation */) which will do nothing but return this for Failure and result of action execution for ok. \$\endgroup\$
    – ony
    Commented Jan 9, 2013 at 10:01
  • \$\begingroup\$ @DušanRychnovský if you don't have a hierarchy like that you are going to have an ok result coming with an error message and that does not make too much sense to me \$\endgroup\$ Commented Jan 9, 2013 at 13:57
  • \$\begingroup\$ @mariosangiorgio exactly - that's one of the drawbacks I see with my own solution and one of the reasons why I like this one. \$\endgroup\$ Commented Jan 9, 2013 at 14:33
1
\$\begingroup\$

I do not know the exact requirements, but two simple approaches would be:

  • Just return a String as result. This can either be null or empty (if everything is ok) or the error message (if something is wrong).

  • Throw an exception

(Other then that: I would try to find a better name for the factory methods. The meaning of the current names is not really clear.)


edit after original post was changed:

If you do not need a general solution or anything special, I would keep the simple way and return a String. KISS principle.

If you want to make it fancy, I would continue with your approach, only slightly modified:

public class DirectoryChecker
{
    private String errorMessage;
    //or private List<String/anything else> errors;

    public static Result ok()
    {
        return new Result(State.OK, null);
    }

    public boolean isThereAnyProblem()
    {
        // check if everything is ok. Set error if not.
        // if the check should be done only once, add a member variable to keep the state
    }

    public String /* or anything else */ getErrorMessage/*s*/()
    {
        return errorMessage /*or anything else*/;
    }
}

Explanations:

  • Some factory method will create an instance of this class. The caller can do: if(directoryChecker.isThereAnyProblem()) { get and handle error }
  • I do not think you need the enum. It represents a state of the internal state. And you only need ok/not ok as far as I got it.
  • As you see this is hardly the same as the String approach, you just wrap an additional class around it.

Side note: You could name the check method isEverythingOk, but personally I prefer this flow of code:

if (something happened)
  { handle it }
go on with normal flow

instead of

if (everything is ok)
  { do normal things }
else
  { handle it }
go on with normal(?) code or other code(?)

About exceptions: I would not use them in this case, it makes life only more complex.

\$\endgroup\$
3
  • \$\begingroup\$ I updated the question and described why I did not choose exceptions to transmit the error message. \$\endgroup\$ Commented Jan 9, 2013 at 9:19
  • \$\begingroup\$ @DušanRychnovský I have updated my post to reflect your changes \$\endgroup\$
    – tb-
    Commented Jan 9, 2013 at 11:55
  • \$\begingroup\$ @tb- I don't think that simply returning a string would be a great idea. I feel better when the domain concepts have their own class and I don't have to guess what is the meaning of a string \$\endgroup\$ Commented Jan 9, 2013 at 13:58
1
\$\begingroup\$

I'd change your enum State, like this:

enum State
{
    OK(null),
    COULDNT_LOAD("Error: Could not load file: %s."),
    COULDNT_PARSE("Error: Could not parse contents of file %s."),
    UNEXPECTED_NUM_FILES("Error: Unexpected number of files - expected %d, actual %d.");

    String message;

    State(String message)
    {
        this.message = message;
    }

    void setReplacements(Object... replacements)
    {
        if(this.message != null)
        {
            this.message = new Formatter().format(this.message, replacements).toString();
        }
    }
}

Then you can have your validation method return a state, and you can do your checking block after, like this:

File fileITriedToLoad = new File("/usr/file.java");
State result = State.COULDNT_LOAD;
result.setReplacements(fileITriedToLoad.getAbsolutePath());

if(result != State.OK)
{
    System.out.println(result.message);
}

And you can delete the rest of your Result class.

Edit: Using the new setReplacements() method would then allow you to add pieces of text at the specified points in your message, so your validation method that returns a State would use it as follows:

State state = State.UNEXPECTED_NUM_FILES;
state.setReplacements(1, 3);

And then printing the state.message will produce:

Error: Unexpected number of files - expected 1, actual 3.

\$\endgroup\$
5
  • \$\begingroup\$ It seems that in this case, the error message would always be like "Okay" or "Error". That's not what I need. I need it to be either undefined (if the state is OK), or a custom error message, like "Could not open file XXX." (if the state is ERROR). Moreover, multiple error messages should be expressible (e.g. "Could not open file XXX.", "Could not parse contents of file XXX.", etc.). \$\endgroup\$ Commented Jan 8, 2013 at 20:52
  • \$\begingroup\$ @DušanRychnovský Well then, if you know what file they tried to load, see my edits. \$\endgroup\$
    – MrLore
    Commented Jan 8, 2013 at 20:58
  • \$\begingroup\$ @DušanRychnovský And to pre-empt the next question: if you don't know the file, wrap the file and Status into a ValidatedFile class with a getState() and getFile() method. \$\endgroup\$
    – MrLore
    Commented Jan 8, 2013 at 21:06
  • \$\begingroup\$ I need a more general solution. I updated the question to make this more apparent. I admit I have not made it clear in my previous comment that not all error messages are related to a single file. \$\endgroup\$ Commented Jan 9, 2013 at 9:17
  • \$\begingroup\$ @DušanRychnovský Edited accordingly :) \$\endgroup\$
    – MrLore
    Commented Jan 9, 2013 at 13:04

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