Monday, October 27, 2008

Control flow has to flow

I've been experiencing MSBuild a bit at work lately. Ignoring the mortal sin of designing a programming language within XML, MSBuild fails on usability in another fundamental way: control flow has to flow.

In general, there are two key approaches to control flow. One approach is the procedural "jump" approach. This includes all manner of conditionals, loops, and more. The second approach involves functional constructs such as recursion and pattern matching. These two approaches are blended between procedural languages, functional languages, and everything in between and beyond.

The procedural and functional mechanisms can be visualized in terms of forwards and backwards. Procedural control flow moves forward in a set of steps which are executed in sequence as defined. Functional control flow begins with the results and works its way backwards pulling in bits and pieces of data and logic along the way.

MSBuild uses neither of these approaches.

That was a lie. MSBuild targets are executed in sequence procedurally. Targets can also define a "DependsOn" property which works functionally to pull in pre-requisite tasks.

The truth is that MSBuild introduces (and often requires) a very unwelcome third approach in the form of the "Condition" property. The "Condition" property is neither forwards nor backwards control flow. It is sideways. MSBuild has only one global namespace of variables (confusingly, called "properties") which can be set or tested at any time.

With either a procedural or functional control flow, you can follow the flow simply in your text editor. In a structured procedure, you simply trace through the code pretending to be a human interpreter. In a functional method or when dealing with sub-procedures, you can trivially search for functions by name as they are called. In a sense, you are "flowing" through the code like water though pipes. You are either pumping water through or sucking water out.

With sideways control flow, you need to search for all references to every variable at every step of both the procedural and functional code flows. This is a laborious and error prone operation because after finding a relevant "Condition", you need to analyze the forward and backwards control flow which led to that sideways control flow construct! There is no "flowing".

Sideways control flow leads to a combinatorial explosion of forward and backwards control flows. The developer is required to keep all of these flows in their heads all at once. Meanwhile, in normal programming languages, you only ever need to deal with just two: forwards and backwards.

Saturday, October 18, 2008

Blog resolution

I now have 9 blog subscribers. Maybe more; if you aren't subscribed to the http://feeds.feedburner.com/YouWorkForMeComputer version, please switch. However, my site gets a pretty strong amount of other traffic from recruiters and other people more interested in my resume than my blog. While feed subscribers never even see the site in its original layout, about 65% of the visitors have a horizontal resolution greater than or equal to 800 pixels. The previous template was designed for a minimum horizontal resolution of 600 pixels, so I added another 100 pixels to the content.

I've been writing my posts with Windows Live Writer. It has a really cool feature:

  1. "Weblog" menu
  2. "Edit Weblog Settings..." menu item
  3. "Editing" tab
  4. "Update Style" button

Poof! Now my editor has the same width as my Blogger template. I love it when software just works. Now if only it could do this automatically...

Wednesday, October 8, 2008

Imaginary APIs

As promised, I will explain my favorite way to design APIs. I call it "imaginary APIs". I'm far from the first person to have thought of this and I've even seen this used in practice by others, but I have no idea if there is an official name for it.

In short, the idea is that you write code which consumes an imaginary API and then you imagine it works. Then, you write some more code which does something more advanced with your imaginary API and imagine that works too. Then, you figure out how to get your magic code to compile, run, and work.

Success is measured by how closely your final API matches your imaginary API and by how much you need to modify your magic code to be a working sample you can ship along side your API. That's it.

As an example, let's imagine an API for a simple home movie editing program. I'm sorry about the excessively narrow blog formatting.

var video = new Movie();
video.StoryBoard.Add(
    Image.FromFile("title.png"));
video.StoryBoard.Add(
    new CrossFade(TimeSpan.FromSeconds(2.0f)));
video.StoryBoard.Add(
    Movie.FromFile("scene1.wmv"));
video.StoryBoard.Add(
    Movie.FromFile("scene2.mpg"));
view.Export("myMovie.avi");

A lot of voodoo is going to have to happen behind the scenes to make this code work! But you will thank yourself when you need to implement the UI and your client developers will thank you when they want to generate a highly customized slide show or develop a nifty flip book drawing application.

Simplicity belongs in APIs

In a recent post, I insisted that flexibility belongs in APIs.

Simplicity also belongs in APIs. There are two key things to simplify in APIs:

  1. Common tasks
  2. Surface area

Since you are jam packing your APIs with loads of powerful and flexible functionality, you might have a lot of functions and data structures. You may have hoards of configuration options or capability bits, and a whole mess of operations to perform on them. However, your UI is simple and has picked sensible defaults for every operation. Your APIs should do the same. In fact, you should expose the same simplified operations your UI uses, so that developers do not have to guess what arguments you pass or in what sequence you composed more complex operations. If you have widely used private helper methods you've developed for your UI, you have identified common tasks for which your helper methods should be promoted to address.

Developers should be unwittingly led to these simplified methods, they shouldn't have to go searching for them. For example, the .NET framework provides a significant number of overloads for opening a file. The most complex of these takes parameters for file access, sharing, creation behavior, and more. The simplest of these, just opens the damn file. Reading all of the lines from a file is a very common task that involves opening a file, iterating over each line, accumulating the results, and then closing the file. Or you could just call File.ReadAllLines and simply get an array of strings.

ReadAllLines is completely implemented using StreamReader, but was placed in the File class instead of the StreamReader class because more people would look there first. Organize APIs by how naive developers would look for them, not experts. Remember that developers, like users, are perpetual intermediates. They are only going to write the code once (as beginners) and iterate on it several times until it works (as intermediates). After that, they will move on to something else. There won't be time for them to become experts.

You have limited screen space for features, but you can always fit more APIs. You develop a great UI by removing the features you don't need. However, you develop a great API by avoiding adding the features that you don't need. If there is a set of parameters no one will ever change, remove the ability to change them. If there are multiple approaches to the same common task, choose one. The less surface area there is for a developer to search and understand, the more likely they are to quickly choose and implement an effective solution.

In a future post, I will discuss my favorite technique for designing APIs.