Avoid Null Checks by Replacing Finders with Tellers
michaelfeathers.typepad.com
michaelfeathers.typepad.com
There are a lot of ways to fix this problem... Many modern languages use exceptions for this. Some languages use out parameters. Languages with multiple return values often return a value and an error. They're all essentially equivalent, and yes, they're all better than just returning null.
I don't really see a difference between:
data_source.person(id) do |person|
person.phone_number = phone_number
data_source.update_person person
end
and data_source.updatePhoneNumber(id, phone_number)
The latter one encapsulates the find, the error handling, and the setting. Yes, you might have a null check in there. Or you might have a try/catch, or you might be checking an out value or a multiply returned error value.... there's really no difference. You've changed the API to signify that the person might not exist and this function might not actually do anything. And that's fine, if that's fine. However, most of the time, if I go to set someone's phone number, I want to know if the system thinks that person doesn't exist.I'm just saying, it has nothing to do with nulls and everything to do with error handling. This teller pattern handles errors by implicitly ignoring them. If that's your intended behavior, that's fine. It also evidently has some nice language support in ruby, and that's cool. But it doesn't really "solve" any "problem". Mostly because there's no problem in the first place. Error handling is not a problem, it's a fact of life in software development.
Instead of dataSource.getPersonById(id) returning null, it could throw a NotFoundException. This is better because it's a specific error (as opposed to a MalformedIdException, for example).
What happens when you need an "else" branch?
Is not having a person for the given ID an error, is it a "do nothing", or does it mean to create a new person?
What does the callback version do if you forget to make your index unique, and end up with multiple people with the same ID; does it get run multiple times? The version that returns a Maybe<Person> at least has an obvious proper response (throw an error).
Then you use one, but null checks generally aren't cases of if/else conditionals, they're more along the lines of "either I have one or mayday mayday". Sometimes slightly softened into "either I have one or use this default thing"
That would be the Perl way of
my $person = $data_source->get_person($id) or die "No person available";
ie, there's code to be executed (to raise the error) in the case that there is no person. The callback structure from the article doesn't allow for this.Any ideas?
Attention is a limited commodity, and I think it's worth preserving for the important things. Sometimes it really is worth reminding people, "Hey, this could be null!" But a lot of the time that is boring.
To me this often comes back to favoring meaning over mechanism. The higher up I go in code, the more I want it to be about what the system means, not the particular mechanism I've chosen to make it work. E.g., Computers may be implemented via impure silicon, but most of the time we can forget that. I try to apply that to any sort of mechanism.
From what I can see, how is this much different than a Null check? I agree it looks a bit better, but what is the real difference? Isn't there still a check for if the Person object exists?
In terms of C++ or C#, is it correct to imagine this as an iterator over one object where inside my iteration loop I am passing my object into a lambda? (Seems overly complicated so I am guessing this is the wrong interpretation?)
Indeed a post at the bottom of the page mentions using foreach, but since sane languages have closures now days (thankfully! Life is so much better now!), how would I apply this to, say, C++?
In C# or Java, the member access expression "a.b" contains a null check. There's no way to do a member access that doesn't do a null check (for reference types)! It's impossible to distinguish member accesses that were expected/assumed by the writer to not fail from member accesses where the check is relied upon.
With an option type / a maybe monad / 'using telling instead of finding', there are no implicit null checks. Only explicit checks, and only when necessary. Either you use Option.map, to do a check and propagate nulls, or you don't, in which case there's no null path. You don't have to worry about null reference exceptions, making a check where you shouldn't, or missing a check where you should have, because the compiler prevents you from making those mistakes.
Applying it to C++: https://github.com/simonask/simonask.github.com/blob/master/...
Applying it to C# (as an augment over null): http://twistedoakstudios.com/blog/Post1130_when-null-is-not-...
Applying it to Java (as an alternative to null): http://java.dzone.com/articles/java-optional-objects
The difference is that the "teller" function incorporates both the function of the finder (which may actually still exist and be used under the hood) and the function of the null check, so that you don't have the boilerplate null-check code every time you need to do a null check.
(It may not actually be doing a null check per se, the finder-equivalent may be returning a set of 0 or 1, or 0 to many if using non-exclusive criteria, and the teller iterating over it, but for inherently unique criteria it could be a finder that returns a single object or null underneath. While whether the criteria inherently will always return at most one or potentially multiple objects to operate on may be important to the caller, the underlying implementation isn't.)
> In terms of C++ or C#, is it correct to imagine this as an iterator over one object where inside my iteration loop I am passing my object into a lambda?
The most general teller implementation would operate as an iteration over a collection returned by the finder; for finders with inherently unique criteria, that collection would always have either zero members or one member.
Particular teller implementations may or may not use that general implementation. For unique cases, the teller could just be a call to the finder with a null check guarding a call to the lambda that passes the non-null result of the finder.
search_for_object_in_database
which returns the result or a failure indication to: if_object_in_database_do { this_with_the_object }
The handling of a failed search is now internal to the API instead of in the client code. In this example, 'this' is simply not executed if the search fails. The client code is passing instructions on what to do when the search succeeds instead branching on success or not.The idea of passing code in addition to data is a powerful one and when the language makes it easy (Ruby blocks, Javascript function objects, lisp functions) it really does change the way you think about the design of APIs.
Twasn't so hard, as long as you don't make your data API return singletons.
If his function is intended to be called with a possibly incorrect personId, the null check is perfectly fine, it's just expressing his problem. His "solution" just hides that for no benefit - actually, it makes it less clear what's happening.
On the other hand I think what he really needs is "assert personExists(personId)" at the beginning of the function.
People who complain about nulls are either lazy or incompetent programmers, and the blame just happens to fall on null due to null being an inherent feature of a programming language. Dealing with nulls is exactly like dealing with any other case where you need to think about multiple possibilities, and possibly assert that some of those can't happen.
The concept of null is fine, but it shouldn't be the default state of everything. Force the programmer to declare his intention ahead of time and have the compiler help him by trying to find and validate all the edge cases, or at least as many as reasonably possible.
There's also the fact that a program crashing is quite often not considered an acceptable way to handle an error condition (it is true however that a crash from a null de-reference vs. a crash from a failed assertion is, aside from security implications, functionally identical). In a large code base, if values default to null or even intentionally return null, it can be very difficult to track down and handle every single case, particular if someone changes something way down in the guts of some function that changes a pointer in a data structure to a null, but that pointer never gets accessed until you're in some completely different area of code. Sure you can handle the null in the location it causes a failure at, but what if what you really want to do is figure out where the null came from and prevent it from being null in the first place?
There's also the added advantage that adding extra effort to allow a value to be null is a nice subtle encouragement to not allow null values if possible in the first place.
ad hominem - there are valid complains against nulls.
And the author doesn't seem familiar with many programming languages. Using his "finders and tellers" idiom isn't as special as he thinks, and doesn't require lambdas or blocks. Here's a similar idea in Python, not using its lambdas:
for person in data_source.person(id):
person.setPhoneNumber(phone_number)
data_source.update_person(person)
# And for other situations:
with open("whatever.txt") as inf:
for line in inf:
print(line)
C++ has had similar functionality in the STL using iterators since before C++98.Lambdas and blocks are convenient and nice features to have, but they're not required for this.
And the context manager can't handle this case, as it's not able to skip the block body.
Since it's a nasty dirty hack that tends to misbehave on bad data instead of failing cleanly, I generally restrict it to experimental/throwaway code. Seeing it recommended as a good idea is a bit WTF.
I was simply pointing out that the technique is available in other languages without using lambda and/or blocks.
This assumes that an empty result set of a finder is "bad data"; there are times when this is true (in which case, the basic "teller" idiom as illustrated here isn't a good choice), but there are lots of times where it isn't, too.
The author's example code appears to be doing the same thing, but iterating using Ruby's block syntax:
data_source.person(id) do |person|
person.phone_number = phone_number
data_source.update_person person
end
I'm assuming data_source.person(id) would return 0 or more Person objects.It's not iterating.
Conceptually, they both say "update this field for every entry having this ID" and at a quick glance the code for each language looks almost the same.
To an extent yes. I'd fully expect the Ruby version to run more code after the block has been executed, the Python version much less so (even though it is possible) for instance.
Ruby's block syntax is essentially a method of passing an anonymous function as an argument to a method call. It is used by the standard libraries iteration method (e.g., Enumerable#each), but its use here is not iteration (though the teller could be implemented with iteration under the covers.)
data_source.person(...) appears to be a method that takes an id, attempts to retrieve the person with the ID, and, if such a person object exists, yields the person object to the block. From the code, it doesn't look like it is designed to be a collection (it could be implemented on top of a finder that returned a collection, or a finder that returned either a record or nil.)
class DataSource
def person(id)
p = find_person(id)
p && yield p
end
end > Many codebases are littered with them [null checks] and,
> as a result they [codebases] are often very hard to understand.
I wager they're referring to null checks complicating the codebase (introducing uncertainty into method signatures and argument values down the chain).And that's what creates a ripe environment for such bugs and vulnerabilities that you mention.
Anyways, my big a-ha moment in programming was when I started thinking in terms of data-structures and mapping functions/transformations across them.
(map update-phone (filter ... users)) data_source.person(id) do |person|
person.phone_number = phone_number
data_source.update_person person
end
I would take that `update_person` call and have the `person(id)` method do that implicitly: data_source.person(id) do |person|
person.phone_number = phone_number
end
(I'd likely optimize for the case where the person wasn't actually modified too.) This way, the caller doesn't have to remember to explicitly update the person.Another way to look at this pattern is as a poor-man's pattern match. For example, using the pattern-matching syntax of my language[1], you could do:
match dataSource person(id)
case person is Person then
person phoneNumber = phoneNumber
dataSource updatePerson(person)
end
Granted, that's more verbose here, but it lets you have other cases if that makes sense for your problem. Magpie has blocks too, so a literal translation would be: dataSource person(id) as person do
person phoneNumber = phoneNumber
end
For very short blocks, you can use an implicit parameter name similar to Scala: dataSource person(id) do _ phoneNumber = phoneNumber
The `do` notation is just syntactic sugar for passing a function as the last argument, so you can also do: dataSource person(id, fn(person) person phoneNumber = phoneNumber)
How did I get derailed talking about Magpie?Blocks provide a conduit to provide compound and possibly complex commands while leaving the object itself in charge.
Person person = dataSource.getPersonById(personId);
if (person != null) {
person.setPhoneNumber(phoneNumber);
dataSource.updatePerson(person);
} else {
user.notify("That's an invalid ID.");
}
So I need an "else", in effect, regardless of whether it's implemented as a conditional, block, lambda, exception, or whatever.Modern compilers for languages like JS that heavily use closures optimize for this.
cmp r0, #0
beq exit"if (ptr == NULL)" is usually two instructions and an instruction pipeline flush. That sucks, but I can't even imagine what the processor ends up executing for the Ruby lambda callback. Slightly more readable code at what cost?
That's not a very good question, `if object == nil` will already be a pretty huge number of instructions in ruby.
Now if the question is "could you have this pattern compile down to little more than a null check", the answer is why not? A bit of flattening/inlining should be able to handle it correctly.
And in most fields, the gain in safety way outweighs the almost unnoticeable (against background noise) loss in efficiency.
The answer for C# and Java, which this issue was directed at, is no.
Email me if you want details.
I'm not sure what you're talking about, you focused on java and C# for odd and unknown reasons, TFA merely uses java as an example of syntactically heavy (or even missing) closures language to illustrate the pattern's gains and differences.
The other day, I saw this example on StackOverflow:
Person person = dataSource.getPersonById(personId);
if (person != null) {
person.setPhoneNumber(phoneNumber);
dataSource.updatePerson(person);
}
This looks like a typical case where we need a null check.If the canonical example under discussion is in Java, I think it is safe to assume the article is about Java.
No. The article is about a pattern of explicit null-check avoidance, java is an example of "a language without blocks or lambdas" but the article is no more about java than it is about ruby, you're not even missing the forest for the trees you're missing the forest for a fallen leaf.
Probably a lot more than the null-check, and yet still undetectable against the background of the db call.
If we keep adopting readability tricks with orders-of-magnitude differences in speed compared to what we're replacing, we'll just keep getting slower and slower code.
I think for most cases where what you want to do isn't throwing an error on null, most of the time null checks are addressing external interfaces (database or otherwise) where the IO is going to be enough bigger than the pattern overhead that if you are able to deal with the load from the basic functionality, the difference between null check and using this "teller" style implementation is going to be negligible.
And, obviously, where you want to error-on-null (whether or not an external interface is involved), this isn't the pattern to apply.
> If we keep adopting readability tricks
Factoring out commonly used boilerplate into a library function isn't a "readability trick", though it does benefit readability and maintainability of code.
And its safety and correctness.
Rust does exactly this.
> your program would be slow as shit due to all the closure allocation in inner loops.
The closure can be stack-allocated or even inlined. It doesn't need to be more expensive than a C block.
> C# and Java people would be pissed.
Meh.
No, Rust solved this problem by using non-nullable references. Closure allocation was a separate consideration.
The question of whether you can invent some arbitrary language that will enable your scenario isn't an interesting one. The problem is languages like Java and C#, which are heavily used and DO have nullable references. This "fix" is not one.
1. its option types provide a number of combinators which use closures, it wouldn't surprise me at all to see this kind of stuff in rust (might even be more efficient, no need to allocate and return an option)
2. and blocks are heavily pushed for use in Rust, all iteration is closure-based for instance
> The problem is languages like Java and C#
I've no idea where you got that from, TFA merely used java to illustrate the "language without syntactically lightweight functions". COBOL being a turd didn't stop people from investigating objects.
Really? We're investigating nullable reference semantics and C# and Java aren't the target? Hell the initial example -- the one that spurred the discussion -- is in Java.
Anyway, "syntactically lightweight" is a red herring -- syntax is surprisingly easy to add.
datastore.ChangePhone( personId, number );
could return a bool to indicate success/failure or throw a "personId not found" exception according to taste.
This is normal Java style and is in line with the article's recommended "tell-don't-get" approach.
Requires boilerplate inside datastore object, but hey, that's not exactly news for OO.
C# kinda does this for nullable types, it is referred to as null lifting.
[[obj find] doThing]
But this will crash on nil: [array addObject: [obj find]]
Nop-on-nil can be convenient, but without APIs that also nop on nil parameters, it can be a little annoying when you still have to check half the time.I remember reading one, I think by Michael Feathers, that was about how sometimes your code gets larger when you refactor and that's expected and good. He compared code to an orange. The rind is the class/method signatures. The pulp is the implementation. If the rind get larger by reducing the size and DRY violations of the pulp, you can consider that worthwhile. If anyone can find this article I'd be grateful. I haven't been able to track it down myself.
That sounds very very wrong, as you're making your code's clients do more work / be more complex just so you can simplify your implementation.
The article, comments on the article's page, and a lot of comments here are all variations on "This perfectly acceptable practice makes my eyes bleed, and is the calling card of shitty programmers everywhere. After I switch some semantics, rearrange the deck chairs, etc, its now perfect in every way, invulnerable to bugs or misuse."
No pattern is universally applicable. There are lots of ways to accomplish a task. Coming up with a different solution does not invalidate what came before.
>> After I switch some semantics, rearrange the deck chairs, etc, its now perfect in every way, invulnerable to bugs or misuse
I don't see anybody claiming that.
(self dataSource getPersonById: id) ifNotNil: [:person |
"operate on person here"
].
(There's also ifNil:ifNotNil: for where you want to handle both cases.)Independently discovering Option.map and realizing its usefulness is good. Point at the existing work instead of making fun.
http://en.wikipedia.org/wiki/Option_type (well.. maybe a bit more introductory than that..)
Sounds like programmable semicolon, or a monad, to me.
The example given in the article is:
data_source.person(id) do |person|
person.phone_number = phone_number
data_source.update_person person
end
You can abstract this pattern a little by putting it in a function: def maybe_bind(x, &blk)
if !x.nil?
blk.call(x)
else
nil
end
end
Now consider the type of this function: nil or X, (X -> nil or Y) -> nil or Y
A monad is any type for which you can define bind and return. They have the Haskell types: bind :: m a -> (a -> m b) -> m b
return :: a -> m a
"maybe_bind" is very similar to "bind"; just replace "m" with "nil or" and uncurry. "return" is trivial to implement.These semantics are basically what you implement by hand when you join together computations that may produce NULL results and check for NULLs in between. But now the computer is doing the checking for you, so the checking is hidden and yet never gets forgotten.
For example, here's some Haskell code the provides a simple lookup database from names (type "a") to phone numbers (type "b"):
-- name phone number
persons = [("Joe", "123-555-7890"),
("Tom", "432-555-0987")]
The lookup service for this database may return a result, thus it returns a value of the Maybe type to indicate that it may not be able to find a "b" for every "a" you give it: lookup :: Eq a => a -> [(a, b)] -> Maybe b
But since Maybe is a monad and its bind rule takes care of the didn't-get-a-result checking for us, we can safely string together lookup computations without having to do any checks by hand. Nevertheless, we can be assured that all the checks will be done.For example, let's create a function that takes two persons' names, looks up their phone numbers, and (if both are found) connects them with a (simulated) call.
-- try to connect person a's phone to person b's phone
connect a b = do
phonea <- lookup a persons
phoneb <- lookup b persons
return $ "connected " ++ phonea ++ " to " ++ phoneb
If we try to connect two names that are known to our lookup service, the call goes through as expected: *Main> connect "Tom" "Joe"
Just "connected 432-555-0987 to 123-555-7890"
But if either or both of the name lookups fail, no call is made: *Main> connect "Tom" "SomeUnknownDude"
Nothing
*Main> connect "SomeUnknownDude" "Tom"
Nothing
*Main> connect "SomeUnknownDude" "AnotherUnknownDude"
Nothing if (something_exists) {
do_something_to(it)
}
It was really useful (and readable).I believe Perl has something similar but it was less readable.
However this only gets topicalised in bare for & while but not if.
So you could do...
for (something_exists) {
do_something_to($_);
}
... as long as something_exists doesn't return a list :)More common approach I've seen in Perl (and other languages where variable declarations return its value) is this:
if (my $it = something_exists) {
do_something_to($it);
}If you think about it, it's not avoiding null checks, it's just moving it to a different place. The null check still needs to happen in order to know when to call the block, it just happens to be in the datasource.whatever code.
you could have pretty much the same code in any language by doing something like:
List personList=database.personListForID(id)
for(person in personList)
{
person.setPhoneNumber(phoneNumber)
}
Personally I prefer that idiom over null checks, it is a lot easier to read.
I do like the OP names for 'Finder' vs 'Teller' though, that is a good way to describe the difference, that I haven't heard before.