Tuesday, March 5, 2013

Behaviors

Problem: Multi-view applications which need to respond to various events (ie mouse events, key events, various system events, download events, etc) tend to devolve into a massive blob of spaghetti code. Even when care is taken to try to "do the right thing" by refactoring code, creating various abstractions, etc, what tends to happen is that several views have similar but slightly different logic scattered around their classes. For those engineers who want to consolidate their logic that responds to events, tend to have classes that start to look something like this:

public class ViewController implements KeyEventListener, RegistrationListener, PowerEventListener, (10 other listeners) {
    ...
    ...
    public void onKeyEvent(KeyEvent e) {
       // logic responding to key events
    }
    public void onRegistrationEvent(RegistrationEvent e) {
        // logic responding to registration event
    }
    // about 600 more lines implementing various observer listeners
}
   
Another problem -- and one especially prevalent with those without much hands on experience -- is with thread management in the observer logic. Many engineers forget that their observer is just one of potentially several observers that need to respond to the event. If the observer takes too long to respond, it can slow down the entire system and cause terrible user experiences. What tends to happen is that the observer logic is written without threading/synchronization considerations, and as various bug are reported vis a vis deadlocks, sluggishness, and other symptoms caused by poor thread management -- they are solved on a case by case basis. So in the release code, some of the observers run in the main application thread, and some are run in custom threads. It's a mess.

Another problem is maintainability. Perhaps the engineers who worked on the original code can continue to effectively make updates, but when new engineers join the team, and have to add new features to the existing code base, they're going to be scared to make any changes for fear of breaking something. And their fears would be justified. Not only will the code be difficult to understand, but it is often brittle. What tends to happen is that the messy code remains entrenched, a virtual bio-hazard line drawn around the code, and the (new) engineers do their best to build new code apart from the old. Should the new code interact poorly with the old -- ie new interactions are introduced which cause further thread/timing issues, or exposed latent bugs, it becomes increasingly difficult to trace the true cause of issues, and iron out the problems without causing new ones.

Idea: Create a design pattern which encourages each new observers & features to be written in a way which both avoids the threading & timing issues inherent in observer-style designs, and allows new engineers to easily understand what's going on, and allows them to be confident to adapt the code to new features / requirements

The main problem with the 'normal' approach to implementing an observer (above) is that it encourages engineers to simply bolt on new business logic to existing classes. Engineers either have to be super disciplined in taking care to observe OO principles, or have the natural instinct to refactor common observer logic, which in my experience is a rare thing. As with most humans, engineers are generally lazy creatures who want to finish their task at hand as quickly and as efficiently as possible. The solution requires a process, or strictly enforced guidelines, which essentially force them to do the right thing.

Enter behaviors.

The idea of a behavior is to use the design principal of 'inversion of control' (IOC), and apply it to the observer design pattern. We see IOC used for such things as application lifecycle methods (for instance, the start, stop, pause, destroy methods in applets or smartphone apps), and in web frameworks like Struts or Spring, where an XML file maps out various website actions mapped to classes, which are invoked by the framework when the user takes certain actions.

IOC is a terrific design principle, because it forces devs to design code the right way (in part, at least). For each action or part of the code, devs are required to create separate methods or classes, and hopefully name those classes/methods in ways that are clear and easily understood by their successors.

As a mobile apps developer, I have used behaviors primarily on two levels: at the app level, and at the view level. The controller class of an app or view must define a list of behaviors which are loaded at the same time the app or view is loaded. The behaviors themselves define a list of events which they respond to. As long as the view/app is active, if a fired event is contained in the list of events defined by the behavior, the behavior is triggered. In my own case, I have a base class which fires the behavior response logic in a thread pool, so that there is no chance of behaviors slowing down other listeners of the event.

The following gives us a flavor for what the dev is required to implement in order to add new business logic which is triggered by some event:

// example of view controller using ViewBehavior
public class ViewController extends BasicViewController{
    ...
    private ViewBehavior[] mBehaviors;
    ...
    ...
    public ViewBehavoir[] getBehaviors() {
        // we can assume that in BasicViewController.load, it calls this method as part of its effort to load the behaviors
        if(mBehaviors == null) {
            // devs simply need to create a new ViewBehavior class, and add it to this list in order to add the logic to this view
            mBehaviors = new ViewBehavior[] { new UpdateHeaderPanel(), new SpinBallWhileDownloading(), new UpdateDatabase() };
        }
        return mBehaviors;
    }
}

/* example of ViewBehavior. Assume we have a view with a "status panel" which gives the user some status information about...something. It should be updated whenever we scroll through the data, or the underlying data model is changed*/

public class UpdateStatusPanel extends ViewBehavior {
    ...
    private BehaviorEvent[] mSubscribedEvents;
    ...
    public void onEvent(BehaviorEvent e) {
        // put logic here which updates header when certain events are triggered.
    }

    public BehaviorEvent[] getSubscribedEvents() {
        if(mSubscribedEvents == null) {
            mSubscribedEvents = new BehaviorEvent[] { DataChangedEvent.getInstance(), CursorChangedEvent.getInstance()};
        }
        return mSubscribedEvents;
    }
}

Take a moment to contrast the ways we are defining the business logic in the ViewController here versus the one which directly implements the observer logic above. Each specific piece of business logic that is triggered by some event is encapsulated in its own class, and named in such a way that makes it easy for a new dev to quickly glance at the view controller, and quickly get a grasp of what sort of business logic is implemented. In the example above, all the new dev can quickly glean from the code is which observers it has implemented. It also keeps the controller class from becoming obscured by tons of various and (usually) disconnected business logic. Maintainers of controllers ought to keep a tight lid on the limit of classes: barring various utility classes consisting of static methods, a good rule of thumb for maximum size of a class should be something like 300-400 lines of actual code (not counting documentation). Anything beyond that, and new devs will just look at the code with glazed eyes and do their best to avoid trying to understand what's going on in the class.

The benefits of behaviors extend beyond just readability. A lot of times in apps with multiple views, I find that many of the features across multiple views are similar, if not exact. Perhaps the same behavior is required, but triggered in slightly different ways. When using behaviors, it is trivial to add similar (or exact) behaviors to any number of different views, or to add to the range of events that trigger the behavior. And if the actual behavior is similar but slightly different, it is also easy to subclass the behavior for the specific needs of a particular use case.

There is some plumbing work that needs to be implemented, which I'm not going to write down here. But it isn't terribly complicated. If you want to ensure that each behavior is run in a separate thread, or launched as a task to a thread pool, there needs to be some code that needs to added to the place where the event is fired. In the example above, I have defined the events as singletons -- this can be done if you control all of the observers and events that are fired. If you are using 2nd or 3rd party libraries, or for some reason can't control events that are being fired, then you can create some adapter classes which are basically observers which fire off a BehaviorEvent when it is triggered.

I can definitely expand upon what I have written here if I get any feedback. I think I've laid out the basic ideas though. If nothing else, I hope it will at least help app devs consider thinking about ways they can use IOC in more of the work they do. It makes code easier to read and understand, and encourages the use of OO design principles among your whole team.

Shun the flying spaghetti code monster!!