QA: A Hillbilly Love Story

The hillbilly turned forty last weekend! I’ve been anticipating this giddily because now, I can be crotchety and people will still find me adorable.

I hate QA. Hate, hate, hate, HATE, HATE, HATE it. I despise it so much, I think I might swear. Here goes….I f—, okay, I can’t do it but I still &*%$ hate it.

Side note: I know all QA people weren’t raised by trolls and/or gnomes but I’m going to generalize here. If you are a QA person and were raised by evil leprechauns, please don’t take offense.

As a hillbilly, I’m an innately optimistic and forgiving person. An ugly codebase to me is a sea of opportunity. Changing requirements? Ha! I can put my OCD up against any client. I can see the positive in almost anything. Except the emotional rollercoaster that is QA.

The end of an iteration is a time of celebration and relief for me. We’ve completed a significant piece of functionality on our application. We’ve followed best practices, left the code in a better state than when we started, and all our unit and UI tests are passing (more or less). Despite my past experience, I always feel great pride when I email our QA department:

Hey there, QA folkery! I come bearing tidings of great joy! We have a new version of the application for your revelry and astonishment! You will surely faint in awe at the sheer glory of it! I await your token sign-off. Acclaim and praise is optional (but recommended).

After that, I sit quietly for a few minutes to bask in my accomplishment of the last couple of weeks then dive into the next iteration, the previous one long since gone from the vestiges of my memory by that point.

The inevitable spartan response:

Please fix the following issues:

  • As per the spec, emails should be sent every hour, not daily
  • I got a 404 error when I went to the public booking URL
  • I know this wasn’t part of the iteration but we should display a message when someone confirms an appointment. Can you include this?
  • You spelled it “cancelled” in the settings screen but “canceled” on the calendar.

Let me know when a new version is up for testing.

By now you will not be surprised when I tell you that the QA process was invented by the Marquis de Sade.

QA goblins: surely you see my point, no? The iteration is over. Done. Finished. There’s no “D.S. al fine” at the end. In my head, I’ve moved on to new functionality. I’ve got tests written and prototypes completed. And you want me to forcibly re-enter the past?!? All the hard problems from that iteration have already been solved. I don’t care about your “cosmetics” or your “functionality”. That new feature you’ve requested? It’s KILLER! It’ll be a great asset to the app. When I get around to it. That 404 error? Just a configuration issue. It’ll be fixed when we deploy. And come on, spelling mistakes? Have you not been on Facebook lately?

In short: I. Don’t. Want. To. Do. It. Right. Now.

And I have to. Virtually everything brought up in a good QA process is valid from bugs to cosmetic issues to features that we need to include this iteration even though they weren’t in the list. Without them, we don’t have a usable product. Users will complain or worse, move on to something else. But my headspace is somewhere else. I’ve already compartmentalized the new features and have shifted my own personal Eye of Sauron on them. Now, there’s leakage that I thought I’d never have to deal with again.

Another psychological issue: I’ve poured everything I’ve got to deliver the best that I’ve got and it wasn’t good enough. It’s never good enough. I’ve just presented to you the end result of four years of university training and over a dozen years of experience as an “expert in the field”. And with a few phrases, you send me slinking back to my computer to do it over again.

It’s a little strange in some regard. I welcome criticism of my coding choices and style. I actively seek it out sometimes. Even code reviews can be made fun again. But something about the subtly adversarial nature of QA raises my dander. The fact that it’s so essential to quality and that I can’t think of an effective way around it doesn’t help…

So to sum up:

  • QA forces me to change my headspace. And I hate it for that.
  • QA points out my flaws as a developer. And I hate it for that.
  • QA is necessary and makes software better. And I hate it most of all for that.

To end on a more positive note, most QA people (including those that do it as part of other duties) I’ve worked with are extremely nice. There’s no accusatory tone in the responses, no blame on messing things up, and no sense of “you’ve *really* screwing things up this time.” In their minds, they’ve done what’s expected and like me, have made the product better for it.

But I still hate you.

Kyle the Unadulterated

AppEngine thoughts

AppEngine has new pricing as of November 7 which has generated much discussion, most of it as exciting as deciding which wine to serve with raccoon (answer: dandelion). So with the segue established, I shall pontificate on what I believe is the single biggest thing keeping it from being a truly great product and a worthy competitor in the cloud hosting space.

First I’ll get out of my system a laundry list of lesser issues:

1. Confusing pricing/configuration

This could be a product of my Microsoft indoctrination but I still have trouble figuring out how we get charged for stuff. It’s based on usage which is a great model on paper. But the usage is broken down into, among other things:

  • Front-end instance hours
  • Back-end instance hours
  • Datastore Write Operations
  • Datastore Read Operations
  • Datastore Small Operations
  • Stanzas Sent
  • Channels Created

Furthermore, you can tweak things like the minimum and maximum idle instances and the minimum and maximum pending latency.

All this requires a lot of research and testing. And the only way to test various settings is: a) in production, or b) using load testing with a separate paid environment. Both of which will end up costing you dollars. But it must be said, those dollars probably won’t add up to the cost of setting up your own test environment. So far, we spend about the cost of a smoothie every day for all versions of our app (depending on where you find the ingredients).

2. No built-in backup and restore

Backing up the datastore is your responsibility. Now, it’s easy to set up a batch process running nightly. But you’re almost guaranteed to hit your Datastore Read Operations quota on a nightly basis once you reach a certain size. Also, the new-fangled High Replication datastore makes things more interesting by not actually being supported for this scenario.

3. No built-in reporting mechanism

The only way to interact directly with your data is through code you’ve written yourself or with GQL, a SQL-like language for the datastore. But you quickly hit limitations. First is that you can return only 20 results at a time (which you can up to 200 by adding a limit parameter to the URL…manually). Second is that you have to create indexes for fields you want to filter or order by. Doesn’t lend itself to ad hoc querying.

4. Admin console bugs

This one irks me almost as much as the fact that I have to restart my computer whenever I update the glorified Notepad that is Adobe Reader*. Too often for my tastes, when clicking around the console, I’ll get a general “An error has occurred” page. And the error page is not a pretty one:

AppEngine Error

Even when it does work, until recently, I had to use Firefox or Internet Explorer to access one of the pages (Datastore Admin) because it didn’t load in Chrome.

Which leads me to the point of this post. The one killer issue in AppEngine that keeps its status below world class hosting environment:

Support as a second-class citizen

If you didn’t notice at first glance, take another look at the error page above. In particular, at the URL at the bottom, which is where the “report” link goes. It’s a Google Group page for AppEngine. To their credit, they’ve addressed many of the issues I’ve had with Google Groups recently. Even so, for a world-class hosting environment and especially for apps we’re paying for, I’d much rather see something like this.

Also, my experience with the group site has been pretty dismal. Much of the time, my questions get not a single response except mine. Even reporting issues on the Google Code site leads to sporadic responses. I get slightly better averages on StackOverflow.

This came to a head for us recently when we converted from Master/Slave to High Replication late one Saturday night. Several of the problems I outline here occurred that night, including the 1990s error screen above. And I couldn’t find a single email address or phone number anywhere that I could contact for help and be assured of a response with even an unreasonable time. I made a post to the forum about an issue we saw the next day. The link is included above in the list of posts that have received no response.

Another support-related area that could use work is the roadmap. Something I’ve noticed in all dealings with the AppEngine team is an almost fanatical abhorrence of delivery dates. Even at Google IO, the best we could get from the team was “we’re working on it” with a lot of nervous laughter when someone asked about SSL access on custom domains, which is one feature we’ve had to make decisions around. I gather it’s a hard problem but even if they said “probably 2012”, that would at least indicate to us “okay, it’s not anytime soon, time to decide which is more important in the short term.”

Had I written this post a couple of months ago, after our High Replication migration, it would have been a lot more acidic in tone. For a few weeks after that, I was actively checking out Amazon Web Services. A Microsoft rep reached out to us serendipitously that same week wanting to talk to us about Azure. If he had not dropped the ball and postponed our meeting at least three times, this here blog thingy might be back in .NET land by this time. (Likely not, but this wouldn’t be a blog if I didn’t make grandiose unsupportable claims that back up my argument.)

These days, I’m not so much frustrated as I am disappointed. For all its faults, there is a lot of good being offered by AppEngine. Like many startups, we have discussions about Google and AppEngine and the general consensus is that we’re happy to see Google focusing on what they’re good at. (Do I lose points if I point out my subtle digs?) But when it comes to support for AppEngine, it feels like it’s run by engineers for engineers. Yes, support is boring and customers can be confrontational and much of the time, the answer is a variation of “you’re doing it wrong”, etc, etc. But it’s not just another IoC container; it’s a cloud hosting platform. I believe it needs a higher level of professionalism than what I’ve seen so far.

Kyle the Untenable

*But not nearly as much as the fact that said update invariably adds an icon to my desktop. C’mon Adobe, who actually opens Reader and then opens a PDF file?

Deploying a new version of a GWT app

For the record, I’ve never even been offered a Microsoft MVP. How’s THAT for street cred! That said, if the MVP lead in my area is reading: even though I don’t speak at user groups these days and hardly blog (and even then, rarely about Microsoft products anymore), I still feel my lack of contribution to OSS projects should count for something…

Now back to our regularly scheduled hoe down.

One of the issues with GWT apps that’s only really discussed in hushed whispers in the back alleys of Google Groups is how to handle new versions. The nature of pure JavaScript applications is a bit of a hindrance in this case.

When converting a Java application to the necessary Javascript, GWT generates (depending on your set up):

  • a .nocache.js file
  • several .cache.js and .cache.html files
  • several .gwt.rpc files

The .nocache.js file has the same name every time you compile. But if you’ve changed any code, the cache files and rpc files will not. Here’s what my folder looks like today for BookedIN:

FolderStructure

 

The next time I GWT-compile (provided I’ve changed some code), the folders and files in red will be deleted and replaced with new ones with different names. Among other things, the scheduler.nocache.js file is used to locate these files on demand while the app is running.

We use the Google Plugin for Eclipse to deploy our application to AppEngine. We almost always deploy to a new version in AppEngine as well so that we can play around with it ourselves before unleashing it on an unsuspecting public. The upshot of this process is that the new version will have new .cache and .gwt.rpc files but not the old ones.

So let’s run through a potential scenario:

  • Our faithful user logs into BookedIN and uses the default version, which I will call “Dandelion”.
  • We deploy a new version, called “Thistle” and make it the new default version
  • The user makes a request for a page that is, say, behind a code-split. One of GWT’s nice optimization features that lets you split JavaScript among several files and loads them dynamically as needed.

At this point, the user has the main page and the .nocache.js file loaded in memory. When it tries to satisfy the request, it will look for a .cache.js file from version “Dandelion”. Only by refreshing the entire browser page will it then load the new .nocache.js file, which knows about version “Thistle”. But this being a GWT-type, AJAX-ified application, there is rarely much call for them to refresh the entire page.

Predictably, we get a 404 error:

404

This leads to some pretty nifty dancing when it comes to deployment time. For example, how do you take down the application for maintenance cleanly? If the user has the page loaded in memory and is just making AJAX calls, you can’t just throw up an appoffline.htm file and redirect all your traffic to it (says the guy who thought differently a few short months ago).

Even if you can take the app down for maintenance, I don’t want to. We’re trying to shorten our deployment cycles which doesn’t lend itself to a page that says “hey, paying user, we’re adding some cool new features so pardon us interrupting you using the old ones” every week even if it just shows for a few minutes. In short, what I’d really like is a hot deployment.

Based on my research and questions, this isn’t 100% possible but we can get close. As was suggested in the previous link, we can trap the appropriate exception in RPC calls and display a message to the user asking them to refresh their browser. Similarly, for code-split .cache.js files, we can trap the 404 error in the onFailure of the RunAsyncCallback (or better yet, use GWTP and have some main presenter implement AsyncCallFailHandler to make this easier) and do the same thing: notify the user that the page needs to be refreshed.

Initially, this kind of left a bad taste in my mouth. But from a marketing perspective, it’s not bad. We have a little popup that we display to users when we’ve implemented something new so this is a nice way to ensure they see it.

Another suggestion that was made (by one of the creators of GWTP, no less) was to use the Channel API to detect when a new version has been released. This has an advantage in that you don’t need to wait until the user does something before informing him of the change.

So far, much of this is theoretical for us because it was easier to write about it than to actually implement it. In any case, few people seem to be discussing it. Besides which, I had to write *something* after that little Microsoft MVP commentary.

Kyle the Filler

Staying home for the night

&*%$ you and all of your @#*!% opinions! There, now that I’ve established my credibility, let’s get started.

I installed Ubuntu recently on a virtual machine. It was insanely easy. Pointed VMWare at the .iso then went back to entertaining myself reading comments on the local news site (http://tribune242.com; I like it because it reads like an offshoot of The Hillbaley Ho Down and Extravaganza).

Tips for people starting on Ubuntu coming from Windows

I jest. Just want to screw with the people who are skimming the headings. Last thing I want to do is claim any sort of proficiency with Linux.

It did get me thinking on the last year and a half with BookedIN though. Since starting this venture, I’ve learned (to varying degrees): Java, GWT, AppEngine, Ruby/Rails, Mercurial, Git, Eclipse, and now, Linux. I had experience in none of them at the start.

The benefits of looking outside your world

Ha, ha, I’m kidding again. We all know it’s a profoundly moving and religious experience to expand your horizons, even if all you get out of it is a vaguely pretentious blog post.

I’m actually going to discuss the opposite view: the benefits of sticking with what you know. I still do some .NET work on the side. It’s not a particularly complicated project, which is why I like it. And after a long day debugging issues with GWT hosted mode and figuring out the proper Cucumber syntax to use for a UI test and trying to massage our AppEngine data in all its NoSQL-ness, it’s comforting to open up Visual Studio and SQL Server Management Studio and whip out code almost without thinking. I know how to wire up the IoC container and once Fluent NHibernate has been wined and dined with all its conventions, it puts out like a two-dollar wh—<ahem>…yes, well, let’s just say Fluent NHibernate is a well-used piece of software at the hillbilly’s shack, let me tell you…

I’m dancing a little jig on a fine line here in my wording because it’s fun I don’t want to imply that I regret moving away from .NET for our project. It’s kind of like moving to a new country. Yes, the weather may be better but gas is also over five dollars a gallon and the power goes out once a week. In short, you substitute one set of problems for another.

I get the question “would you write your next application in .NET?” fairly regularly. My honest answer is “I have no idea.” I wouldn’t shy away from it, that’s for sure. If it was for a company whose staff consists almost entirely of C# developers, then yes, probably. If it was some little thing to help organize the schedule for my daughter’s soccer team, I probably would too. Because I could do it quickly.

(I could also use the opportunity to learn a new language. But I get enough of those opportunities running the development process of our company. These days, unpaid side projects are ones I’d like to get off my plate quickly.)

I guess my ultimate point is that going with what you know has its place. Not necessarily for long-term career satisfaction, mind you, but there is a certain satisfaction to being able to fly through a codebase without having to look up syntax for some new library. As long as you aren’t using it as an excuse to remain stagnant…

Kyle the Tap Dancer

Hurricane Irene

Herein lies my account of our encounter with Hurricane Irene. It’s late coming for reasons I’m too lazy to get into (which, now that I think about, is the reason it’s late).

First order of business is to dispel some myths. We live near Nassau which did not get hurricane force winds. They reached 60 – 70 mph sustained with higher gusts. Even still, I’ve heard reports that there were 180mph winds going on, even in the eye, which sounds like journalism by way of the Chinese Whispers game. Closer to the eye, the winds hit about 120mph sustained while Irene was passing by us. That’s still plenty fast enough to cause major destruction but it still pays to get the facts straight.

As natural disasters go, hurricanes are fairly sporting. They give you plenty of notice before they bring down the fist of pain. Similarly, there’s a reason you don’t see a lot of coverage about hurricanes in the Bahamas on CNN. As a general rule, most Bahamians don’t pick that time to start seeking out their fifteen minutes of fame by going all Chicken Little when someone sticks a microphone in their face and asks them “what do you think of the oncoming hurricane?” They just do what needs to be done, ride it out, clean up, then go back about their business. No mandatory evacuations, no public emergencies, no national guards, and certainly no sense of “why is this happening to us?” But enough thinly-veiled social commentary.

We spent part of the day Tuesday and all day Wednesday prep20110824_154640aring. We just got hurricane shutters in July and they are easy to set up. The majority of the time is simply making sure everything that could blow away is either secured or moved indoors somewhere. Not particularly hard or back-breaking work but it does take time and start wearing you down.

(The boarded up door in the picture is mostly precautionary. The door is too big for the same style hurricane shutters. The glass is impact-resistant but we had the plywood so we threw it up anyway.)

As for the storm itself, it wasn’t as exciting as one might think. We hunkered down in Syd’s room on Wednesday night and the power quit around 2am. Our generator wasn’t set up yet (just got installed a couple of weeks ago) so we lived like the pioneers did, assuming our forefathers had concrete walls, hurricane shutters, and gas water heaters and stoves. And Monopoly.

The shutters worked as advertised. So much so that we were able to open the windows they were protecting to get a much needed breeze.

By mid-afternoon, winds had died down a little and we ventured out to assess damage. Some trees knocked over and about 8 or 10 shingles missing (well, not technically missing; we later discovered them in the pool). Jake and Syd played superhero in the wind for a little while and we went back inside. Played some board games and built a fort out of sofa cushions and did other lo-fi activities.

In the evening we invited friends over and we barbecued some wings and cooked perogies. We have a small courtyard out front that’s protected on all sides and we ate out there, although still under the roof since it was still fairly blowy and rainy.

Later in the evening, tempers started flaring a little since we had to go to sleep and it was fairly hot out. Then lo! the electricity came on! We all praised Zeus, the god of lightning and, by association, electricity, flipped on the AC, and went to bed.

All in all, it ranks as a pretty entertaining camping trip.

Final thought: Be careful when you use the phrase “come hell or high water.” Mother Nature will test you.

Designing an API: Is JSON/JSONP an and/or decision?

Executive Summary: Who knew serving up JSON for a public API was so complicatedly anti-hillbilly?

Our next major phase at BookedIN’s plan for world-except-for-Edmonton-and-certain-parts-of-Winnipeg-domination is underway. That’s the public-facing site which will provide a way for YOU THE PUBLIC to book appointments online at your favorite…ahem…”service providers”. Let me explain our marketing strategy in detail.

Ha ha, I jest of course. That last sentence is the sum-total of what I know about our marketing strategy. I have a hard enough time trying to keep myself entertained through code. (As a general rule, I start with the code reviews.)

To populate the public site, we’re building an API around our appointment manager. And we’ve opted for JSON as the default format mostly because I love the way people pronounce it, accenting both syllables like badly-translated anime.

In this app, we are making two API calls from two different places. When we first load the page for a particular vendor, the server makes a call to retrieve the company details and a list of services. After the page is loaded, jQuery comes along and populates the list of available appointment times. In this way, we get the benefit of SEO being able to see the vendor details and services and a snappy user interface when the user navigates to different dates within a particular vendor page, as outlined on the Google Webmaster Blog (though I didn’t actually discover that link until after we decided on the structure).

In the appointment manager, serving up JSON is pretty simple. Configure the servlet, get the data, convert to JSON (we’re using Jackson), and write it to the response. This is working just fine with the server-side company details call.

For the client-side call, it’s not. Depending on how you configure the AJAX call in jQuery, we get one of the following:

  • The API call is never made
  • The call is made but cancelled
  • The call is made and returns but has no data

All are symptoms of the same issue: cross-domain client calls, which aren’t allowed. I read that it’s for security reasons which, due to my loathing of all things security-related, was enough for me not to read further.

From what I can tell, you can’t make a call to another domain in jQuery (or likely any JavaScript library) and expect to get JSON back.

Here’s an example. Follow this link in your browser: https://github.com/api/v2/json/repos/search/zenboard

You’ll probably get this back:

   1: {"repositories":[{"type":"repo","username":"amirci",

   2: "url":"https://github.com/amirci/zenboard","watchers":5,"owner":"amirci",

   3: "has_wiki":true,"open_issues":0,"score":6.994658,"followers":5,"forks":3,

   4: "has_issues":true,"language":"Ruby",

   5: "description":"Companion to agile zen to provide extra calculations and functionality",

   6: "pushed":"2011/05/18 15:25:54 -0700","fork":false,"size":1348,

   7: "created_at":"2010/11/24 17:28:33 -0800","name":"zenboard","has_downloads":true,

   8: "private":false,"pushed_at":"2011/05/18 15:25:54 -0700",

   9: "created":"2010/11/24 17:28:33 -0800","homepage":""}]}

(Side note: If you’re using AgileZen, the project above, ZenBoard, is an awesome companion for it.)

Now let’s try this in jQuery:

   1: function loadProjects( ) {

   2:     var url= "https://github.com/api/v2/json/repos/search/zenboard";

   3:   $.ajax({

   4:       url: url,

   5:       type: "GET",

   6:       dataType: "json",

   7:       success: function(data) {

   8:           alert( 'moo' );

   9:       }

  10:   });

  11: }

Throw this into a $( document ).ready call, load it up, and you get nothing. The browser developer tools give you a vague hint of what’s going on:

CanceledRequest

 

The request to the GitHub API was canceled. Let’s make one small change to the JavaScript:

   1: function loadAppointmentTimes( ) {

   2:     var url= "https://github.com/api/v2/json/repos/search/zenboard";

   3:   $.ajax({

   4:       url: url,

   5:       type: "GET",

   6:       dataType: "jsonp",

   7:       success: function(data) {

   8:           alert( 'moo' );

   9:       }

  10:   });

  11: }

The only difference here is the datatype is now jsonp instead of json. Load this page up and we get a hearty “moo” alert.

But take a look at the headers for the request we’ve made:

RequestWithURL

There’s an extra parameter: callback. jQuery added this. Furthermore, here’s the response:

RequestWithResponse

This ain’t quite JSON. It’s a JavaScript function call wrapped around JSON.

The upshot of all this: When you make a request without the callback parameter, GitHub will give you JSON. Unless it’s called from JavaScript from the browser in which case, I believe it’s the browser itself that says “Papa don’t preach that way” and cancels the request completely because it won’t allow JSON to come back.

But when you tell jQuery to make the call as jsonp, it adds an auto-generated callback parameter (I believe you can specify the name of the callback if you so desire). GitHub is nice enough to adjust the response accordingly, wrapping the JSON in the callback function. Further, jQuery is nice enough to strip it off again and give you back the intended JSON.

When I first started this post, it was going to lead up to this point where I ask: When creating an API, is it normal to support both JSON and JSONP requests? In fact, I ask the question in the title already.

But it appears the answer is yes based on other services that offer APIs. Obviously, GitHub supports it. So does BitBucket, Twitter, AgileZen, and Flickr (though Flickr uses a different callback parameter name). So…thanks for listening, I guess…

Final Note

JSONP (and any cross-domain request, I believe) is read-only. I.e. it supports GET requests only, not POSTs (or PUT or DELETE, I suppose). The odd thing is: I only discovered this today while researching for this post. This baffles me because I’ve been doing AJAX apps since 2000 and don’t remember ever having to deal with this. I suppose they were all same-domain, or if they were cross-domain, it was read-only. Anyway, score one for blogging because within the next two days, we would have run into this very issue at BookedIN and wasted a bunch of time tracking down the cause.

To get around this limitation, there are two options (probably more depending on how academic you want to make your research):

Use a proxy

That is, you POST the request to the same domain and in the server code, forward the request on to the other domain. Remember, cross-domain security is only on the browser.

Cross-Origin Resource Sharing

This is new to me as well as of this morning. It’s a draft specification to get around the same origin policy I’ve spent these many minutes describing. I know nothing about except what I read on AgileZen’s documentation. The salient points are:

  • Works on newer browsers only. This essentially translates into “Works on IE8 or higher and all versions of Firefox and Chrome that you’re likely to see in the wild.”
  • May have issues with some proxy servers and firewalls

This was enough for us to postpone this route to a future release when our client isn’t the only one for the API.

Kyle the Applicable

An Apple Account, or “How to try to change your ways”

Thanks to Scott Hanselman for reminding me why I don’t like security. Or to be more accurate, I hate that we need security. I don’t like reading stories like this. Discolours my view of the world. Hillbillies ain’t what you might call a pessimistic lot.

Alas, we are still realists. So after reading Scott’s carefully worded tale, my first reaction was to head on over to the Apple store and reset my password. The fact that I’m blogging about it should indicate that it didn’t go well but we’ll started with background.

I’ve always hated the Apple Store. The authentication confuses me to no end. Early on, I addressed this by not making as many purchases as I normally would. Mostly because I often can’t so I’ve stopped trying.

The first sign of trouble was when my credit card expired. I got a replacement but was never able to enter it in. It didn’t like the Bahamian address. That’s understandable, many companies don’t. I have a replacement card with a US address for exactly this purpose. But for whatever reason, it always said the postal code was wrong. I called my credit card company to verify it and checked statements and it was exactly as I entered. I even called Apple support though that was over a year ago and I have no recollection of the experience. Issue has never been resolved.

At Christmas, I set my daughter up with an Apple account of her own for her shiny new iThing. Entered my credit card info (the US card) for her account and it went through with no issues. (She doesn’t know the password which gives me a false sense of order.) She doesn’t install too many apps anymore because she’s gotten tired of my swearing whenever I have to go through the authentication process.

You see, because we don’t install apps very often, the Apple terms and conditions have usually changed since the last purchase. And the process to purchase an app in this case is (from memory):

  1. Select the app I want to install
  2. Enter your password
  3. Click OK on the notice that says I have to accept the new terms
  4. Accept the new terms
  5. Re-purchase the application
  6. Re-enter my password

(Side note: my password requires several shifts between various symbol sets on the iThing keyboard.)

(Side note 2: This process is identical if I want to update an already installed application.)

But this isn’t what I set out to talk about today. Back to changing my password.

First a minor quibble. I went to Apple.com to change my password and saw nothing to indicate where to do this. No “My Account”, no “Log in here”. I did find it relatively quickly by clicking “Store” but it was a toss-up between that and “Support”. “Store” appears first in the menu.

I still noticed a lack of “Here’s where you sign in” but the “Account” button was suspicious. I clicked it and discovered I was already logged in. I can’t remember the last purchase I made on the Apple store website. I don’t think I ever have. Maybe it’s linked to iTunes on my computer in which case the answer is “sometime in spring”. That’s a heckuva cache in any case.

Next link (I’m not doing screenshots because I’m too lazy to blur out the sensitive information) was “Change account information” wherein I discovered that I wasn’t actually logged in; my daughter was. “No matter,” says I, “a good opportunity to change her password as well.” And at the same time, I decided to remove the credit card attached to her account.

Except there’s no obvious way to do this. It should the credit card attached to her account and there are options to change the card number or type but nothing to say “Remove this card” or “Forget this information” or even “None” in the list of card types.

I figured out fairly quickly that you can do it by removing the card number. So here was my process:

  1. Delete the card number
  2. Click Continue
  3. Receive error message to enter my password
  4. Enter my password twice and press enter
  5. Receive error message to correct messages in red
  6. Scroll to the bottom and select a value for “Where will you primarily use the product you are purchasing?” because it is imperative Apple knows this
  7. Click Continue
  8. Receive error message to enter my password
  9. Enter my password twice and press enter

At this point, I’m starting to realize just how many chances a potential hacker has for sniffing out my password.

(Side note: My shipping address is a PO Box. This is apparently not allowed and I would randomly get validation messages to correct it. But sometimes not. At the time of writing, my shipping address remains a PO Box.)

With the credit card successfully removed, I was now able to start the task of changing the password on my own account.

This remains to be done and I will give it another try as soon as I click Publish. The primary impediment is that there is no Sign Out button anywhere that I can see on the Apple store. When I click Account, it helpfully reminds me that I’m logged in as my daughter. But there is no “not Syd?” link the likes of which you see on Amazon, Go Daddy, and my plumber’s website that hasn’t been updated since the late 20th century.

This appears to be a frequent issue based on my search. I’m not going to clear my cache or cookies because I want to try to solve this in a way that I can relate to my mother over the phone when she inevitably tries to do the same thing.

The lack of an obvious “I want to leave your store” mechanism was the final straw that broke my “I can’t be bothered to blog about this” camel’s back. That’s where things stand now. For all the gripes I made about the Samsung Tablet, I must admit that I don’t have similar issues with it in this regard.

What makes this even more frustrating is that this is Apple of all companies. They’re all about user experience and yet they have bungled what, in my mind, is the most important part from their perspective: a customer wants to give you money. Or a customer wants to update their account to make it easier to give you money.

Kyle the Insecure

Samsung Galaxy Tab 10.1 Review

I got one of these at Google I/O. Despite it being generally inferior to an IPad (more on that later), I like it quite a bit. There are two main reasons. The first is home screen widgets. These are widgets you add to the screen that tell you more information than “you have 13 unread messages” or “3 people pinged you on Facebook”. The GMail widget, for example, lists the unread messages. The calendar widget shows your upcoming appointments.

But that’s the main reason I like it. Because it’s so inferior to the IPad, my wife and daughter aren’t constantly borrowing it so I don’t have to fight for its use.

Why is the IPad better?

Try them both for a month and you’ll see. And that doesn’t mean try them with a thinly veiled bias toward Google because Apple is not developer-friendly and is closed and uses unfair business practices, etc, etc, and so on and so forth. Just use the two devices and tell me which one is better. If you still believe the Android is better, then we simply disagree and neither of us will convince the other.

That said, here are my reasons.

First, the IPad is just generally more intuitive. The first thing you see on the IPad is “Slide here to open”. On the Android, two concentric circles. If I recall correctly, you need to slide the inner one outside of the outer one to get it going.

Second is the design of the device. The power button and volume buttons are positioned in such a way that they are easy to hit accidentally, depending on serious an Angry Birds player you are. Furthermore, in lieu of a big black home button baked into the hardware, there are three omnipresent software “buttons” in the bottom left of the screen. These represent, in order, “Go back”, “Go home”, and “Show some recent apps”. The last ones a little iffy. I know Android allows apps to run in the background so it may be all the currently running apps. Regardless, the positioning of these buttons, which is right below the keyboard when it’s on-screen, has led to a great deal of frustration.

There’s another thing about the black button on the front of the IPad that you don’t really notice until you’ve played with the Samsung tablet. When you look at an IPad, you know where the power button is based on where the button is on the front. Not so with the Samsung. There is but one feature on the front that can help you orient yourself. That’s the camera lens which is very easy to miss at a glance. More often than not, I pick the thing up and run my hands along all sides looking for the power button.

There are several other little things that add up to a general sense of annoyance. First is the fact that the screen will dim at odd times. Normally, it will dim when not in use but not always. Sometimes, it will happen when I’m in the middle of doing something on it.

Next is a general philosophy thing with Google, I think. Here’s an example: when the keyboard pops up, there is a little keyboard icon in the bottom left. Clicking it displays four options:

  • English (US) keyboard
  • English Voice
  • Samsung keypad (selected by default)
  • TalkBack keyboard

There’s also an option to Configure input methods.

This, in my opinion, is configuration by committee. As far as I know, Apple offers a single keyboard configuration. If they don’t, then the default one works great and I’ve never had to seek out another. On the Samsung, I tried all of these when I first got it, then switched back to the default one. Now, three months later, I can’t even remember what the TalkBack keyboard was. What I do remember is that each and every one of these options had their own individual set of settings.

The predictive text is another example. First, it’s not on by default. This is good because turning it on leads to another set of options. Do you want word completion? Next word prediction? Auto-substitution? I have no clue because all of those sound the same to me. Other cryptic options include: Recapture and XT9 my words.

The implementation for predictive text is also cumbersome. As you type, suggestions appear above the keyboard. Often, the application above it will jump up and down as the suggestion bar appears and disappears. This, to me, is just plain sloppy user interface design. An even worse design: One of the input settings I mentioned above was auto-substitution. Inexplicably, this is active in password fields. That is, whatever you’re typing in a password field, it will auto-substitute what it feels you should be typing. And because it’s a password field, you can’t actually see what’s being substituted in.

I have a laundry list of other minutiae but I’ll wrap up with something more substantial. I’ve had to reset the device twice in the three months I’ve owned it. The first time, I kept seeing “Entering upload mode. Upload_cause: unknown” when I’d boot up. I was still able to boot up so I did the ol’ factory reset from somewhere in the sea of settings.

The second time, I hit what I’ve now discovered is a known issue called the “boot loop”. That is, the initial boot splash screen would keep playing over and over again in an infinite loop. (Infinite being relative to my patience, of course.)

Solving this involved:

  • Installing USB device drivers
  • Installing the Android SDK
  • Downloading a recovery image from a sketchy website
  • Booting through the USB drive
  • Reinstalling the recovery image

The quotes are because I don’t know the actual terminology. Searching for this problem led me on a merry journey involving terms foreign to me like “rooting” and “fastbooting”. As with the tablet itself, there are at least three options you can use to access your tablet from your computer: adb, fastboot, and PDANet.

Determining which one to use and how to use them is not the sort of thing I’d ask my wife to do. The information is strewn across message boards and very often, the poster assumes the reader is not of the “I don’t want to recompile the thing and hack around; I just want my &*%$ tablet back” ilk.

 

So overall, if I were to choose, I would definitely go with an IPad. There are some nice features in the Android but they’re not quite there. It doesn’t appear the same thought to the overall user experience was part of the process.

Using AppEngine (and GWT) in a CI environment

These days, I don’t much like being ahead of, behind, askance of, or otherwise deviated from the curve. It used to fit well with my “blogging as a nervous tic” phase of early 2008 when I would apparently post about every single compiler error I encountered and how it fit in with the grand scheme of things. But now, I prefer my technology much more Google-able. Does wonders for my productivity. In light of that, this post will fall into the category of “interesting to people searching for a solution to a specific problem.”

And so it was that I went and tried to set up a CI process for our brand spankin’ new AppEngine app. (Note to you fetishists: I didn’t mean that “spankin’” part literally. I don’t pretend to understand the euphemisms I use; I just use ‘em.)

It’s essentially the same process we’re using for our main BookedIN application, which also runs on AppEngine:

Compile and test configuration
In this configuration, we just compile the app and run the unit tests.

Run stable UI tests
This configuration performs the following steps:

  1. GWT compile the application. I.e. do all the magic that converts Java into JavaScript
  2. Launch GWT hosted mode
  3. Launch the UI tests

The first two steps are performed with Ant. The last is in Rake because the UI tests are done in Capybara. We launch hosted mode first because it’s orders of magnitude faster to run UI tests against a local hosted mode version than it is against a deployed version, especially because we clear out the datastore before each scenario.

Let me add some foreshadowing here. In order to launch hosted mode, we use the following Ant target:

   1: <target name="devmode" depends="" description="Run development mode">

   2:     <java fork="true" classname="com.google.gwt.dev.DevMode" 

   3:         dir="${basedir}/war" spawn="true">

   4:         <classpath>

   5:             <pathelement location="src" />

   6:             <path refid="project.class.path" />

   7:             <path refid="tools.class.path" />

   8:         </classpath>

   9:         <jvmarg value="-Xmx512M" />

  10:         <jvmarg value="-javaagent:${appengine.folder}/lib/agent/appengine-agent.jar" />

  11:         <jvmarg value="-Duser.dir=${basedir}/WAR" />

  12:         <arg line="-war" />

  13:         <arg value="${basedir}/war" />

  14:         <arg line="-logLevel" />

  15:         <arg value="INFO" />

  16:         <arg value="-server" />

  17:         <arg value="com.google.appengine.tools.development.gwt.AppEngineLauncher" />

  18:         <arg value="net.bookedin.bam.BAM" />

  19:     </java>    

  20: </target>

Note that this is running dev mode using the AppEngine agent. So it’s mimicking AppEngine behind the scenes…somehow.

The key to this target is where it says: spawn=”true”. This allows TeamCity to launch DevMode, then continue about its business. I.e. it doesn’t wait until DevMode is finished.

Run flaky UI tests
We have this one in here just because I got tired of seeing three or four failing tests every single time and 95% of the time, it was because of a PayPal sandbox issue. Having these “sometimes they work, sometimes they don’t” tests in a separate project means I can take the ones that fail in the previous configuration that much more seriously. All this configuration does is run UI tests that are tagged as @flaky.

Kill DevMode
We have this as a separate configuration because we want it to run always. If it’s set up as a separate step in one of the previous configurations, TeamCity won’t run it if any of the UI tests fail. As a separate configuration, it can be set to run after any build of “Run flaky UI tests” regardless of whether that build succeeded or failed.

The new app…

…is in AppEngine also but not in GWT (for reasons outlined elsewhere). Which I thought meant this would be easier. Alack, this is not true.

There is a corresponding “dev mode” in AppEngine. But it can’t be launched and then forgotten like it can in GWT. Here’s the target we originally had for it:

   1: <target name="runserver" description="Starts the development server.">

   2:     <java classname="com.google.appengine.tools.development.DevAppServerMain"

   3:           classpath="${appengine.tools.classpath}"

   4:           fork="true" failonerror="true">

   5:       <jvmarg value="-javaagent:${appengine.folder}/lib/agent/appengine-agent.jar"/>

   6:       <arg value="--address=localhost"/>

   7:       <arg value="--port=8080"/>

   8:       <arg value="war"/>

   9:     </java>

  10: </target>

Same idea as the previous one but with a different class name. And unfortunately, you can’t add spawn=”true” to this one. Something in DevAppServerMain doesn’t like spawn=”true” and complains. So if you run this target, it sits there and waits until you forcibly stop the server, either with Ctrl-C or something like the following (in another command window):

   1: <target name="stopserver">

   2:     <property environment="env"/>

   3:     <exec executable="${env.JAVA_HOME}/bin/jps" output="pid.out.file"/>

   4:     <loadfile srcfile="pid.out.file" property="pid.out">

   5:       <filterchain>

   6:         <linecontains>

   7:           <contains value="DevMode"/>

   8:         </linecontains>

   9:         <tokenfilter>

  10:             <deletecharacters chars="DevMode"/>

  11:             <trim/>

  12:             <ignoreblank/>

  13:         </tokenfilter>

  14:           <tailfilter lines="1" />

  15:       </filterchain>

  16:     </loadfile>

  17:  

  18:     <echo>Killing appengine java process with PID - "${pid.out}"</echo>

  19:  

  20:       <exec executable="taskkill">

  21:           <arg value="/f" />

  22:           <arg value="/fi"/>

  23:           <arg value='"PID eq ${pid.out}"'/>

  24:       </exec>

  25:  

  26:     <delete file="pid.out.file"/>

  27: </target>

Side note: Both of these were modified from a comment thread on an open issue with AppEngine, whereby the default “runserver” command leaves a stray java.exe process running after you kill it.

This method works pretty well for local development. But won’t for a CI configuration that’s relying on the “runserver” target to give up control so it can “run UI tests”.

“No matter,” says I, “surely someone else has discovered just how much money and time is saved through the practices of UI testing and CI and is doing the same on an AppEngine application.”

If you just chuckled to yourself at my apparent naïveté, then same on you! I remain convinced that such a person does exist. He or she just doesn’t like to brag is all…

A post to the AppEngine Google Group led to my eventual solution which came in the usual manner: it occurred to me during the process of typing out my question and/or one of my responses.

The basis for the solution is: the process works for GWT DevMode with an AppEngine agent; so run the app in GWT DevMode with an AppEngine agent.

Setting up a minimal GWT environment for the app was simple:

  1. Add gwt-dev.jar to my classpath (just for the purpose of testing; it’s not included when we deploy the app)
  2. Create a stub .gwt.xml file
  3. Use the same Ant target above to launch the app in DevMode

The stub .gwt.xml file is an almost empty XML file:

   1: <?xml version="1.0" encoding="UTF-8"?>

   2: <module rename-to="gunton">

   3: </module>

DevMode complains if this file is missing. The location is defined in the final argument of the <java> task in the Ant target.

That’s it. We can now launch our AppEngine app in a CI environment and it won’t wait for the user to forcibly close dev mode. We can use a similar “kill dev mode” target to stop it when the UI tests are done. Our one remaining issue is that “kill dev mode” target. At any given time, we may have up to two DevModes running on the CI server: one for the GWT app and one for the AppEngine app. When we go to kill them, we can’t distinguish one from the other…

…which leads to one final footnote. We’re using Windows. If you are using Linux on your CI server, much of this would be moot, as was suggested in the thread. From my understanding, you can more easily run a process in the background, in which case, you can go with the original “runserver” target. Which also makes it easier to figure out which process to kill when the UI tests have finished.

Kyle the PID off.

Deciding on technology? It depends, or “How to GWT off”

Humble apologies to those who were on Twitter the evening of Independence Day. I celebrated the birth of the US by converting my personal blog to WordPress and the Twitter plugin wanted to get acquainted with its new home by announcing every single post I’d written, including ones you won’t find on CodeBetter.

Executive summary: We’re building a public directory/search portal for BookedIN and need to decide whether to continue with GWT for it.

I love summer. Not because of the weather mind you because down my way, the mercury wavers tauntingly between “Mabel, where’s my mesh shirt?” and “Whoa now, didn’t realize projectile sweating was so well…documented”. The reason I like summer is that, much like “weekend” and “daylight”, it’s a one-word excuse for any lack of productivity. Haven’t kept up with blogging? What can I say, my kid’s off. Need to repair the roof? Sorry, hon, summer’s wasting. Are you thinking of showering in the near future? Pffft….sum….MER!!!

Alas, for whatever reason, I’ve taken on a position of responsibility over at BookedIN. So far, we’ve built a kick-ass appointment manager. Next step: the public search engine. Wherein we must now return to the question: What do we build it in?

All languages are on the table. .NET, of course, because I wouldn’t be the hillbilly you read before you today without it. Ruby because I want to see if my natural self-effacing nature can withstand the onslaught of being encased in such a hipster. Python, Go, whatever language the kids speak on Facebook (it looks more like a programming language than English). All are contenders to varying degrees.

GWT with an AppEngine back end is our obvious first choice given that the existing app is built on that (concerns about the readability of the resulting code, notwithstanding). It’s been good to us the last year and a half. There were some initial issues when dealing with UI tests but after overcoming those in the beginning, our development process with it is sharper’n a thumbtack slurpee.

There have been recent discussions on web sites vs. web applications that I’ve been reading more than any one hillbilly ought to. Specifically about SEO and Ajax sites. One of our big selling points will be searchability for our clients. The idea is: you search for “farm animal massage in Atlanta”, you should get “Perry’s Pig Patdowns” with a list of available appointments for good ol’ Wilbur and Napoleon.

So I’ve been researching on searching. How does GWT handle SEO? On the one hand, our favorite GWT framework, gwtp, supports crawlability. But alas, the answer is still not so clear-cut. Forthwith is the state of the world as I believe it to be vis-à-vis searching Ajax sites.

Summary: It’s hard.

Google has a spec that it’s using to crawl Ajax-heavy sites. This is the quietly famous #! (and to make this post more searchable, we include the actual name of the symbol: NumberSignExclamationPoint) tag that has made its way to Twitter, Facebook, Gawker, and Lifehacker. When Google comes a-crawlin’, they see this symbol, convert it to something else and request that page. As far as I can tell, your server should be able to convert that back into a #! page and serve up the corresponding HTML for it.

There have been quite a few complaints about whether this breaks the web and is a big hack and whatnot. (Start here and follow the links.) This makes for interesting reading but for the most part, doesn’t help me decide whether or not GWT is a good fit for an appointment search portal. Except for one post (and more importantly, the comments) from the inside of Twitter.

I’ve distilled much of my research down to what appears obvious in hindsight: If you’re building a web application, like GMail or Twitter or, indeed, or BookedIN scheduling application, GWT is a good fit. It helps you build a compelling user experience on the web. And if you want crawlability, that’s possible, though convoluted.

But if you’re building a web site, like Gawker or our upcoming public booking portal, it’s better suited to more traditional technology like JSP or ASP.NET with a sprinkling of Ajax to enhance certain features. People aren’t looking for fancy page transitions on a news site or search portal, I don’t think. They want to find the information they need, then leave.

Side note: Virtually ignored in all of this discussion is: what about other search engines? Do they support #!? I still haven’t been able to figure that out yet. As far as I can tell, this is a Google specification and only they support it.

The net result is that we won’t be using GWT for the public site. SEO is too important to our prospective clients for us to have to jump through a lot of technical hoops to implement it. Plus there isn’t as compelling a reason to make the site a heavily AJAX’d one. That is: we don’t feel the user experience will be enhanced by heavy use of AJAX.

Thus ends today’s mental roadmap.

Kyle the public