MarkBernstein.org

Most of the tech press acts as if the surface design of gadgets is what that matters. Sometimes, they’re right. Most of the time, design lies deeper.

Lots of programming pundits right now will tell you that simplicity is what matters. Or testing. Sometimes, they’re right. A lot of the time, design lies deeper

The Problem

In a Tinderbox Six outline, pressing up-arrow moves the selection up, and pressing down-arrow moves the selection down. In a long, long outline, press and hold an arrow key. You might scroll through a page or two of notes and then, suddenly, stall. Now when it ought to be scrolling, Tinderbox would beep at you.

Clues

You always scroll through all the currently-visible notes, and then some more notes, before stalling; you never seem to stall immediately. ❧ There’s nothing special about the note on which you stall. ❧ Things are worse when you press and hold the arrow keys. Moving more deliberately, one click at a time, you’re less likely to stall.

Note that all these hints can be explained away by saying, “It’s unlikely that you’ll stall on any given note.” That turns out not to be the explanation, but that is the simplest explanation and Occam is often a terrific companion for the software detective.

Undercover

In an outline, each note has a MapItemView that represents the note on screen. Inside a MapItemView, we have a text field for the name of the note, and some other subviews. When we press the arrow key, we do several things:

  1. figure out which note is the next or previous note.
  2. select it, giving it first responsibility for handling events
  3. if that note is off screen, scroll until it is visible.

This is pretty simple, and it’s hard to see how you could make it simpler and still do what needs to be done.

In fact, it’s really too simple, because a big outline will have hundreds of notes, maybe thousands of notes, but only a few dozen notes are going to be visible at any one time. Views are big, complicated things that take lots of work and lots of memory, and the views that are outside the scroll area aren’t doing us any good. So, we add some logic:

  1. if the note is offscreen, put its view into the recycling pool
  2. if the note is onscreen…
    1. if it has no view, get one for it (preferably a recycled view)
    2. if it’s changed, ask the system to redraw it when convenient

That’s simple, too. When do we need to do this? Well, obviously, we need this when we scroll. And when we press up-arrow or down-arrow, we’d better be sure that the note has a view before we select it and put it in charge of event handling.

Crime

And there’s our answer! At the bottom of the screen, we press down-arrow several times in a row, very quickly. The note we’ve now selected is, say, five notes below the bottom. It’s now in charge. And we press again, so it’s going to hand off to the next note.

BUT… scrolling is animated — jt plays out over time, on its own schedule. So now, perhaps, we scroll the screen a bit. And maybe we look around for any off-screen views we can recycle. We’re pressing down-arrow, so a bunch of views have scrolled off the top of the screen. Recycle them!

Oh — and there are some views beneath the bottom of the screen, too – because we’ve pressed the key so quickly. They’re offscreen. Recycle those, too!

And so the view that’s responsible for handling events goes into the recycling pool. Sure, before the note gets to the edge of the screen, we’ll make it a new view. But that view won’t be handling events. And the old view won’t be, either, because we’ve removed it from its window and that takes it off duty. So no one handles events, and we start beeping.

Investigator’s Report

I began with the usual suspects and routine probing, but got nowhere. This persisted through several attempts, concentrating on finding attempts to set the keyboard focus. Ultimately, every Tinderbox method that explicitly set the keyboard focus was examined, but none appeared to by setting it incorrectly.

Once I knew this investigation was at least a bear and possibly an epic, I concluded that the actual crime had to be that some unknown rogue object was covertly stealing the keyboard focus. To catch the thief, I eventually built all sorts of monitors, ultimately subclassing NSWindow so I could intercept every call to makeFirstResponder:, only to find that no one was stealing the keyboard focus. If no one was stealing the focus, why was firstResponder a note one minute and nil the next? Oh: someone is stealing the view, and that view was holding the keyboard focus. To steal the Torkington Tiara, they abducted Lady Torkington.

Verdict

Just don’t recycle a note that’s selected. Wait until it’s not selected, even if it’s offscreen.

Punishment

On the surface, this was a tiny UI glitch, one that’s barely an inconvenience. In practice, it might require an unnecessary mouse click for every 100-250 arrow key presses. Say a mouse click takes 3 seconds, and the average user makes $100,000/year. That’s about a nickel per click. But a lot of that scrolling isn’t really productive anyway: lots of scrolling is actually a displacement activity, something people do while they’re thinking or worrying or waiting to get off the phone. So making that scrolling a little slower or less fun isn’t good, but it’s not really costing anyone any money. How many unnecessary mouse clicks do we need to save to justify the twenty or thirty hours it took to solve this?

Of course, solving the crime is a good thing in itself. Now that it’s fixed, we understand the system more completely. The user experience is now more solid, and that reassures everyone. A byproduct of all that debugging is some refactoring that clarifies the code.

That wasn’t the case ten years ago, by the way: before refactoring, long debugging adventures tended to make the code worse, not better. You’d add instrumentation and sensors and loggers, you’d try experiments, and inevitably some of that scar tissue made its way into the final product.

The Official Party Line, of course, calls for infinite attention and polish and emphasizes the importance of a pristine user experience. This makes some sense on its own, and also because relentlessly solving this problem revealed a small but systematic problem in recycling. We have lots of separate processors performing their own tasks, we have animations going on all over the place, and any of these might at any moment need to make sure that some note it’s working on has a view – any might recycle our offscreen view and foul up event handling.

But it’s not always that simple. Lots of small UI glitches turn out to be small, esoteric bugs that don’t matter. Lots of outright crashes turn out to be small, esoteric bugs that will matter only to the four of people who will ever experience them. This particular bug had a one-line fix, but that one line probably cost a few thousand dollars. For a few thousand dollars, you might be willing to put up with some visual artifacts.

Policy Implications

Would better testing have caught this? Probably not, because the critical factor here is that scrolling is animated and asynchronous. Asynchronous tests are difficult to write, slow to run, and therefore costly. There was no particular reason to expect this Spanish Inquisition.

Would better debugging practice have solved this faster? I’m not sure, even in hindsight. The firstResponder was correct here and wrong there; the natural assumption was to ask “who is changing the first responder?”

Would better design have prevented this? I’m not wild about the view recycling, which smells of premature optimization. But Apple does it conspicuously in NSTableView, which suggests (a) it’s desirable and (b) the rest of the system expects it. Tinderbox users build much larger documents than you’d expect, and that means we could wind up with a ton of views and that will bog down your computer. But we’re not building this for your computer: we’re also building this for your computer in 2024. And remember, Moore’s Law says that your 2024 computer is going to have 64GB of memory, 4TB of hard drive, and 64 cores.

At one of the first computer science conferences I attended, someone asked markup language pioneer Brian Reid about the prospects for WYSIWYG editors. Reid pointed out that paragraph formatting was just too CPU-intensive, observing that it could “bring a dedicated 370 to its knees.” Now, of course, we do that on our pocket phones.

Avoiding premature optimization sometimes means, “keep the software slow and simple, and let the computers catch up.”

I think fixing this was right, but it’s a near-run thing. We never talk about the bugs we should let be.

by Jennifer Egan

Second reading of this marvelous, strange book. My original notes stand up well, I think.


In this fascinating and compelling novel, every chapter begins with a timeshift and a new point of view. What might seem a tedious experiment in late postmodernism becomes, in Egan’s hands, a natural way to tell a complex story about the intersecting lives of a group of friends and acquaintances over the span of many years.

At one point, a successful music industry pro is taking his kids and his (very) personal assistant on an African safari. His sulky daughter Charlie, one night, joins in a dance around the fire and launches us through time:

Lou lets go of Mindy’s hand and sits up straight. He wants to grab his daughter’s skinny arm and yank her away from these black men, but does no such thing, of course. That would be letting her win. The warrior smiles at Charlie. He’s nineteen, only five years older than she is, and has lived away from his village since he was ten. But he’s sung for enough American tourists to recognize that in her world, Charlie is a child. Thirty-five years from now, in 2008, this warrior will be caught in the tribal violence between the Kikuyu and the Luo and will die in a fire. He’ll have had four wives and sixty-three grandchildren by then, one of whom, a boy named Joe, will inherit his lalema: the iron hunting dagger in a leather scabbard now hanging at his side. Joe will go to college at Columbia and study engineering, becoming an expert in visual robotic technology that detects the slightest hint of irregular movement (the legacy of a childhood spent scanning the grass for lions). He’ll marry an American named Lulu and remain in New York, where he’ll invent a scanning device that becomes standard issue for crowd security. He and Lulu will buy a loft in Tribeca, where his grandfather’s hunting dagger will be displayed inside a cube of Plexiglas, directly under a skylight.

It should not come as a surprise that, much later, we will meet Lulu once more, or that she will play an important (if unconscious) part in resolving an injustice that appears in the book’s earliest chapter (though not its first) and which no one seems to have noticed.

Mar 14 26 2014

Divergent

I wasn’t expecting great things from Divergent, although the book was a lark. It’s a well made movie, about as good as it could possibly be.

The director lost his nerve (or feared for his PG-13 rating) in the boot camp assault, and that’s politically unfortunate, but perhaps the plot can’t really bear that additional weight. It certainly keeps moving.

Ruined Chicago never looked better.

Brent Simmons had a monster class – in his case a 2500-line view controller – that he wanted to break up into smaller classes. He describes this as “creating a basketful of bunny objects.”

Refactoring: Taming The Bunnies

We used to have one big mother class that was complicated and hard to understand. Now we have a smaller mother class and a bunch of very simple new bunny classes.

This is the way one hopes things will work. But, as Brent notes, it doesn’t always work that way.

The separation into separate objects wasn’t nearly clean, in other words. I had to expose a bunch of internal methods and properties of the view controller to its bunnies. Way more than I felt comfortable with. (And there’s no @bunnies keyword to protect those.)

And some of the bunnies had to know about each other: the drag controller delegate needed to know about the table view delegate and datasource. The timeline cell delegate needed to know about the datasource. Etc.

Refactoring: Taming The Bunnies

We have a basket of bunnies and a ball of yarn: the bunnies are cute, but the tangle isn’t. The new objects seem simple, but they all know too much about each other and they all communicate in complicated ways – ways so complicated that life was better when everything was in one monster mother class.

Sometimes, that’s the answer. But sometimes, refactoring further can drive you to a new design. Test-driven design throughout will help prevent errors from creeping in – and that’s important because we’re going to be yanking code all over the place.

Push Data Down

First, you may be able to simplify things in the old mother class by pushing properties and instance values down from the old class to the bunny class that is its primary user. This can eliminate a lot of communication between the bunnies and the mother class. Ideally, only one bunny needs this data. If a few bunnies need to share, that might be OK.

Herding Bunnies

When possible, it’s better for the mother to tell the bunnies what to do than to ask them about their internal state. This can often simplify coordination and improve encapsulation.

If bunny methods repeatedly need to get stuff from the mother object, consider passing the information they need as arguments. If you have too many arguments, make an argument object. If a method on bunny A needs to talk to some other bunny, pass the bunny as an argument.The point here is to unravel the tangled strands of yarn and to knit up your raveled sleeve of care.

Pull Methods Up

When bunnies need to talk to each other, you wind up with lots of inter-bunny murmuring.

self.depth = [self.controller.driller depth];

Get rid of this by adding convenience methods in the mother class. (Yes: this makes the mother class bigger, and yes, the whole point of the exercise was to make the mother class smaller. Trust me on this.) The extra convenience methods are trivial, they’re easy to write, and they’re unlikely to break. At the same time, you:

  • avoid violating the Law of Demeter
  • reduce inter-bunny murmuring

In fact, you may soon be able to remove a bunch of dependencies between bunnies at the cost of a few tiny methods in the mother class.

Continue this refactoring until the bunnies no longer need to know about each other, and talk only to their mother.

Extract A Nanny Class

The last refactoring round made the bunnies better, but now we’ve made the mother class worse. No worries! Take all those little methods you just made in the mother and sprout a Nanny class.

Refactoring: Taming The Bunnies

Now, when the bunnies want something from the mother or from another bunny, they talk to the Nanny. When the mother class wants a bunny to do something, it tells the Nanny.

The Nanny knows where to find each bunny and the mother, but otherwise it has no state. It’s got lots of intimate knowledge – it #include’s each bunny – but that’s OK because it doesn’t really do very much with that knowledge. It just relays messages between the bunnies, and passes messages from the bunnies to the mother and from the mother to the bunnies.

The nanny, in short, is a new class that abstracts that tangle of yarn. In trade school lingo, it’s a multiple Facade, an interface between bunnies and the mother class and also an interface amongst in homogenous bunny classes.

Special Friends

Often, you may find that the Nanny has a bunch of methods that talk to the same two or three bunny classes. If the Nanny starts to get complicated, you can hare off the parts that deal with that group of bunnies into its own Nanny.

Refactoring: Taming The Bunnies

Small Bunnies Really Are Better

I mistrust box-and-line diagrams; they’re often an indication of architectural astronautics. Is all this refactoring getting us anywhere? We started with a 2500-line mother class. Perhaps we end up with 8 classes. The mother’s still 1000 lines, and the nannies and bunnies are 200-400 lines. Are we better off?

Yes, we probably are.

First, everything is much simpler. Five years from now, you get a crash report right in the middle of Driller, because it’s being passed a WarpDrive and those hadn’t even been invented back in 2014. With only a handful of methods, fixing Driller to work with a WarpDrive won’t be hard. You probably won’t even need to glance at the other bunnies.

But even if you need to look through every single bunny and every nanny, you’re still better off. Small classes are easy to understand. 2500-line classes make you nervous now, and they’ll make you nervous in 2019.

Testing bunnies is likely to be straightforward. To test the old mother class, you probably need to build a model and then mock a view, and you’re going to need to plant sensors or breakpoints to get internal state. Trust me, you don’t want that.

Just this morning, Tinderbox was crashing when a table inside a popover called reloadData, thanks to some asynchronous code that was bashing the model just as we were reloading. In a five-method bunny that was so simple it couldn’t possibly crash, a concurrency error was an obvious place to look and the fix took twenty minutes. In a fifty-method mother, especially one you don’t really trust, you could spend days hunting that crash.

I’m pretty sure that the advantage of bunny classes can, in fact, be quantified. And perhaps it has! Let me know.


This recent series has been getting a lot of traffic from colleagues, and also at least one college class. (Hi, all you Manitobans!) I’ve not been seeing much email. Have something I should know about? Email me.

by Sam Miller, Jason Wojciechowski

This book is the heir to the old Bill James Baseball Abstract. I used to rush out and devour Baseball Abstract as soon as it arrived. Today, we have far more knowledge and far better tools, and where Baseball Abstract was always tinged by the derision of the baseball world, many modern Sabremetricians are honored (and employed) throughout the major leagues.

But it’s a lot less interesting and a lot less fun.

Why? First, I think I watch less baseball now. I miss it, sometimes, but there’s never time and you can always tell yourself you’ll catch a game tomorrow. I used to go to Fenway or Wrigley six or eight times a year, usually on the spur of the moment; now, it’s just too expensive. (Funny, isn’t it, how back when you didn’t have any money you could afford things that are too damn expensive nowadays?)

But when Bill James wrote about Rickey Henderson’s place in history, he was writing first about history and only incidentally about Henderson. Today’s Baseball Prospectus comments are focused entirely on the player and his immediate prospects, mostly with an eye to fantasy drafts. I understand: I buy the book for my own fantasy draft. I’ve been part of the Eddie Plank League since 1993. But I’d much rather read about the game than whether this scrub might help my team a bit.

Anyway, I just fouled up my whole season by writing a “10” next to the name of Anibal Sanchez. Or, rather, I intended to write a “10” but actually wrote a “0”. So welcome my new ace starter for his third stint with the Mallards (sigh), Anibal Sanchez. Nobody knows the trouble I’ve seen.

James used to write about topics that were fascinating even if they didn’t help with your Rotisseries league. What would Mike Schmidt hit if he always batted against the Cubs? Is taking the Cubs to win the series at 100-1 a reasonable proposition? Was it going to harm the characters of promising rookies Mark McGwire and Jose Canseco to be stuck on the hapless A’s?

That was an interesting question, before we knew the answer. Cobb would’ve caught it.

by Gerald Elias

Old, blind violinist Max Jacobus makes an unlikely mystery detective, and the dense and unlikely plot does not make his efforts easier. Yet there are some very good scenes here, and some fine ideas about music and some musicians.

Mar 14 21 2014

Lamport

Congratulations to Leslie Lamport, winner of the Turing Award.

Mar 14 19 2014

Subclassing

Brent Simmons poses an interesting puzzle about subclassing, and finds a nice solution.

Yes, novices subclass too often; they have learned about inheritance and, having overcome the obstacles, want to use it everywhere. But that’s not what we’re talking about here. Are subclasses a code smell?

I think that’s a mistake. I use (shallow) inheritance all over Tinderbox, and it’s essential.

Here’s one example, the Get Info popover in Tinderbox Six. It gives you information about the selected note.

Subclassing

At the left, we have a source list of various types of information we might find about a note. “Book” has the ISBN, the publisher, and tools for getting more information from libraries and other Web services. “Map” takes the address or the latitude and longitude and shows it to you on a map. “Similar” finds notes in this document with similar text.

Each of these panes is managed by its own class of view controller. We have TbxBookInfoController for books, and TbxMapInfoController for maps. The BookInfo controller knows how to talks to GoodReads and it knows how to validate ISBNs; that’s not stuff the MapInfo controller needs to know about. The MapInfo controller knows about geocoding, which isn’t very useful for the BookInfo controller.

But both kinds of controller have something in common: their views fit into the right panel of the GetInfo popover. So there’s lots of stuff that they share in order to manage the view and in order to talk to the rest of the system. The common stuff – the stuff about being a swappable Get Info pane – belongs to the superclass. The specialized stuff – books, maps, texts, tf-idf similarity measuring – belongs in the individual pane controller.

We could do this by delegation, but we don’t want to! After all, the first thing that a Get Info pane needs to know is, “what UI elements do I control?” We don’t want to discover them through some narrow delegation API: we want them to be our properties. They’re bound to be different for each kind of pane. New panes will come fairly often; we don’t want to expand the delegate protocol every time a new flavor of pane needs some new detail.

Over in the test lab, we’ve got another little inheritance hierarchy. Some tests inherit directly from XCTestCase, but lots of tests inherit from TbxTestCase, a subclass that provides a bunch of convenience functions for making and validating test documents. A few tests inherit from another subclass, AcceptanceTestCase, which builds a really complex text fixture that’s essentially a functional Tinderbox window; this is far too cumbersome for most testing but it provides a full text fixture for functional tests.

A few weeks ago, I wrote about another example. TbxMapViewController used to have 82 different methods that performed actions in response to menus or other user interface actions. That was a mess. Instead, we have TbxMapViewControllerAction, a superclass that manages a small mountain of tiny little classes that perform one action apiece. Each subclass knows one thing and only one thing; the superclass provides lots of convenience functions so they can concentrate on what they want to do. And the superclass provides a factory so TbxMapViewController doesn’t need to know about any of the subclasses:

- (TbxMapViewControllerAction*) actionFor: (SEL) selector;

The C++ world long ago came to grips with excessive reliance on inheritance and instituted a convention: public inheritance means that the subclass is a kind of its superclass. If you inherit, you promise to do everything the superclass does, and more. If you inherit, anyone that expects the superclass should be happy to receive you. That’s restrictive – Java is far more relaxed about inheritance – but it does rein in novice excesses.

What gets confused here is a different (and still recently-discovered) code smell: subclassing framework classes is undesirable. Inheritance is intimate, and requires extensive understanding; frameworks want to keep things more distant and formal so they can hide implementation details. As the undesirability of framework inheritance has become clearer, frameworks have begun to move away from requiring applications to do this: Cocoa, with its reliance on delegation, was an early leader in this movement. But this is a separate issue concerning proper relations between framework and application; application design itself is a separate issue entirely.

You don’t need inheritance every day, but when you need it, you really do need it.

by Ruth Ozeki

A diary washes up on a beach in British Columbia. In Ruth Ozeki’s new novel, a Japanese-American Canadian novelist named Ruth finds the diary, wrapped in freezer bags and protected by a Hello Kitty lunchbox. In its pages, she finds a fascinating story of Nao, a suicidal Japanese schoolgirl, and her friendship with her great grandmother Jiko, a radical feminist Buddhist nun who was 104 years old and whose gentle, philosophical son died in a kamikaze attack on the American fleet.

This is a charming little story about growing up, and also about getting past writer’s block. There’s also more than a little reflection on the meaning of being Japanese: Ruth is an American nisei living in Canada, and Nao thinks of Sunnyvale, California as home and her life in Tokyo as an exile imposed by her parents’ failure. This is quite good. The book ends with a quantum-mechanically flavored coda that is less so, but even that is redeemed by the cat Pest — short for Pesto, which is itself an informal name because the cat’s proper name is Schrödinger.