The latest Basecamp for iPhone release involved an immense amount of refactoring with how HTTP requests and HTML pages were rendered. In a previous release, my coworker Sam wrote several methods that didn’t serve to reduce duplication, but instead their purpose was to increase clarity. Since the focus is on comprehension rather than just reusability, it’s easier to understand what is going on and jump in to solve the next problem.
This pattern has been eye-opening, and I’ve been using it to hide implementation details and remove comments explaining what chunks of code are doing. Instead, the name of the method explains what is going on. The gist here to communicate Intention Not Algorithm:
The most important thing about any (without loss of generality) method is what it accomplishes or why one would call it, not how it does whatever it does.
Here’s one example from Basecamp for iPhone of this. Since we make heavy use of web views, we need to keep track of when the page loaded with content. How we accomplish this actually doesn’t matter, but the why matters greatly.
In this method that is mixed into every UIViewController that makes requests via AFNetworking, the intent of remembering when the page loaded feels tangled up with the code surrounding it:
module Browser
def enqueue(request)
operation = client.HTTPRequestOperationWithRequest(request, success:
lambda { |operation, response|
normalRenderer.new(self, operation).render(response)
@pageLoaded = true
}, failure: lambda { |operation, error|
fallbackRenderer.new(self, operation).render
})
client.enqueueHTTPRequestOperation(operation)
end
end
All we need is an instance variable to keep track of when a page has been loaded, but if you come back to where that variable is set on its own, it’s confusing. The how doesn’t matter here, but the why does. Let’s try pulling that out into its own intention revealing method:
module Browser
def enqueue(request)
operation = client.HTTPRequestOperationWithRequest(request, success:
lambda { |operation, response|
normalRenderer.new(self, operation).render(response)
markPageAsLoaded
}, failure: lambda { |operation, error|
fallbackRenderer.new(self, operation).render
})
client.enqueueHTTPRequestOperation(operation)
end
def markPageAsLoaded
@pageLoaded = true
end
end
Now, the intention is clear and the implementation is just details. A simple attr_writer :pageLoaded could have accomplished the same job, but it still doesn’t give a clear message as to why it was called.
Our recent release focused heavily on improving offline detection. This module’s viewWillAppear method listens for two custom NSNotificationCenter events that fire when the device goes on and offline:
module Offline
def viewWillAppear(animated)
super
on "WentOffline" do |notification|
showOffline
end
on "BackOnline" do |notification|
restoreFromOffline
end
end
def showOffline
if isPageEmpty
OfflineRenderer.new(self).render
markPageAsLoaded
end
end
def restoreFromOffline
request if client.isInactive
end
end
I could have shoved the code for what happens when the device switches states into their respective blocks, but now the intent is clear. Did the device go offline? Show the offline page (unless if something was on the page previously). We’re back online? Restore the controller from the offline state by requesting the page again.
Give this a try the next time you come across some code or a comment that covers What But Not The Why, instead of leaving it as a knot to unravel months later.