-2

I have a question regarding best practices.

Lets say I have this class:

public class test {
    int value = 0;
    int value2 = 0;
    boolean valid = true;

    test(int a, int b) {
        if (a>5){
            this.value = a;
        }
        else{
            this.valid = false;
            return;
        }

        if (b>100){
            this.value2=b;
        }
        else{
            this.valid = false;
            return;
        }
    }

    public static void main(String[] args){
        test t = new test(6,120);
        System.out.println(t.valid);
        System.out.println(t.value);
        System.out.println(t.value2);
    }
}

As you can see, I want to construct the class, but I also want to check if the values are between an expected range. If not, forget anything else, because the result will be wrong. In that case, it would be useless to continue constructing the object, so I can also exit right away.

Using a return does the job, as the outputs prove at the end. But is that considered best practice? Or should I rather raise exceptions? Or does it not matter?

Using an exception I could see which one exactly failed, but I could also just print it out...

Thanks!

thx.

Arpton
  • 7
  • 4
  • 1
    Throwing an exception and/or logging are good options. – Tim Biegeleisen Jan 31 '20 at 10:54
  • 2
    If you `return` from constructor you'll have a *partially created* instance which is almost certainly incorrect; `throw exception` to let other know that the instance is erroneous one – Dmitry Bychenko Jan 31 '20 at 10:54
  • Dmitry, but that would be enough in that case, because in the function that is supposed to used the constructed object, I do check if it is valid, and if its not, I disregard it. – Arpton Jan 31 '20 at 10:55
  • There is most likely no point in keeping an instance of an "invalid" object, which would happen if you used `return`. The instance would still be created. Throwing an exception is the right way to go. – f1sh Jan 31 '20 at 10:55
  • f1sh, so an exception would prevent the instance to be created? But then i can not check if it is valid later. – Arpton Jan 31 '20 at 10:56
  • Perhaps the use of [assertions](https://docs.oracle.com/javase/8/docs/technotes/guides/language/assert.html) would be suitable for your use-case? – Abra Jan 31 '20 at 10:56
  • 1
    @Arpton If the constructor throws an exception then you know it is invalid, you do not need to check if it is valid later. – mwarren Jan 31 '20 at 11:01
  • Point of order: please read ["No best practices"](https://www.satisfice.com/blog/archives/5164) by James Bach. You should avoid asking for "best practices" because there really is no such thing as a "best practice". – Stephen C Jan 31 '20 at 13:01

5 Answers5

1

Throwing an exception is good, as it clearly prevents an illegal usage, and can be informative, as opposed to have a lingering usage of the object. This QA principle is called fail fast.

You opt for a less heavy usage. The class Optional ensures a safe usage too. In a yes or no way.

public class Test {
    public final int a;
    public final int b;

    private Test(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public static Optional<Test> create(int a, int b) {
        if (a <= 5 || b <= 100) {
            return Optional.empty();
        } else {
            return Optional.of(new Test(a, b));
        }
    }

    public static void main(String[] args){
        Optional<Test> t = Test.create(6, 120);
        System.out.println(t.isPresent());
        t.ifPresent(x -> System.out.printf("(%d %d)%n", x.a, x.b));
    }
}

In fact Optional is a kind of solution when wanting to associate a value type with a boolean (valid).

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
1

I don't know if it's formally recognized as a best practice but in my opinion you should prefer throwing exceptions in this case. Returning invalid instances can lead to use them when they shouldn't and that'll be bad for sure. You can forget to check if they're valid or not, so errors will happen sooner or later.

It also has another effect. If you create and reference the instance it won't be removed by the garbage collector. This means that you will use memory when you don't have to. There is already another post where this is explained in detail.

About validating the input, maybe you can use annotations to improve readability and stop worrying about doing it yourself. It's up to you.

Zevesh
  • 81
  • 1
  • 9
1

Well, you have 2 options:

If you return from constructor

   public MyClass() {
     ...

     if (somethingWentWrong)
       return;

     // This initialization will never run on somethingWentWrong

     myField = ...

     ... 
   }

you'll have partially created instance which is almost certainly incorrect one; even worse: since constructor's code is a private affair of the class we don't know what's wrong with the instance.

   MyClass myClass = new MyClass();

   // Is it safe to execute the line below? We don't know
   DoSomething(myClass.getMyField());

On the other hand, throwing exception

public MyClass() {
  ...

  if (somethingWentWrong)
    throw new SomethingWentWrongException("bla-bla-bla");

  myField = ...

  ... 
}

ensures that you'll have a valid instance or no istance at all:

   ...

   MyClass myClass = new MyClass();

   // It's safe now : if somethingWentWrong appeared 
   //  1. myClass will not be assigned 
   //  2. This line will never be executed
   DoSomething(myClass.getMyField()); 
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • But if I need to further use the instance, I will have to check if it is valid, or if it exists, I so no difference in complexity. – Arpton Jan 31 '20 at 12:24
1

I think the best solution in this case is throw an IllegalArgumentException:

throw new IllegalArgumentException();

0

If something goes wrong in a constructor there are two choices:

  1. The constructor could return:

    • This implicitly returns the object that was being constrcted, not null or an instance of a different class.

    • The caller gets a partially created object.

    • The caller typically has to test for this. That implies:

      • There need to be fields in the partial object to record the error information, and methods to check the status and return the error information.

      • The caller1 needs to remember to test the object.

    • The classes other API methods may also need to deal with partially constructed objects to avoid exceptions and other anomalous behavior.

    • Subclasses of the class need to deal with the case where super() delivers them a partial instance of the superclass.

  2. This constructor could throw an exception.

    • The explanation and details of the problem can (should) be part of the exception.

    • No object will be returned.

    • The caller can catch the exception or allow it to propagate.

    • Since no partial object can be returned:

      • The caller doesn't need to test the result.

      • The class doesn't need to implement the status and/or error information fields or methods

      • The class doesn't need to consider the behavior of a partial object in other methods.

      • A subclass doesn't need to consider a partial superclass. (A constructor cannot catch an exception thrown in super()!)

    • The main downside is that something ultimately needs to catch and handle the exception. For checked exceptions, there is also the (coding) overhead of adding throws clauses.

    • A second downside is that passing or propagating exceptions across thread boundaries complicates things.

In the partial object approach, the above factors (I claim) make both the class and the caller more complicated, and more prone to hard-to-debug errors2.


As a general rule, I claim that error handling in Java is easier using exceptions. That is what they are primarily designed for. It is also best if the exceptions are thrown as close as possible to the source of the error, and allowed to propagate to the best place to handle them.

In short, my opinion3 is that is nearly always better to throw an exception than to return a partially constructed object.


1 - Most likely immediately after or close to the call. Passing partial objects is likely to complicate the problem, and increase the number of places the caller code needs to check and deal with partial objects.

2 - I claim that a bug that causes a stacktrace is usually easier to debug than a bug that causes incorrect output or unexpected behavior. Exceptions are your friends!

3 ... and probably of most experienced Java programmers ...

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Regarding that the caller needs to remember to test, if I want to use the instance further, I need to check if it is exists, rather than to check if it is valid. – Arpton Jan 31 '20 at 12:19
  • Maybe yes, maybe no. It depends on where you catch the exception, and what you do afterwards. But it is is easier to debug the case where you got it wrong; i.e. where you have mishandled the possibility of `null`. You get an NPE! – Stephen C Jan 31 '20 at 12:56
  • Exceptions are useful for handling exceptional behavior at run time. Using an exception in place of a validation that can be done at complie time will only increase complexity. In the question above, an exception can be used to handle wrong type of input (Example: Expected int, but received string). However, the checking for whether the input integer is greater than '5' can be handled by an 'if' condition. – Gopinath Jan 31 '20 at 13:08
  • Yea, sure compile time type checking is good. So are static analysers, static assertion validators, and so on. However. We are not talking about validation that can be performed at compile or build time in this Q & A. (We are not even talking about validation that cannot be done before run time but *should be* done before calling the constructor!) We are talking about / comparing two different ways to deal with the case where a problem *is* detected at runtime *in the constructor*. – Stephen C Jan 31 '20 at 13:21