Posted by tball
on November 20, 2004 at 9:49 PM PST
A "quick-and-dirty" GUI is a mess until it is refactored using the MVC pattern. With each step the design became simpler, until it is now "simple", the highest compliment one can pay a design.
'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:
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
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.