Small Helper Objects Take You Far

Integrating new functionality is fun. But revisiting 18-months old code isn’t. Back then I created a protocol InvokesWindows to define methods like -showPreferencesWindow which I imported in the menu bar controller to show the preferences when the user selects a pop-up menu item. But I didn’t actually delegate to any instance of InvokesWindows. I used NSApp. (Insert facepalm here.)

Which version do you like better?

- (void)preferencesMenuItemSelected:(id)sender
{   
    [NSApp sendAction:@selector(showPreferencesWindow) to:nil from:self];
}

- (void)preferencesMenuItemSelected:(id)sender
{
    [self.router showPreferences];
}

Well, the NSApp-thing helped me move quickly without creating another two files for an actual “window invoker” in Objective-C. Coming back to this code base from my Swift projects of the past months, I feel super-slow. Writing Objective-C feels weird again.

As Matt Kremer recently blogged, there’s two phases of a project, and two developer personalities which fit each phase best:

  1. there’s a growing phase of the project where all functionality is initially developed; it’s great to make progress fast and ship early
  2. there’s a maintenance phase where you have problems understanding the shortcuts you or a team mate took; this is where lots of people begin to clean up

The new router property is the result of me maintaining the old code base. Back then, I was worried about other things and didn’t put “refactor NSApp calls away” onto my running to-do list, it seems.

InvokesWindows is now gone in favor of RoutesModules, a name which captures the intent much better, and which is actually used:

@protocol CTWRoutesModules <NSObject>
- (void)showHistory;
- (void)showFileMonitoring;
- (void)showPreferences;
- (void)showLicense;
@end

The actual implementation is very, very simple. And it’s written in Swift.

import Foundation
import FileMonitoringModule

@objc(AppModuleRouter)
class AppModuleRouter: NSObject, CTWRoutesModules {

    private var history: CTWShowHistory?

    func showHistory() {
    
        history = CTWShowHistory()
        history?.showHistoryWindow()
    }

    func showFileMonitoring() {
    
        FileMonitoringModule.showFileMonitoringWindow()
    }
}

Back then I created a “Show History” use case object which I still stick to. But the new file monitoring module is an actual module, a Swift Framework. And for it I created another framework which bootstraps all components with default values for the actual application – this takes care of itself and offers a single facade to all underlying functionality:

private let bootstrapping = BootstrapFileMonitoring()
private var bootstrapping_token: dispatch_once_t = 0

@objc(FileMonitoringModule)
public class FileMonitoringModule: NSObject {
    
    public static func bootstrapModule() {

        dispatch_once(&bootstrapping_token) {
            bootstrapping.bootstrapModule()
        }
    }

    public static func showFileMonitoringWindow() {
    
        bootstrapping.showFileMonitoringWindow()
    }
}

I write simpler code nowadays. I think Swift taught me some tricks I haven’t come up with in Objective-C. Also I learned a lot more about software architecture in the past year.

I am not getting rid of most calls to NSApp. Not because it didn’t work or because there are too many of them – it’s because the result is easier to read and understand. And I can finally unit test the actual routing by injecting a test double.


Photo Credit: Please Clean Up Your Mess by Allen Goldblatt. License: CC-BY-2.0.