The Lurker

Latest posts | Archive

posted by ajf on 2019-02-09 at 10:21 pm

In this post about TDD Rhyno van der Sluijs describes a system of two major components, and contrasts the experience of their parallel development:

I split this system into two major components: the rules engine, which performed the calculations, and the export engine, which exported the results to the payroll team. The rules engine was a hit. It was so fluid and fully unit tested. However, the export system barely held together despite also being fully unit tested.

This sounds like a kind of natural experiment that we rarely get the opportunity to think about. But the two components lent themselves to very different testing approaches:

[T]he successful and fluid rules engine had unit tests only testing the main public method. All the hundreds of tests in this area would interact with this single entry method despite their being many other helper methods in the process. However, the export engine was strictly run TDD on every single public method including smaller helper methods in different classes.

A while back, I worked on a system that had a problem a little like this: a query returns two sets of results (exact matches and approximate matches). The core of the task was to take those two lists and concatenate them to produce the final result list.

interface Flattener {
  List<Result> flatten(List<Result> exactMatches, List<Result> approximateMatches);
}

The first unit test for this would be very simple. We can create two input lists, and check that the output contains all the items from the first list in order, followed by all of the items in the second list. If we're feeling particularly conscientious, we can add a few more tests:

So far, our tests look a little like the rules engine tests from the blog post: they all just call one method on the Flattener and check for the expected output.

But we had other content that needed to be inserted between the results. Advertisement cells would be placed at regular intervals. We introduced the idea of an "item" here. So far, we had Result Items and Ad Items.

We could have very easily added code to the Flattener implementation, and updated the interface to work with Items. But another developer, recognising that there were several more rules like this still to come, had a better idea. We introduced the idea of an "Injection", which is an object that knows what positions in the result list it would like to place items. As the Flattener inserts each Result Item into the output, it invokes each Injection, providing the number of Result Items inserted so far.

interface Injection {
  List<Item> injectAfterIndex(int index);
}
interface Flattener {
  List<Item> flatten(List<Item> exactMatches, List<Item> approximateMatches);
}
class InjectingFlattener {
  private List<Injection> injections;

  InjectingFlattener(List<Injection> injections) {
    this.injections = injections;
  }
  public List<Item> flatten(List<Item> exactMatches, List<Item> approximateMatches) {
    int count = 0;
    List<Item> output = new ArrayList<>();
    for (Item item: exactMatches) {
      output.add(item);
      count++;
      injectAfter(output, count);
    }
    for (Item item: approximateMatches) {
      output.add(item);
      count++;
      injectAfter(output, count);
    }
    return output;
  }
  private void injectAfter(List<Item> output, int index) {
    for (Injection injection: injections) {
      output.addAll(injection.injectAfterIndex(index));
    }
  }
}

Now at this point, we can insert Ad Items at regular intervals between Result Items, by implementing the Injection interface. But how do we test it?

We don't want to end up in the same place as this project:

The payroll export unit tests on the other hand, were written very strict TDD on ALL public helper methods that were only used by higher public methods. As requirements changed or became clearer, it was cumbersome to change the internal implementation as we would break hundreds of units tests, which meant time and effort to fix. We were reluctant to refactor and clean up our code, leading to an accumulation of technical debt that slowed down development in this area.

The blog post goes on to explain that the Time Calculation process relied on Holiday Calculation, Weekend Calculation, and Overtime Calculation components. A change to the Holiday Calculation logic would break its own unit test, which is good—but it would break Time Calculation tests too.

How did we avoid falling into this trap?

Our InjectingFlattener does not directly depend on the AdInjection class, nor will it need to change when we add new Injections. So our unit test for InjectingFlattener won't break if AdInjection's behaviour changes.

In each InjectingFlattener test, we can supply Injections with whatever behaviour we need to create the scenario we want to check. It's a little fiddly, though, because we have to create a list of injections in each test.

We can see that InjectingFlattener's injectAfter private method has the same signature as Injection's method. We can extract it (and the List<Injection> it uses) into a new Injection implementation, CompositeInjection (named for the Gang of Four design pattern). Now our InjectingFlattener's constructor can receive a single Injection. That makes it that little bit easier to construct an InjectingFlattener instance in each test method, and what were complications in that unit test are untangled to become straightforward test cases in CompositeInjection's tests.

Trying to write a test has driven us towards a better design. (Sorry.)

As we tackled successive requirements, we sometimes added a new implementation of Injection, but other times we needed to make breaking changes to the interfaces.

We needed to inject an Item between the exact matches and the approximate matches—but not if the latter list was empty. We needed to inject a "no results" Item if both lists were empty. Here I proposed that we remove awareness of "exact" and "approximate" from the Flattener interface, and take a List of "Result Tiers" instead. (A Result Tier is itself just a list of Result Items.) Then we created a TierInjection interface:

interface TierInjection {
  List<Item> inject(List<Item> tier, int precedingResultCount);
}

In the end, we ended up with three layers of these injections:

Breaking it down into these pieces meant we had a lot of very small components, and it was very easy to write unit tests for them. But there's no free lunch. At each layer above the Item injections, we ensured there was no concrete knowledge of the layer below. But the concrete stuff has to happen somewhere.

The good news was that we had reduced this to "just wiring": it was a simple matter of constructing Item injections, aggregating them into Tier injections, and building those up through Query injections. But none of our unit tests covered any of the business rules. Those business rules have tricky corner cases, and it would be crazy not to have tests in place to catch regressions.

Enter the InjectionsBusinessRulesTest. (The overall design was a collaborative effort between maybe half a dozen developers, but that appalling test name is entirely my fault, and I accept full responsibility.) It works just like the "rules engine" tests: all of the tests invoked the top-level injection method in the same way, and this test didn't need to change when we changed details of how one injection layer interacted with the next.

Having this test meant that some changes would break two tests: if you change the behaviour of one of the low-level injections, you need to change its test, and you're changing the expected results in InjectionsBusinessRulesTest too. We did find that a little disappointing, but I think the trade off was worth it: most mistakes were caught by (and easily identified due to) the very simple unit tests, and we always found out when a change had unforeseen consequences.

We still had cases where test failures could "overlap", but it could only occur between InjectionsBusinessRulesTest and a unit test.

The conclusion that follows goes against the current norm: unit tests written on helper functions could be seen as a code smell. Do not write unit tests on helper methods as this ties you to an implementation.

I feel like this is the wrong conclusion to draw: it's not the unit test that "ties you to an implementation"—the pain happens because the class itself is tied to its helper methods' implementation.

I don't want to draw an overly-broad conclusion like "helper methods are an anti-pattern", but if a colleague came to me with the Time Calculation problem, I would suggest trying one or both of these ideas (and both of them would eliminate the idea of "helper methods" from this code):

When changing your tests hurts, you don't just need to change how you write tests. You need to change the code you're testing.

Related topics: Rants Mindless Link Propagation

posted by ajf on 2012-11-09 at 08:20 pm

Patrick Nielsen Hayden calls this perhaps the greatest flounce of all time. It's easily the best right-wing election freak-out I've seen so far:

I'm choosing another rather unique path; a personal boycott, if you will. Starting early this morning, I am going to un-friend every single individual on Facebook who voted for Obama, or I even suspect may have Democrat leanings. I will do the same in person. All family and friends, even close family and friends, who I know to be Democrats are hereby dead to me. I vow never to speak to them again for the rest of my life, or have any communications with them. They are in short, the enemies of liberty. They deserve nothing less than hatred and utter contempt.

I strongly urge all other libertarians to do the same. Are you married to someone who voted for Obama, have a girlfriend who voted 'O'. Divorce them. Break up with them without haste. Vow not to attend family functions, Thanksgiving dinner or Christmas for example, if there will be any family members in attendance who are Democrats.

Do you work for someone who voted for Obama? Quit your job. Co-workers who voted for Obama. Simply don't talk to them in the workplace, unless your boss instructs you too for work-related only purposes. Have clients who voted Democrat? Call them up this morning and tell them to take their business elsewhere.

Have a neighbor who votes for Obama? You could take a crap on their lawn. Then again, probably not a good idea since it would be technically illegal to do this.

[...]

When I'm at the Wal-mart or grocery story I typically pay with my debit card. On the pad it comes up, "EBT, Debit, Credit, Cash." I make it a point to say loudly to the check-out clerk, "EBT, what is that for?" She inevitably says, "it's government assistance." I respond, "Oh, you mean welfare? Great. I work for a living. I'm paying for my food with my own hard-earned dollars. And other people get their food for free." And I look around with disgust, making sure others in line have heard me.

Buttons. Boy, you can have a lot of fun with this. I plan to make up a bunch of buttons, and wear them around town, sayings like "Democrats are Communist Pigs," or "Welfare moochers steal from hard-working Americans," "Only Nazis support Seat Belt laws" or "No Smoking Ban: Nanny-Staters go Fuck Yourselves."

[...]

Hell, there were UNITED NATIONS POLL WATCHERS at our polling places yesterday. If that isn't proof enough how far we've gone towards the dark side of international socialism, I don't know what is.

[...]

I say we've got two to three years left before they start rounding up dissenters and sending us off to Nazi-style concentration camps. I've got a little more time, cause I live in Texas.

[...]

I disowned them this morning. On Facebook and through an email. But fortunately my parents are diehard Republicans, and a sister. It's only the fucked up brother in Delaware, piece of shit, scumbag mother fucker who is a Democrat, and another sister in Philly who won't tell me, but I'm almost certain voted for Obama.

They are dead to me now. And I will not under any circumstances attend their funerals in 30 or 40 years.

Harsh, but a reality.

I'm not sure this guy has much contact with reality.

Related topics: Politics Quote of the day Mindless Link Propagation

posted by ajf on 2012-05-03 at 07:20 pm

Via an ABC News story about Myki we learn from the CEO of Public Transport Victoria (which is not itself responsible for Myki):

"Any project like this that is as complex as this - you don't get everything right to start with," he says. [...] "Tweaking software is a dangerous thing to do on a project like this at this stage."

So, to summarise:

Related topics: Mindless Link Propagation News Rants Quote of the day

posted by ajf on 2011-11-25 at 07:55 pm

Most of the discussion of this criticism of Apache by Mikeal Rogers has focused on the merits of git, which I think misses the bigger picture.

To me the interesting thing here is that, never mind "community over code", Apache appears to value bureaucracy over community:

Prior to this migration I brought up, on more than one occasion, the topic of git with ASF members and was told that the real barrier was just getting someone to maintain the server and that there was no policy barrier that required subversion. Once a project actually attempted to resolve this by maintaing a server and migrating their project to git they were hit with a whole new slew of requirements along with reasons they should be outright denied the right at all.

[...]

After a fierce battle CouchDB has been allowed to begin the move to git. The process appears to be going well and is being led by committer Paul Davis.

Enter PhoneGap. The PhoneGap project has been on GitHub for quite a while and already contains an enviable list of contributors. The project has been very successful and the move to Apache is a result of Adobe's recent acquisition of Nitobi, creators of PhoneGap.

By ASF regulations the project must spend time in the "Incubator" even though it has already proven itself as a technology and as a community to the rest of the world. The project requested git as its version control rather than subversion, for obvious reasons. The request was met with some hostility and new pressure has now come down on the CouchDB "experiment".

It sounds awfully like some within Apache are more than willing to disrupt an existing developer community for the sake of shielding Apache from the need to adapt.

I've long been amused by the warning in Apache's Incubator process against "excessive fascination with the Apache brand", since so many of the projects going through that process seem to be driven there specifically by some corporate sponsor's desire to find someone "respectable" to catch their code once they throw it over the wall. When this happens, it seems more like spin: we're not abandoning this software, we're contributing it to a serious open source community. It's hard to see any tangible benefit to PhoneGap contributors or users that would derive from joining Apache.

Related topics: Mindless Link Propagation Rants

posted by ajf on 2011-02-11 at 05:05 pm

As I arrive at North Brighton station one of Metro's friendly staff announces that the next train will arrive in five minutes. Apparently I blacked out for a little while, because the train arrived before I'd even managed to open my newspaper — still, it's not as though I want to complain about not having to wait for a train.

The journey is uneventful until we pass through Richmond station. Incredibly, the train proceeds directly to Flinders Street without a lengthy delay somewhere near the arse end of Federation Square. I'm on platform 13 at about 8:36.

It's a short walk to platform 10, where the 8:40 Werribee train waits. Things are going really smoothly today.

But don't worry: we can fix that.

As I'm walking toward my carriage, the display goes blank. Another friendly microphone-wielding customer service gentleman confidently confirms that this is, indeed, the 8:40 Werribee train. He proves to be absolutely correct.

But not yet.

As I board, a different announcer declares that the 8:40 Werribee service will now depart from platform 12, right next to the train I'd left five minutes earlier. By the time I get back there, the train has arrived, and when I board the driver confirms that he is heading to Werribee — but not until network congestion clears up ahead. But before he can even finish saying so, yet another helpful Metro employee has boarded the train to share his idea about getting around the congestion: we can all, passengers and driver alike, get out of this train and return to the other one back on platform 10.

Oh, and as my train approaches Footscray station, it's starting to rain. If that train had left on time, I'd've been dry in the office by now.

Related topics: Rants

posted by ajf on 2010-12-05 at 09:08 pm

One of the highlights of YOW Melbourne last week was Guy Steele's talk on designing algorithms for parallelism. He started with a punch card he'd written in the 1960s, which dumped the machine's memory, walking us through the series of contortions required to load it as a debugging tool — limited to one card, self-modifying code to produce 16-bit values in memory which couldn't be read from the 12-bit card, determining experimentally that the hardware permitted "getting away with" certain undefined operations, reading from the address containing an instruction because that instruction happens to be represented by a useful number, and so on.

Steele called it the dirtiest code he had ever written. Not only did later hardware require fewer such "dirty tricks", tools that improve developer productivity — macro assemblers, garbage collection — sometimes take away the degree of control that made many of those tricks possible.

But then I stumbled on this interview from a couple of months ago compares things he has created with HTML5 to games developed for the Atari 2600 (my emphasis):

Something else that took me a while to internalize: you have to accept that with Web development, anything that's worth anything will be a hack. Not just prototyping; production code as well. That's hard to swallow when you're used to proper, clean, sterile programming. [...]

And eventually that battery of hacks in your sleeve might make you stand above. My crude and jaded metaphor of Web development is button mashing when playing video games. Everyone hates button mashers, but working with cutting-edge Web really is flying blind a lot of the time — you're trying out all sorts of things that sometimes don't logically make a lot of sense. But they somehow work. If you get used to that mentality and you get familiar with those hacks, you will train your instincts to know which buttons to mash first, and give yourself more buttons as well.

We're not talking about programs restricted to eighty 12-bit columns, we're not talking about getting the most out of early, primitive gaming hardware like the 2600, we're talking writing code for enormously complex machines, conforming to specifications that require millions of lines of code to implement.

Can somebody explain to me why the fuck this is considered acceptable?

Having spent the last eight months working with a team of people producing a high-quality applications for mobile phones, it really pounds home the feeling that writing a browser-based application is like tying your hands behind your back and trying to type with your molars.

Related topics: Web Mindless Link Propagation Rants

posted by ajf on 2010-11-03 at 12:01 am

And I thought I was cynical:

The markets want money for cocaine and prostitutes. I am deadly serious.

Most people don't realize that "the markets" are in reality 22-27 year old business school graduates, furiously concocting chaotic trading strategies on excel sheets and reporting to bosses perhaps 5 years senior to them. In addition, they generally possess the mentality and probably intelligence of junior cycle secondary school students. Without knowledge of these basic facts, nothing about the markets makes any sense — and with knowledge, everything does.

What the markets, bond and speculators, etc, want right now is for Ireland to give them a feel good feeling, nothing more.

[...]

In lieu of a proper budget, what the country can do — and what will work — is bribe senior ratings agencies owners and officials to give the country a better rating. Even a few millions spent on bumping up Ireland's rating would save millions and possibly save the country.

Bread and circuses for the masses; cocaine and prostitutes for the markets. This can be looked on a unethical obviously, but since the entire system is unethical, unprincipled and chaotic anyway, why not just exploit that fact to do some good for the nation instead of bankrupting it in an effort to buy new BMWs for unmarried 25 year olds.

Related topics: Mindless Link Propagation Politics Rants Quote of the day

posted by ajf on 2010-10-12 at 11:37 pm

John Gruber on the relationship between Telstra and Apple:

There you go. He thinks his carrier is Apple's customer. Thus the conflict, because Apple treats iPhone owners as its customers.

As Gruber would have it, the tension between phone carriers and Apple is over whether the carrier or the customer should have control over the device in that customer's pocket. That is disingenuous.

Here's the real bone of contention: Telstra believes Telstra should control what you can do with your iPhone. Apple believes Apple should control what you can do with your iPhone.

You'll notice the customer doesn't get the final word in either vision. Gruber doesn't care because he happens to think (like a hell of a lot of satisfied customers) that Apple's walled garden is particularly nice.

There's a very straightforward litmus test here: when Apple stops actively seeking to prevent iPhone users installing software other than via the iTunes Store, on that day they can be said to be putting the customer's interests first. Not before.

Related topics: Quote of the day Rants

posted by ajf on 2010-10-08 at 09:43 pm

National Novel Writing Month is imminent. I don't write fiction; for me, NaNoWriMo is just an annual reminder that I neglect my blog. I file this rant under "shit that is stupid that I cannot possibly hope to do anything about".

In an interesting discussion of decentralised addressing (as an alternative to centralised DNS), Daniel Kahn Gillmor writes (ending with slightly less emphasis than I'm using here):

If they're looking for John Smith because the word on the street is that John Smith is a good knitter and they need a pair of socks, they can just examine what information we each publish about ourselves, and decide on a sock-by-sock basis which of us best suits their needs.

But if they're looking for "John Smith" because their cousin said "hey, i know this guy John Smith. I think you would like to argue politics over a beer with him", then what matters is the introduction.

This is how HTTPS should work:

But this is how HTTPS actually works:

SSH uses a different method:

Supporters of HTTPS claim that their method is better, because even the first connection is verified by an organisation they trust. But consider the real risk that people actually face:

What you want is to know that, after a trusted introduction, that you are still communicating with the same party that you were introduced to. What HTTPS gives you instead is an assurance that the party is who they claim to be, even if that claim has been crafted to deceive you into thinking it is another party.

Related topics: Web Rants

posted by ajf on 2010-08-20 at 10:08 pm

One of the founders of the Australian Sex Party quotes advice he received from a prescient Don Chipp:

The first thing you've gotta give it a name that no-one forgets. And make sure to stay true to your core issues, which are censorship and personal freedom. In the years ahead, Labor and Liberal will desert that whole area because they're being increasingly infiltrated by church and morals groups and the Greens will probably go the same way as they get bigger and start to take on those kind of trappings. For the next twenty years Australia is going to need a really strong civil liberties party.

At the close of the most disappointing election campaign in my lifetime, I find the policies and priorities of the governing party and the opposition abhorrent and embarrassing. Instead of trying to inform the public and persuasively argue for what they stand for, today these parties are trying to guess what will appeal to the masses. I see no cause for hope that it will be different in the future.

I have given up.

Related topics: Politics Rants

All timestamps are Melbourne time.