« Guiding Software Development with Design Challenges | Main | Flipping Assumptions with 'Programmer Anarchy' »

June 17, 2013

Comments

cbp

This reminds me of working with jQuery, although jQuery achieves similar results in a different way - its finder methods return collections which can be operated on, regardless of how many items are in the collection.

Of course working with jQuery is tons of fun, but jQuery code often lacks the sanity checks that we require in our business logic. For example:
$('#foo').addClass('selected');

I've often seen the above code run for months on end before anyone realises, causing some strange bug because #foo doesn't exist.

Sourcedelica

Since it's side effecting, in Scala you could do

for (person - data_source.person(id)) { // should be a left arrow after person
person.phone_number = phone_number
data_source.update_person person
}


or

data_source.person(id) foreach { p =>
p.phone_number = phone_number
data_source.update_person p
}


I like @codemonkeyism's idea re: Iterable. That would be a great idea for Java 8 Optional[T].

Andrey

You are not avoiding null check, you just moving it in "person" ("getPersonById") method.

Michael Feathers

Andrey: I bet you could write a findPersonById function without a null if you tried. Regardless, if we are moving a null check into library code rather than application code it is still a win.

Brian Slesinsky

There are times when passing in a callback is useful but this is an unconvincing example. There is nothing wrong with if statements. The version with the null check is perfectly readable and the version with the lambda will be both harder for beginners to understand and run slower.

Michael Feathers

Brian: I don't buy either of those reasons. We should be aiming for an industry where higher levels of abstraction are the norm, and that involves raising the bar. A world where every programmer understands what a block is is, to me, not unreasonable or unattainable. I don't buy the inefficiency argument either. The number of places where a block is impermissible overhead are rapidly approaching zero.

Brian Slesinsky

I think you're partially right that if there were a compelling reason to eliminate null checks then my reasons for having them would be unconvincing. However, I also find your reasons for getting rid of null checks unconvincing. So where should the burden of proof be?

My perspective: I've recently started programming in Go, which doesn't have a lot of things we take for granted such as exceptions, generic types, inheritance, or fancy collection classes. I'm finding I don't miss them, because Go has its own abstractions, and where it doesn't, writing few more lines of code isn't actually all that bad once you get over the loss of a familiar idiom. The benefits seem a bit clearer: there are anecdotal reports of people switching from Ruby to Go for small programs and getting 10x performance increases and better stability, without Go being much harder to write.

I'm not going to actually make a language-war argument that Go is superior (I'm liking it, but I certainly don't have evidence for that). But the existence of interesting alternatives creates room for doubt. It raises the question of whether our attachment to fancy high-level language features really gives us as much benefit as we think, or whether it's just extra complication, much in the way Perl golf was just extra complication and other language designers largely didn't borrow language features from Perl (except for Ruby, partially).

The place where I think callbacks are compelling is for asynchronous programming in single-threaded languages like JavaScript; the trend towards using Promises and Streams to deal with concurrency issues seems to be solving a real knotty problem. By contrast, replacing a for loop with a map call or replacing null checks with callbacks seems more like codebase churn than a real improvement.

Dw

Can you give an example of this technique in C# or VB?

Seth

As hinted by Arseny this is probably a point where enumerator blocks could be abused, once again:

class PersonDatasource
{
public static IEnumerable ById(int id)
{
if (...)
yield break;
if (...)
yield return some_person;
}
}

// client code
foreach (var person : PersonDatasource.ById(42))
{
person.PhoneNumber = newNumber;
person.Update();
}

Robert Annett

The problem with avoiding null checks is that you can often miss important error conditions. Why is the person object null? Maybe a user has entered an incorrect ID? Perhaps an administrator has removed the user? Perhaps there is a logical error in the system and the person has disappeared for a while during an update (delete and insert not atomic). Receiving a null and performing a null check gives you an opportunity to correctly report this state.

Using a NullObject pattern means that you end up performing operations on a 'fake' object that get thrown away and using a lambda/empty loop pattern means no object is processed. Either way you are not dealing with a potentially real error (in the above example a user thinks they've updated their phone number but they haven't).

If using lambdas you need to make sure that before performing any operations you check to see if the inputs make sense with a chunk of code that tests if the ID is valid and then performs any error reporting. This should be in a transaction to make sure you don't find the object is deleted between checking the ID and performing an update etc. If you write this code you'll see it's as long winded and difficult to read as the null check.

I avoid passing nulls wherever possible but I see a lot of problematic 'concise' code where the edge, error conditions are just dropped.

Ja

Diletantish, Ruby-minded person tries to run away from a null-check. Is that a good reason to write a whole article? I don't think so.
Null is not an error! It's a VALID VALUE, indicating some special state in a program. If you just close your eyes on a null, null doesn't disappear and you have to deal with it.
In a given example: I read person by ID, but such person was deleted:
1) If user was able to do something on nonexisting data, it's a PROBLEM. Check your sh*ty code.
2) If user doesn't exist, rest of the function has no meaning!
3) User HAVE to be notified if proper person wasn't found - silently swallow this fact is a STUPID CODING. Thanks to author he exposed his level of professionality - never ever will hire him.

Justin

It's a little better looking in C#:

dataSource.Person(id, person => {
person.PhoneNumber = phoneNumber;
dataSource.UpdatePerson(person);
});

Either way, I like the thinking behind it. Null checks are one of those things where you don't like seeing them but aren't sure of the alternatives all the time. Good article.

Chris

Visitor pattern:
class Visitor
{
public:
virtual void Visit(Person& person) = 0;
};

PersonList list;
...
MyVisitor visitor;
list.GetPeopleWithGender(visitor, GENDER::MALE);

Erving

Your soul is very vulnerable, if a single 'if' makes it bleed.

I recommend rest, and a change of profession.

You can easily write it in good old C:

checkObjectBlock(array_or_list, object)
{
whatever
}

- if you #define checkObjectBlock to hide the 'if'.

But why bother?!

Is that the deep programming tasks you are solving?!

Jess

Was wondering if blocks were something mystical and new.. then I realised they were just codereferences (eg Perl). I agree with the folks that pointed out that you are just moving the null / doesn't exist check elsewhere. Yes, it is indeed prettier to move it into library code.. but it's not disappearing.

Couldn't you do it in Java with Future tasks now? Or with a Runnable, that's basically a block/coderef.

The comments to this entry are closed.