June 30th, 2009

How to close the last/only tab in Firefox 3.5

I recently downloaded the latest version of Firefox. I quickly discovered that one of my favourite features was gone: the ability to close a tab if it’s the only one open.

It’s turns out that the feature isn’t missing; it’s just no longer enabled by default. I figure this makes sense since a fresh install of Firefox doesn’t show the tab bar at all for a single tab.

Anyway, here’s how to do it, just so you don’t have to mess around with the options to figure it out (like I did):

  1. Open about:config.
  2. Set browser.tabs.closeWindowWithLastTab to false.

And that’s all! Back to “normal” browsing.

EDIT: Looks like I beat mozillaZine by about 4 minutes on this. They now have a longish thread discussing this exact topic.


June 15th, 2009

Trusting your users is hard for you, but good for them

Default options should handle the most common, most trustworthy case.

Gmail got this right when they stopped asking for confirmation for users’ actions and started allowing people to undo them instead. For example, most of the time, I am sure I want to trash that email, so asking for confirmation does nothing for me; my default action becomes to auto-confirm, since it’s what I do most often. Ben Bryant has a good post on how trust worked for Wikipedia. And to quote Neil Davidson on avoiding the real problems: “don’t create rules for the many based on the sins of the few”.

When I built the prototype for my cell phone bill analysis tool, I had an item pretty high up on my list of core features to have user accounts. When I got to it, I titled my head to the side and let out a nice long “Hehhhnh?”. I realized that it really didn’t make much sense. If people wanted to come and try the service, having to register is a pain. I can’t even list off the apps – free or paid – that I wanted to try out once but stopped when I had to register. Seriously, it’s really, really annoying.

So I asked myself: “Why would people want to register?” The only answer I could come up with was that they might want to save their bill data and come back at a later date. I figured most people wouldn’t be doing that to start, so user accounts dropped priority, and I instead implemented persistent temporary sessions.

Apparently, Jason Kester feels the same way:

Our stated goal with Twiddla is to get the hell out of your way so that you can get some work done. We’ve taken that idea so far that most of our users will never see a login screen of any description. Some might not ever know they’ve used Twiddla at all, since we keep our Logo hidden away in the corner where it’s not in your way.

That’s a great example of putting your users first. Registering for a service is a hassle for the user, and that’s something that’s true whether there’s a good reason for registration or not. Maybe capturing a user’s information helps you market to them down the line. Maybe capturing a user’s information makes it easier for you to personalize the user’s experience. It doesn’t matter though. It’s not your choice. It’s the user’s choice.

And respecting a user’s choice is a great way to get them to trust you with their money.


May 13th, 2009

Grow great software without getting hung up on the past

Smart people can invent solutions to problems you don’t actually have yet. The problem is, it’s easy to think of problems you don’t have yet. Stopping to solve them all now is a recipe for paralysis.

– Chip Morningstar, Smart People Can Rationalize Anything

It’s important to focus on your current difficulties. And if you don’t have a current difficulty, pick any of your brilliant ideas and solve them in the simplest way first: with people. At MeshU 2008, Reg Braithwaite left me with some great advice: don’t automate what you haven’t already done manually.* And I hope I got that right, because he’s a smart guy and it’s stuck in my head now. What I do know is that anything you can do to increase your understanding of a problem will help you formulate a solution.

But you can’t really solve problems that don’t exist, because only problems have solutions – the absence of a problem necessarily implies the absence of a solution. Still, even the simple solutions of simple problems tend to reveal additional simple problems.

Here’s a real-world example. Several years ago, our developers designed a poll widget for our web sites. At the time, they didn’t have a semblance of the manual effort required to insert/delete/update poll questions. So they didn’t solve for it, and this was a very rational decision at the time, because it wasn’t a problem then. However, when it did manifest itself as a problem, they didn’t update their assessment of possible solutions. That’s why it’s important to revisit solutions over the course of their respective lifetimes; you need to keep solving the right problems while throwing away inadequate solutions.

It’s easy to neglect old features in light of new and exciting features, but that’s how projects get bloated to the point where anyone who touches the code gets this burning desire to just rebuild it from scratch.

Most people have a vague notion of the fact that new things get old, but it’s much rarer to find people who understand that old things get older. It’s not a binary definition. Things aren’t either “new” or “old”. They have an age that can be measured in continuous time units. It’s like how on the books, your company’s assets depreciate in value over time. That new widget-maker your company bought last week will have a different value after 4, 8, 15, 16, 23, or 42 years. Eventually, the machine itself has no value, but at any point in the interim, it does still have at least a bit. Accountants will take a look every year during tax season and advise the appropriate stakeholders that they need a new way to make widgets. And so a new machine will be purchased and the cycle starts all over again.

Software is the same way. Certain features depreciate in value over time. Eventually, those features no longer deliver any value to the end user and they become more costly to maintain than the benefit they deliver. That’s probably one of the best reasons to use a modular design that won’t break when you remove a feature.

But every project team has a different way of determining when a feature or product has lost its value. Ruby on Rails decided to *6 components after two years. On the other hand, Intel went on adding new features to their x86 chip for over 30 years without removing extraneous features: “The 8086 was the first processor in the x86 family launched in mid 1978. All future members are backwards compatible with it.”

Features – and feature sets – are built up from an initial spec, regardless of what methodology you follow. What’s important is how often you revisit those initial specs and evaluate them in light of the true, underlying requirements of the project, which themselves necessarily change over time. I agree with Jurgen Appelo’s concept of growing software rather than building it, although he’s wise enough to not claim it as his own :oP.

We also talk about building software. And (in many cases) that’s incorrect too. What we build are lines of code, design documents, and compiled assemblies. What we grow are user interaction, data repositories, social networks, and (for the systems that I grow myself) extensive bug reports and issues. The sum of all that we call software systems.

Software is versatile. It’s programmable (of course). More importantly, it’s re-programmable. To throw away that kind of power by refusing to acknowledge the changing nature of users, developers, business owners, and all their various, also-changing needs is a fruitless exercise.

Focus on delivering the best possible value regardless of your past decisions.

*The corollary is that if you’ve done something manually and it can be automated, do so.


April 23rd, 2009

Using a Database to Store Transient Data

The Motivation

Off The Clock was initially built to analyze phone usage data pulled from a database, but I also wanted to be able to analyze transient data. Storing transient data in a database is a great way to accomplish this; it allows the analysis code to treat the transient data as if it were persistent, thus avoiding unnecessary duplication of code and providing a consistent analysis.

The Solution

Storing transient data in a database was actually quite simple. The first step was to understand that all of the user’s data has to be tied to their current session. For all intents and purposes, their current session acts like their “account”. That means that the session id works as the account id.

With that in mind, I decided that until a user registered, I could just store the relevant data in the database along with the current session id, which is automatically generated by PHP.

So, the corresponding table would look a little like this:


CREATE TABLE usage_data
(
  call_id INT NOT NULL PRIMARY KEY,
  call_duration INT,
  the_session_id TEXT
)

And I can insert the values into the database like this:


mysql_query("
  INSERT INTO usage_data
    (call_duration, the_session_id)
  VALUES (" .
    "'" . $callDuration . "'," .
    "'" . session_id() . "'" .
  ")"
);

With this design, the code that fetches and reports on the data can match the current session id with the session id in the database, and report only on data uploaded during the current session. Every query just needs to have the current session id in the WHERE clause, like so:


mysql_query("
  SELECT call_duration
  FROM usage_data
  WHERE the_session_id = '" . session_id() . "'" .
  ")"
);

As an extra bonus, the code doesn’t need to be duplicated and re-written to report on one-off data sets. This also means that a user can upload and get stats and multiple phone bills within a single session, without having to register for an account.

In short, this allows the tool to function in demo mode by default.

The next step is to determine the best way to allow a user to save their session by way of registering for an account.


April 15th, 2009

Choose a Language by Focusing on the Right Information

One of our development team’s current projects is porting our network of web sites from Classic ASP to ASP.NET. All of our current scripts and apps are written in VBScript (of course), so when switching to ASP.NET, the obvious choice is to use VB.NET, right? I mean, our team already knows VB syntax and, as a result, we would find it easier to learn VB.NET.

That’s not the only thing that matters, though. The architectural differences between Classic ASP and ASP.NET are significant enough that knowing VBScript won’t in itself help us be great VB.NET programmers.

VB.NET and C# actually share a lot of similarities in terms of functionality. But that doesn’t make them identical. Google Trends tells me that, as of this writing, C# is 3 times as popular as VB.NET. That translates into a lot of extra resources for learning and troubleshooting.

This signifies the importance of hiring versatile programmers over <insert language here> programmers. With the latter, then you almost have no choice when refactoring; you’re pretty much forced to use the same (or similar) language, even when another language may be more appropriate for the job at hand.

The hard part about picking a language for a new project is determining if it’s the right tool for the job. That means figuring out the advantages and disadvantages of each reasonable option.

In our case, picking VB.NET over C# means that our team will probably be able to pick it up faster, and we’ll be able to get a version of our system released sooner. Yes that’s a very clear benefit, but using that as the only criterion to make the decision is akin to taking a loan without first asking about the rate of interest.

According to Alan Kay, the right language alone is not good enough. Language adoption is almost as much about marketing as anything else.

Let’s say the adoption of programming languages has very often been somewhat accidental, and the emphasis has very often been on how easy it is to implement the programming language rather than on its actual merits and features. For instance, Basic would never have surfaced because there was always a language better than Basic for that purpose. That language was Joss, which predated Basic and was beautiful. But Basic happened to be on a GE timesharing system that was done by Dartmouth, and when GE decided to franchise that, it started spreading Basic around just because it was there, not because it had any intrinsic merits whatsoever.

PHP is another example. I was talking to a former colleague of mine the other day about how easy it was for me to just start coding with PHP. Because it’s such a popular language, there are a lot of libraries and resources out there to do a wide variety of simple things. Its popularity also results in the community around it growing even further, which some might classify as progress:

There was a tight feedback loop as increased knowledge enabled us to discover and manufacture more tools, and these tools allowed us to discover and learn more knowledge, and both the tools and knowledge made our lives easier and longer. The general enlargement of knowledge and comfort and choices – and the sense of well-being – was called progress.

Of course, PHP gets its fair share of detractors, so I’m not even going to try to argue about how great or terrible it is. All I’m saying is that the fact that it’s popular carries with it some benefit.

Picking a popular language can reduce time to market, which may determine whether or not the project will succeed, while picking a maintainable language may allow you to last longer in that market. But if you pick too niche a language, it will be hard to find programmers who are skilled in that development environment, which means you’ll end up paying more for experience and/or training.

This is a decision that needs to be made at the project level, and it needs to be made with the right information, not just the information that’s easiest to gather.