We don't need no stinking UML diagrams

A comparison of code vs UML

In my talk on functional DDD, I often use this slide (in context):

We don't need no stinking UML diagrams

Which is of course is a misquote of this famous scene. Oops, I mean this one.

Ok, I might be exaggerating a bit. Some UML diagrams are useful (I like sequence diagrams for example) and in general, I do think a good picture or diagram can be worth 1000 words.

But I believe that, in many cases, using UML for class diagrams is not necessary.

Instead, a concise language like F# (or OCaml or Haskell) can convey the same meaning in a way that is easier to read, easier to write, and most important, easier to turn into working code!

With UML diagrams, you need to translate them to code, with the possibility of losing something in translation. But if the design is documented in your programming language itself, there is no translation phase, and so the design must always be in sync with the implementation.

To demonstrate this in practice, I decided to scour the internet for some good (and not-so-good) UML class diagrams, and convert them into F# code. You can compare them for yourselves.

Regular expressions

Let's start with a classic one: regular expressions (source)

Here's the UML diagram:

And here's the F# equivalent:

That's quite straightforward.

Student enrollment

Here's another classic one: enrollment (source).

Here's the UML diagram:

And here's the F# equivalent:

The F# mirrors the UML diagram, but I find that by writing functions for all the activities rather than drawing pictures, holes in the original requirements are revealed.

For example, in the GetSeminarsTaken method in the UML diagram, where is the list of seminars stored? If it is in the Student class (as implied by the diagram) then we have a mutual recursion between Student and Seminar and the whole tree of every student and seminar is interconnected and must be loaded at the same time unless hacks are used.

Instead, for the functional version, I created an EnrollmentRepository to decouple the two classes.

Similarly, it's not clear how enrollment actually works, so I created an EnrollStudent function to make it clear what inputs are needed.

Because the function returns an option, it is immediately clear that enrollment might fail (e.g student is not eligible to enroll, or is enrolling twice by mistake).

Order and customer

Here's another one (source).

And here's the F# equivalent:

I'm just copying the UML diagram, but I have to say that I hate this design. It's crying out to have more fine grained states.

In particular, the Confirm and Dispatch functions are horrible -- they give no idea of what else is needed as input or what the effects will be. This is where writing real code can force you to think a bit more deeply about the requirements.

Order and customer, version 2

Here's a much better version of orders and customers (source).

And here's the F# equivalent:

I've done some minor tweaking, adding units of measure for the weight, creating types to represent Qty and Price.

Again, this design might be improved with more fine grained states, such as creating a separate AuthorizedPayment type (to ensure that an order can only be paid with authorized payments) and a separate PaidOrder type (e.g. to stop you paying for the same order twice).

Here's the kind of thing I mean:

Hotel Booking

Here's one from the JetBrains IntelliJ documentation (source).

Here's the F# equivalent:

I have to stop there, sorry. The design is driving me crazy. I can't even.

What are these EntityManager and FacesMessages fields? And logging is important of course, but why is Log a field in the domain object?

By the way, in case you think that I am deliberately picking bad examples of UML design, all these diagrams come from the top results in an image search for "uml class diagram".

Library

This one is better, a library domain (source).

Here's the F# equivalent. Note that because it is code, I can add comments to specific types and fields, which is doable but awkward with UML.

Note also that I can say ISBN: string option to indicate an optional ISBN rather that the awkward [0..1] syntax.

Since the Search and Manage interfaces are undefined, we can just use placeholders (unit) for the inputs and outputs.

Again, this might not be the perfect design. For example, it's not clear that only Active accounts could borrow a book, which I might represent in F# as:

If you want to see a more modern approach to modelling this domain using CQRS and event sourcing, see this post.

Software licensing

The final example is from a software licensing domain (source).

Here's the F# equivalent.

This diagram is just pure data and no methods, so there are no function types. I have a feeling that there are some important business rules that have not been captured.

For example, if you read the comments in the source, you'll see that there are some interesting constraints around EntitlementType and LockingType. Only certain locking types can be used with certain entitlement types.

That might be something that we could consider modelling in the type system, but I haven't bothered. I've just tried to reproduct the UML as is.

Summary

I think that's enough to get the idea.

My general feeling about UML class diagrams is that they are OK for a sketch, if a bit heavyweight compared to a few lines of code.

For detailed designs, though, they are not nearly detailed enough. Critical things like context and dependencies are not at all obvious. In my opinion, none of the UML diagrams I've shown have been good enough to write code from, even as a basic design.

Even more seriously, a UML diagram can be very misleading to non-developers. It looks "official" and can give the impression that the design has been thought about deeply, when in fact the design is actually shallow and unusable in practice.

Disagree? Let me know in the comments!

Last updated

Was this helpful?