Stateful

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(Unforgiving Stateful Nightmare)
Current revision (05:13, 5 May 2010) (edit) (undo)
 
(9 intermediate revisions not shown.)
Line 4: Line 4:
in memory).
in memory).
-
In contrast to this a stateless object represents just one state for the its whole life-cycle.
+
In contrast to this a stateless object represents just one state for its whole life-cycle.
Modeling this as an immutable object, whose fields are only initialized once during
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.
construction and then remain unchanged, seems to be the most straightforward design technique.
Line 11: Line 11:
There is one common problem that everyone has to face when using [[API]]s based on [[stateful]] objects.
There is one common problem that everyone has to face when using [[API]]s 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 [[cluelessness|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''.
+
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 [[cluelessness|clueless]]) to find out which of the methods can be called immediately. As a result one may easily call inappropriate method designed for different ''phase''.
Imagine a [[stateful]] [[API]] for displaying a progress:
Imagine a [[stateful]] [[API]] for displaying a progress:
Line 29: Line 29:
== Call State Machine ==
== Call State Machine ==
-
During my last visit to Linz University I was presented to a research
+
During my last visit to [[Linz University]] I was presented to a research
project to create an [[annotation]] based state machine [[javadoc]] enhancement.
project to create an [[annotation]] based state machine [[javadoc]] enhancement.
The plan is simple. Why not annotate the methods with:
The plan is simple. Why not annotate the methods with:
Line 37: Line 37:
Then the compiler could check whether the order of calls for each object is
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
correct or not. The project team was describing this with great enthusiasm
-
(deserved, tracking state during object flow is really tricky), but I was
+
(deserved, tracking object state throughout program flow is tricky), but I was
-
immediately thinking: This is not beautiful (in the sense of not [[rationalistic]])!
+
immediately thinking: This is not beautiful (in the sense of not being [[rationalistic]] solution)!
-
Rather than writing poor stateful [[API]] and then trying to fix it with
+
Rather than writing poor [[stateful]] [[API]] and then trying to fix it with
-
additional [[annotation]]s, one shall rather write [[good]] [[API]] to
+
additional [[annotation]]s, one shall write [[good]] [[API]] to begin with!
-
begin with!
+
== Converting to stateless [[API]] ==
== Converting to stateless [[API]] ==
Line 51: Line 50:
Such [[API]] makes use of uninitialized objects impossible. Either one is
Such [[API]] makes use of uninitialized objects impossible. Either one is
-
in in ''configuration phase'', and then there is no ''progress'' method,
+
in ''configuration phase'', and then there is no ''progress'' method,
or one enters the ''progress phase'' and then it is obviously clear that
or one enters the ''progress phase'' and then it is obviously clear that
the object has already been configured.
the object has already been configured.
This is an [[API]] that naturally guides users through the whole life cycle
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
+
of an object (to be fair, I have to mention that it solves only the initialization
 +
path, there is no easy way to make an already existing object ''obsolete'').
 +
In each phase only appropriate methods are available. Only
such methods are shown in code completion inside an IDE. The possibility of
such methods are shown in code completion inside an IDE. The possibility of
making an error is minimized.
making an error is minimized.
Line 62: Line 63:
== A little bit of forgiveness ==
== A little bit of forgiveness ==
-
TBD
+
I am going to claim that [[stateful]] [[API]] is a [[:Category:APIDesignPatterns:Anti|software antipattern]] (those who fight against [[stateful]] [[singleton]]s 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]]! Or at least their feelings are not going to be as negative as mine, whenever I have to deal with [http://bits.netbeans.org/dev/javadoc/org-netbeans-api-progress/org/netbeans/api/progress/ProgressHandle.html this one].
 +
 
 +
<comments/>
 +
 
 +
[[Category:APIDesignPatterns]]
 +
[[Category:APIDesignPatterns:Anti]]
 +
[[Category:APIDesignPatterns:Clarity]]

Current revision

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 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. As a result one may easily call inappropriate method designed for 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 being rationalistic solution)! 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 (to be fair, I have to mention that it solves only the initialization path, there is no easy way to make an already existing object obsolete). 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 who 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! Or at least their feelings are not going to be as negative as mine, whenever I have to deal with this one.

<comments/>

Personal tools
buy