I'm working on a codebase ported from Objective C to Java. There are several usages of method chaining without nullchecks
dog.collar().tag().name()
I was looking for something similar to safe-dereferencing operator ?. in Groovy instead of having nullchecks
dog.collar?.tag?.name
This led to May开发者_开发百科be monad to have the notion of Nothing instead of Null. But all the implementations of Nothing i came across throw exception when value is accessed which still doesn't solve the chaining problem. I made Nothing return a mock, which behaves like NullObject pattern. But it solves the chaining problem.
Is there anything wrong with this implementation of Nothing?
public class Nothing<T> implements Maybe<T> {
private Class<T> klass;
public Nothing(Class<T> klass) {
this.klass = klass;
}
@Override
public T value() {
return mock(klass, Mockito.RETURNS_DEEP_STUBS);
}
}
As far as I can see
- It feels odd to use mocking library in code
- It doesn't stop at the first null.
- How do i distinguish between null result because of null reference or name actually being null? How is it distinguished in Groovy code?
I wouldn't really recommend just checking for the nulls. Really, I recommend not even ever returning null, but instead throwing exceptions.
In either case, you are not going to make the code any faster by doing this null object method, and I think you'll just end up confusing someone just because you want to replicate the feature of a different programming language. I think you should just adapt to the language you're using.
I love this operator as well, but it's not available on Java--plus it's a stop-gap patch for sloppy code: If a dog.collar doesn't have a tag, it should probably branch differently, and if a tag doesn't have a name--it's probably an error and should throw an exception.
Not that it solves anything to say "code better" when you are inheriting code you didn't write.
I suggest that first you refactor your code--if code like that is happening all over the place, perhaps it needs to be moved into a centralized function anyway. If dog.getName() delegates to dog.collar() to get it's tag and finally the tag's name, then you only have to fix this bug in one place (The dog has to have a name even if it's not on the collar, so dog.getName should be able to traverse any path to solve your problem--now do it again for the NEXT spot of crappy code.
Whenever you see yourself patching the same code in 2 places, there is a factoring issue. If this only happens once for each group of attributes (if it's already fully factored and OO) then it can't be a serious problem, just a couple patches.
Heck, doesn't code like this even violate a few rules of familiarity?
You could use closures to explicitly implement this as maybe monad.
I ran across this the same time as I ran across your question.
http://groovyconsole.appspot.com/script/205001
The ?. safe dereferencing is obviously syntatically cleaner in groovy for null checking, you can end the chain with ?: to return a Nothing value instead of null, such as "" in this context.
If you want to understand monads I would recommend learning Haskell, and then you naturally start to see how these concepts can and are used in other languages. Languages that allow mutability, even if functionalish (such as Scala) often showcase "impure" solutions rather than purely functional ones.
There is no "safe-dereferencing" operator in Java.
I think this is a good thing, as "safe-dereferencing" is often a bug is disguise. You have basically two possibilities:
- The value shouldn't ever be null. In which case a null indicates a serious error in your assumptions and you should be throwing a NullPointerException if a null somehow appears (this is the fail-fast principle)
- The value can be null. In which case you typically should be doing some explicit null handling that is more sophisticated than just "don't call this method". You have to think: what does a null mean in this situation?
Personally, I usually see long method chains as a hint to refactor your code into smaller, clearly named methods (which can wrap up proper null handling inside if needed). This is probably more sensible than introducing monads in most circumstances.
精彩评论