Tuesday, 26 October 2010

“If It Ain’t Broke…” – Why Good Code Doesn’t Guarantee Happy Users

For years, my sites have processed online payments using a firm called Protx. Protx was acquired by Sage a few years back, rebranded as SagePay, and since then they’ve been gradually making changes to their system. Nothing too drastic, and always with plenty of warning that changes were forthcoming. Their payment API is generally rock-solid, powerful, flexible, and has allowed us to cope with all sorts of complicated payment scenarios without too much fuss. So far, so good.

About two months ago, SagePay announced that a preview of their new administration system was running on their test servers, and they invited everyone to go and have a look. Their old system was kinda clunky, but it worked, and our accounts team had been using it quite happily for years. So I went. I looked. The new system had lots of shiny Ajax – OK, fair enough, this is 2010 after all – and horizontal scrolling. No, really. Check this out:

image

I have a 1600x1200 screen because information is nice and I like being able to see lots of it at once. SagePay have decided, for some reason, that our payment transactions are best presented in a fixed-size 800x300px window. Fine, though – that’s what technical previews are for; lots of people reported this and were assured it would be sorted out.

Well, the new system launched over the weekend. The old one’s gone, they new one’s live, and things like the horizontal scrolling have not been sorted out, and customers’ reactions to the new system is, well, a bit negative. A thread on their support forums is full of gems like:

“A complete crock. Should be rolled back immediately.” (here)

“this is a complete and utter disaster. This system needs rolling back as soon as possible.” (here)

“The system is rejecting the order because of the delivery address line 2 which contained 58-62 xxxxx Road. Your system is not allowing the - in the address (This address is the formatted address that is provided by Royal Mail). This needs to be fixed quickly.” (here)

OK, fine. Big company rolls out shiny upgrade, upsets customers, breaks APIs, whatever. Big companies do this a lot.

But - I was expecting something different here, because last week, Mat – SagePay’s “Chief Nerd” – published this blog post, in which he talks quite rationally and eloquently about the forthcoming improvements. And it sounds like quite good stuff, too. Stuff like:

“To accommodate this we’ve changed the way we develop software, not only at a language level, but also at the process level. We’ve embraced Test Driven Development, Agile methodologies, Continuous Integration and parallel automated testing.”

“By writing tests first and only then writing code that passes those tests, we know that our software does what it is supposed to.”

“This demonstrable code quality gives our developers much more confidence in their code, frees them to refactor software that behaves sub-optimally, and ensures the test team’s time isn’t wasted on trivial bugs.”

(quotes from http://sagepay.wordpress.com/2010/10/18/if-it-aint-broke/)

OK. Mat, I believe you. If your comments about improved capacity and security are correct, this actually represents a huge achievement for the SagePay technical team. But I have to wonder… if the chief nerd is doing everything right, why are the customers so upset?

…if the CHIEF NERD is doing EVERYTHING RIGHT, why are the CUSTOMERS so UPSET?

Well, this is what I think probably happened. First, they’ve been working on this since June 2009; the first real customer preview was in September 2010, and it’s now live, just over a month later. That’s not a series of incremental improvements, delivering discrete chunks of business value every few weeks or months. That’s the big rewrite wearing a not-very-convincing Agile disguise.

Second, one of the core tenets of the Agile Manifesto is “customer collaboration over contract negotiation” – and to get that right, you have to know who your customers really are, and collaborate with them. This is tricky, because to the developers, the ‘customer’ is probably the product owner – but in reality, the customer should be the person who’s going to use the product. Now a firm like SagePay probably can’t call their customers in off the street to collaborate on a big project like this – but they can talk to them.

I think it’s really easy – particularly in big companies – to get this wrong. I have made this mistake many times. You launch version 1.0, you spend a couple of years babysitting it and fielding the support calls, and you end up thinking you know exactly what all your customers want… except you don’t, because happy users never call support. They just use the product and get on with their lives. You can find out what your users don’t like from support calls and complaints, but to find out what they do like, you need to get out there and talk to them, and it sounds like many of the features in the new SagePay system were based on feature requests and complaints from a vocal minority who weren’t really representative of the user base as a whole. It feels like you’re doing exactly the right thing, but you’re not actually collaborating with your customer.

REAL USERS don't care about TEST DATA

Second – technical previews are all very well, but real users don’t care about test data. When I tried the new MySagePay tech preview last month, it had about a dozen transactions in it, from 2006, when we were testing an upgrade to our own payment system. That’s not a realistic test of the system, because there’s no incentive to actually do anything with it. The only way to get real feedback is to get real people to use the software to do real work – and you can’t do real work with test data.

In a nutshell, I think they missed the distinction between have we built the right thing? and have we built it right?

TDD, Continuous Integration, refactoring – these will all help you build it right, but agile’s also about making sure you’re building the right thing, and I think SagePay dropped the ball on this one.

Mat even says (my emphasis)

“…any change introduces risk. … We might produce new software that performs identically to the old for a given payment protocol only to find that two thousand customers are using a non-documented “feature” of the old system that we’ve now written out."

Well, that’s exactly what they did. On Friday, https://live.sagepay.com/txstatus/txstatus.asp was returning real-time status for previous payment transactions – and now it’s gone. Vanished. What used to take 500 milliseconds in an automated script now takes a real person 90 seconds or so – including all that lovely horizontal scrolling they have to do.

Still, all is not lost. If SagePay really do have a clean, new architecture, and full test coverage, and a decent agile process in place, then it should be straightforward to respond to this customer feedback. Respond to change, instead of following a plan. Some tweaks at the presentation layer, maybe a couple of new properties on various view models and controllers, a handful of new methods on the supporting services, and it shouldn’t take long to deliver a product that combines the scalability and security of the new system with a UI that does exactly what the customers need.

Mat, if you’re reading this, I’d be really interested to hear what you guys did today whilst your support team were running around putting out fires. I’d love to see what your product backlog looks like right now, and how you’re condensing the torrent of feedback into user stories and work items. There’s not enough sharing in this industry, and if you and your team can be as open about things today as you were this time last week, you’ll probably help a whole bunch of people – including me – next time we find ourselves babysitting a tricky launch.

Tuesday, 12 October 2010

Making iPhone Ringtones using Free Software

iPhone ringtones are audio files encoded as MPEG-4 audio and then renamed to use the .m4r file extension. If you don’t fancy paying £0.99 for twelve seconds of Van Halen, it’s pretty easy to roll your own iPhone ringtones, and you don’t even need to buy any software to do it. You’ll need:

  1. An MP3 or WAV file of the song or noise that you want to use as a ringtone. Legally speaking, this could constitute copyright infringement, so please stick to original recordings or your own compositions.
  2. Audacity, an open-source audio editor.
  3. ffmpeg, the most awesomely powerful audio/video encoder ever. You’ll want to grab one of the Windows binary builds from http://ffmpeg.arrozcru.org/autobuilds/

Aside: I normally have Cygwin, a set of command-line Unix-a-like tools for Windows, installed into c:\windows\cygwin\, and then I add C:\Windows\cygwin\bin\ to my system path. Having done this, it makes sense to install ffmpeg into c:\windows\cygwin\ – it uses the same bin/doc/lib structure as most *nix ports and means I don’t have to add another path to the system PATH variable.

Import your source track into Audacity. Highlight the section you want, copy it, Ctrl-N to get a new file, and paste it.

image

At this point, playing around with effects might be worthwhile – especially selecting the first 1-2 seconds of the clip and using Effect –> Fade In, and applying a corresponding Fade out to the last second or two of your clip. When you’re happy with it, File –> Export as WAV…, and put it somewhere useful – D:\Ringtones\ works for me.

Then you’ll want to turn your WAV into an m4a file:

D:\ringtones>ffmpeg –i ringtone.wav ringtone.m4a

Input #0, wav, from 'ringtone.wav':
  Duration: 00:00:40.30, bitrate: 1411 kb/s
    Stream #0.0: Audio: pcm_s16le, 44100 Hz, 2 channels, s16, 1411 kb/s
Output #0, ipod, to 'ringtone.m4a':
  Metadata:
    encoder         : Lavf52.80.0
    Stream #0.0: Audio: aac, 44100 Hz, 2 channels, s16, 64 kb/s
Stream mapping:
  Stream #0.0 -> #0.0
Press [q] to stop encoding
size=     427kB time=40.31 bitrate=  86.8kbits/s
video:0kB audio:413kB global headers:0kB muxing overhead 3.447648%

Once you’re done, either via Windows Explorer or the rename command, rename ringtone.m4a to ringtone.m4r

Finally, fire up iTunes, go to File –> Add File to Library… and browse to your new ringtone.m4r file. After a moment’s processing, it should show up in the Ringtones library. Right-click, Get Info… and you can replace the name, title, artist and so on, and then you just need to make sure it’s selected under the Ringtones heading on your iPhone sync menu.

image

Happy ringtoning.

Thursday, 7 October 2010

/usr/bin/tawgo: Instant Geek Cred for Movies

I had this idea a while back. You know how computers in movies are always some sort of interactive brightly-coloured touch-screen kind of deal? (“It’s a UNIX system… I know this!”) And you remember how excited all us techno-geeks got when Trinity used sshnuke to crack the security mainframe system in The Matrix Reloaded?

There should be a utility /usr/bin/tawgo, that’s standard on all Linux distros, that combines the audience appeal of brightly coloured cheese with the geek cred of technical accuracy.

[[email protected]]$ cd /home/dr.evil/
[[email protected]]$ tawgo "PREPARING TO COPY SECRET FILES..."
[[email protected]]$ cp -Rf * /mnt/floppy
[[email protected]]$ tawgo "SECRET FILES COPIED"

[[email protected]]$ tawgo --help

tawgo: Tell Audience What's Going On

Usage: tawgo [option] MESSAGE

Displays MESSAGE in big bright coloured letters, probably in some sort of futuristic animated dialog box.

-a --animation Show cheesy animation
-w --warning Use yellow & black warning stripes
-s --self-destruct Initiate fake countdown sequence
-v --voice Reads MESSAGE in a Female Computer Voice

Use -v -s if you need Female Computer Voice counting down the seconds to our hero's impending destruction.

[[email protected]]$ tawgo "INITIATING SATELLITE ALIGNMENT"
[[email protected]]$ /usr/sbin/comsatctl -a --lat=53.47.6 --lon=1.29.2
[[email protected]]$ tawgo "SATELLITE ALIGNED."
[[email protected]]$ tawgo "BEGINNING FIRING SEQUENCE"
[[email protected]]$ /usr/sbin/comsatctl --target 01 --fire
[[email protected]]$ tawgo -v -s "FIRING SEQUENCE INITIATED"

They’d save a fortune on expensive UI mockups, too – all you’d need is a laptop running Ubuntu and you’ve got your snazzy whizz-bang computer hacker Unix system all ready to go.

Monday, 4 October 2010

Stap Me Vitals! Scanning Domain Servers using System.Management

In preparation for an overhaul of our hosting & network infrastructure, I wanted to gather stats on exactly what was running on all of our remote servers – stuff like how much physical memory is installed in the servers, what’s their Dell service tag, what sort of disk interface they’re running, what version of Windows, that kind of thing.

There’s a command-line tool wmic.exe that does some neat stuff by hooking into the Windows Management Instrumentation interface – little snippets like:

C:\>wmic /node:myserver cpu get description
Description

Intel(R) Xeon(TM) CPU 3.00GHz
Intel(R) Xeon(TM) CPU 3.00GHz
Intel(R) Xeon(TM) CPU 3.00GHz
Intel(R) Xeon(TM) CPU 3.00GHz


C:\>


- hey, looks like myserver has a quad-core Xeon CPU. Nice. Anyway, doing this across a whole lot of properties on a whole lot of servers would clearly be a bit time-consuming, so here’s how you do it using C# and the System.Management namespaces. The username/password specified in ConnectionOptions will need admin rights on all the servers you’re connecting to, and I’m sure you can reformat the output to look a little nicer, but in a nutshell, this’ll scan all the servers you specify and dump their vitals into D:\servers.txt

Simple - especially once you work out that the documentation for the cryptic magic strings inside the WMI classes is at http://msdn.microsoft.com/en-us/library/aa394084.aspx

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;

namespace ServerScan {
class Program {

static string[] servers = new string[] { "hugh", "pugh", "barney", "mcgrew", "cuthbert", "dibble", "grubb"};

// see notes on Win32 instrumentation classes at http://msdn.microsoft.com/en-us/library/aa394084.aspx

static Dictionary<string, string[]> wmiClasses = new Dictionary<string, string[]> {
{ "Win32_DiskDrive", new string[] { "Caption", "InterfaceType", "MediaType", "Name","Size" }},
{ "Win32_PhysicalMedia", new string[] { "Tag", "SerialNumber" }},
{ "Win32_Processor", new string[] { "Name", "ExtClock", "CurrentClockSpeed", "DeviceID" }},
{ "Win32_OperatingSystem", new string[] { "Name", "CSDVersion", "Description", "TotalVisibleMemorySize" }},
{ "Win32_SCSIController", new string[] { "Caption" }},
{ "Win32_SystemEnclosure", new string[] { "SerialNumber" }}
};

static void Main(string[] args) {
var output = File.CreateText(@"D:\servers.csv");
ConnectionOptions options = new ConnectionOptions();
options.Username = @"MYDOMAIN\big.boss";
options.Password = "password";
foreach (var server in servers) {
ManagementScope scope = new ManagementScope(String.Format(@"\\{0}\root\cimv2", server), options);
try {
scope.Connect();
foreach (var wmiClass in wmiClasses.Keys) {
ObjectQuery query = new ObjectQuery("SELECT * FROM " + wmiClass);
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
Console.WriteLine("Searching {0} / {1}", server, wmiClass);
var results = searcher.Get();
foreach (var result in results) {
foreach (var thing in result.Properties) {
if (!wmiClasses[wmiClass].Contains(thing.Name)) continue;
output.Write(String.Format("{0}\t{1}\t{2}\t{3}\r\n", server, wmiClass, thing.Name, thing.Value));
}
}
}
} catch (Exception ex) {
output.Write(String.Format("{0}\tFAIL\tFAIL\t{1}\r\n", server, ex.Message));
}
}
output.Close();
Console.Write("All Done!");
Console.ReadKey(false);
}
}
}