3 ms·
This is a strange article. The biggest problem with null checks isn't that they make code difficult to read - it's that people forget to add them and that can
by jlarocco 13y ago
This is a strange article. The biggest problem with null checks isn't that they make code difficult to read - it's that people forget to add them and that can cause bugs and security vulnerabilities. I've never known anybody to complain that they made code difficult to read. Unless they're mixed in with "real" program logic, like "if (obj != NULL && obj->value()<20)" then they're really not too hard to ignore.
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.
- masklinn 13y agoThe issue here is that the semantics are weird, an iteration block is expected to iterate 0..n times, not 0..1. I know I'd never expect a loop primitive to be used as a conditional. And the context manager can't handle this case, as it's not able to skip the block body.
- tbrownaw 13y agoI do this in my PL/SQL code occasionally, when I'm feeling lazy and either don't want to handle errors properly or want to take advantage of auto-defined iteration variables. 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.
- jlarocco 13y agoI didn't mean to imply it's a good idea or not. In my own code I'd use the null check and throw an exception. It's suspicious that it's trying to update a non-existent record, so I'd usually want it to break so I can fix the underlying logic bug. I was simply pointing out that the technique is available in other languages without using lambda and/or blocks.
- dragonwriter 13y ago> Since it's a nasty dirty hack that tends to misbehave on bad data instead of failing cleanly 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.
- tbrownaw 13y agoThere's also the case where I forget a unique index or get the query wrong, and get several records when I'm only expecting one.
- dragonwriter 13y agoThe teller strategy can work for those; that's all checking that can be done in the teller (or, in the case of teller-implemented-on-top-of-finder) in the finder. There's no reason to repeat the logic checking for this case all over the application.
- untothebreach 13y agoIt seems like the reason for the author recommending the use of blocks/lambdas is to put the null-checking code in the `data_source` library, rather than user code. That is, if `data_source.getPersonById` doesn't find a person for that id, the block/lambda doesn't get executed at all, and there only had to be a null-check/not-found-check in one place. In your first Python example, both `.setPhoneNumber` and `.update_person` would be executed, and both would have to be smart enough to not persist anything from a "null object" (assuming data_source.person(id) returned a valid `Person` object with null fields).
- jlarocco 13y agoNo, data_source.person(id) will return a collection of 0 or more Person objects, and the for loop will iterate over them and call .setPhoneNumber and .update_person methods on each them. The null check is replaced by iteration over a possibly empty collection. 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.
- masklinn 13y ago> The author's example code appears to be doing the same thing, but iterating using Ruby's block syntax: It's not iterating.
- jlarocco 13y agoDoes it matter? The null check is gone, so it achieves the author's primary goal. 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.
- masklinn 13y ago> Does it matter? 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.
- danneu 13y ago> 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))