Red Squirrel Reflections
Dave Hoover explores the psychology of software development


Sat, 22 Jan 2005Quote Manager Project  Day 6 An ongoing pet project blog...I've added multiplie author fields to the interface. Here is the ugly screen...
And the code that produced it... package com.redsquirrel.quotes.gui; import ... public class QuoteManagerPanel extends JPanel { private static final int NUMBER_OF_AUTHORS = 6; ... private final JTextField[] authorUrls = new JTextField[NUMBER_OF_AUTHORS]; private final JTextField[] authorFirstNames = new JTextField[NUMBER_OF_AUTHORS]; private final JTextField[] authorLastNames = new JTextField[NUMBER_OF_AUTHORS]; ... public QuoteManagerPanel() { setLayout(new GridBagLayout()); for (int i = 0; i < NUMBER_OF_AUTHORS; i++) { authorUrls[i] = new JTextField(15); authorFirstNames[i] = new JTextField(7); authorLastNames[i] = new JTextField(7); } addButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Source source = new Source(QuoteManagerPanel.this.source.getText(), page.getText(), isbn.getText()); Collection<Person> authors = new ArrayList<Person>(); for (int i = 0; i < NUMBER_OF_AUTHORS; i++) if (authorFirstNames[i].getText() != "") authors.add(new Person(authorFirstNames[i].getText(), authorLastNames[i].getText(), authorUrls[i].getText())); Person quoter = new Person(quoterFirstName.getText(), quoterLastName.getText(), quoterUrl.getText()); new Quote(quoteText.getText(), source, authors, quoter); } }); layoutForm(); } private void layoutForm() { ... } }Dave the customer is feeling a bit better now that I can enter the authors I need, but Dave the developer is not happy...still no tests to speak of. Let's take a quick look around and find some easy wins... How about that anonymous ActionListener ?
I'm going to try to test it...
I've brought up my test template in IDEA and I have a blank test method... package com.redsquirrel.com.quotes.gui; import org.jmock.MockObjectTestCase; public class AddButtonActionListenerTest extends MockObjectTestCase { public void testShould() { } }So how do I test this thing? Currently it is coupled to all of the JTextComponents in QuoteManagerPanel .
The code that I'm most nervous about is the for loop with the if condition.
I'm going to need to promote this ActionListener to a concrete class,
but I don't want to pass in ten objects to the constructor...or call ten different setters.
How about I pull the related fields into their own JPanel s?
The thought occurred to me when I had to create three separate author arrays.
I'm pulling into the station.
I'll check in and extract the fields into
I'll start with the code that handles authors' private void layoutForm() { GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; int y = 0; addLabel("Quote", constraints, y++); addField(quoteText, constraints); addLabel("Source", constraints, y++); addField(source, constraints); addLabel("ISBN", constraints, y++); addField(isbn, constraints); addLabel("Page", constraints, y++); addField(page, constraints); for (int i = 0; i < NUMBER_OF_AUTHORS; i++) { addLabel("Author " + (i+1), constraints, y++); addFullName(authorFirstNames[i], authorLastNames[i], constraints); addLabel("URL", constraints, y++); addField(authorUrls[i], constraints); } addLabel("Quoter", constraints, y++); addFullName(quoterFirstName, quoterLastName, constraints); addLabel("URL", constraints, y++); addField(quoterUrl, constraints); constraints.anchor = GridBagConstraints.EAST; constraints.gridx = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridy = y++; add(addButton, constraints); }Time to extract method... private void layoutForm() { GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; int y = 0; addLabel("Quote", constraints, y++); addField(quoteText, constraints); addLabel("Source", constraints, y++); addField(source, constraints); addLabel("ISBN", constraints, y++); addField(isbn, constraints); addLabel("Page", constraints, y++); addField(page, constraints); for (int i = 0; i < NUMBER_OF_AUTHORS; i++) { layoutAuthor(i, constraints, y); y += 2; } addLabel("Quoter", constraints, y++); addFullName(quoterFirstName, quoterLastName, constraints); addLabel("URL", constraints, y++); addField(quoterUrl, constraints); constraints.anchor = GridBagConstraints.EAST; constraints.gridx = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridy = y++; add(addButton, constraints); } private void layoutAuthor(int i, GridBagConstraints constraints, int y) { addLabel("Author " + (i+1), constraints, y); addFullName(authorFirstNames[i], authorLastNames[i], constraints); addLabel("URL", constraints, y+1); addField(authorUrls[i], constraints); }It's a start. But I need to group the fields into a JPanel .
I'll start with inlining the methods inside layoutAuthor ...
private void layoutAuthor(int i, GridBagConstraints constraints, int y) { constraints.gridx = 0; constraints.gridwidth = 1; constraints.gridy = y; add(new JLabel("Author " + (i+1)), constraints); constraints.gridwidth = GridBagConstraints.RELATIVE; constraints.gridx = 1; add(authorFirstNames[i], constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 2; add(authorLastNames[i], constraints); constraints.gridx = 0; constraints.gridwidth = 1; constraints.gridy = y+1; add(new JLabel("URL"), constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 1; add(authorUrls[i], constraints); }Bletch! That's ugly. Let's introduce that JPanel ASAP...
private void layoutForm() { GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; int y = 0; addLabel("Quote", constraints, y++); addField(quoteText, constraints); addLabel("Source", constraints, y++); addField(source, constraints); addLabel("ISBN", constraints, y++); addField(isbn, constraints); addLabel("Page", constraints, y++); addField(page, constraints); for (int i = 0; i < NUMBER_OF_AUTHORS; i++) { JPanel authorPanel = layoutAuthor(i, constraints, y); y += 2; } addLabel("Quoter", constraints, y++); addFullName(quoterFirstName, quoterLastName, constraints); addLabel("URL", constraints, y++); addField(quoterUrl, constraints); constraints.anchor = GridBagConstraints.EAST; constraints.gridx = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridy = y++; add(addButton, constraints); } private JPanel layoutAuthor(int i, GridBagConstraints constraints, int y) { JPanel authorPanel = new JPanel(new GridBagLayout()); constraints.gridx = 0; constraints.gridwidth = 1; constraints.gridy = y; authorPanel.add(new JLabel("Author " + (i+1)), constraints); constraints.gridwidth = GridBagConstraints.RELATIVE; constraints.gridx = 1; authorPanel.add(authorFirstNames[i], constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 2; authorPanel.add(authorLastNames[i], constraints); constraints.gridx = 0; constraints.gridwidth = 1; constraints.gridy = y+1; authorPanel.add(new JLabel("URL"), constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 1; authorPanel.add(authorUrls[i], constraints); return authorPanel; }Ouch, that's not much better, and now the author fields are missing in the GUI. I need to add that JPanel ...
Hack, hack, hack, it's looking OK...
private void layoutForm() { GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; int y = 0; addLabel("Quote", constraints, y++); addField(quoteText, constraints); addLabel("Source", constraints, y++); addField(source, constraints); addLabel("ISBN", constraints, y++); addField(isbn, constraints); addLabel("Page", constraints, y++); addField(page, constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 0; for (int i = 0; i < NUMBER_OF_AUTHORS; i++) { constraints.gridy = y++; JPanel authorPanel = layoutAuthor(i); add(authorPanel, constraints); } addLabel("Quoter", constraints, y++); addFullName(quoterFirstName, quoterLastName, constraints); addLabel("URL", constraints, y++); addField(quoterUrl, constraints); constraints.anchor = GridBagConstraints.EAST; constraints.gridx = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridy = y++; add(addButton, constraints); } private JPanel layoutAuthor(int i) { JPanel authorPanel = new JPanel(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; constraints.gridwidth = 1; authorPanel.add(new JLabel("Author " + (i+1)), constraints); constraints.gridwidth = GridBagConstraints.RELATIVE; constraints.gridx = 1; authorPanel.add(authorFirstNames[i], constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 2; authorPanel.add(authorLastNames[i], constraints); constraints.gridx = 0; constraints.gridwidth = 1; constraints.gridy = 1; authorPanel.add(new JLabel("URL"), constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 1; authorPanel.add(authorUrls[i], constraints); return authorPanel; }Now I can extract the body of layoutAuthor into an inner class
and inline the layoutAuthor method...
private void layoutForm() { GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; int y = 0; addLabel("Quote", constraints, y++); addField(quoteText, constraints); addLabel("Source", constraints, y++); addField(source, constraints); addLabel("ISBN", constraints, y++); addField(isbn, constraints); addLabel("Page", constraints, y++); addField(page, constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 0; for (int i = 0; i < NUMBER_OF_AUTHORS; i++) { constraints.gridy = y++; add(new AuthorPanel(i), constraints); } addLabel("Quoter", constraints, y++); addFullName(quoterFirstName, quoterLastName, constraints); addLabel("URL", constraints, y++); addField(quoterUrl, constraints); constraints.anchor = GridBagConstraints.EAST; constraints.gridx = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridy = y++; add(addButton, constraints); } private class AuthorPanel extends JPanel { AuthorPanel(int i) { setLayout(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; constraints.gridwidth = 1; add(new JLabel("Author " + (i+1)), constraints); constraints.gridwidth = GridBagConstraints.RELATIVE; constraints.gridx = 1; add(authorFirstNames[i], constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 2; add(authorLastNames[i], constraints); constraints.gridx = 0; constraints.gridwidth = 1; constraints.gridy = 1; add(new JLabel("URL"), constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 1; add(authorUrls[i], constraints); } }Great, still looking good, but those three author arrays are bugging me. Let's get rid of those...
I could show you all the things I did to remove those arrays, but this post is getting
way too long...and I'm sure it's very boring.
It only took a few minutes.
I simply replaced the three arrays with an
Hopefully we're a bit closer to testing.
I'll have to wait until tomorrow, though.
My stop is coming up...here's what the private class AuthorPanel extends JPanel { private final JTextField firstName = new JTextField(7); private final JTextField lastName = new JTextField(7); private final JTextField url = new JTextField(15); AuthorPanel(int authorNumber) { setLayout(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; constraints.gridwidth = 1; add(new JLabel("Author " + authorNumber), constraints); constraints.gridwidth = GridBagConstraints.RELATIVE; constraints.gridx = 1; add(firstName, constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 2; add(lastName, constraints); constraints.gridx = 0; constraints.gridwidth = 1; constraints.gridy = 1; add(new JLabel("URL"), constraints); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.gridx = 1; add(url, constraints); } public boolean hasText() { return firstName.getText() != ""  lastName.getText() != ""  url.getText() != ""; } public String getFirstName() { return firstName.getText(); } public String getLastName() { return lastName.getText(); } public String getUrl() { return url.getText(); } } [/projects/quotes] permanent link Enjoying Laurent's post on Analogies. This is a topic that will need to be continually brought up until people stop using analogies they don't understand. I like what Steve McConnell said:"By comparing a topic you understand poorly to something similar you understand better, you can come up with insights that result in a better understanding of the lessfamiliar topic." Code Complete, 1st edition, p. 7What I take from this is: Don't compare a topic you understand poorly to something similar that you understand better if you want insight into the topic you understand better. Mary Poppendeick is the best example I've found of someone who had a deep understanding of a topic (lean manufacturing) and applied it to something new (software development), with great success. 