The Source for Java Technology Collaboration
User: Password:



Tom Ball's Blog

Community: JavaDesktop Archives


Divide and Conquer

Posted by tball on April 01, 2008 at 09:07 AM | Permalink | Comments (1)

As anyone following the JavaFX Script compiler project knows, we have a schedule gun to our heads to finish a useful tool quickly, so the interpreter-based prototype shown at last year's JavaOne can be retired. It's a big job since the language doesn't have a specification yet, but our team of four engineers plus some really good volunteers are set to deliver what looks to be a great product. Of course I'm biased in this appraisal since I'm one of its contributors, but feel free to browse the project's source or subscribe to its dev and/or commits email aliases and make your own assessment.

Unfortunately, it has become more obvious as the project progresses that when our clients say "compiler" they really mean a full SDK, similar to what is delivered by the JDK. In particular, our management just assumed that a version of the javadoc tool which understands JavaFX Script would magically appear without any stated requirements or staffing. So we had a sub-project with a short deadline, no engineer available, and frustrated customers.

To make matters worse, javadoc engineers are hard to find since the job requires two unique skill sets: compiler engineering and web designing. Why does the JDK's javadoc output look dated? Because compiler engineers have owned it for the past few years. Why was it lagging behind the language a few years ago? You guessed it, our doc-writers were supporting it. Now, our team understands compilers, obviously, but none of us have any design skills worth noting. However, we have some engineers with great design skills on other teams. The "correct" solution would be to pull one engineer from each team and have them work together, but there wasn't time nor anyone available. Sound familiar?

What we did was follow the Unix rules for tool development: break the task apart, define a tool for each part, and use ASCII text to communicate between them. I wrote a simple doclet that dumped all information available from the Doclet API to an XML file (other people have done this over the years), then copied the JDK's javadoc source and enhanced it to provide JavaFX Script-specific information for that doclet (the doclet also works with Java sources when used with the JDK's javadoc). Joshua Marinacci then wrote an XSLT translator to generate XHTML pages similar to what javadoc's standard doclet creates. We use this tool to generate the project's UI Runtime documentation, and soon, all JavaFX Script-based API. The full tool source is here.

This divide-and-conquer strategy really pays off. Neither Josh nor I put much time into this project (as shown by how short both the doclet and XSLT script are), so our main jobs weren't impacted much. This approach also makes it much easier for our community to contribute: don't like the (currently primitive) output? An XSLT file is much less intimidating than javadoc's source, so jump in and make it better. But why limit ourselves to one output format? Just use the tool's -xsltfile option to use your own translator. And why limit ourselves to documentation generation? That XML file defines a model of any JavaFX Script and/or Java-based program, which can be used by other tools. As the Unix tool masters learned early on, breaking a big tool up into smaller, specialized tools opens the door to all kinds of unanticipated uses.



Elephants and the JavaFX Script Compiler

Posted by tball on November 10, 2007 at 04:42 PM | Permalink | Comments (4)

I don't know what it is about elephants, but they sure work well in analogies. Whether we are ignoring them, seeing pink ones or trying to sell white ones, elephant analogies seem to communicate ideas well. An elephant analogy came to me when I was recently grilled about exactly when the JavaFX Script compiler team will deliver our first milestone release. "I can't give you an accurate date," I said. "It's like pushing an elephant through a door*; until a critical mass makes it past the threshold you just don't know when you'll be finished. Once you pass that threshold, though, the rest happens quickly and in a manner that can be more accurately predicted." And then the tension in the room broke, as I think everyone has been in that situation sometime in their respective pasts.

In a way, this compiler project has turned out to be very similar to some of the GUI toolkit ports I've worked on in the past. We're making lots of measurable progress, which anyone can monitor in our test/features directory; for example, we recently implemented support for our "big three" language hurdles: sequences, multiple inheritance, and functional attributes (aka closures). Since the primary focus of JavaFX Script is as a domain-specific language for Rich Internet Applications, however, few people want an early access release of what we have so far -- like GUI toolkits, it doesn't get interesting until you can start working visually. So even though the JavaFX Script UI runtime needs an overhaul, we're porting it as is for the first release so those necessary GUI features are there.

Another similarity this project has to a GUI port is that most graphical toolkits have a few core components that depend upon most of the rest of the toolkit; call those classes Widget, Component, JComponent, Window, (graphic) Node, it doesn't matter -- there are so many benefits to sharing code between GUI components that these elephant-sized classes are just part of the GUI toolkit territory. So porting a GUI toolkit means that often there will an excruciating period where there is little visible progress as you work through all the dependencies, and then your first window appears, and then most of the remaining toolkit seems to just fall into place quickly. That's what happened many years ago when I was part of a team porting Presentation Manager from OS/2 to CTOS (which was about as unlike OS/2 as could be possible in those days). For several weeks, we slogged along without any visible progress (as voiced daily by our frustrated management), then we finally got the login panel showing and, in less than a week, all of the core applications were up-and-running. That login application was the critical part of our elephant, since the closure of its toolkit requirements encompassed most of the system. It wasn't really the login panel itself, of course, because whatever application is chosen first will be the hardest.

This is all a long-winded way of saying that we are really close toward delivering our first milestone release, but when the exact date will be remains unknown. If anyone is interested in monitoring our progress, check out our JIRA issue tracking system (courtesy of Atlassian), and/or subscribe to our developers alias (comments from experienced elephant pushers are welcome). Once we hit this first milestone, we'll be able to provide a much better road map for the remainder of this release.


*No elephants were hurt during the writing of this blog entry.

The Birth of the JavaFX Script Compiler

Posted by tball on July 20, 2007 at 06:49 AM | Permalink | Comments (3)

Today a new JavaFX Script compiler incubator project, openjfx-compiler went live. Yes, it has source code, issues, mailing lists and a wiki, but the compiler itself actually does very little right now (although Chris Oliver did offer a nice tease). So why open it now? The answer is that our team wants to open not just its source, but the design and development of the compiler itself. We are also asking for feedback on the JavaFX Script language to make it fully specified and compilable.

If you have an interest in watching or participating in a compiler for JavaFX Script, please join our project and help shape its future. If the past couple of months is any indication, it's going to be fast, furious, and fun!



Are Closures Just Delegates?

Posted by tball on August 25, 2006 at 02:29 PM | Permalink | Comments (20)

The recent blogs on closures have left me with a real sense of déjà vu. A few months after the JDK 1.0 released (over ten years ago), Microsoft proposed that the Java language be extended with type-safe method references, which they called delegates. The AWT team I was a part of liked this proposal and pushed for implementing it, as it fit well with the new GUI event model we were designing to support Java Beans. The Java language team shot the proposal down, however, responding publicly with the About Microsoft's "Delegates" white paper, which described how everything you might want from a delegate can be better handled with inner classes. Microsoft responded with The Truth about Delegates, and the two companies eventually went their separate ways, language-wise, with C# supporting delegates, and adding anonymous delegates (which look a lot like what's described in the Java closures proposal) in C# 2.0.

When the Java language team shot down the delegates proposal, the AWT team grumbled quite a bit since delegate-like equivalents are frequently used for event-driven application development (which is undoubtedly why Microsoft's GUI team drove the proposal from their side). But then an interesting thing happened: due to this constraint (no delegates), our event design improved significantly. As the old saying goes, sometimes unanswered prayers are blessings. If we had delegates back then, the AWT might be (even) slower and (even) harder to use than it is today.

I don't mean to suggest closures are evil (I live close enough to the old Xerox Parc site that some Smalltalk fanatics might pass messages to me wrapped around a rock!), but language features do impact API and performance work. In this case, the AWT has a design constraint that was better addressed without using delegates: lots of different event methods that need to be dispatched to interested components very quickly. This may be hard to believe, but in Mustang the AWT and Swing define over ninety component events, while toolkit performance must keep improving even as the complexity of desktop applications increases.

In 1.0, the AWT routed events up component hierarchy until a component "consumed" the event, which doesn't scale well to even small applications due to the number of events fired during normal usage. What made this worse was that frequently fired events such as mouse movement or scrolling events weren't interesting to most applications, so their dispatch took the longest since no component consumed them. Think I'm exaggerating? During the early Swing days, Hans Muller's favorite performance test was to open a scrolling window with a lot of text or a big graphic, click-and-hold on the vertical scrollbar, and wiggle it up and down quickly to check for lags. Not the most elegant test, but it quickly and reliably uncovered performance issues because thousands of events get fired during that wiggling, and if they aren't dispatched and handled quickly, then the whole GUI bogs down.

But what does this have to do with closures/delegates/better design? I'm getting to that. What's the fastest performance Big O number? O(log N)? O(1)? Try zero. The fastest code is the code that isn't executed. That's why in 1.1 the AWT moved to a publish-and-subscribe event model (the "new" event model), so that events the application didn't care about weren't dispatched. This is great for performance, but it makes the API more complex because you need a separate method for each event, thus making it harder to use. It also encourages code duplication in similar event handlers, making the application potentially less robust (fixing a bug in multiple places is less reliable than just one).

We defined groups of GUI events to address the API complexity issue, by declaring a shared data instance (Event class) and event interface (Listener) for each group. The advantage of listeners is that related event methods are in one class, each of which share a common Event class for event data. This reduced the complexity and data sharing issues to a certain extent. The problem then became that developers didn't want to write event handlers for events they weren't interested in, so Adapter classes were created so uninteresting events get no-op'd. This combination of listener interfaces with Event objects and Adapter classes balances the optimization needs of the toolkit with developer ease-of-use.

Tom, WHAT DOES THIS HAVE TO DO WITH CLOSURES/DELEGATES/BETTER DESIGN?!? Simple: multiple event Listener interfaces and Adapter classes aren't possible with delegates or closures, as each event requires a separate delegate. If delegates had been added to 1.1, the AWT team would have used them instead of the current design (since delegates are well-suited for event dispatch systems), and AWT development would now be (even) harder while performance would be (even) slower.

I'm therefore on the fence regarding adding closures to Java. While I like functional programming and agree that certain problems can be coded more succiently using closures, I'm worried about how overuse of them can hurt performance and API design. Read the whitepapers linked above, and then tell me what points I missed.

Finally, a Good Use for Finalizers

Posted by tball on August 17, 2005 at 03:46 AM | Permalink | Comments (12)

For years Java developers have been warned about the dangers of using finalize methods to release system resources. Josh Block describes the issues thoroughly in his book, Effective Java (Item 6: Avoid finalizers), but just Google for "avoid finalizers" or "finalizers considered harmful" to find hundreds of similar discussions not just about Java, but most other languages that offer the facility. Yet I just found out that the problem cropped up again in a recent internal Mustang build. What is it that is so alluring about this bad programming practice that it can blind even very experienced Java developers with its false charms?

Finalizers bit me hard back in the JDK pre-1.0 days, because they were originally used to free the Windows resources from AWT graphic objects. This technique gave Tumbling Duke the power to cause the Blue Screen of Death, but such power is cannot be entrusted to a mere applet, or for any class for that matter. Any class that allocates a critical resource needs an explicit "close", "release" or similarly named method in its API. Its clients must then always use that method.

The problem is that a library provider cannot always regulate its use, or misuse, by developers who use that library. The java.io documentation, for example, can warn, argue, even plead that developers close file streams after using them, but it cannot enforce that requirement. So what a lot of library engineers do is add a fallback routine in a finalize method to check whether a resource has been released, and if not, release it during finalization. It's an ugly hack, but often necessary to avoid granting Tumbling Duke-like powers to their client programs.

But cleaning up after one's messy clients doesn't encourage good behavior on their part. I found this to be true while on vacation this summer with our children, who didn't understand why food wrappers cannot just be thrown on our hotel floor since a maid will pick them up later. "Because you have to" may work for making children clean up after themselves (it didn't for me, but maybe for other parents), but such an approach is guaranteed not to work for developers. As long as the maid (or library) silently cleans up, messes will be left to clean.

But what if the maid left a nasty note whenever such a mess was left? Something like (using java.util.Logger):


protected void finalize() throws Throwable {
   if (handle != null) {
       Logger.global.log(Level.WARNING, "my handle not released"); // new
       release();
   }
}
Now when a client tests his poorly written code which seemed to work because the library was silently cleaning up after it, several nasty-grams start appearing. Even better, add a stack trace which will point to where in the client code is the problem:

private Throwable trace;
...
   // during handle allocation
   callerTrace = new Throwable("handle allocation");
...
protected void finalize() throws Throwable {
   if (handle != null) {
       Logger.global.log(Level.WARNING, "my handle not released", trace); // new       
       release();
   }
}
This takes advantage of how scary stack traces are to clients, plus it forwards all the information a library engineer has to the offending developer. Finally, we have a way finalizers can improve quality instead of detracting from it, by throwing up warning flags when library resources are being leaked.

Easy Version Support with ClassLoading

Posted by tball on July 13, 2005 at 03:10 PM | Permalink | Comments (0)

Versioning, which I'm defining for this entry as how a Java application manages its external library dependencies, has been a tough issue ever since Java first released. Back when Java was born, the vision was that each machine would have a single Java runtime and standard libraries which would always be fully backwards-compatible. The reality has been that for most apps, the only reasonable alternative to testing a full matrix of released JREs and libraries is to instead package everything the app needs, install the whole hairball on each customer's system and use a custom classpath to access it. The problem with a custom classpath is that it is easy for your customers to break in subtle (and not so subtle) ways, which makes them cranky and can drive your tech support engineers crazy. Some work has been done in the JDK via its Package Versioning Specification and API, but there are still times when your app really needs to keep specific libraries under tight control.

NetBeans has this problem with its javac bridge, which allows its editor and refactoring modules access to javac's error checking and parsing support. The problem is that javac doesn't have a public API, so while a tool can leverage a specific version of javac, it cannot rely on whatever is on the customer's machine since its internal API may be radically different. We have a recent version of javac that works with our bridge, but just adding it to the NetBeans classpath won't work for two reasons:
  • NetBeans supports many different JDKs, each of which have their own version of javac; and
  • The Mac OS X includes the javac classes in its bootclasspath (and supportable products shouldn't whack the bootclasspath if possible).
The solution proved fairly trivial to implement while being quite robust: define a ClassLoader to isolate the bridge's use of javac classes from the rest of the IDE. Here is the ClassLoader implementation we use; NetBeans has several similar loaders for specific modules, but Tomas Hurka wrote this Factory class for the javac bridge:

    private static class GJASTClassLoader extends URLClassLoader {
        private final PermissionCollection permissions = new Permissions();

        public GJASTClassLoader(URL gjastJar) {
            super(new URL[] {gjastJar}, Factory.class.getClassLoader());
            permissions.add(new AllPermission());
        }

        protected Class loadClass(String n, boolean r) throws ClassNotFoundException {
            if (n.startsWith("com.sun.tools.javac") || n.startsWith("org.netbeans.lib.gjast")) {
                // Do not proxy to parent!
                Class c = findLoadedClass(n);
                if (c != null) return c;
                c = findClass(n);
                if (r) resolveClass(c);
                return c;
            } else {
                return super.loadClass(n, r);
            }
        }

        protected PermissionCollection getPermissions(CodeSource codesource) {
            return permissions;
        }
    }
As you can see, we rely on URLClassLoader to do all the heavy lifting. Our version isolation support is in loadClass(), where a test is made of the requested class name to see if it is in one of the packages to be isolated (here, we test whether the class is a javac or bridge class). If it is an isolated class, URLClassLoader.findLoadedClass() and findClass() look it up in the jar file we supplied in the constructor; otherwise we let URLClassLoader.loadClass() delegate to the parent classloader.

Now, we need to interact with classes loaded by this classloader. What works best for us is to define a simple interface which the versioned classes and their client code shares, and a factory class that uses reflection to load the class which implements that interface (in 1.0, you needed a default constructor and used Class.newInstance()). Here's a simplified example (from the same Factory class):

public interface ErrorChecker {
    int parse() throws CompilerException;
}

public final class Factory {
    private static Factory instance = null;
    private static Constructor newErrorChecker;
    
    public static synchronized Factory getDefault() {
        if (instance == null) {
            instance = new Factory();
            Class[] newCheckerTypes = new Class[] {
                ECRequestDesc.class
            };

            File gjastJar = InstalledFileLocator.getDefault().locate("modules/ext/gjast.jar", "org.netbeans.modules.javacore", false);
            try {
                ClassLoader loader = new GJASTClassLoader(gjastJar.toURI().toURL());
                Class c = Class.forName("org.netbeans.lib.gjast.ASErrorChecker", true, loader);
                newErrorChecker = c.getConstructor(newCheckerTypes);
            } catch (Exception e) {
            }
        }
        return instance;
    }

    public ErrorChecker getErrorChecker(ECRequestDesc desc) {
        try {
            return (ErrorChecker) newErrorChecker.newInstance(new Object[] { desc });
	} catch (Exception e) {
	    Throwable t = e.getCause();
	    throw new RuntimeException("Cannot create errorChecker: " +
                                       t != null ? t : e);
	}
    }
}
In the above, we fetch the ASTErrorChecker constructor via reflection, then use it whenever the client requests a new ErrorChecker implementation. Because the interface doesn't directly or indirectly reference any class types in our private javac copy (CompilerException is also shared), objects created using its classes can interact with the client without conflict.

There is one thing to watch for (there always is), however: sometimes you can find yourself pondering the impossible, like I did yesterday:

debugshot.png

What caught me off-guard is that the debugger shows the type of "ex" is EmptyScriptException, but if it were that type then it should have been caught by previous catch block. Worse, a "(ex instanceof EmptyScriptException)" watchpoint returns "false", when it "obviously" should be true. The issue is that a class isn't just defined by its bytecode (the classfile's contents), but by the combination of bytecode and classloader. Here, there were two copies of EmptyScriptException loaded: once by Jackpot's private classloader, and once by the NetBeans one. Instances of one class copy will fail instanceof and catch tests with the other. I frequently forget this subtlety until reminded by a few head bangs against my monitor. The fix is to add the class to your list of classes which your classloader ignores and therefore shares with its parent classloader.

Over time, I have learned the value of this behavior (the classes not mixing, not the head banging). Since I'm pretty lazy, the extra work required to share classes between classloaders means that my designs do as little class sharing as possible. It is easier to maintain a really strict isolation with only a few, simple interfaces, than it is to maintain a big list of shared classes and deal with the headaches of managing their dependencies. A nice bonus is that this sort of isolation lends itself to distributed and parallel designs, where the more lightly coupled remote objects are to each other, the better they work together. Besides, it's hard to convince your manager you need the latest fire-breathing multi-processor workstation if your design is hopelessly interlocked.

This blog entry is way too long. I hope however that it dispells the idea that writing a classloader is rocket-science or limited to a few obscure uses. Managing application versioning is a problem many application teams face, and some judicious classloading can make it much easier.

No Regrets with Undo

Posted by tball on December 17, 2004 at 11:47 AM | Permalink | Comments (8)

One of the traps to avoid when aging is regret; the more one accomplishes in life, the harder it can be not to regret some of your decisions along the way. Some days it seems life would be perfect if only we had the ability to "hit undo" on those mistakes. Life doesn't have such an undo facility, but happily we can easily code them into our applications. "Easily?", most developers might ask, cringing at the thought of an "undo" product requirement especially if it has to be added to an existing, working project. Traditionally it's been considered very difficult to determine the correct undo points and to test that the facility works correctly. But with a good design, a robust undo facility can indeed be easy to implement.

Lately I've been focusing on isolating model and controller code in my application to simplify its design and make it more testable. One key to successful unit testing is having tests that run quickly, so you aren't inclined to skip running them in a crunch (when you need them the most). Independent data models can be tested very quickly since very few secondary classes need to be loaded for the JVM to execute each test. Now, Swing has a powerful, fully-featured undo facility, but it is difficult to use in a way that doesn't cause lots of other Swing classes to be loaded (here are the Swing classes from the UndoManager's transitive closure, generated with this tool).

While working on Jackpot, we used a much simpler undo facility that James Gosling wrote, which has been improved and incorporated into Huckster's Undo class. This class defines a single Undo class, to which custom Entry instances are added which implement the undo and redo methods. Since an undo command from the user's perspective can consist of several associated state changes to the model, you define the start of each action by calling Undo's newCommand method. The application's undo and redo commands invoke Undo.undo() and redo(), not specific entries. That's all there is to his framework.

To use this facility in your application, find each place where your model is changed. Ideally, you have already refactored your application so that all of these mutations are in small, separate methods (ideally in the model class itself); if not, do this refactoring first as it will help your design regardless of whether it supports undo or not. Here is a simple example:
    public void setLocation(int x, int y) {
        currentX = x;
        currentY = y;
    }

Next, define a singleton Undo instance for your application (we'll call it "undo"). Then for each mutating method, add a new Undo.Entry type to undo, moving the method's current code into the Entry's redo method:
    public void setLocation(final int x, final int y) {
        undo.add(new Undo.Entry() {
            public void undo() {
            }
            public void redo() {
                currentX = x;
                currentY = y;
            }
        });
    }

The undo command still doesn't work, but all existing unit tests should pass, since adding an Undo.Entry to the Undo instance invokes the entry's redo method. Next, figure out the state you are changing and define a n instance variable for each, initializing it to the current value before the change:
    public void setLocation(final int x, final int y) {
        private final int oldX = currentX;
        private final int oldY = currentY;
        undo.add(new Undo.Entry() {
            public void undo() {
            }
            public void redo() {
                currentX = x;
                currentY = y;
            }
        });
    }

Finally, determine how to undo the redo method's code. If you have refactored to a fine granularity (important for maintainability and testability), it should be fairly obvious:
    public void setLocation(final int x, final int y) {
        private final int oldX = currentX;
        private final int oldY = currentY;
        undo.add(new Undo.Entry() {
            public void undo() {
                currentX = oldX;
                currentY = oldY;
            }
            public void redo() {
                currentX = x;
                currentY = y;
            }
        });
    }

Your code now supports fine-grained undo. To define user-level undo command actions, find where the start of each command is and call undo.startCommand() before the first modification. You can move these startCommand calls around, add or delete them depending on the UI spec and/or what "feels right" during user testing. If you want to flush the undo list (say, when saving a document), just replace the application's Undo instance with a new one.

Testing an undoable model is pretty straightforward: you need deep-copy and equality methods. Some classes already have these, such as the collections classes, but for custom data structures you'll probably have to write them yourself. The good news is that these methods increase the value of your model as a separate data type, rather than just being a testing chore. I added duplicate and equals (with hashCode, of course!) to an abstract syntax tree model for test purposes, and later found them useful for productive code enhancements.

Now if anyone has a way I can undo my own past, please let me know. There are several old JDK warts I would love to erase!

'Tis a Gift to be Simple

Posted by tball on November 20, 2004 at 09:49 PM | Permalink | Comments (7)

'Tis a gift to be simple, 'tis a gift to be free,
'Tis a gift to come down where we ought to be,
...
To turn, turn will be our delight
'Till by turning, turning we come round right.

-- Old Shaker hymn

It was supposed to be a quick-and-dirty mini-project: add a visual unit test execution monitor to NetBeans, similar to the top part of my favorite test tool, JUnit's Swing-based TestRunner:

JUnitTestRunner.gif


After enhancing Ant's JUnit task to log JUnit's TestListener events as messages (hopefully the Ant team will accept these changes soon), the rest seemed easy: listen for Ant build events and display the result of these new JUnit event messages. But things started to get messy when I tried to create a single class that handled the build listening and the display, as there are threading issues with Ant running in one thread and the AWT event dispatch thread in another. To make matters worse the class was hard to test, which is especially embarrassing in a test tool!

I loathe writing GUI tests, so looked to the MVC pattern to move as much code as possible into non-GUI classes which can be tested from the command-line. I found yesterday that this is a recommended strategy by the Test-Driven-Development crowd in this article, Agile User Interface Development by Paul Hamill. I wasn't avoiding unpleasant work, I was just following TDD guidelines (even though I didn't know it then!).

First, the GUI panel was moved to a new View class. Initially I kept the current state for each form element in the View, but since the goal was to make the utility more testable, that state was moved into a simple Model class which fires PropertyChangeEvents. Although PropertyChangeEvents have a reputation for being heavyweight and slow, they make sense here because few events are fired (one event per test suite, one per test, and one per error or failure). The Model's unit test was written quickly and proved important, as it found three bugs in the space of ten minutes typing. The thin View was now isolated from the other classes, as it could reference the model when needed via PropertyChangeEvent.getSource().

So my monitor now had a Model and a View, but where was its Controller? Swing documentation has always been pretty vague about its Controller definition, but I understand a Controller to be the code that translates events into model updates. A button controller, for example, takes a mouse click or an "Enter" keyboard event and converts it into a button model "fire". With that definition, my "build listener" is really a Controller that filters events from Ant's BuildListener interface and updates my Model when it finds a change. For example, my Controller translates an "endTest" TestListener event into an "incrementTestsRun" method invocation on the Model. Things are getting simpler.

With that perspective, it proved easy to separate out the actual Controller code from the application logic, which creates and wires together the Model, View, and Controller instances. Testing outside of the IDE became much easier as well (this is a nice side-effect of design improvement), as a standalone build listener app is now just a few lines of code. I think the secret to finding Controller patterns in existing Swing applications is to remember that controllers "listen" to event streams and update models based on those events. Not all EventListener implementations are true controllers, of course, but when looking to factor out controller logic into its own class, look to the event listeners as they tend to hold much of that logic.

Ensuring thread-safety proved trivial after separating the model, view and controller, as is much easier now to comprehend each thread's control flow. Upon receiving an Ant build event, the controller updates the model, which fires a PropertyChangeEvent. The view's propertyChange method invokes SwingUtilities.updateLater() with a method that updates the form values and then calls its revalidate method. There's no back-and-forth between the model, view and controller: the controller updates the model, the model notifies the view, and (on the event dispatch thread) the view reads the model to redraw itself.

Some engineers pride themselves on writing complex, hard-to-understand code, and sneer at simple code as being "obvious". I believe that writing simple code requires extra work and discipline, but that work is justified by the code's quality, ease of testing and comprehension by my co-workers. When a design gets refactored to the point where it is "obvious", it means that it's time to move to other areas which are not so clear.

The proof that this refactoring exercise was worth the effort came this week when I had the present this JUnit enhancement code to another engineer for the first time, yet the code required little discussion before he fully understood and accepted it. All of this "turning, turning" had "come round right"; even though the Shakers were gone long before programming began, their focus on simplicity remains still important to developers today.



Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds