PurelyFunctional.tv Newsletter 412: module depth is bogus

Sign up for weekly Clojure tips, software design, and a Clojure coding challenge.

Issue 412 - January 25, 2021 · Archives · Subscribe

Design Tip 💡

module depth is bogus

In A Philosophy of Software Design, John Ousterhout presents the concept of module depth. He claims a module should be "deep" as opposed to "shallow," and uses this measure to explain why various software is poorly designed. Here is a picture from the book.

The idea has some interesting details. The interface of the module is the black bar. An interface with a lot of methods and other concepts you need to know is very "wide" and costly to learn and use. The amount of functionality is the height of the module. So we get a rectangular model, where the width is the cost in terms of complexity and the height is the benefit in terms of functionality. We should prefer deep modules because they have low cost and high benefit, whereas shallow modules have high cost and low benefit.

He uses this model to explain why the java.io.FileInputStream is poorly designed. We examined this in a previous issue. You'll remember that he says this is bad design:

FileInputStream fis = new FileInputStream("inputfile");
BufferedInputStream bis = new BufferedInputStream(fis);

He explains that we have to do too much (high interface cost) to achieve so little functionality (reading bytes from a file). It is therefore a shallow module, and hence a bad design.

That all sounds good, but on close examination, the idea of depth doesn't hold water. Can you really compare interface complexity to amount of complexity? At what scale are we drawing those rectangles? We could easily change the scale and make any rectangle appear tall and thin instead of short and wide. Is choosing that scale for your project part of the process of design?

And how do you measure the amount of functionality? Some functionality is obviously better than others. For instance, we can imagine a deep module that packed in lots of automatic features, like syncing files to Dropbox, running backups, error correcting codes, and file formats. All of that added functionality would make the rectangle taller. If it does it all by default, then the interface won't grow any bigger. And so we've made a deeper module. Win! Right?

In issue 407, I argued that the Java IO module is a great design. It neatly picks apart the various concerns into different classes. FileInputStream knows how to read bytes from the filesystem. BufferedInputStream knows how to grab blocks of bytes at a time. And Reader knows how to turn bytes into characters. It is an instance of the decorator pattern done right.

What's missing from the Java API is a convenience layer that makes it easier to use for people. Ousterhout is right that it is tedious work to always wrap your FileInputStream in a BufferedInputStream. But I don't think the answer is to make a FileInputStream buffer automatically. The answer is to make another module built on top. The folks on the Java API team didn't do that, but luckily, anyone can make a new package in Java. The Apache Commons IO library does provide those conveniences.

Module depth is an attempt to rationalize an intuition. However, it is a dangerous idea. Instead of clarifying, it mystifies. It presents a logical front, wrapped in a pseudo-geometrical guise, yet hides all of the difficulty behind poorly understood ideas like interface complexity and functionality.

In The Design of Everyday Things, Don Norman explains the idea of affordances:

An affordance is a relationship between the properties of an object and the capabilities of the agent that determine just how the object could possibly be used.

Norman uses the idea of affordances for everyday objects, like doorknobs and blenders. In our context, the object is our module and the agent is the programmer. The affordances would be the classes and interfaces of the module, along with their methods and type signatures.

A perfect interface should have exactly as much complexity as the control it seeks to provide. That is, if there are two things you can do to a thing, it should present an affordance that allows exactly those two things. If there are 100 things, likewise the affordance should allow those 100 things. That, of course, leads to another design question: What control should we provide? We'll talk more about this in the next issue.

State of Clojure Community Survey 📋

Please support the ongoing development of Clojure by filling out the annual survey. There are still a few days left.

This survey has been going on for years and it collects very important statistics on the Clojure community. I find the information valuable myself. I can only imagine how important it would be for the folks in Clojure's core team to know this stuff.

Please fill it out, regardless of how you use Clojure. It's all great information.

The survey ends January 29, so take it while you can!

Podcast episode🎙

This week on the podcast, I read One Man's View of Computer Science, the 1968 ACM Turing Award lecture from Richard Hamming. He was very interested in education and what and how we should teach computer science.

Quarantine update 😷

I know a lot of people are going through tougher times than I am. If you, for any reason, can't afford my courses, and you think the courses will help you, please hit reply and I will set you up. It's a small gesture I can make, but it might help.

I don't want to shame you or anybody that we should be using this time to work on our skills. The number one priority is your health and safety. I know I haven't been able to work very much, let alone learn some new skill. But if learning Clojure is important to you, and you can't afford it, just hit reply and I'll set you up. Keeping busy can keep us sane.

Also, if you just want to subscribe for a paid membership, I have opened them back up for the moment. Register here.

Stay healthy. Wash your hands. Wear a mask. Take care of loved ones.

Clojure Challenge 🤔

Last issue's challenge

Issue 411

Please do participate in the discussion at the submission links above. It's active and it's a great way to get comments on your code.

This week's challenge

Valid names

This challenge looks like a fun experiment in building a simple rules-based validator.

Definitions:

  1. A name is a sequence of terms separated by a space. It must have at least 2 terms. The last term must be a word.
  2. A term is either an initial or a word.
  3. An initial is a single capital letter followed by a period.
  4. A word is a capital letter followed by one or more letters (upper or lower case).

Write a function that checks whether a string is a valid name.

Examples

Valid names:

  • George R. R. Martin
  • Abraham Lincoln
  • J. R. Bob Dobbs
  • H. G. Wells

Invalid names:

  • J R Tolkien (no periods)
  • J. F. K. (must end in word)
  • Franklin (must have at least two terms)

Thanks to this site for the challenge idea where it is considered Expert in JavaScript. The problem has been modified from the original.

Please submit your design process as comments to this gist. Discussion is welcome.

Rock on!
Eric Normand