High-Altitude Pair Programming Record?

With great courage and at great personal risk, Chet and Ron attempt a new world’s record in high-altitude pair programming.

Pair in the Air

Chet and I attended Agile Open NorthWest, a delightful Open Space conference held in Portland, Oregon, January 30 and 31. We returned together on NW218, from Portland to Detroit via Minneapolis. On the PDX-MSP leg, we decided to do a little pair programming so as to capture the record in high-altitude pair programming. The plane was flying at well over 30,000 feet at the time. We lashed our computers together with Cat-5 for safety, and set to work.

Our previous refactoring of the CreatePatternImage object had cleaned up the code a bit, and we had moved over a few bits from the CreateShadedPatternImage code. Our mission at 30,000 feet was to merge the two programs into one main program that produces both files. (More on that later.)

You can view the starting code for CreateShadedPatternImage in the Shot Clouds article.

A Simple Process

After a quick look at the two programs, we took a simple somewhat brute approach: we copied all the code from the Shaded code and pasted it into our newly refactored CreatePattern version. Eclipse, with the help of the Java compiler, graciously highlighted all the errors that wouldn’t compile. We fixed them one by one, working the Shaded code to look like the CreatePattern code. Where a block of Shaded code already existed as a CreatePattern method, such as to display the target or center of mass, we replaced the code block with a call to the method.

Everything proceeded without surprises. Calling the existing methods handled most of the necessary conversions to the unit square. When we were nearly finished, we had the whole picture drawn … except no gray squares. Conveniently, the bug could only be in the grayBlob() method:

    private void grayBlob(Graphics2D g2d, RectangularGrid grid, int row, int col) {
        int count = grid.hitsIn(col, row);
        int color = color(count);
        g2d.setColor(new Color(color));
        g2d.fillRect(col*rectangleWidth, row*rectangleHeight, rectangleWidth, rectangleHeight);
    }

A little research told us that the fillRect method on a Graphics2D can only support integer coordinates (well done, Java people). Since we were miles high and under constraints as to use of radio transmitters, we were literally working without a Net!1 It was indeed quite fortunate that we had downloaded the Java Tutorials to our laptops. We found the solution quickly:

   private void grayBlob(Graphics2D g2d, RectangularGrid grid, int row, int col) {
        int count = grid.hitsIn(col, row);
        int color = color(count);
        g2d.setColor(new Color(color));
        Rectangle2D rect = new Rectangle2D.Double(
          col*rectangleWidth*width, row*rectangleHeight*height, width, height);
        g2d.fill(rect);
    }

After a brief tour through making the shaded area be the full rectangleWidth and Height, we had it working. The picture is now the same as it ever was, same as it ever was. I’ll include the code at the bottom of the article, but there’s nothing special about it. More important, we think, is that we then did a little design, on my tablet PC, in order to clinch the paired design altitude record as well.

A Bit of Design

There is a not-uncommon misconception that Agile methods, XP, and Chet and I, don’t do any design other than in code. That is not the case. I’ve often spoken about our design discussions, in this series of articles and others, but have rarely been very specific about what we’ve done or shown what materials we have used.

Ordinarily we’ll toss around some cards CRC-style, or draw little pictures on the cards. This time, since we were pairing at 30K anyway, we looked at some pictures on my Tablet. I’ll reproduce them here and talk a bit about them.

We had just completed combining the two picture-drawing programs into one program that draws both pictures. The program in question clearly has multiple functions: it knows about and opens input files; it knows about and opens output files; it draws pictures; it saves pictures in files. Clearly this little program needs to be broken up. We are happy to have one program rather than two, but we wanted to talk about how to break it up as we move forward.

Much of what’s shown here would have been drawn on cards and tossed away, or saved and never looked at. Some of it we might have held on to. For your edification I’ve saved all six of the pictures I drew as we chatted.

imageAbove, as we chatted, I started to draw a “UML” diagram of the system as it now exists, from the viewpoint of the drawing program. It uses a library, which I represented by the Graphics2D box, and calls off to the ShotPattern and other such objects. I showed and highlighted that there were two main drawing methods, and recall commenting that if this was a drawing object, it was probably perfectly OK that it knew how to draw multiple different pictures.

We felt, though, that having that same object know about files and folders and such was probably not so good. We think that since the drawing methods basically return an Image, that might be all that the object should do.

imageOn the sheet above, I just started to list, as on a CRC card, some of the responsibilities of our current program. They are obviously too diverse for just one object. We moved on …

imageHere above, there are two sketches. On the left was a guess on my part as to what a typical client report’s code would look like. Underneath that is a sketch of the report as I envisioned it. On the right is a sketch of what the folder structure might look like for a client. It’s crossed out because we decided it was wrong.

imageThe above sheet reflects a bit better what Chet envisions the report structure will be. The client will have perhaps four “setups”, each of which will have perhaps four “sheets”, each representing one blast at the paper, and being reflected in one ShotPattern object.

imageAbove is another cut at the file folder layout. The red line shows roughly what would be there for each setup.

imageThis final picture is a sketch of the code that will produce the report. There’ll be a loop over setups, each one looping over sheets, reading files of some conventional name, here shown as “filename1″, and producing outputs perhaps called by that same name with a convenient meaningful suffix, _center, _shaded, whatever.

Summary Remarks on Design

These pictures are similar, in some way, to most of the kind of design discussions Chet and I have as we go, and which have been verbally sketched, if not detailed, in the text of earlier articles. We think and talk about what we’re going to do, sketch some pictures, toss some cards around, then possibly record or save one or two, especially if we’re not going to move immediately to code. If we do move immediately to code, we are less likely to save the paper. We are not terribly likely to look back at the paper, and quite often we wind up throwing it away without referring to it again.

As Paul Oldfield has put it:

The model that really matters is the model a person has in his mind. All other models exist only to get the right model into the right mind at the right time.

The way this notion converts into action in our practice is that we might look back at some piece of paper to remind ourselves of something, but we would then most likely redraw the picture. Most likely it’d be a bit different, elaborated in some new way. Sometimes it’ll be just the same. Either way, the notion is to get the ideas into the head.

I’d think that any wise team would do something similar. We wrote about this notion way last century, in our chapter Quick Design Session. Whether you draw UML, flowcharts, or little duckies2, do whatever you can to get the same ideas into all the necessary heads.

There’s lots of design in good software development. Much of it can involve the code, and the closer we are to code, the better we like it. But there’s plenty of room for judicious amounts of discussion and picture drawing. We’ll try to do a better job of reflecting that in our articles here.

See you next time!

Resulting Code

Here, prior to any footnotes you might want to check out, is the code as it presently stands:

public class CreatePatternImage {

    final String outputFolder = "Output\\";
    final String inputFolder = "Data\\";
    final int width = 2048;
    final int height = 1536;

    final int numberOfSquares = 20;
    final double rectangleWidth = 1.0/numberOfSquares;
    final double rectangleHeight = 1.0/numberOfSquares;

    public static void main(String[] args) throws IOException  {
        CreatePatternImage image = new CreatePatternImage();
        image.doit();
    }

    public void doit() throws IOException { 
        RenderedImage image = drawPointOfImpact();    
        File file = new File(outputFolder+"patternImage.jpg");
        ImageIO.write(image, "jpg", file);
        RenderedImage rendImage = patternImage();

        file = new File(outputFolder+"shadedPatternImage.jpg");
        ImageIO.write(rendImage, "jpg", file);
    }

    public RenderedImage drawPointOfImpact() {
        ShotPattern pattern = new ShotPattern(inputFolder+"PB270011.bmp");    
        BufferedImage bufferedImage = 
            new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);    
        Graphics2D g2d = bufferedImage.createGraphics();
        clearImage(g2d);
        drawCenterOfPattern(g2d, pattern);
        drawTarget(g2d);
        drawHits(g2d, pattern);
        g2d.dispose();    
        return bufferedImage;
    }

    public RenderedImage patternImage() {
        ShotPattern pattern = new ShotPattern(inputFolder+"PB270011.bmp");

        BufferedImage bufferedImage = 
            new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);    
        Graphics2D g2d = bufferedImage.createGraphics();
        g2d.setColor(Color.white);
        g2d.fillRect(0, 0, width, height);
        // g2d.translate(1024, 768);
        // g2d.scale(1, -1);

        grayBlobs(g2d, pattern);

        drawCenterOfPattern(g2d, pattern);

        drawTarget(g2d);
        g2d.setColor(Color.BLACK);
        drawHits(g2d, pattern);
        g2d.dispose();    
        return bufferedImage;
    }    

    private void clearImage(Graphics2D g2d) {
        g2d.setColor(Color.white);
        g2d.fillRect(0, 0, width, height);
    }

    private void drawHits(Graphics2D g2d, ShotPattern pattern) {
        int shotRadius = 4;
        g2d.setColor(Color.BLACK);
        for (Hit hit: pattern.hits) {
            g2d.fillOval((int)(hit.x()*width) - shotRadius, (int)(hit.y()*height) - shotRadius,
                    shotRadius*2, shotRadius*2);
        }
    }

    private void drawTarget(Graphics2D g2d) {
        g2d.setColor(Color.RED);
        g2d.drawOval(width/2-20, height/2-20, 40, 40);
        g2d.drawOval(width/2-10, height/2-10, 20, 20);
        g2d.setStroke(new BasicStroke(3));
        g2d.drawLine(width/2-25, height/2, width/2+25, height/2);
        g2d.drawLine(width/2,height/2-25,width/2,height/2+25);
    }

    private void drawCenterOfPattern(Graphics2D g2d, ShotPattern pattern) {
        int centerRadius = 15;
        g2d.setColor(Color.RED);
        g2d.fillOval((
                int) (pattern.centerOfMass().x()*width)-centerRadius, 
                (int)(pattern.centerOfMass().y()*height)-centerRadius, 
                centerRadius*2, centerRadius*2);
    }   

    private void grayBlobs(Graphics2D g2d, ShotPattern pattern) {
        RectangularGrid grid = new RectangularGrid(pattern, rectangleWidth, rectangleHeight);
        for ( int row = 0; row < numberOfSquares; row++)
            for (int col = 0; col < numberOfSquares; col++)
                grayBlob(g2d, grid, row, col);

    }

    private void grayBlob(Graphics2D g2d, RectangularGrid grid, int row, int col) {
        int count = grid.hitsIn(col, row);
        int color = color(count);
        g2d.setColor(new Color(color));
        Rectangle2D rect = new Rectangle2D.Double(col*rectangleWidth*width, row*rectangleHeight*height, width, height);
        g2d.fill(rect);
    }

    private int color(int count) {
//      int gray = (255 - count*4 + count/2) & 0xFF;
        int gray = (255 - count*6) & 0xFF;
        int rgb = (gray << 16) | (gray << 8) | (gray << 0);
        return rgb;
    }   
}

1. Sorry.

2. imageimageimageimage
Quack Design Session (thanks to George Dinwiddie)

Posted on:

Written by: Ron Jeffries

Categorization: Articles

Recent Articles

Build it right to build the right thing

You can’t build the right product if you can’t build the product right.

I tweeted that this morning, in response to a Twitter-vibe that was flitting around. I’d like to follow it up here with a bit more meat.

The …

Estimate This! (or not)

My friends and colleagues in the #NoEstimates circles have some good ideas, as I've commented elsewhere. I'm left, though, with a couple of concerns.