So you've inherited a pile of buggy, spaghetti code, and now you're supposed to add some new features. We've all been there. It's very easy to say, "The code is already bad, so I'm not going to make the new features any nicer" but you shouldn't. This is your pile of spaghetti now, so you should work to make it incrementally better.
One big step in making bad code better is adding tests (I'm a firm believer in writing the tests first, so you shouldn't have much untested code, but it doesn't always happen). I'm not suggesting you drop everything and add tests to make sure you get to X% code coverage. But, as you add new features, you'll find yourself digging through old code. As you touch different parts of the code, refactor them where needed and add some tests. It will make you more confident in that part of the code, make life easier for yourself, and it will probably uncover some problems that you didn't know existed (and that have probably been there for a long time). A steady approach to testing will make any code better.
Wednesday, October 14, 2009
Sunday, October 11, 2009
Checked vs Unchecked Exceptions
The debate of whether to use checked or unchecked exceptions are a long debated topic in the Java world, and I'm still not sure that either side has emerged as a clear winner in the eyes of the development community. I personally think that unchecked exceptions are the way to go.
When a checked exception is thrown, there are several typical ways to deal with it.
These methods are the most appropriate ways to deal with a checked exception:
1. Handle it. Do something in response to the exception other than just re-throwing it or logging. If you have an I/O exception, maybe retry the operation.
2. Rethrow it. If you can't handle it, rethrow it, and maybe someone else above you in the call stack can handle it.
3. Wrap it as a runtime exception and rethrow it.
#2 and #3 become a bit more painstaking when methods throw multiple checked exceptions since Java doesn't support multiple exception catch blocks.
There are other ways to deal with it. These are not recommended, but any developer has seen these scenarios:
4. Log it/print stack trace and swallow the exception
5. Do nothing at all and swallow the exception
It's obvious that #4 and #5 can have very bad effects. These exceptions could be indicating some serious problems. While #4 will result in an entry in the log, it still could leave the application in a state that results in some unpredictable behavior. #5 should never be done.
With unchecked exceptions, you have all of the same options. However, #4 and #5 are much less likely since developers frequently do these out of laziness or because their IDE provides this as the default behavior.
Those that favor checked exceptions will say that you are much less likely to handle an unchecked exception since you the compiler does not force a method to declare that it is thrown. While this is true, the same can happen if the method wraps the checked exception in an unchecked one. And, in all actuality, how frequently do you actually handle exceptions vs logging and rethrowing them. If you're like me, you spend much more time doing the latter.
To be fair, there are some times when you really do want to handle the exception. Take the case of the IO exception when you want to retry an operation. Even if it was an unchecked exception, you could use annotations on the method you want to retry (not necessarily the direct method that throws the IO exception), which will work for an unchecked (or checked version) of the IO exception.
The @RetryOnException idea puts the responsibility on the method that needs to be retried but doesn't require everyone in the call stack to declare an IOException. This idea will work the same for checked or unchecked exceptions.
When a checked exception is thrown, there are several typical ways to deal with it.
These methods are the most appropriate ways to deal with a checked exception:
1. Handle it. Do something in response to the exception other than just re-throwing it or logging. If you have an I/O exception, maybe retry the operation.
2. Rethrow it. If you can't handle it, rethrow it, and maybe someone else above you in the call stack can handle it.
3. Wrap it as a runtime exception and rethrow it.
#2 and #3 become a bit more painstaking when methods throw multiple checked exceptions since Java doesn't support multiple exception catch blocks.
There are other ways to deal with it. These are not recommended, but any developer has seen these scenarios:
4. Log it/print stack trace and swallow the exception
5. Do nothing at all and swallow the exception
It's obvious that #4 and #5 can have very bad effects. These exceptions could be indicating some serious problems. While #4 will result in an entry in the log, it still could leave the application in a state that results in some unpredictable behavior. #5 should never be done.
With unchecked exceptions, you have all of the same options. However, #4 and #5 are much less likely since developers frequently do these out of laziness or because their IDE provides this as the default behavior.
Those that favor checked exceptions will say that you are much less likely to handle an unchecked exception since you the compiler does not force a method to declare that it is thrown. While this is true, the same can happen if the method wraps the checked exception in an unchecked one. And, in all actuality, how frequently do you actually handle exceptions vs logging and rethrowing them. If you're like me, you spend much more time doing the latter.
To be fair, there are some times when you really do want to handle the exception. Take the case of the IO exception when you want to retry an operation. Even if it was an unchecked exception, you could use annotations on the method you want to retry (not necessarily the direct method that throws the IO exception), which will work for an unchecked (or checked version) of the IO exception.
@RetryOnException(type=IOException.class)
public void saveState() {
State state = getState();
writeStateToFile(state);
}
private void writeStateToFile(State state) {
// write the state to file
}
The @RetryOnException idea puts the responsibility on the method that needs to be retried but doesn't require everyone in the call stack to declare an IOException. This idea will work the same for checked or unchecked exceptions.
Tuesday, October 6, 2009
Getting Groovy with XML Parsing
I generally like to use Groovy for data processing, particularly XML parsing. If you've used Groovy before, this won't be anything new, but if you haven't, this example might be enough to make you drink the groovy juice. Given this xml file of an address book, I want to print out all of the people in the address book.
Here's the Java way of doing it (if you've ever done any Java XML parsing, this will look very familiar):
That's 25 lines of Java code to print out a few names (and I didn't even include the public static void main(String[] args) declaration).
Now let's look at the groovy way of doing it:
Just 2 lines, that's it.
Happy groovy-ing.
<addressBook>
<person>
<firstName>John</firstName>
<lastName>Smith</lastName>
</person>
<person>
<firstName>Jane</firstName>
<lastName>Doe</lastName>
</person>
</addressBook>
Here's the Java way of doing it (if you've ever done any Java XML parsing, this will look very familiar):
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder parser = factory.newDocumentBuilder();
Document document = parser.parse("xml/sample.xml");
NodeList personNodes = document.getElementsByTagName("person");
List<String> names = new LinkedList<String>();
for (int i = 0; i < personNodes.getLength(); i++) {
String firstName = null;
String lastName = null;
Node personNode = personNodes.item(i);
NodeList children = personNode.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
Node child = children.item(j);
String nodeName = child.getNodeName();
String nodeValue = child.getTextContent();
if ("firstName".equals(nodeName)) {
firstName = nodeValue;
} else if ("lastName".equals(nodeName)) {
lastName = nodeValue;
}
}
names.add(firstName + " " + lastName);
}
for (String name : names) {
System.out.println(name);
}
That's 25 lines of Java code to print out a few names (and I didn't even include the public static void main(String[] args) declaration).
Now let's look at the groovy way of doing it:
def addressBook = new XmlSlurper().parse('xml/sample.xml')
addressBook.person.each { p -> println "${p.firstName.text()} ${p.lastName.text()}"}
Just 2 lines, that's it.
Happy groovy-ing.
Friday, October 2, 2009
Java Swing Threading
I've recently spoken with a few developers that were writing Swing apps (some for the first time) but were not familiar with the Swing threading model and the event dispatch thread, so I thought I'd review some of the basics. The main rule of Swing programming is that any code that updates the GUI (this applies only after a component has been realized, meaning its paint method has been called) must be invoked on the event dispatch thread (EDT). This can be accomplished using the methods SwingUtilities.invokeLater and SwingUtilities.invokeAndWait to run tasks on the EDT asynchronously and synchronously respectively. There are some exceptions to the rule, and Sun details those in an article http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html#exceptions (that whole article is a very good explanation of Swing threading as well).
While this does sound like a straight forward rule, there are some easy ways to get into trouble. One of these ways is when code is updating a GUI's model, which in turn updates the GUI component, that code must be invoked on the EDT. For example, calling model.setDataVector on the DefaultTableModel to update the data in a table must be called from the EDT. Even though you are just updating the model, the model fires an event that updates the GUI (note that some GUI libraries may already handle this for you, but it's not safe to assume this in general).
Violating this rule can cause unpredictable effects. Just like any threading error, reproducing and debugging the problem can be very difficult, so be sure to obey this rule religiously.
While this does sound like a straight forward rule, there are some easy ways to get into trouble. One of these ways is when code is updating a GUI's model, which in turn updates the GUI component, that code must be invoked on the EDT. For example, calling model.setDataVector on the DefaultTableModel to update the data in a table must be called from the EDT. Even though you are just updating the model, the model fires an event that updates the GUI (note that some GUI libraries may already handle this for you, but it's not safe to assume this in general).
Violating this rule can cause unpredictable effects. Just like any threading error, reproducing and debugging the problem can be very difficult, so be sure to obey this rule religiously.
Labels:
event dispatch thread,
Java,
Swing,
threading,
user interface
Wednesday, September 23, 2009
Spring Bean Constructor Args
Spring bean configurations frequently contain beans to which you pass constructor arguments. When setting constructor args, you can either reference them by index or by type. This example shows referencing by index.
In this example, the spring configuration will break if the order of the constructor arguments change. A more robust way to wire this is to set the constructors by type.
Now if the order of the constructor arguments change, the bean configuration does not need to change.
It's not always possible to do this, particularly if you have multiple constructor arguments of the same type (although you can mix and match by index and by type), but I recommend referencing by type whenever possible.
<bean id="MyBean" class="com.mycompany.MyClass">
<constructor-arg index="0" ref="SomeOtherBean">
<constructor-arg index="1" ref="SomeOtherBean2">
<bean>
In this example, the spring configuration will break if the order of the constructor arguments change. A more robust way to wire this is to set the constructors by type.
<bean id="MyBean" class="com.mycompany.MyClass">
<constructor-arg type="com.mycompany.MyClass1"
ref="SomeOtherBean">
<constructor-arg type="com.mycompany.MyClass2"
ref="SomeOtherBean2">
<bean>
Now if the order of the constructor arguments change, the bean configuration does not need to change.
It's not always possible to do this, particularly if you have multiple constructor arguments of the same type (although you can mix and match by index and by type), but I recommend referencing by type whenever possible.
Monday, September 14, 2009
Continuous Integration and Dependency Management
If you're not using continuous integration and dependency management systems, you should be. I use Maven with Hudson, and the results have been spectacular (although Hudson/Cobertura seems to have some issues with double reporting test coverage in maven2 projects, but I've been able to work around that).
My introduction to continuous integration was a few years ago, and I immediately saw the benefits of the continuous build. I was working on a multi-site team and didn't always know what everyone was doing at any given time. Having a continuous build gave me a high degree of confidence that the system was working reasonably well. I knew when tests broke immediately and it was generally clear where the problems were occurring so they could be fixed easily. Watching the build logs, I could see which parts of the system were most active and it was easy for me to focus on learning more about those parts. But, I think by now a lot of us have seen these benefits.
Dependency management, on the other hand, seems to be a newer technology. Although Maven and ant have been around for a while, in projects I've worked on, they weren't as widespread. If you aren't using a dependency management system, you're probably doing something like uploading the .classpath file from Eclipse into your repository, which now binds everyone to Eclipse, or people are responsible for manually setting the project in their IDE to link to all of the libraries in the lib folder and finding that something doesn't work when a new library gets added.
Enter the POM. In maven, the POM has a nice, clean declarative xml syntax for declaring a dependency.
And that's it, now you've pulled in the maven-project jars (maven supports transitive dependencies as well, so if X depends on Y, and you import X, it will import Y as well). Maven provides an impressive central repository (http://repo1.maven.org/maven2/) and there are numerous repository manager tools (Apache Archiva and Sonatype Nexus for examples). If you haven't realized by now, I'm excited by Maven and happy to rid myself of ant.
My introduction to continuous integration was a few years ago, and I immediately saw the benefits of the continuous build. I was working on a multi-site team and didn't always know what everyone was doing at any given time. Having a continuous build gave me a high degree of confidence that the system was working reasonably well. I knew when tests broke immediately and it was generally clear where the problems were occurring so they could be fixed easily. Watching the build logs, I could see which parts of the system were most active and it was easy for me to focus on learning more about those parts. But, I think by now a lot of us have seen these benefits.
Dependency management, on the other hand, seems to be a newer technology. Although Maven and ant have been around for a while, in projects I've worked on, they weren't as widespread. If you aren't using a dependency management system, you're probably doing something like uploading the .classpath file from Eclipse into your repository, which now binds everyone to Eclipse, or people are responsible for manually setting the project in their IDE to link to all of the libraries in the lib folder and finding that something doesn't work when a new library gets added.
Enter the POM. In maven, the POM has a nice, clean declarative xml syntax for declaring a dependency.
<dependency>
<groupid>org.apache.maven</groupid>
<artifactid>maven-project</artifactid>
<version>${mavenVersion}</version>
</dependency>
And that's it, now you've pulled in the maven-project jars (maven supports transitive dependencies as well, so if X depends on Y, and you import X, it will import Y as well). Maven provides an impressive central repository (http://repo1.maven.org/maven2/) and there are numerous repository manager tools (Apache Archiva and Sonatype Nexus for examples). If you haven't realized by now, I'm excited by Maven and happy to rid myself of ant.
Labels:
continuous integration,
dependency management,
maven
Tuesday, September 8, 2009
Automated UI Testing
I've often been asked how to automate testing for a UI. There isn't an easy solution to this problem, and automated UI testing is very difficult. An application's UI is frequently changing, sometimes just minor layout changes, and sometimes major reworks. Regardless, the change can be enough to throw off a lot of automated UI testing tools. So does that mean we shouldn't test the UI?
Every situation is different, but these are a few tips that will help make automated UI testing easier:
Every situation is different, but these are a few tips that will help make automated UI testing easier:
- Make the UI as thin as possible. Don't put business logic in UI components, even though it can be tempting (patterns such as Model-View-Controller help with this). An example:
Difficult to test:
JButton button = new JButton("Check if even");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int num = Integer.parseInt(numberField.getText());
if ( num % 2 == 0 ) {
System.out.println("num " + num + " is even");
}
}
});
This is difficult to test because now the whole UI needs to be brought up just to test this function, and this is not conducive to automated testing. This can be refactored as follows:
JButton button = new JButton("Check if even");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int num = Integer.parseInt(numberField.getText());
if ( NumberUtils.isEven(num)) {
System.out.println("num " + num + " is even");
}
}
});
public static class NumberUtils {
public static boolean isEven(int num) {
return num %2 == 0;
}
}
While this may not look like a big deal, the change is signficant. The NumberUtils class can have a simple unit test written for it and now that code is being tested outside the UI. It's easy to see that as functions become more complex, it becomes even more important to move them outside the UI logic. - Wait until the last responsible moments to write UI tests. Don't write automated UI tests while the UI is still in a great state of flux. You'll spend more time maintaining them than you will find useful. This will vary from project to project, but I find this doesn't generally happen until later on in the project.
- Maintain automated UI tests like you maintain code. They should be run as part of a regression test (even if it's a nightly test as opposed to a continuous build since they may take longer to run). They generally can't be simple record/playback tests. While this is a good place to start, they really should be refactored by a developer to make sure that when the UI changes and tests break, minimal changes will be needed to the UI tests. For example, if multiple tests are looking for a particular table column, make that a static string somewhere (or even reference it from the code under test if you can and the string itself is not under test). This way if the string changes, you don't need to update the string in multiple places.
Labels:
automated testing,
UI testing,
user interface
Subscribe to:
Posts (Atom)