My mother tried an iPhone for the first time. Here’s my Hallway Usability Testing report.
Slide to Unlock
Easy Start. Knew to take her finger, touch the “slider” and move it to the right all the way. The instructional text and appearance of a groove that affords sliding the slider along it win the day.
Problem occurred during release. She’s being very deliberate with her actions, which caused an issue here and later. She’s thinking about what she’s seeing and her next action, which causes a delay: she didn’t just quickly lift her finger from the screen to complete the unlock.
Instead, she unintentionally let her finger slide to the left a bit while waiting for the iPhone to do something (she’s used to computers also taking a while to do something, so it may not just be her being tentative but also her waiting to see if the machine needs to “catch up” to her actions” (a sadly common occurrence)).
When nothing happened, she lifted her finger from the screen only to watch the slider spring back to its original virtual resting position.
She tried again, only more slowly since the machine wasn’t operating as expected, so perhaps she has to do things even more slowly. Same result.
I hinted at her to try lifting her finger faster, and then she was able to succeed.
Suggestion: Slide to Unlock would benefit by taking into account time spent with the finger down on the unlock position and/or additional slop as to what’s considered a valid unlock request.
Initial Screen
Seemed fine enough. No problem thinking of icons as touchable buttons. No expressed puzzlement of the two tiers of icons on the top and another set of icons along the bottom (the dock).
I took the liberty of moving all the apps I knew she wouldn’t use to the second page and even the Utility apps (Voice Memos, Calculator, etc) out of their confusing group and onto the normal page.
She mentioned the Clock is wrong. She thought it odd since the other, textual clocks were correct (the first on the Slide to Unlock screen and the uppermost status bar clock). The Calendar app’s date is correct, so she has no reason to believe that the Clock’s icon just happens to be static. I never noticed this inconsistency myself.
Suggestion: while I’d probably be annoyed at it (especially with the second hand), the Clock app’s icon should be live and accurate.
In the same vein, she noted the Weather app must be showing the inside temperature (72°) since it’s currently in the mid-30°s outside when she was looking at it.
Suggestion: the Weather app should display the current location’s weather/temperature or a new, different static icon that’s clear isn’t trying to represent current conditions.
I told her to press the Mail icon. Again she got snagged by her deliberate movements, and accidentally triggered wiggle (icon rearrangement) mode. Apps wouldn’t launch and she didn’t know how to fix it.
Suggestion: wiggle mode is such a bad idea. Better to have a dedicated app to arrange Springboard pages/groups.
Safari
Like many (most?) people, she doesn’t understand URLs, so I typed in Google from her and then puzzled how to navigate to Google News (google.com now hides it under their burger button). She used Google News on her Mac, so I wanted to show her the same information is available on the iPhone. I wanted her to see something that offered the safety of familiarity.
She was immediately thwarted when tapping a headline link opened the page in a new tab. Her Google News page was pushed away and the Los Angeles Times’ page came frontmost. Because it was a new page, the back button wasn’t enabled. She was at a loss at how to go back, not noticing the nondescript tabs button that held the key to her return.
Suggestion: Safari should initially operate in a simpler single-page-at-a-time mode, which is how nonadvanced users use the web or do a better job of visually indicating and managing multiple tabs. Granted, this is a hard design problem.
Miscellaneous
When she opened the Weather app, she wondered whether it was showing the upcoming weather or historical weather.
I showed her Google Maps and had her type in a old address. She informed me that her fingers were too large to possibly hit the little keyboard. I assured her to try anyway, and after wrestling with finger placement to compensate for her nails (see below) she was able to input an address and switch back to the alphabetic keyboard without prompting (I prompted her on how to switch to the numeric keyboard).
I sent her a link via iMessage to look at a page on Amazon. The link was long, and wrapped extensively. For some reason tapping anywhere on the link except the first line didn’t work to open the page. This bug reduced her confidence that blue underlined text is universally tappable on iPhones.
Physicality
I told her a few times to press the home button. She was confused as to which button it was. I considered calling it the Rounded Rectangle Button, but that seemed excessively wordy. I wish Apple would have used a house icon (something like ⌂) instead of the current quite-rounded rectangle. The downside is a house icon suggests an orientation, and could be annoying if your primary use is in a different orientation.
She tried to use her fingernails to touch things, which are long enough that they do a good job of keeping her fingertips away from being detected by the capacitive touch screen. Here a resistive touch screen actually has an edge (not that I’d ever go back).
When you create a new git repo, you get a master branch for free. That’s fine in many cases.
Lately I’ve been using a different branching system for code that’s meant to be used as a library (perchance a git submodule or a subtree).
Here’s how it works:
Name the branch based on the code’s Semantic Version. Examples:
Brand-new code base that’s still being written: name the branch semver-0.x.
The x in the branch name denotes a variable, so this branch could house v0.0, v0.1, v0.1.42 and so forth.
You probably could get away with a simple v0.x or even 0.x, but I think the explicitness is helpful indicator for humans and paves the way for machine recognition of semantic branch names.
First version of the code base with a stable public API: semver-1.x.
Upcoming not-yet-stable second version of code base: semver-2.x-beta
Initially I went with semver-2.x-unstable, but I found myself confused when I came back to the project later. Does that mean the code is unstable and crashy? Or just the API is subject to change? Both?
Whereas with beta it was instantly clear to me this branch holds the work-in-progress for version 2.
Second version of the code base with a stable public API: semver-2.x.
There’s no master branch.
“master” is too generic a concept and it’s an attractive nuisance for submodules. Too often I look at a project, see lots of branches and no way to immediately tell what’s going on, so I reach for the familiar: the master branch.
Learning from my own behavior, I see it’s important to take away that crutch and force perspective library consumers to understand the project’s current state and semver branch structure. Fortunately GitHub makes it easy to select a non-master default branch, so it should be mostly transparent to your users.
Even better, switching the default branch gives the library’s author the ability to easy start migrating new users to newer versions of your library (while retaining backwards compatibility with your existing users). For example, update your default branch from semver-1.x to semver-2.x.
It’s best if you start off immediately using semver branching, but it’s straightforward to migrate to them even after you publicly release your library using the old fuddy-duddy master branch model. In that case you’ll have to weigh the conceptual clarity and usability advantages of deleting the master branch against breaking the assumed-master branch of your clients.
Objective-C unfortunately lacks namespaces. This leads to both compile-time woes and runtime woes. You may have been witness to this runtime warning:
Class SUUpdater is implemented in both MyApp.app/Contents/Frameworks/Sparke and MyAppPlugin. One of the two will be used. Which one is undefined.
While Kyle Sluder has been leading the charge towards getting the feature as a first-class citizen in Objective-C, I’ve been successfully using a nine-line preprocessor hack for a few years now that effectively works-around the problem. I call it NS:
#ifndef NS
#ifdef NS_NAMESPACE
#define JRNS_CONCAT_TOKENS(a,b) a##_##b
#define JRNS_EVALUATE(a,b) JRNS_CONCAT_TOKENS(a,b)
#define NS(original_name) JRNS_EVALUATE(NS_NAMESPACE, original_name)
#else
#define NS(original_name) original_name
#endif
#endif
Instead of writing your classes like so:
@interface Person : NSObject
@property(strong) NSString *name;
@end
Write it like so:
@interface NS(Person) : NSObject
@property(strong) NSString *name;
@end
#define Person NS(Person)
Then use the class like you normally would:
Person *p = [Person new];
Now your code supports compile-time prefix insertion. If you don’t do anything else, your classes will remain unprefixed.
However, you can now prefix all participating classes with one easy flick of a preprocessor definition:
-D NS_NAMESPACE=MyFramework
That turns the code above into:
MyFramework_Person *p = [MyFramework_Person new];
With judicious targeting of your -D NS_NAMESPACE= use, this should enable you to work-around class collisions at both compile-time and runtime.
P. S. I recognize it’s probably a bad idea for me to squat on something as Apple-generic as NS, but I don’t care.
Update: Uli Kusterer points out this hack doesn’t cover XIBs, since they reference classes by name and avoid the preprocessor’s reach.
I’ve only used this technique with pure code libraries, so that limitation hasn’t bitten me.
If you run nginx on OS X via Homebrew, you’ll probably be interested in my nginx-homebrew-support.
I wish it were possible in C to access an enum’s name in string form given a type and its integer value. It’s great for logging/debugging.
Until that day arrives, here’s a small Ruby script which automates the process of vending an NSString given typed enum value:
def NSStringFromEnum(input)
inputArray = input.lines.collect
typeNameLine = inputArray[-1]
typeName = typeNameLine.match(/(\w+);/)[1]
output = "NSString* NSStringFrom#{typeName}(#{typeName} value) {\n switch (value) {\n";
constantLines = inputArray[1..-2]
constantLines.each {|constantLine|
constantName = constantLine.match(/(\w+)/)[1]
output += " case #{constantName}:\n";
output += " return @\"#{constantName}\";\n";
output += " break;\n";
}
output += " default:\n";
output += " return [NSString stringWithFormat:@\"<unknown #{typeName}: %d>\", value];\n";
output + " }\n}"
end
Example input:
typedef enum {
JRStream_Disconnected,
JRStream_Connecting,
JRStream_Connected,
JRStream_Disconnecting
} JRStreamState;
Example output:
NSString* NSStringFromJRStreamState(JRStreamState value) {
switch (value) {
case JRStream_Disconnected:
return @"JRStream_Disconnected";
break;
case JRStream_Connecting:
return @"JRStream_Connecting";
break;
case JRStream_Connected:
return @"JRStream_Connected";
break;
case JRStream_Disconnecting:
return @"JRStream_Disconnecting";
break;
default:
return [NSString stringWithFormat:@"<unknown JRStreamState: %d>", value];
}
}
Update: Guess I should mention how I use it. I put this script into an Automator Service. Apparently you can also use TextExpander and probably Typinator as well. Finally, Benedict Cohen wrote a C macro version that looks promising.
Update: I extended Benedict Cohen’s excellent work and I’m pleased to offer JREnum.
I finally put my finger on something that’s been bugging me about Xcode’s comment indentation since forever.
For short comments, I typically use //:
// This is a
// short comment.
That’s fine unless comments grow long:
// Lengthy essay
// about the high-level
// overview of the code
// perhaps with parameter-level
// machine-readable API documentation.
Having to prefix each of my lines with an // gets on my nerves, so I switch to classic C comment style:
/*
Lengthy essay
about the high-level
overview of the code
perhaps with parameter-level
machine-readable API documentation.
*/
However, Xcode wants to format it like this:
/*
Lengthy essay
about the high-level
overview of the code
perhaps with parameter-level
machine-readable API documentation.
*/
Note each line after the initial comment opening is prefixed by one space.
I think this is because Xcode wants to help you line up the asterisks, like so:
/*
* Lengthy essay
* about the high-level
* overview of the code
* perhaps with parameter-level
* machine-readable API documentation.
*/
However the entire reason I use classic C comments is when I don’t want to have to bother with per-line prefixes.
For now I just manually delete the extraneous spaces, but I wish I could easily suppress this behavior.
Alrighty guys: best map/reduce/filter categories for NSArray/NSSet/etc — what do you have/use? /cc @atnan @orj @rob_rix
I made a brief aside about this in the latest Edge Cases episode about blocks, so Tony’s question is topical. Here’s the ones I know about, roughly in order of my first-glace preference:
I haven’t used any of these in anger yet (but have been happy with Underscore.js).
[NEW] You can now pass .xcdatamodeld paths to mogenerator. mogenerator will look inside the directory, read its hidden .xccurrentversion file and use the “current” .xcdatamodel file. (Alexander Zats)
[NEW] Replaced mogenerator’s previous testing system (the test mule) with a new Rakefile-based system that eases building & testing from the current source tree and tests both MRC and ARC. (rentzsch)
[NEW] Property declarations generated from attributes can now be qualified as readonly by adding a mogenerator.readonly to an attribute’s userinfo. (crispinb)
[NEW] --configuration option that limits generation to the specified configuration. (Sixten Otto)
[NEW] --base-class-import option for fine-grained control of base class import statements. (David Aspinall)
[CHANGE] Optimized keyPathsForValuesAffectingValueForKey: generated code (returns after first match). (Sean M)
[CHANGE] Add default private class extension to human source template. (Jonas Schnelli)
[FIX] Align generated code’s pointer asterisks more consistently. (Tony Arnold)
[FIX] Missing import when using mogenerator.customBaseClass entity userinfo key. (Thomas Guthrie)
[FIX] Handle case in generated fetch request wrapper machine code when predicate variables are repeated. (Sergei Winitzki)
[FIX] Explicitly set mogenerator project’s deployment target to 10.6 to avoid segfaulting on 10.8 for some reason. issue 121 (reported by Sixten Otto, diagnosed by Florian Bürger)
[FIX] Cast to unsigned in machine source to avoid clang format string warning. (rentzsch)
[FIX] Don’t attempt to report errors through -[NSApp reportError:] in generated machine source unless targeting AppKit. (rentzsch)
[WORKAROUND] Recent versions of Xcode use an empty string to mark entities that do not have a custom subclass. (Matthias Bauch)
[CHANGE] make_installer.command: assume PackageMaker now lives in /Applications/Utilities. (rentzsch)
Invented a design pattern I call “simple code”. When I need the code to do something new I “modify it”.
I’ve wasted too much brain power over the years over-engineering software, bracing my designs for scenarios that fail to ever materialize.
My suggestion, at least for the first cut: do the simplest thing that could possibly work.
If you’re like me, that engages your intellect towards simple solutions instead of over-engineering against invisible foes.
After my poor experience with Software RAID on Mac OS X 10.7.3, I’m pleased to report that software RAID on OS X 10.8 seems improved over 10.7.
Enabling Software RAID does have some tradeoffs:
You lose OS X’s Recovery Partition. Internet Recovery should still work and you could always build your own Recovery Disk.
FileVault isn’t supported.
For my server purposes, I don’t care about either of these limitations, so I’m happy that OS X Software RAID is realistic option again (the “Repair Disk” button actually works).
Here’s how I set up my new 2x1TB Mac mini Server:
SuperDuper the Mini’s boot drive (“Server HD”) onto an external drive.
Boot from the external drive.
Repartition both drives into three slices each: 50GB Boot, 50GB Boot Backup and the rest Data.
Create three new RAID 1 (Mirroring) sets encompassing those slices, respectively.
SuperDuper the external drive back onto the Boot RAID volume and boot off it.
Here’s my set-up as it appears in Disk Utility:

This set-up enables me to perform system software updates with a bootable backup safety-net in place.
I’m backing up that Data partition offsite, so I’m not backing it up locally on the Mini itself (I’m just SuperDuper’ing the Boot volume to the Boot Backup volume nightly).