1

One of my colleague decided to declare some API code like this:

public interface Filter<T> {
    /**
     * Test whether the given input is valid.
     * @param input the input
     * @return null for neutral, {@code Boolean.TRUE} for accepted and {@code Boolean.FALSE} for rejected.
     */
    Boolean apply(T input);
}

The idea generated a war on the IRC. While some is advocating the Boolean approach, the others think it bad practice and should use an enum of three elements to represent the indiviual state, like this:

public interface Filter<T> {
    Result apply(T input);
    enum Result {
        ACCEPT, UNKNOWN, DENIED;
    }
}

So, in terms of performance, readability, maintainability or, well, if it really matters, less code, what is the best practice in production?

EDIT: Here, ACCEPT, UNKNOWN( or NEUTRAL), REJECT( or DENY) is what @erickson means. If you gpt confused, please refer to his anwser if you get confused.

In respond to the "put on hold", I clarify that my question mainly concerns on: * Is there a clear convention of this?* this is the reason for accepted anwser

glee8e
  • 6,180
  • 4
  • 31
  • 51
  • 3
    http://thedailywtf.com/articles/What_Is_Truth_0x3f_ :p –  Feb 19 '16 at 16:40
  • 5
    The enum for readability & maintainability. Every time you introduce a new developer to the project you are going to have to sit them down and explain The Crazies. Also this should not be an enum on the Filter IMO. – Michael Lloyd Lee mlk Feb 19 '16 at 16:40
  • The problem with null `Boolean` is that at some point there's always someone doing some `if (theBoolean)` (and some wild NPE appears) –  Feb 19 '16 at 16:42
  • Also that is not a "Filter" IMO, you are transforming `T` into `R`esult. – Michael Lloyd Lee mlk Feb 19 '16 at 16:43
  • If you REALLY want to go with boolean, make it an Optional, and don't do Optional.of(null) for the love of god. – Diego Martinoia Feb 19 '16 at 16:43
  • 1
    I have a particular bug-bear about the term "filter", because it's an overloaded term: it can mean both keep and remove (as in "filter out"). I have worked on a project where both meanings were used by different sub-teams, and it confused the heck out of everybody as to which was which in any given context. A two-element enum (e.g. "keep" and "remove") was a pain to get into the finger muscle memory, but saved a lot of bugs down the road. – Andy Turner Feb 19 '16 at 16:43
  • 1
    What does it mean to have neutral result? If it is for processing where you run e.g. a list of filters and first non-`null` value stops processing with the result, `null` makes perfect sense (although you should prefer `Optional` instead). If it is to be used alone, it doesn't make much sense. In any case, you should really update the documentation. – StenSoft Feb 19 '16 at 16:44
  • The other advantage of enums is the fact that you can add more values later. The downside is that if there were only two values previously, you might have been tempted to write `if (value 1) { ... } else { ... }` (or similar ternary statements), which now broken. – Andy Turner Feb 19 '16 at 16:47
  • Possible duplicate of [When should null values of Boolean be used?](http://stackoverflow.com/questions/11185321/when-should-null-values-of-boolean-be-used) – Didier L Feb 19 '16 at 17:10

3 Answers3

5

The principle of least astonishement recommends using booleans only for true/false, and enums for anything else, such as { Trueish, Falsish, Undecideish }. Additionally, unlike Boolean, you can actually document the enum's values so that it is immediately evident what that MyEnum.Truieish actually means in the context of your application.

tucuxi
  • 17,561
  • 2
  • 43
  • 74
4

Use of a ternary value is well established in filtering applications. More often, you'll see the options named ACCEPT, DENY, and NEUTRAL. This is extremely useful in short-circuiting filter chains. However, these mechanisms don't use null to represent NEUTRAL.

Using a 3-valued enum instead gives you more utility.

Values can have methods. The implementation can be different for each value, or it could be a common implementation that works sort of like the Visitor pattern. If you use Boolean and null, you can't provide custom methods, and even if you could extend Boolean, you can't extend null.

You can use enum values as keys in maps more readily than null, which isn't accepted by many Map implementations. This can be useful for keying handlers by status.

You can use enum values in switch statements, and the compiler helps to ensure that you've covered all the options. If you fail to handle true, false, and null in a cascade of if-else-if-else, the compiler won't notice.

Finally, you should use the @FunctionalInterface annotation on your functional interfaces. It will cause the compiler to complain if you carelessly add another abstract method, or do anything else that makes your type ineligible for use functionally.

erickson
  • 265,237
  • 58
  • 395
  • 493
0

As a general programming advice, at least for Java & Scala, Never, ever, ever, use null as a return value, especially if you are coding public API.

Basically if you do it, you are going to violate the contract (the interface) you expose to the consumers of your method. The interface proudly states "there will be a value, and it will be a Boolean !" but instead your implementation will LIE ! WRONG ! If you want to expose the absence of something, just go for Optional (java 8 or Guava !). It might make your code a bit less-readable but it will safe, and testable :-)

To come back to your question, the enum sounds a good and clean solution. It will be meaningful for whom consumes the API and it will improve testability and readability !

Louis F.
  • 2,018
  • 19
  • 22
  • 1
    It's not true that returning `null` violates a contract. A lot of Java methods return `null` (e.g. `Map.get`, a lot of methods in `System`, reflection, …) and it's perfectly acceptable if it is documented behaviour. – StenSoft Feb 19 '16 at 17:48
  • Generally speaking, In an Object Oriented paradigm, where everything SHOULD be an object, null does not stand for anything. Those methods return `null` only because they have been coded a long time ago (java 1.2) and Optional was not there at this time. In modern language programming, such as Scala, a `get` on a Map yields an `Option`, because there are two cases to handle : either something, either nothing and you should handle both cases ! – Louis F. Feb 19 '16 at 18:27
  • `null` (or `nil` or `none`) stands for no value ever since Simula introduced the concept of OOP itself. Scala also has `null`. – StenSoft Feb 19 '16 at 18:46
  • It has null but it does not mean it's a best practice to code with it – Louis F. Feb 19 '16 at 18:50