Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Generating UML and Sequence Diagrams

by Phil Crow
August 03, 2006

Imagine yourself in a meeting with management. You're about to begin your third attempt to explain how to process online credit card payments. After a couple of sentences, you see some eyes glazing over. Someone says, "Perhaps you could draw us a picture."

Imagine me handling a recent request from my boss. He came in to the bat cave and said (in summary), "We want customers to sign up for email accounts without calling customer service. All the account creation code is in the customer care app." It didn't take long to find the relevant web screen, where the CSR presses Save to kick off the account creation, but there sure were a lot of layers between there and the final result. Keeping them in mind is hard enough when I'm deep in the problem. Three months from now, when an odd bug surfaces, it'll be nearly impossible without the right memory aid.

In both of these cases, the right diagram is the sequence diagram. (I'd show you mine for the situations above, but they're secret.) Sequence diagrams clearly show the time flow of method or function calls between modules. For complex systems, these diagrams can save a lot of time--like the time you and your fellow programmers spend during initial design, the time spent explaining what's possible to management, the time you spend remembering how things work when you revisit an old system that needs a new feature, and especially the time it takes a new programmer in your shop to get up to speed.

In short, sequence diagrams help with complex call stacks just as data model diagrams help with complex database schema.

While the sequence diagram is useful to me, I don't like on-screen drawing tools. Therefore, I wrote the original UML::Sequence to make the drawings for me. With recent help from Dean Arnold, the current version has many nice features and is closer to standards compliance (but, both Dean and I prefer a useful diagram to a compliant one). Using UML::Sequence, you can quickly make proposed diagrams of systems not yet built. You can even run it against existing programs to have it diagram what they actually do.

Reading a Sequence Diagram

If you already know how to read sequence diagrams, you can skip to the next section.

Because most uses of UML involve object-oriented projects, that's where I've drawn my examples. Don't think that objects are necessary for sequence diagrams. I've diagrammed many non-OO programs with it (including some in COBOL).

A simple example will work best for a first look at UML sequence diagrams, so consider rolling two dice. My over-engineered solution gives a nice diagram to discuss. In it, I made each die an object of the Die class and the pair of dice an object of the DiePair class. To roll the dice, I wrote a little script. Here are these pieces:

    package Die;
    use strict;

    sub new {
        my $class = shift;
        my $sides = shift || 6;
        return bless { SIDES => $sides }, $class;
    }

    sub roll {
        my $self       = shift;
        $self->{VALUE} = int( rand * $self->{SIDES} ) + 1;

        return $self->{VALUE};
    }

    1;

The Die constructor takes an optional number of sides for the new die object, but supplies six as a default. It bundles that number of sides into a hash reference, blesses, and returns it.

The roll() method makes a random number and uses it to pick a new value for the die, which it returns.

DiePair is equally scintillating:

    package DiePair;
    use strict;

    use Die;

    sub new {
        my $class     = shift;
        my $self      = {};
        $self->{DIE1} = Die->new( shift );
        $self->{DIE2} = Die->new( shift );

        return bless $self, $class;
    }

    sub roll {
        my $self   = shift;
        my $value1 = $self->{DIE1}->roll();
        my $value2 = $self->{DIE2}->roll();

        $self->{TOTAL}   = $value1 + $value2;
        $self->{DOUBLES} = ( $value1 == $value2 ) ? 1 : 0;

        return $self->{TOTAL}, $self->{DOUBLES};
    }

    1;

The constructor makes two die objects and stores them in a hash reference, which it blesses and returns.

The roll() method rolls each die, storing the value, then totals them and decides whether the roll was doubles. It returns both total and doubles, saving the driver from having to call back for them.

Rather than modeling a real game like craps, I use a small driver, which will simplify the resulting diagram.

    #!/usr/bin/perl
    use strict;

    use DiePair;

    my $die_pair          = DiePair->new(6, 6);
    my ($total, $doubles) = $die_pair->roll();

    print "Your total is $total ";
    print "it was doubles" if $doubles;
    print "\n";

Figure 1 shows the sequence diagram for this driver.

the sequence diagram for the die roller
Figure 1. The sequence diagram for the die roller

Each package has a box at the top of the diagram. The script is in the main package (which is always Perl's default). Time flows from top to bottom. Arrows represent method (or function) calls.

The vertical boxes, or activations, represent the life of a call. Between the activations are dashed lines called the life lines of the objects.

You can see that main begins first (because its first activation is higher than the others). It calls new() on the DiePair class. That call lasts long enough for DiePair's constructor to call new() on the Die class twice.

After making the objects, the script calls roll() on the DiePar, which forwards the request to the individual dice.

This diagram is unorthodox. The boxes at the top should represent individual instances, not classes. Sometimes I prefer this style because it compacts the diagram horizontally. Figure 2 shows a more orthodox diagram (divergent only in the lack of name underlining).

a more orthodox UML diagram
Figure 2. A more orthodox UML diagram

You can see the individual Die objects that the DiePair instance aggregates, because there is now a box at the top for each object (use your imagination when thinking about the driver as an instance). The names do not come from the code; they are sequentially assigned from the class name.

Diagrams like this are especially helpful when many classes interact. For instance, many of them start with a user event (like a button press on a GUI application) and show how the view communicates with the controller and how the controller in turn communicates with the data model.

Another particularly useful application is for programs communicating via network sockets. In their diagrams, each program has a box, and the arrows represent writing on a socket. Note that UML sequence diagrams may also have dashed arrows, which show return values going back to callers. Unless there is something unusual about that value, there is no use to waste space on the diagram for those returns. However, in a network situation, showing the back and forth can be quite helpful. UML::Sequence now has support for return arrows.

UML 2.0 Pocket Reference

Related Reading

UML 2.0 Pocket Reference
By Dan Pilone

Pages: 1, 2

Next Pagearrow