10

I've worked on a large Rails app where the use of ActiveRecord callbacks was rampant and harrowing. Saving a record often had unexpected side-effects and it was a challenge to reason about the system.

At the same time, I've seen hooks used to good effect as part of inheritance (e.g. a parent class using a template-method to allow subclasses to add specialized behavior without needing to know about the parent's internals), and in plug-ins (e.g. an emacs mode running a hook when it's activated, allowing users to add custom behavior around that mode).

I realize that a Rails app and a Lisp interpreter are vastly different systems, but I'm curious if there are any well-known criteria people look to when deciding whether hooks are the right design choice for the problem they're facing.

The theme that jumps out at me is predictability. Misuse of hooks seems to lead to spooky action at a distance and surprising behavior, whereas good use can lead to a framework that's predictable without tight coupling.

As I'm still only a few years into my programming career, I consider myself a noob in many respects, and suspect people have put a fair amount of thought into this topic. What are some guidelines that can steer this decision?

5

Hooks are a good design choice when you want to decouple the implementation details of an abstraction from its consumers by transfering control to the receivers of the hook.

You can do it by an anonymous broadcast (like events or anonymous callbacks), or by using typed abstractions (like Interfaces or parent classes).

You should use anonymous broadcast when:

  • The call of the hook is optional
  • The caller doesn't care who reaceives the hook.
  • The order of execution of the receivers is irrelevant.
  • You want to broadcast the object state to all the subscribers to the hook.

You should use a typed abstraction when:

  • Calling the hook is mandatory.
  • The calling object needs to identify the receiver of the hook.
  • The order of execution of the receivers is relevant for your object.

An example of a broadcasting hook is a KeyPressedEvent. The class firing the event doesn't care who receives it and is broadcasting the keyboard state to anyone subscribed to the event. The execution order of the receivers has no effect upon the object state of the class firing the event.

An example of a typed abstraction hook is the template method you mentioned. In this case, the parent class requires an implementation for its template methods, it knows the receivers will be childs of it, and it has a specific execution order for the template methods, as defined by the parent class.

7

Most languages, and most platforms, use hooking, even if it's dressed up as something else. Whenever you hear of the term "event," "message," "trigger," "signal," or other terms of that nature, you're probably dealing with hooking, although there are exceptions to the rule, which probably don't matter for this discussion.

You use them because the platform provides them to you, or possibly even requires their use for performance reasons. Using hooks often reduces code complexity, helps enforce business requirements, and reduces CPU usage, extending battery and hardware life. You should use them whenever you want the best performance from your code.

Even your harrowing experience with Rails pretty much identifies a common use case for hooks: business rules must be enforced, and hooks are pretty much universally used to enforce business rules. Unfortunately, not all rules make sense, and as you can tell, it makes things harder for a developer when they're arbitrary, but that's why documentation of a system is just as important as the code itself.

As a general rule, use hooks because they are performance features, and will make your code run more efficiently than the alternative (which is called "polling", where you wait in a busy loop to check for events). However, also make sure that you use appropriate documentation and keep it up to date. Hooks are used in virtually every language you'll encounter, and it's important to know why they exist.

1

I've previously worked on a program that made in my opinion a good use of hooks. In my book, a true hook is a call (publish) to a list of callbacks (subscribers). It's powerful, abuse is easy. The best question to ask for use case is: do I want to enable or disable behavior at runtime ? For example, you might want to allow your users to provide scripts you will run on certain events, or make a generic program made of smaller plugins. Then hook calls are appropriate. Generally, you have little other options.

If not, you should get away with a good arch of you modules. Template-methods work well with overloaded functions, you have no need for runtime logic for this. Consider it would enforce loose coupling is an illusion: there is the same dependancy level between something you directly call or you hook, the callee have to fill its contract anyway.

Note that, maybe some people call template-method with overloaded functions hooking, but that is different from the above: here you have a static linking, with lower risk of getting into a maintenance nightmare.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.