Stateful

From APIDesign

Revision as of 08:02, 2 May 2010 by JaroslavTulach (Talk | contribs)
Jump to: navigation, search

A stateful object is an instance of a class that may morph itself into various states. For example an object can be created, but not initialized, later initialized and ready for being used and at the end it can be disposed (but still remain accessible in memory).

In contrast to this a stateless object represents just one state for the its whole life-cycle. Modeling this as an immutable object, whose fields are only initialized once during construction and then remain unchanged, seems to be the most straightforward design technique.

Contents

Unforgiving Stateful Nightmare

There is one common problem that everyone has to face when using APIs based on stateful objects. Often, depending on the object state only some methods of its public interface are accessible (e.g. can be called without throwing an exception). However not only these accessible methods are visible in Javadoc. All object's methods are visible in the public signature of its class and (without reading carefully its javadoc) it is not easy (and definitely not clueless) to find out which of the methods can be called immediately and in which order onces need to wait until the object switches to different phase.

Imagine a stateful API for displaying a progress:

Code from ProgressStateful.java:
See the whole file.

public abstract class ProgressStateful {
    public static ProgressStateful create(String name) {
        return createImpl(name);
    }
    public abstract void start(int totalAmount);
    public abstract void progress(int howMuch);
    public abstract void finish();
}
 

and consider following usage. Is it correct or not?

Code from ProgressTest.java:
See the whole file.

ProgressStateful p = ProgressStateful.create("WrongOrder");
p.progress(10);
p.finish();
 

No, it is not. This yields a runtime exception. The usage forgets to start the progress by calling its start method, which (according to documentation) has to be called sooner before notifying any progress.

The right question to ask is why such restriction is in place? Why cannot the API be more forgiving and automatically perform start when its first progress method is called? Is this an attempt of the API writer to punish its API readers for their cluelessness? Obviously blaming customers is not the right way to win their hearts. They need help to deal with states!

Call State Machine

During my last visit to Linz University I was presented to a research project to create an annotation based state machine javadoc enhancement. The plan is simple. Why not annotate the methods with:

Code from ProgressStateMachine.java:
See the whole file.

public abstract class ProgressStateMachine {
    /** Annotation that helps users and IDEs to understand what
     * is the allowed order of method calls on instances of given class.
     */
    @Documented
    public @interface StateChange {
        /** What is the required state of the object before given method
         * is called.
         * @return "*" means any state, otherwise specify the state's name
         */
        String from() default "*";
        /** The state object enters after the method successfully returns.
         * @return name of new state
         */
        String to();
    }
 
    @StateChange(to="ready")
    public static ProgressStateMachine create(String name) {
        return createImpl(name);
    }
 
    @StateChange(from="ready", to="started")
    public abstract void start(int totalAmount);
 
    @StateChange(from="started", to="started")
    public abstract void progress(int howMuch);
 
    @StateChange(to="finished")
    public abstract void finish();
}
 

Then the compiler could check whether the order of calls for each object is correct or not. The project team was describing this with great enthusiasm (deserved, tracking object state throughout program flow is tricky), but I was immediately thinking: This is not beautiful (in the sense of not rationalistic)! Rather than writing poor stateful API and then trying to fix it with additional annotations, one shall write good API to begin with!

Converting to stateless API

If we split the methods into phase groups, then we can ensure proper transition between individual phases:

Code from ProgressStateless.java:
See the whole file.

public static ProgressStateless create(String name) {
    return createImpl(name);
}
public abstract InProgress start(int totalAmount);
 
public abstract class InProgress {
    public abstract void progress(int howMuch);
    public abstract void finish();
}
 

Such API makes use of uninitialized objects impossible. Either one is in configuration phase, and then there is no progress method, or one enters the progress phase and then it is obviously clear that the object has already been configured.

This is an API that naturally guides users through the whole life cycle of an object. In each phase only appropriate methods are available. Only such methods are shown in code completion inside an IDE. The possibility of making an error is minimized.

A little bit of forgiveness

I am going to claim that stateful API is a software antipattern (those we fight against stateful singletons can easily agree). Splitting such phased API into individual interfaces for each phase shall be good, rationalistic way of eliminating all associated problems.

However software engineering is not art, one cannot always start from scratch, with clean design. Sometimes one needs to maintain already created stateful API. In such situation any effort resembling the state machine checks can become more than welcomed. Such check is still a dirty, ugly, post-fix (made in the line of empiricism), but at least it helps to eliminate the most obvious misuse of such stateful API.

If none of the above is available, then please at least follow credo of MartinRinard's presentation Image:RinardOOPSLA06.pdf - don't throw needless exceptions unless you have to!. Of course, during development strive for proper order of calls (e.g. start must be before progress). But in production environment do your best to survive even wrong call order (which is easy in this case, just call the start automatically, if it has not been called yet).

Help your users to be clueless. They will reward you by not hating your API!

Personal tools
buy