Tuesday, March 27, 2012

API Usability Testing

I often joke that API hackathons are basically API usability testing days, the best opportunity for API teams to see first-hand how developers use their APIs and what problems they ran into - so when I was invited to an AT&T API day, I assumed it was another hackathon. But in fact, it was a formal "API Usability Day", structured and designed to find out what AT&T needs to do to improve their developer experience. It was the first I'd heard of any developer-facing company putting together something like that, so I was intrigued to check it out. (Okay, plus they rewarded us monetarily for our time spent).

The Subject Selection

Before being admitted a seat at the event, we answered a survey about our development experience, provided resumes, and gave links to our LinkedIn/Github profiles. The survey asked questions like what languages we usually develop in and what mobile platforms we've used. I imagine they used the survey information to ensure that developers had some degree of prerequisite knowledge and perhaps also to get a range of developers at the testing day. It was a small group, about 12 of us in all.

The API Tests

When we arrived at the AT&T lab, we were each given a seat, WiFi, and power outlets. We used our own laptops, as they wanted each of us to use our development environment of choice - to mimic what it'd be like if we were at home. We were given four packets describing tasks, and instructed to try to finish them without consulting eachother , and if we did, we were to invite them over so they could witness what we discussed. If we really ran into a stumbling block that we couldn't go over (like an API error that just wouldn't quit) we could invite the API engineer over, and they would take notes and videotape as we worked through it. The tasks increased in difficulty, so much so that we had 2 hours alotted for the last one and as far as I know, none of us get through it all.

Here's what we were tasked with:

  • Download the SDK
  • Use command-line to send and receive SMS
  • Built an oauth flow for authorizing a user and reporting their device location to them
  • Build a webapp for users to buy access to the app, then upload pictures via MMS and browse them in a gallery.

We used new APIs in each test, so that by the end, we were familiar with almost all of their API offerings and had seen most of their documentation. The final two tasks also involved us setting up a frontend (which we each did in our language of choice, like Python for me and Go for Anton) but we weren't meant to spend much time on that - just enough so that we could test how the API flow worked for a webapp.

The Evaluation

At the end of each task, we filled out a survey about how we did and what we thought. Some of the questions asked were:

  • Did you complete the task? How hard was it?
  • Would you recommend this API?
  • What would have made the experience of using the API better?
  • Whose API does it better than we do?

Then we had an AT&T engineer come and chat with us more, asking us to describe how we set about doing the task (like what documentation we tried to use and where we got misled), and just getting our general feedback on the experience. We also emailed a zip of our code to them, though I don't know what they'll be doing with that. Oh, and because I'm a documentation-aholic like that, I also took extensive notes in a Google Doc that I've shared with them over email.

We were quite solitary throughout this whole process, trying to solve the problems using our own approaches and encountering our own sets of problems. At the end of the day, we all came together for a 30 minute debriefing where we talked about our general impressions of the AT&T APIs and developer experience, comparing and contrasting them with other APIs, and explaining what would make us want to use or not use AT&T's offering. One of my own suggestions to them was for them to hold an internal hackathon using their competitor's APIs - so they could see for themselves how their experiences differed and learn from them (or steal, whatever works :).

The First of Many?

Product usability testing is now common place, and as APIs are increasingly becoming the product these days, I think that API usability testing like the AT&T day could become more than just a rarity, and I think they can come in many forms. The AT&T day was really about how easy it was to use their documentation — you could also have tests centering around how intuitive an API syntax is, how easy it is to find answers to common errors.

An API usability testing day should not be the only way for API companies to understand their developer experience, of course. API companies should still be learning as much as they can from looking at how developers use their site, what developers complain about in the forums, what features they request in the issue tracker, etc. And as Anton pointed out during the de-briefing, API companies should be using the APIs internally as much as possible and learning from their own experiences. But it's definitely an interesting way to put new developers under a microscope and zoom in on their experience.

If you're still reading... have you ever participated in or organized a day like this? If you have an API, is it something you'd do?

Saturday, March 24, 2012

Working around Android Webkit

I use PhoneGap to output the Android app for EatDifferent, and that means that my app runs inside an embedded Android browser. As I've discovered and re-discover everytime I work on a new version of the app, I am not the biggest fan of the Android browser. And that's an understatement.

Sure, the Android browser is Webkit-based, so it technically supports modern HTML5 elements and CSS3 rules, but in practice, the browser can sometimes struggle with rendering the new shiny CSS stuff, especially when some user interaction causes it to repaint the DOM. It's not just that the browser slows down — it actually fails to re-paint DOM nodes (or as I like to describe it, it "white outs" those nodes). When the whited-out nodes are my navigation menu or form buttons, then my app is rendered basically un-usable. That's a shitty user experience, of course, so as the developer, I want to do whatever I can to make sure that a user doesn't have to experience that.

Unfortunately, these white-outs are difficult to debug. When I run into one, I try to replicate it a few times (so I know what user interactions caused it), and then I start stripping out CSS rules until I can't reliably replicate it anymore. Since the glitches only happen on the device themselves (and not in Chrome, where I usually test CSS changes), I have to re-deploy the app everytime I test a change. Needless to say, it's a slow process. The white-outs are also impossible to programmatically test for, as far as I know, so there's nothing I can add to my test suite to guarantee that changes in my code haven't brought any back.

So, yeah, they suck. But they suck less when you know what to look for and what to change, so here are some of the changes I made to out the white-outs.

But first... detecting Android

I use the same HTML, CSS, and JS codebase for both the Android and iOS versions of my app, and for most of the changes, I only wanted to make them for Android. To do that, I have a function that detects if we're on Android or if we're testing Android mode on the desktop. I can use the results of function in my initialization code to add a "android" class to the body tag, which I can reference in my CSS.

My Android detection function checks by looking at the user agent (which isn't as simple as just looking for "Android", thanks to HTC and Kindle) and as a backup, looking at the device information served by the PhoneGap API.

Note that my Android detection function only checks if we're on an Android operating system, not if we're specifically in the built-in Android WebKit browser. My app only needs to check if it's on an Android OS, since it's wrapped inside PhoneGap and not accessed from arbitrary mobile browsers. If you're writing a website accessible from a URL and want to employ these workarounds only on the built-in Android Webkit browser, then you need a check that looks for the Android OS and a non-Chrome Webkit browser. Thanks to Brendan Eich for pointing that out in the comments.

  function inUserAgent(str) {
    return (new RegExp(str)).test(navigator.userAgent.toLowerCase());
  }

  function isAndroid() {

    function isAndroidOS() {
      return inUserAgent('android') || inUserAgent('htc_') || inUserAgent('silk/');
    }

    function isAndroidPG() {
      return (window.device && window.device.platform && window.device.platform == 'Android' || false;
    }

    return isAndroidOS() || isAndroidPG();
  }

  if (isAndroid() || getUrlParam('os') == 'android') {
    $('body').addClass('android');
  }

The case of the shiny modals

I use the modals from Twitter Bootstrap for dialogs in the mobile app, and I noticed white-outs and other visual oddities would happen when a modal rendered. To workaround that, I overrode the modal CSS rules to reset various CSS3 properties to their defaults so that the browser processes them as never being set at all.

.android {
  .modal {
    @include box-shadow(none);
    @include background-clip(border-box);
    @include border-radius(0px);
    border: 1px solid black;
  }
}

After making that change, I decided to go ahead and strip down the Bootstrap buttons, nav bar, and tabs as well — not because I necessarily knew their CSS3 rules were causing issues, but because I'd rather have a useable app than a perfectly rounded, shadowed app. I later realized that the stripped down CSS conveniently matches the new Android design guidelines quite well, so my changes are actually a performance and usability gain. You can see all my Android CSS overrides in this gist, and see the visual effect of the overrides in the screenshot below.


The case of too many nodes

When my app loads the stream, it appends many DOM nodes for each of the updates, and those DOM nodes can include text, links, buttons, and images. Android was frequently whiting out while trying to render the stream, understandably. I made a few changes to improve the performance of the stream rendering:

  • Stripping the CSS3 rules from the buttons (as described above) plus a few other classes.
  • Implementing delayed image loading. I had already implemented that for the web version of the app, since it is pretty silly from a performance and bandwidth perspective to load images that your users may not scroll down to see, and I discussed that in detail in this post.
  • Pre-compiling my Handlebars templates. This is a change I actually made for the iPhone, after discovering super slow compile times on iOS 5, but it helps a bit on Android as well.

The case of the resizing textarea

My app includes a textarea for the user to enter notes which defaults to a few rows. Sometimes users get wordy though and type beyond the textarea, and I wanted the textarea to resize as they typed. I got this plugin working, but I kept seeing my app's header and footer white out when the textarea resized. I eventually figured out that Android didn't like repainting the header and footer because they were position:fixed (it didn't white out when I made them absolute), and couldn't figure out how to get Android to not white them out. So, I opted here to just make sure the code never resized the textarea while the user was typing by adding the resizeOnChange option, and setting that to true for Android. It's not ideal, but well, that's life.

A better future?

As recently announced, there's now a Chrome for Android which is significantly better than the built-in Webkit that ships with it, and I'm hopeful that Android apps will be able to use Chrome for their embedded WebView in the future (see my issue filed on it here). I look forward to making app design decisions based on making a better user experience, and not on preventing a horrible one. :)

Saturday, March 17, 2012

Porting jQuery Plugins to Zepto: Tips & Tricks

As I've written about it here in the past I've spent a fair amount of time porting jQuery plugins to work with Zepto, a lightweight alternative with a smaller API surface. You can read those posts here and here to see how I ported specific plugins over, but today I thought I'd give a general guide for people taking on the task of porting other plugins over. There are an approximate shit ton of jQuery plugins out there (it was basically the defacto way of creating Javascript UI widgets for the last few years), so I see a lot of porting in our future. Here's the strategy I employ:

  1. Start by setting up your page so you can easily switch between serving jQuery and serving Zepto, and modify the last line of the plugin code to work with either:
    })(window.jQuery || window.Zepto);
  2. Test your code with Zepto, and look for errors in the logs about undefined methods. That means the plugin is using a method that is not in your Zepto.js, and you need to either not use that method or more likely, find a way to define it in your Zepto copy:
    1. Do a CTRL+f on the Zepto docs to see if the method is described there. Sometimes it's defined in the optional files, and you just have to bring those into your copy (like the data() and animate() methods).
    2. Look up the method in the jQuery docs and read the description. Some jQuery methods are very similar to other methods, and can be safely substituted in some situations (like contents() and children().)
    3. Search the Zepto issue tracker to see if other developers have filed an issue about lacking functionality. If they have, they often include a patch or link to a gist which you can copy into your Zepto copy.
    4. Check the jQuery source code (either using this source browser or by CTRL+Fing in the uncompressed source code) to see how they accomplish the functionality, and bring that code into your Zepto copy. Just think carefully about how much of that code you actually need for the modern mobile browsers, and whether there are edge cases you can safely remove.
  3. Once you've gone through the undefined errors, you might think your plugin is good to go — but then you try to use it and it just doesn't do what you expect it to. Don't worry, there's still hope yet! Sometimes a plugin uses an existing jQuery method but uses it with optional arguments or alternative constructors, and that can lead to code behaving differently when run on jQuery versus Zepto. To see if that's happening, place strategic console.log statements around the code, run it with both jQuery and Zepto, and see where variable values differ. When you find a misbehaving area of the code, look carefully at the arguments that the plugin is sending into a method, compare that to what Zepto is accepting (both type and number). If you discover a discrepancy, follow the steps above to supplement your Zepto copy to handle the other method usage.
  4. Now that you've hopefully successfully ported a plugin, contribute it back to the community. At the very least, stick it in a gist with "Zepto + PLUGIN-NAME" and tweet it, or even better, write a blog post explaining the changes, and contribute patches to Zepto (of additions) and/or the plugin (of subtractions/simplifications). You may find that the Zepto authors don't want to bloat the library with that functionality, and you might find that a plugin author doesn't care about making their code play nicer with lighter weight jQuery alternatives, but it doesn't hurt to put your patches out there.

And if all else fails (or if porting is just taking too damn long) — find a library that does what you want, but doesn't rely on jQuery. Or write it yourself. Happy porting!

Using FancyBox with Zepto

On the web version of the EatDifferent site, I use the jQuery Fancybox plugin to lightbox meal photos when you click on them. On the mobile version, I left them as clickable images. I've realized that's not a great experience because the user feels like they're leaving the app (because they are!) and on the iPhone, they can't simply press the back button to go back to the app (like you can on Android). So, for the next version of the mobile app, I decided to use lightboxes for the photos there as well.

Then I had to decide how to lightbox them on mobile. I could use FancyBox, use Twitter Bootstrap modals (which I already use on mobile), or find a mobile-optimized lightbox solution. After doing a quick check in the browser to see how FancyBox works at small screen sizes — and seeing it actually works pretty well — I decided to go for it.

But, there's a catch: I use jQuery on the web, and the more lightweight Zepto on mobile, and very often jQuery plugins use obscure jQuery functionality that Zepto does not support, like optional arguments, alternative constructors, and little known methods. So when I want a jQuery plugin to work with Zepto, I either have to modify the plugin code to use the more "standard" functionality or I have to add the extra functionality to my Zepto copy. My preference is not to mess with plugin code, as that makes it hard to upgrade to new plugin versions and its usually difficult to figure out what exactly the plugin author intended. But, sometimes it's easier. In the case of FancyBox, I did a little of both. Here's a run-down:

FancyBox Changes:

You can check out the diff or the full fancybox.js.

  • As usual, I changed the last line of the plugin file to say window.jQuery || window.Zepto, to make it work with either.
  • The plugin used contents(), and I changed it to use children() instead, which seemed to work.
  • The code used a concept called global event triggers in jQuery which lets you trigger an event on no element in particular ($.event.trigger('fancybox-cleanup'). Zepto doesn't have that, so I changed the code to simply trigger on the two elements that listened to that global event.

Zepto Changes:

You can check out the diff or the my full zepto.js. My Zepto is fairly customized for my use (beyond just FancyBox), so you probably don't want to just grab it all.

  • The code used $.support to detect opacity support on browsers. Since I only use Zepto on mobile and it doesn't have the support module, I hard-coded support for it ($.support = {opacity: true};)
  • The code called the append() method with multiple arguments, which Zepto doesn't support out of the box. I found this gist that adds support for it and merged that into my copy.
  • The code used "is:visible" to detect DOM element visibility. Unfortunately, that pseudo-selector was completely made up by jQuery and it won't work in Zepto since it uses the browser's built-in query selector engine. There's a long discussion about how to best generally handle these fake selectors, but in the meantime, I just added a special check to my filter function to just handle is:visible, based on this gist.
  • The code used fadeTo(), fadeIn() and fadeIn(), which aren't in the core Zepto files but are available if you include the fx.js and fx_methods.js files.
  • Unfortunately, the code also used stop() to stop fading animations, which isn't in those optional Zepto files, so I then had to copy the files from this Zepto fork instead.

If this post was of interest to you, you may also be interested in reading my first post on porting most of my plugins to Zepto.

Tuesday, March 13, 2012

The Red Queen: Sex, Gender, & Evolution

As you can tell from my reading list, I love reading books about evolution, science, and sex. I just finished reading "The Red Queen", and it's definitely one of my favorites. I've been sharing tidbits from it with everyone around me who will listen, because I've found them so interesting that I can't keep them to myself. I've put together quotes below to give you an idea of what the book covers and make you want to read the book too.

It starts with an important question: why does sex exist at all?

Sex gives variety, so sex makes a few of your offspring exceptional and a few abysmal, whereas asex makes them all average.
Yet it is stasis, not change, that is the hallmark of evolution. Sex and gene repair and the sophisticated screening mechanisms of higher animals to ensure that only defect-free eggs and sperm contribute to the next generation—all these are ways of preventing change.

Then it introduces the Red Queen theory, which we see throughout the book.

The struggle for existence never gets easier. However well a species may adapt to its environment, it can never relax, because its competitors and its enemies are also adapting to their niches.

Next it goes into more on how sex may exist because it is the best defense against parasites:

By the time the next generation comes around, the parasites will have surely evolved an answer to the defense that worked best in the last generation. It is a bit like sport. In chess or in football, the tactic that proves most effective is soon the one that people learn to block easily. Every innovation in attack is soon countered by another in defense.
In about ten years, the genes of the AIDS virus change as much as human genes change in 10 million years. For bacteria, thirty minutes can be a lifetime. Human beings, whose generations are an eternal thirty years long, are evolutionary tortoises.
The AIDS virus is craftiest of all. According to one theory, it seems to keep mutating so that each generation has different keys. Time after time the host has locks that fit the keys and the virus gets suppressed. But eventually, after perhaps ten years, the virus’s random mutation hits upon a key that the host does not have a lock for. At that point the virus has won. It has found the gap in the repertoire of the immune system’s locks and runs riot. In essence, according to this theory, the AIDS virus evolves until it finds a chink in the body’s immune armor.
And in an extraordinary discovery made by Wayne Potts of the University of Florida at Gainesville, house mice appear to choose as mates only those house mice that have different histocompatibility genes from their own. They do this by smell.
Sex is about disease. It is used to combat the threat from parasites. Organisms need sex to keep their genes one step ahead of their parasites. Men are not redundant after all; they are woman’s insurance policy against her children being wiped out by influenza and smallpox (if that is a consolation). Women add sperm to their eggs because if they did not, the resulting babies would be identically vulnerable to the first parasite that picked their genetic locks.

After establishing why we have sex, he goes into how animals decide who to have sex with (or as it turns out, how the females decide who to have sex with):

Where males gather on communal display arenas, a male’s success owes more to his ability to dance and strut than to his ability to fight other males.
In other words, the choosier the females, the brighter and more elaborate the male ornaments will be, which is exactly what you find in nature. Sage grouse are elaborately ornamented, and only a few males get chosen; terns are unornamented, and most males win mates.

Next is a chapter on the differences between the sexes, which I find quite interesting given all the "women in tech" discussions that pop up around me:

Men and women are human beings, and human beings are mammals with one highly unusual characteristic: a sexual division of labor...Men look for sources that are mobile, distant, and unpredictable (usually meat), while women, burdened with children, look for sources that are static, close, and predictable (usually plants).
Of the many mental features that are claimed to be different between the sexes, four stand out as repeatable, real, and persistent in all psychological tests. First, girls are better at verbal tasks. Second, boys are better at mathematical tasks. Third, boys are more aggressive. Fourth, boys are better at some visuo-spatial tasks and girls at others. Put crudely, men are better at reading a map and women are better judges of character and mood—on average.
The mind is immune to testosterone unless it was exposed to a sufficient concentration (relative to female hormones) in the womb. It would be easy to engineer a society with no sex difference in attitude between men and women. Inject all pregnant women with the right dose of hormones, and the result would be men and women with normal bodies but identical feminine brains. War, rape, boxing, car racing, pornography, and hamburgers and beer would soon be distant memories. A feminist paradise would have arrived.
Baby girls are more interested in smiling, communicating, and in people, boys in action and things. Shown cluttered pictures, boys select objects, girls people. Boys are instantly obsessed with dismantling, assembling, destroying, possessing, and coveting things. Girls are fascinated by people and treat their toys as surrogate people. Hence, to suit their mentalities, we have invented toys that suit each sex. We give boys tractors and girls dolls. We are reinforcing the stereotypical obsessions that they already have, but we are not creating them.
Studies of male conversation find it to be public (that is, men clam up at home), domineering, competitive, status-obsessed, attention-seeking, factual, and designed to reveal knowledge and skill. Female conversation tends to be private (that is, women clam up in big groups), cooperative, rapport-establishing, reassuring, empathetic, egalitarian, and meandering (that is, to include talk for talk’s sake).

He goes on to discuss homosexuality, fashion (which he can't quite figure out), incest, and beauty.

Darwin said, "If all our women were to become as beautiful as the Venus de' Medici, we should for a time be charmed; but we should soon wish for variety; and as soon as we had obtained variety, we should wish to see certain characters a little exaggerated beyond the then existing common standard." This, incidentally, is as concise a statement as could be made for why eugenics would never work.

In his final and most speculatory chapter, he discusses theories behind the big brain size of humans, and puts forth the idea that its due to sexual selection.

Yet the argument still has considerable force because big brains do not come free. In human beings, 18 percent of the energy that we consume every day is spent in running the brain. That is a mighty costly ornament to stick on top of the body just in case it helps you invent agriculture, just as sex was a mighty costly habit to indulge in merely in case it led to innovation

He finishes by reminding us that many of the theories he espouses are likely to be proved wrong later, because we are so early on in researching the area:

The study of human nature is at about the same stage as the study of the human genome, which is at about the same stage as the mapping of the world in the time of Herodotus. We know a few fragments in detail and some large parts in outline, but huge surprises still await us and errors abound.

You can't learn anything from just these quotes alone, of course — you have to read the whole book. So, any suggestions for what book I should read next?