Stateful

From APIDesign

Revision as of 07:26, 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 state during object flow is really 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 rather 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 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

TBD

Personal tools
buy