Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This has long been one of my favorite methods of using OO code to my advantage; and is one of the main reasons that my code is OO in the first place.

There are many cases where it's easier/lazier to have if-statements.

Going further, you can take the things learned in this article to make your general purpose code potentially faster, as well. For example, if a set of things that must be performed in order; and sometimes some of those steps are missing, instead of performing a null-check, you can have a default no-op case; that way, instead of:

   if(firstAction != null) firstAction();
   if(secondAction != null) secondAction();
   if(thirdAction != null) thirdAction();
in your inner loop, you can have:

   firstAction();
   secondAction();
   thirdAction();
While whenever firstAction, secondAction, thirdAction are set, you can say:

   firstAction = newFirstAction ?? noop;
   // etc.
All in all, I'm glad this sort of knowledge is getting out there. I'm just grateful for my having really, really good CS teachers back in highschool ( I don't remember my college talking about OO as a way of removing if-statements ).

* Users of Java, of course, will just need to write a NoOp instance of their interface to take advantage of this.



I'm not clear on how that makes your code more maintainable though. The first case makes it explicit that any of the actions can fail to occur, whereas the second one, on first glance, seems to have all the actions occuring. I, as a newcomer to this code, will almost definitely make that mistake, which will make debugging or maintenance harder.

The only way the second is even -as good- is if I'm constantly holding in my mind the various idioms you've used in the code, in this case that your function pointers are never null but will instead refer to an action that might do nothing. As far as I can tell, this is more work for me, for no gain.

I would love to hear your reasoning why this way of doing it is -better-, as opposed to just -more object oriented- (or -marginally less typing-).


I may expand on this later; but, whenever you're going to a new code-base, you're going to have to learn the various idioms that are at work in that code base. This is especially true when you're working with some more complicated languages where no one uses the whole set of it (see: C++).

When I'm writing my code, I personally find that being able to trust what my code is doing to be more readable. In the case I wrote above, more than likely, I would have arrived at that point by first writing whatever the first action was; and then coming to know that there could be two different actions that could have taken place (causing a method call/inheritance to occur) and then I realized that sometimes, nothing might happen. Now we have a nothing case. The nothing case did not negatively effect my code flow. I am not 100% sure I would write code like I had above in the first place, it would grow to that state organically; but, the advantage of trust later on, was worth noting.

I originally heard this concept a long, long, long time ago; and one of the interesting selling points that the person that told me it was that code could be faster run if it had no if-statements, reason being branching and branch prediction forces the processor to rewind; whereas a guaranteed jump is potentially less expensive, especially if the code is in the cache. Of course, this would be a premature optimization, but if the code occurred in the inner-most loop, there may be some gains to be had that otherwise wouldn't be.

I suppose, for me, it looks cleaner when you're dealing with larger projects. That said, as I contemplate it further, I could certainly see where it would slip up some people, especially newcomers to my code. This sort of creativity would probably primarily spring up in organic/fluid code where OO paradigms are already in place.

Thanks for making me think on this further :)


It's an interesting point about the speed. In this case, since it's just a function pointer, there's no polymorphic overhead, so avoiding branches is a no-brainer.

On the other hand, if you were actually extending a class to make a DoNothingClass version, then the overhead of dynamic binding plus the function call would make it somewhat slower (branch prediction on a NULL comparison will cost at most 5 clock cycles in a single-thread pipeline, or none if you predict right) on those checks where the DoNothingClass is the one you find. For instance, if you had a sparse array of Class* and wanted to iterate over them, the NULL check would probably be more efficient than pointers to a singleton NullClass, especially since branch prediction will start correctly predicting NULLs more often.

So, you know, trade-offs.


"will cost at most 5 clock cycles in a single-thread pipeline"

Being a little pedantic here: you can't safely say this part without knowing which kind of branch predictions the processor uses; and, more importantly, how deep the branch prediction can go. There are some processors that will branch predict once ... and then again, and again and again. The first one they get wrong, they have to roll back to that first one. But then, you're right. The more times that single method is called, depending on the implementation of branch prediction, the more often it's going to be right, and so as long as those values aren't changing often (I don't see why they would in the case we're trying to suggest), it may eventually fade into nothing.

Needs moar testing!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: