Posts Tagged ‘software development’

JavaScript development and the paradox of choice

There are a lot of folks trying to sell their miracle cure for the problem of writing efficient, testable, maintainable JavaScript. And there’s an equal number of folks decrying the proliferation of almost-there libraries and flash-in-the-pan frameworks.

Bootstrap. Backbone. Handlebars. Angular. I’ve spent so much time hearing snatches of conversation about these tools, and trying to make sense of them, that after awhile it all starts to sound like some crazy beat poetry.

Listen:

angular backbone bootstrap cordova handlebars lawnchair underscore jasmine karma testacular grunt yeoman blueprint ember bower require sencha dojo mootools phonegap modernizr prototype meteor…

If you shouted that on a street corner while wielding a bottle of bourbon, you wouldn’t look out of place. I’ve seen the best minds of my generation destroyed trying to understand this mess.

Police Chief Wiggum and a raving derelict, from Simpsons episode 3F02.

Pictured: a seasoned JavaScript developer.

A good JavaScript is hard to find

Part of the reason there are so many snake-oil salesmen is that the cure is so badly needed. Web development is both 1) hard and 2) absolutely crucial. Facebook and Gmail have set the bar high enough that nowadays everyone expects beautiful, responsive, browser-based applications that take milliseconds to download and work on every rectangular-shaped device you can throw at it. It’s a tall order.

And the reason it feels like snake oil is that none of these tools solves the entire problem. I’ve tried many of them, hoping that I had finally found the JavaScript silver bullet, and I’ve always felt vaguely disappointed afterwards. The medicine tastes good going down, I get excited watching YouTube tutorials and reading GitHub pages and coding in a new paradigm, and then afterwards I still end up sweating feverishly over the Chrome Developer Tools, trying to center a disobedient div or figure out why my event isn’t firing. I exchange one set of problems for another.

And then I lay awake at night wondering, “Well, maybe instead of JQuery UI, I should have used YUI or Bootstrap or…”? Then it’s back for another dose of the same old medicine.

Grandpa Simpson selling some 'revitalizing tonic,' from Simpsons episode 2F07.

Step right up and put some fury in your JQuery, some zest in your CSS!

Another world is possible

This situation really frustrates me, primarily because I come from a Java background. And in Java Land, the platform is mature enough that there’s a basic suite of components that have emerged as the brain-dead, obvious solutions to common problems.

  • Need to test your app? Duh, use JUnit.
  • Need basic HTTP operations? Double duh, use Apache HTTP Client.
  • Need ORM? What are you, stupid? Use Hibernate.
  • Need a package manager? Cripes, it’s the 21st century: use Maven. Or Ivy, if you want something even simpler.

And if you use modern Java-based frameworks like Android or Grails, you’ll see that a lot of these third-party tools are already baked in: e.g. JUnit and HTTP Client for Android; Ivy, Hibernate, and JUnit for Grails. New Java developers pick up stuff like JUnit without thinking about it, as if it were just part of the language. And it practically is.

Even Java itself is mature enough that I’ve honestly felt satisfied since Java 6, and haven’t seen much need to upgrade. String switches in Java 7? Yawn, I’ve been using Enums since Java 5. Lambdas in Java 8? No need, Google Guava has me covered.

No silver JS bullet

JavaScript, on the other hand, is anything but mature. There is no “obvious” choice for third-party components – with the exception of JQuery, which is so omnipresent nowadays that it almost is JavaScript.

But aside from JQuery, there’s no one-stop solution that everyone rallies behind. For each of my “easy” questions for Java above, you get a forest of forked decision trees in JavaScript:

Mr. Burns contemplates Ketchup vs. Catsup, from Simpsons episode 2F07.

Ketchup or catsup? Karma or Selenium?

The paradox of choice

When you’ve got dozens of popular frameworks, many of them with overlapping or even conflicting goals, the choices can be overwhelming. And even after you choose one, it’s easy to end up second-guessing yourself and fretting endlessly over your decision. It’s a familiar case of the phenomenon popularly known as the paradox of choice.

So what’s a poor JavaScript developer to do?

Let’s say, for instance, that your boss tells your team that you need to write a mobile webapp. Do you choose JQuery Mobile, Sencha Touch, or Dojo Mobile? And what if you need to write a regular data-driven Ajax app? Do you choose Angular, Ember, or Backbone? Each of them has a snazzy self-laudatory website and fierce partisans on Stack Overflow. Looks like you’ve got some reading to do!

I’m new to web development, but I’ve come to believe that the only surefire solution to the problem of competing frameworks is to try them all. Not for a mission-critical project, of course – instead, you should just write a stub app. That way, you’ll discover each framework’s strengths and shortcomings, you’ll understand the problems it’s trying to solve, and you’ll be able to make an informed decision when it really counts.

In my opinion, it’s better to have three developers on your team take a week to write stub apps in three different frameworks, rather than blindly embark down a single path based on the attractiveness of a documentation page or the charisma of a YouTube evangelist.

My own stub app

I decided to try this approach recently with three frameworks I was curious about – Angular, Bootstrap, and PhoneGap. They seemed to have orthogonal goals, so in theory they should play nicely together.

My objective was to write a webapp with nice MVC features (Angular) that would look pretty (Bootstrap) and could work as a native Android or iOS app (PhoneGap). For the task itself, I chose to write an end-of-game score calculator for one of my favorite Euro-style board games, Imperial. This had the benefit of being a well-defined problem that scratched a personal itch, and plus it gave me something to show off to my board gamer buddies.

For the feature specifications, the usual suspects applied. I needed to persist user data, because presumably users would want to see their saved games, or resume a game if they accidentally closed the tab. The design had to be responsive in order to accomodate multiple screen sizes, because you could imagine using this app in your browser as well as on a smartphone. It had to support deep-linking, because what if you wanted to share the game results with your friends? And of course, the UI had to present the data in a useful way: who’s in first place, who came in second, are there any ties, etc.

When I first described this project to one of my coworkers, his reaction was “that sounds like way more than a stub app.” Which is true – as soon as you exceed a certain level of complexity, you run into interesting problems, for which the frameworks are supposed to provide useful solutions. This is exactly the point of writing the app.

The end result of this experiment is the Imperial Score Calculator. It’s available as both a mobile-friendly webapp and an Android app (iOS version coming soon). And of course the source code is on GitHub.

Imperial Score Calculator desktop-sized screenshot

Imperial Score Calculator

I’ve learned something today

In the end, I’m very satisfied with the project. Not because the app itself is the best I’ve ever written (it’s not), but because it taught me some hard lessons that I’ll take with me to my next web project. For instance, here are some of the lessons learned:

  • Bootstrap does not magically make everything responsive. Do not design for the desktop and then hope that when you resize the viewport everything will “just work.” Some assembly required.
  • Angular is a godsend. It’s as if someone stepped out of a time machine and showed us what HTML6 will look like, today. I initially wrote the app in JQuery; a naïve Angular rewrite resulted in about 20% less code.
  • That being said, Angular does not instantly replace JQuery, unless you really grok directives. I still had to fall back on the good ol’ $ from time to time.
  • Lawnchair is a cool idea, but it’s poorly documented, and the asynchronous approach means you can’t save user data in the onbeforeunload event. In the end, I just went with LocalStorage.
  • PhoneGap is awesome. But man oh man, do not try debugging it without Weinre, unless you like pulling your hair out.

These are all opinions that I hold after working on this app. And I don’t expect you, dear reader, to swallow any of them just based on my say-so. The only way you can learn these lessons is to build a stub app yourself.

And perhaps you’ll have a totally different experience and come to totally different conclusions. Your mileage may vary. But you won’t know until you take the car out for a test drive.

Imperial Score Calculator mobile-sized screenshot

I ended up using a completely different layout for the mobile version.

Conclusion

JavaScript development is hard. The community is going through some growing pains, with everyone defending their cherished framework. The only solution to this problem of fragmentation and “There’s More Than One Million Ways To Do It” is time.

I do see some rays of hope in projects like Meteor and Yeoman, which are very opinionated meta-frameworks that attempt to combine multiple “best of class” JavaScript solutions into one easy package for web developers. In a sense, they’re trying to solve the problem that’s already been solved in Java Land.

But since Java Land is an increasingly irrelevant, fading power next to the ascendant hegemony that is the People’s Republic of JavaScript, the solution can’t come soon enough. In the meantime, I’ll keep writing stub apps.

Make your workplace more fun with a Jenkins alarm system

At every development team I’ve worked with, we’ve used Jenkins to notify us when the build broke. (Or Hudson, as it was called before Kohsuke Kawaguchi nailed a proclamation to the church wall.) Everyone on the team would receive an email when a unit test failed, or when someone forgot to commit a file, or when some other random blunder occurred.

Jenkins is really invaluable for finding problems early. And of course, publicly shaming the guilty party is always a great source of fun. The Continuous Integration Game plugin is even better for this.

But at my current employer, my colleague Alexandre Masselot had a singularly brilliant idea: instead of just firing off an email, why not add a visual cue as well? So he set up a physical flag system, attached to a USB servo device, with a cron script that would raise the flag whenever a Jenkins build failed. It looked like this:

Everyone on the dev team loved it. If the flag was raised when we walked into the office in the morning, the first question at the scrum meeting would be, “Who broke the build?” And as soon as the flag started to rise, you could hear the servo cranking, and the guilty developer would announce, “That was me!” A good time was had by all.

But could we do better?

The developers on the far side of the room couldn’t always hear the cranking sound, so they often didn’t notice when the flag was raised. So we decided to add an audio cue as well. Every time the build was broken, the machine would play the Star Wars “Imperial March” (aka Darth Vader’s theme), using the Unix beep command, since this particular machine had no speakers. It sounded like this:

And every time the build was fixed, it would play the main theme of Star Wars, to celebrate the joyous occasion:

This solved the immediate problem. The cacophonous beeping that signaled a build failure could be heard on the far side of the room. And beyond, where even the non-devs in the office could enjoy the sweet sound of pure geekiness.

But could we do better?

Whenever Darth Vader thundered his beeping fanfare, all the developers would immediately stop and check Jenkins to see which component broke. We didn’t have a quick way to know “whodunnit.”

So we added a new feature: an Android phone attached via USB, plus a simple text-to-speech app that would announce the name of the guilty party and the component that he or she broke.

The end result is that whenever the build is broken: the following events occur:

  1. The flag goes up
  2. The dreaded “Imperial March” sounds
  3. A robotic voice says “So-and-so broke the build, in the project such-and-such.”

The final system looks like this:

 

Then, when the build is back to normal, the system announces who fixed it, and all is forgiven:

 

So nowadays, whenever we start to hear the infernal beeping from our Jenkins machine, everyone takes off their headphones and patiently waits to hear who broke the build. Possibly with some apologies/excuses/complaints from the accused individual. (“It’s because the downstream project built before I was ready!”)

In any case, it makes our office much more melodious and much more fun. And luckily, the nearest non-dev is a Star Wars geek, so she doesn’t mind our antics.

Do it yourself

If you’d like to recreate our setup, it will only cost you about 40 bucks and a little bit of development time. You’ll need:

  1. A Yocto-Servo device ($25)
  2. A micro USB cable to connect it ($6)
  3. A micro servo to work the flag ($8)
  4. The flag itself (we used a Canadian flag, because it’s what I had, eh)
  5. The cron script to call Jenkins and raise the alarm
  6. The SimpleTalker app, if you have an Android device available. We use an old HTC Magic.

Bonus features

We also experimented with a few other features. If you use this excellent Python script to convert MIDI files into beep format, for instance, you can find any MIDI you like and make it into euphonious beep music. Here’s what this Super Mario Bros. theme from VGMusic.com sounds like:

And you could use the Mario “game over” theme for a build failure:

Alternatively, the Android app I wrote supports specifying an MP3 file on the device’s storage, in addition to the text-to-speech. Maybe each of your developers prefers a personalized “failure” and “success” theme? The sky’s the limit.

And if the beeping sound isn’t annoying enough, I will humbly point out that Yoctopuce also offers USB modules to operate an emergency rotating light. I take no responsibility for the mental health of your office-mates if you actually install such a device.

Summary

Programming is fun. And in our office, we’ve found that turning programming into an audiovisual experience makes it even more fun. I hope you’ll get a kick out of the code we’ve written, and that your boss won’t think it’s a waste of time. (Ours didn’t, although she keeps the door to her office firmly closed now.)

State of the Android app union

I thought it might be useful to report on how all my apps are doing on the Android Market, in terms of downloads and active users. Hopefully this information will be helpful for someone looking to write their own app, or wondering what their chances of success are.

It’s worth mentioning that I’ve never marketed any of my apps, except for a short “house ad” campaign I did in Chord Reader to promote KeepScore. App development is a hobby for me, so I’ve found it more interesting to just release my apps into the wild and see whether they sink or swim. I’ve relied almost solely on the Android Market and word-of-mouth to build up my user base.

This may have worked better when I first started writing apps, which was around March of 2010, when Android was still in its infancy. Back then, the Android Market had less than 20,000 total apps, so you could get a decent amount of visibility by simply publishing your app. Today, the Android Market boasts over 250,000 apps, so it’s much easier to get lost in the crowd.

My Personality Type

For instance, when I released my second app, My Personality Type, in March of 2010, it was able to gain 3,000 downloads in a single week without any advertising. Most likely this is just because personality tests are fun, mine was free, and it was also only the second or third of its kind to be released on the Market. The app was later removed due to a takedown request from psychologist David Keirsey, so there’s no way of knowing if it could have maintained that stellar rate of growth, but it’s pretty impressive nonetheless. (Yes, Pokédroid was not my first run-in with copyright issues. I tried to work out a licensing agreement with Dr. Keirsey, but eventually he stopped responding to my emails.)

By comparison, my most recent app, KeepScore, has grown much more slowly. KeepScore only broke 1,000 downloads very recently, even though it’s been on the Market for almost two months, and despite the fact that I promoted it through house ads (where it got 8,091 impressions and 243 clicks). I’m guessing this is because the Android Market is already saturated with tons of score-keeping apps, so KeepScore doesn’t even show up in the top 10 in a search for “score,” “score keeper”, etc. Even though it’s the best of the bunch, it’s hard to stand out over apps that have been around longer, with more downloads and more reviews.

In general, though, I’ve found that the best determiners of an app’s success in the Market are 1) search engine optimization, 2) constant updates, and 3) short, easily understandable app summaries. I’ll describe each one in turn.

Search results for "score keeper."

Search engine optimization is important for the obvious reasons. Most users are going to discover your app by searching for some problem they’re trying to solve – “save battery,” “calorie counter,” “weather widget,” etc. Try to think of what need your app fulfills, and be sure to include those terms in your Android Market description. I always just add a section at the end where I write “seo:” and then list a bunch of terms related to the app.

Constant updating might not be something you’d imagine would contribute much to an app’s success, but anyone who’s worked in Android development long enough can testify to this. Back in the old days, this technique was enormously effective, because the Android Market app had a prominent “Just In” page that simply listed the most recently released or updated apps. For this reason, you’d often see spam apps (such as “Sexy Hot Girl #12”) releasing a new version every day, perhaps under the imaginative title of “version-20100614”. Anybody could just change a string, release a new version of their app, and watch the number of downloads spike.

Google seems to have cracked down on this practice since then, and the new Market app doesn’t even include a “Just In” page. Instead we now have “Top New Paid,” “Top New Free,” and “Trending,” which seem relatively free of spam. But updating from time to time can still be a boon to your app’s success. Users love getting updates, and when the updates stop rolling in, they tend to lose interest in your app and uninstall it. Go long enough without any updates, and you may even start hemorrhaging users. (We’ll see an example of this later.)

Search results for "logcat."

And finally, short, easily understandable app summaries are a crucial part of promoting your app through the Market. I believe most users will decide whether or not to install your app based on a glance at the search results, which means the icon and the name are key. The description, the reviews, and even the star rating are of secondary importance, in my opinion. (The star rating is almost meaningless, because all halfway decent apps will have at least 4 stars.)

So when you design your app, you need to ask yourself: 1) Is the icon attractive, and does it hint at the app’s functionality? and 2) Is the name simple, and does it effectively communicate what the app does? For illustration, I’ll point out that aLogcat beats out my own app, CatLog, by this measure. CatLog has a cute icon (which many users have complimented me on!), but I’ll admit it requires a little bit of extra mental effort to figure out what the app does. CatLog still has fewer downloads than aLogcat.

All of these are just tips based on my own personal experience, which means they’re mostly hunches and guesswork. Make of them what you will. But of course, the Android Market also provides us with some wonderful reporting tools, so I have some hard data to offer as well!

So without further ado, here’s the current state of all my apps in Android Market, in the order I wrote them. I report the total number of downloads, as well as the number of active users (i.e. installed copies of the app). Each graph shows the change in active users since January of 2011, which is when Google started providing these detailed statistics. You can ignore sudden spikes in the graph – I think those are bugs in the reporting tool.

Japanese Name Converter

Released March 2010
39,144 downloads
7,464 active users (19%)

My first app, which I never updated beyond version 1.0, is still fairly popular. Its popularity also seems to be pretty constant, since I imagine most people download it, get a kick out of it, and then uninstall it soon afterwards. That doesn’t bother me much, though, since this was basically just a “Hello World” app for me.

My Personality Type

Released March 2010
3,286 downloads
287 active users (8%)

As I noted above, My Personality Type only spent one week on the Android Market, due to a takedown notice from the author of the test. (I actually based this app on an assignment from one of my undergraduate computer science classes, so I didn’t know the test was under copyright.) Given it’s been off the Market for a year, I’m kind of amazed the app still has any active users at all.

Pokédroid

Released April 2010
451,492 downloads
125,576 active users (27%)


This chart still breaks my heart a little. The sudden bump in March corresponds to when I released the update for Pokémon Black/White, and the dip in June, of course, corresponds to when I removed the app from the Android Market due to a takedown notice from The Pokémon Company. At its height, it had 170,000 active users.

Offline Browser

Released June 2010
17,032 downloads
2,820 active users (16 %)

I wrote this app while I was attending the 2010 NAACL conference, because I wanted to be able to browse the conference proceedings, which were distributed as raw HTML and PDF files, on my phone. This app didn’t hold much interest for me afterwards, so I never looked back. (By the way, here’s my paper from that conference.)

App Tracker

Released July 2010
3,183 downloads
344 active users (10%)

With only 3,000 downloads over the course of a full year, App Tracker is a certified dud. As I was developing it, I actually believed it was going to be my breakthrough app, and that the revenue from the Premium version would allow me to quit my day job and do app development full-time. Unfortunately, the graveyard of lost ambitions is littered with such failures, and App Tracker never really got off the ground.

CatLog

Released August 2010
24,614 downloads
10,330 active users (41%)

Ah, CatLog – the phoenix that rose from the ashes of App Tracker. After App Tracker’s failure, I refashioned its log-reading component into a straight-up Logcat app, and now CatLog perseveres as my third-most popular app. In fact, it’s probably the app I’m most proud of after Pokédroid.

Chord Reader

Released October 2010
30,533 downloads
11,607 active users (38 %)

It’s a shame I never found this app very compelling to work on, because it’s actually my most popular app on the Market (now that Pokédroid is gone). I’m not really sure why it started bleeding users in late June, but if I had to guess, I’d say it’s because I haven’t updated it much over the past year. Like I mentioned above, users tend to lose interest when you don’t update, and I think that’s especially true when there are much-needed features they keep clamoring for. (In my case, users keep asking for an auto-scroll feature and setlists.)

KeepScore

Released June 2011
1,207 downloads
873 active users (72 %)

I would really, really like to see this app succeed more than it has. My goal with KeepScore was to create the end-all be-all best score keeper for Android, and in a sense I’ve failed simply because the app still doesn’t have much visibility in the Android Market. As I mentioned above, it doesn’t even come up in the top 10 in searches for “score” or “score keeper,” meaning that most users will probably never find it, and instead settle for an inferior app. I’m not sure what to do, though, other than wait for it to gain more downloads and ratings. I have no control over Google’s search rankings.

So that concludes my app-by-app report. But because it’s a lot of data to take in individually, I also created some charts comparing all the apps side by side:

Total downloads and active users

Total downloads and active users

This chart shows the total number of downloads and active users (as of today) per app. Obviously Pokédroid is an order of magnitude more popular than my other apps, so I also created a log-based version of the same chart:

Total downloads and active users (log)

Total downloads and active users (log)

Here it’s a little easier to compare the non-Pokédroid apps. Chord Reader, CatLog, and Japanese Name Converter are all reasonably successful, whereas Offline Browser and App Tracker are less so. With KeepScore and My Personality Type, it’s difficult to compare, because they’ve spent much less time on the Market than the others. So I also went ahead and created a chart showing the total number of downloads and active users divided by the approximate number of days spent in the Market:

Downloads and active users per day spent in Market

Here it’s easier to see which apps were more popular on a day-to-day basis. The most surprising finding is that My Personality Type apparently had more potential than I thought. Even though it was only on the Market for one week, it looks like it could have been as popular as Pokédroid if it hadn’t been taken down. (Funny that my most successful apps are also the ones that get targeted for copyright infringement! Take note, kids: you walk a fine line when you reuse other people’s content.)

And here’s the same graph with a log-based y-axis:

Downloads and active users per day spent in Market (log)

So there you have it. Of the apps I still have on the Market, Chord Reader, Japanese Name Converter, and CatLog are the most popular. Offline Browser and KeepScore take up the second tier, whereas App Tracker is an unmitigated failure. (I couldn’t even show App Tracker’s active users per day on this chart, because the value was less than 1, meaning the log value actually went negative.)

I wish I could say there was a way to know in advance whether an app is going to be a hit or a miss, but I think the Android Market is just too unpredictable for that. You really can’t know how popular an app is going to be until you put it out there. For me, though, this is the excitement of app development. More so than with any other kind of software development, you get immediate confirmation of whether or not people find your app useful. So if nothing else, it’s fun to throw darts at the board and see what sticks.

Your Froyo users are an army of testers

The error reports page in the Android Market Developer Console is one of my favorite additions to Android 2.2 (Froyo):

It gives you a nice, organized view of the stacktraces reported by users when your app has crashed or frozen. This is great because, for the most part, users are not particularly eloquent when describing bugs. Usually they just say something like “Doesn’t work anymore, please fix.” And when they do give more information, it’s often tantalizingly incomplete: “When I go to Settings I get a force close.”

Stacktraces, on the other hand, don’t beat around the bush:

java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=0, result=-1, data=Intent { (has extras) }} to activity {com.nolanlawson.pokedex/com.nolanlawson.pokedex.PokedexActivity}: java.lang.NullPointerException
      at android.app.ActivityThread.deliverResults(ActivityThread.java:3515)
      at android.app.ActivityThread.handleSendResult(ActivityThread.java:3557)
      at android.app.ActivityThread.access$2800(ActivityThread.java:125)
      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2063)
      at android.os.Handler.dispatchMessage(Handler.java:99)
      at android.os.Looper.loop(Looper.java:123)
      at android.app.ActivityThread.main(ActivityThread.java:4627)
      at java.lang.reflect.Method.invokeNative(Native Method)
      at java.lang.reflect.Method.invoke(Method.java:521)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
      at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
      at com.nolanlawson.pokedex.VoiceSearcher.guessMonsterUsingDirectLookup(VoiceSearcher.java:132)
      at com.nolanlawson.pokedex.PokedexActivity.receiveVoiceResults(PokedexActivity.java:1206)
      at com.nolanlawson.pokedex.PokedexActivity.onActivityResult(PokedexActivity.java:1178)
      at android.app.Activity.dispatchActivityResult(Activity.java:3890)
      at android.app.ActivityThread.deliverResults(ActivityThread.java:3511)
 ... 11 more

NullPointerException. Bam. Go to the line in the code, figure out what’s null, and fix it. Nothing clarifies a bug like a good old-fashioned stacktrace.

Before Android 2.2, you had to get this kind of information from users by having them download a Logcat app and go through all the tedious effort of recording the log, reproducing the bug, and sending the stacktrace to you. Users can hardly be blamed for a lack of enthusiasm about this process. When you’re trying to complete a task and an app force-closes on you, the last thing you think is “Oh goody! I should tell the developer about this!”

Oh goody!

So Froyo’s error reporting framework is a godsend. From the user’s point of view, it’s a lot less painful to just click the “Report” button than to go through the rigamarole of downloading a Logcat app and emailing the developer. (Although, there is a splendid little Logcat app out there.) And from the developer’s point of view, your users have become an army of testers – and good ones, at that! They give you stacktraces and everything!

Plus, even when the stacktrace is not enough information, the additional comments from users are sometimes enough to squash the bug. For instance, I recently had an ArrayIndexOutOfBoundsException that was reported by almost a hundred Pokédroid users after the release of version 1.4.4. Try as I might, though, I couldn’t reproduce it. Then I noticed the user comments:

  • 셋팅 클릭시 프로그램 종료됨
  • Every time I open settings it shuts it self down. Galaxy ACE
  • se cierra al entrar en “Settings”
  • 렉 너무걸린다
  • När man ska gå in på inställningar så hänger programet sig
  • Happens every time I go to the settings
  • whenever I go to settings, the program crashes
  • I has updated this app and now when i want to change settings that not working! I mean i cant change settings! :-( sorry about my bad english :-(
  • It crashes while I try to enter the settings
  • trying to open settings. always happens. samsung galaxy s. android 2.2
  • when click on settings freeze
  • Telkens als ik naar settings wil gaan dan flipt ie
  • i was trying to go to the settings-screen… crashes whenever i try it…
  • Opened settings

Hmm… there sure are a lot of non-English comments here. So I set my phone to French and, aha! It turned out the bug only occurred if your phone’s language was set to something other than English. The bug was fixed and I shipped out 1.4.5 that same day. (Another great thing about the Android Market – no review process!)

When you have an app with a lot of downloads, though (like Pokédroid, which just hit 250,000), you start seeing some strange little bugs:

java.lang.NullPointerException
     at com.lge.media.SprintMultimedia.isStreaming(SprintMultimedia.java:27)
     at android.media.MediaPlayer.setDataSource(MediaPlayer.java:738)

Caused by: android.database.sqlite.SQLiteDatabaseCorruptException: database disk image is malformed
     at android.database.sqlite.SQLiteQuery.native_fill_window(Native Method)

java.lang.NullPointerException
     at com.motorola.android.widget.TextViewHelper.drawCursorHalo(TextViewHelper.java:306)
     at android.widget.TextView.onDraw(TextView.java:4175)
     at android.view.View.draw(View.java:6742)

Caused by: java.io.FileNotFoundException: res/drawable-hdpi/ic_dialog_alert.png
     at android.content.res.AssetManager.openNonAssetNative(Native Method)

Most of these just aren’t worth the effort to fix. For instance, they might only be reported by one or two users, and reflect situations that you, as a developer, don’t have a lot of control over (“database disk image is malformed”?). Others may be bugs in proprietary builds of Android, like the Motorola and Sprint bugs above. Obviously, I’m not going to go out and buy every flavor of Android phone just to test a few stray bugs.

If you’re lucky, you may also run into the Bigfoot of Android bugs:

java.lang.RuntimeException: Unable to get provider com.nolanlawson.pokedex.PokedexContentProvider: java.lang.ClassNotFoundException: com.nolanlawson.pokedex.PokedexContentProvider in loader dalvik.system.PathClassLoader[/mnt/asec/com.nolanlawson.pokedex-1/pkg.apk]
     at android.app.ActivityThread.installProvider(ActivityThread.java:4969)
     at android.app.ActivityThread.installContentProviders(ActivityThread.java:4696)
     at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4652)
     at android.app.ActivityThread.access$3000(ActivityThread.java:140)
     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2225)
     at android.os.Handler.dispatchMessage(Handler.java:99)
     at android.os.Looper.loop(Looper.java:143)
     at android.app.ActivityThread.main(ActivityThread.java:5097)
     at java.lang.reflect.Method.invokeNative(Native Method)
     at java.lang.reflect.Method.invoke(Method.java:521)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
     at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.ClassNotFoundException: com.nolanlawson.pokedex.PokedexContentProvider in loader dalvik.system.PathClassLoader[/mnt/asec/com.nolanlawson.pokedex-1/pkg.apk]
     at dalvik.system.PathClassLoader.findClass(PathClassLoader.java:243)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:573)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:532)
     at android.app.ActivityThread.installProvider(ActivityThread.java:4954)
... 12 more

I want to believe.

I call this the Bigfoot Bug because, if you Google it, you will find a lot of puzzled developers saying that they’ve only ever seen this bug reported in the Android Market, and they can’t reproduce it themselves. I mean, “ClassNotFoundException”? The class is right there! I was stumped by this bug myself, until I saw one developer suggest:

AFAICT, the installation process sometimes leaves the app in a corrupt state leading to weird errors like this.

So apparently, this bug is just one of those unavoidable parts of life, like sitting through red lights or having the sushi fall apart when you dip it in the soy sauce. You just have to put up with it.

Still, if you can manage to not get overwhelmed by the sheer number of reported bugs (Pokédroid has gotten over 500), and if you can prioritize them based on how many users they affect, the Froyo error reports can be an invaluable tool in making your app more stable. For instance, I had a layout-related bug a few months ago that I could not reproduce. It was reported by enough users, though, that I finally decided to rewrite that bit of the code and do some long-overdue optimizations. I haven’t seen the bug since.

Why it’s still your bug, even when it’s not

In the previous post, I talked about a workaround for a bug that occurs when integrating with the Facebook Android app. It brings up an interesting question: when software A integrates with software B, and software B has a bug, whose responsibility is it to fix it?

Intuitively, you might answer B. “It’s B’s fault. The developer of A can wash his/her hands of the whole matter and go to bed with a clean conscience.” Justice prevails, right?

But I disagree. In my opinion, the responsibility falls on whomever the user chooses to blame. Because when it comes down to it, the user does not care whose bug it is. They just want someone to fix it, or else heads will roll.

Case in point: the Facebook ACTION_SEND bug. To recap, the ACTION_SEND Intent is used to send arbitrary text from one app to another. Many apps answer the call – Facebook, Gmail, Twitter, Yammer – any app that may be interested in the text. Intuitively, Facebook should let you share the text as a status update, like Twitter and Yammer do, but instead they stubbornly interpret it as a URL. If it’s not a URL, then it barfs up an error.

In my own apps, I could have just used the ACTION_SEND Intent as-is. Let Facebook clean up their own mess – my hands are clean. But obviously, because I’m one man and because Facebook is Facebook, users would assume the bug is mine. So here’s what would have happened if I had chosen to ignore it: I’d get a ton of emails and Android Market comments saying, “Facebook doesn’t work” or “4 stars until you fix Facebook.”

Maybe then I would have smugly responded, “It’s Facebook’s bug, not mine.” Or, if the comments persisted, I might have added a popup saying, “Facebook doesn’t work correctly, due to a bug in their app. Please avoid Facebook.” But as we know from a previous post, users don’t read anything. So I would have continued getting angry comments and emails, and I would just have to respond to each one with a triumphant “Not my bug.” In the end, my users would be unhappy, but hey – at least I stood up for myself, right? At least I’d have the satisfaction of knowing I did the right thing.

But this is not the right thing. At least, not if you believe that the point of software is to make people happy. In fact, there’s a grand tradition of unsung heroes in software development fighting to fix the other guy’s bug. Once again, my main man Joel Spolsky explains:

[I heard this] from one of the developers of the hit game SimCity, who told me that there was a critical bug in his application: it used memory right after freeing it, a major no-no that happened to work OK on DOS but would not work under Windows where memory that is freed is likely to be snatched up by another running application right away. The testers on the Windows team were going through various popular applications, testing them to make sure they worked OK, but SimCity kept crashing. They reported this to the Windows developers, who disassembled SimCity, stepped through it in a debugger, found the bug, and added special code that checked if SimCity was running, and if it did, ran the memory allocator in a special mode in which you could still use memory after freeing it.

From Microsoft’s point of view, they had a good reason for doing this. If a customer was upgrading from DOS to Windows, and suddenly all their favorite applications stopped working, they’d simply return Windows. Microsoft would bear the fallout from the other guy’s mistake. Once again, the one who gets blamed is not necessarily at fault, but it’s still his/her responsibility to fix the bug.

In Android development the most infamous instance of this occurs in the Android Market, and it’s the bane of Android developers everywhere:

“Error 18” occurs when the Android Market fails to download and install an app. And even though it has nothing to do with the app itself, this is the most common bug you will see reported by users. In fact, I’ve gotten so many reports of this bug that I’ve created a “canned response” in Gmail that I send out to all of them. I believe I recall reading that Arron La, the developer of the immensely popular Advanced Task Killer app, has said that he simply stopped responding to these kinds of emails, because he gets too many of them.

The reason that poor schmucks like Arron and me get inundated with these emails is due to the interface of the Android Market itself. When an app fails to install, the user will be on this page:

It may as well say, “Direct your frustration here.” And from the user’s point of view, this is understandable. When they get an error like this, whom are they supposed to ask for help? Google is not famous for their tech support. So, as the quaint proverb goes, shit rolls downhill. And it hits the developer first.

So we’ve established that the one who gets blamed is not necessarily the one at fault. But how do you determine who gets blamed? In the case of SimCity, it was the OS rather than the app. In the case of “Error 18,” it’s the app rather than the OS. I think the explanation for this is pretty simple.

In the smartphone world, your OS is wedded to your phone, and your phone is very dear to you. Perhaps you even consider it an extension of your psyche, the way some people might feel about a trendy haircut or designer jeans. So if an app breaks on your phone, you return the $2 app, rather than the $500, psyche-extending phone. In Microsoft’s case, the OS was not very closely tied to the hardware back in the 90’s, and the cost of an application was about the same as that of the OS. Or at least, the gap wasn’t as steep as $2 vs. $500. So in that case, it’s Windows that would get returned.

I think this can all be generalized as follows. The bug becomes your bug if:

  1. You are more accessible than the other guy, e.g. they represent understaffed call centers in far-flung corners of the world, whereas you represent a single ordinary person (who must have oodles of spare time to answer emails, if you can afford to waste it writing apps).
  2. You are more visible when the problem occurs, e.g. the bug pops up when you use the other guy’s library, and that library is invisible to the user.
  3. You will suffer the most from the blame. I think this is the big one. Even if user opinion is split 50/50 on whose fault it is, it becomes your bug if the blame hurts you more than the other guy. The Android Market bug is an instance of this, since “Error 18” might cause a developer to lose a sale, but it hardly affects Google’s bottom line.

Unfortunately for me, even though the “Error 18” bug is my bug by this definition, there’s not a whole lot I can do about it. There’s nothing I can change in the software that would stop the error from occurring, or lessen the pain for the user. So the best I can do is keep responding to these emails.

Or, if I’m feeling snarky, maybe I can link them here.