Quality Coding
Shares

Spike Solutions: 7 Techniques You Can Use

Shares

When you’re hiking and don’t know which way to go, what should you do? Stop. Get your bearings.

The same applies to Test Driven Development; sometimes you need to stop doing TDD, and get some answers.

Stop. Get your bearings. Spike Solution Techniques by Jon Reid

Those answers can come by switching from clean code to a style of quick-and-dirty hacking called a “spike solution.” I explained the basic idea in How to TDD the Unknown with a Spike Solution.

But what does it look like in practice?

The key thing is to learn what you need, then get out. Don’t try to use the code from your spike solution; it’s dirty on purpose. Ultimately, we want to switch back to writing clean code, driven by tests.

As I explained in TDD Sample App: 20 Topics that May Spill Out, the main thing I want the Marvel Browser app to do is pass a name prefix to the Marvel Comics API and get comic book characters back. There are two things I’m uncertain about: how the NSURLSession works, and how Marvel handles request authentication.

This post is my way of letting you look over my shoulder so you can see how I do things. Then let’s see if any techniques emerge for spike solutions in general.

[This post is part of the series TDD Sample App: The Complete Collection …So Far]

Where to put it?

The template project shows a view, so I put a breakpoint on -[ViewController viewDidLoad] to make sure it’s hit when I build and run the app. It is hit, so let’s play there.

Now normally, networking code should never, ever be present in view controller code. I will address this in a series on iOS Architecture, after this post. But for a spike solution, dirty code is fair game.

I created a branch called “spike-networking”. Mainly, I want to avoid having the spike solution go into the master branch. But I also want you to have access to it, so I’ve pushed the branch to the GitHub repo. If you want, you can follow along commit by commit.

Hide those API keys

The Marvel documentation has a helpful page on Authorizing and Signing Requests. It includes a warning to keep the private key private. Since I am making my code public, I don’t want to check it in to MarvelBrowser repo. Instead, I made a file MarvelKeys.m that looks something like this:

static NSString *const MarvelPublicKey = @"my-public-key";
static NSString *const MarvelPrivateKey = @"my-private-key";

To keep it out of the repo, I specified MarvelKeys.m in a .gitignore file.

(In a true production app, I would also want to obfuscate the private key in some way. But that would be a silly thing to do when the code is open for anyone to see.)

First steps

On the Authorizing and Signing Requests page, we’re interested in the section called Authentication for Server-Side Applications. Apparently, we need to concatenate a timestamp, the private key, and the public key. This will will be crunched into an MD5 digest, so it needs to be a plain C string. Even for a spike solution, I want to work in small steps, verifying things manually:

#import "MarvelKeys.m"

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Concatenate keys per https://developer.marvel.com/documentation/authorization
    NSString *timeStamp = @"1"; // Hard-coded for spike
    NSString *keys = [NSString stringWithFormat:@"%@%@%@",
                      timeStamp, MarvelPrivateKey, MarvelPublicKey];
    char const *keysString = [keys UTF8String];

    // Confirm manually:
    NSLog(@"%s", keysString);
}

As you can see, I don’t even care about generating a proper timestamp; a hard-coded value will do fine. I build, run, and check the log to manually confirm that it shows the concatenated string I expect.

Create MD5 hash

Now I need to create an MD5 hash of the concatenated string:

    // Create MD5 hash:
    unsigned char digest[CC_MD5_DIGEST_LENGTH];
    CC_MD5(keysString, strlen(keysString), digest);

Running this with a breakpoint, I see that digest is getting filled with data, but it doesn’t look like anything that can be passed in a URL. Oh wait, we need to convert it to hex digits. A quick search on the Internet yields a solution which I copy & paste:

    NSMutableString *hash = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
    for (NSUInteger i = 0; i < CC_MD5_DIGEST_LENGTH; ++i)
        [hash appendFormat:@"%02x", digest[i]];

Since we want a 32-digit hexadecimal number for MD5, let’s log it and check:

    // Manually confirm that it's 32 hex digits:
    NSLog(@"%@", hash);

So far, so good. Since my warnings are beefed up, I do get a warning about integer precision. Do I care? No.

Create URL string

Now let’s create a URL string to search for character names starting with “Spider”. I’m combining what I see about authentication with their Interactive API Tester:

    // Manually confirm URL string:
    NSString *URLString = [NSString stringWithFormat:
        @"http://gateway.marvel.com/v1/public/characters?nameStartsWith=Spider?&ts=%@&apikey=%@&hash=%@",
        timeStamp, MarvelPublicKey, hash];
    NSLog(@"%@", URLString);

Build & run. So far all I have to go on is visual verification, but things look okay as far as I can tell. (In fact, I just introduced an error but don’t discover it until I actually call the Marvel Comics API.)

Create the data task

Now let’s create the NSURLSession data task.

    // Create data task:
    NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                          delegate:self
                                                     delegateQueue:nil];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                            }];

I could have specified nil as the delegate, but I tried self because it seems like a common pattern. AppCode then warned me that the class doesn’t conform to a protocol, so with a quick Option-Enter, I let AppCode generate the required declaration:

@interface ViewController () <NSURLSessionDelegate>
@end

But there were no further warnings here, so I learned that this protocol contains only optional methods.

Again, I’d never have a real view controller implement that protocol. But the purpose of the spike solution is to learn.

Fire the data task

Okay, let’s add logging to the completion handler, and actually fire off the task:

    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:URLString]
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                NSLog(@"Data: %@", data);
                                                NSLog(@"Response: %@", response);
                                                NSLog(@"Error: %@", error);
                                            }];
    [dataTask resume];

Whoops, it doesn’t work! In the logged response, I see “status code: 409”.

Diagnose and fix

According to the “Authorization Errors” section of the Marvel page, there are a few possible causes of a 409 error. Maybe the logged data will tell us more:

Data: <7b22636f 6465223a 224d6973 73696e67 50617261 6d657465 72222c22 6d657373 61676522 3a22596f 75206d75 73742070 726f7669 64652061 2074696d 65737461 6d702e22 7d>

Copying the part between angle brackets, I paste it into 0xED, my favorite hex editor:

Spike solution: Using 0xED to read NSData error

Now I can read the JSON response:

{"code":"MissingParameter","message":"You must provide a timestamp."}

The hard-coded timestamp value shouldn’t be a problem. But it’s not getting through somehow. Let’s use AppCode’s refactoring to Extract Variable and examine the URL:

    NSURL *url = [NSURL URLWithString:URLString];
    NSLog(@"URL: %@", url);
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url

I examine the logged URL, and ah! I have a question mark that doesn’t belong. Let’s remove that, try again, and… success! I’m now getting a lot of data, and the response shows a successful status code of 200. Copying the hex data and pasting it into the hex editor, I now see:

Spike solution: Using 0xED to read NSData results

It looks valid, but there are some unprintable characters in there that I don’t trust. Let’s copy the text from the right hand portion and paste it into a JSON parser, like Json Parser:

Spike solution: Using Json Parser to verify JSON

Hurrah, no errors! Our spike solution is done.

Techniques in my spike solution

Looking back, here’s a summary of the techniques I used while developing this spike solution:

  1. Work in a branch, and don’t merge back. And when you no longer need to refer to it, delete the branch.
  2. Stick the spike any place that will run quickly. By quickly, I mean with a single keystroke. Avoid having to navigate anywhere in a running app.
  3. Work in small steps, verifying your progress. Any time you can get feedback, get it.
  4. Copy and paste liberally. StackOverflow is your friend, because at this stage we’re just hacking, not doing TDD.
  5. Use AppCode. The better you learn it, the less typing you’ll do. Use the “inside-out” coding style I show in my Better TDD: The AppCode Advantage screencast. Conform to protocols with a single keystroke. Extract statements into variables. The list goes on.
  6. Don’t write code that you don’t need. To peer into that NSData, I just log it, then copy-and-paste into a hex editor. To check the JSON, I paste it into a JSON parser. Remember, the goal is to get answers, not code.
  7. Don’t put your spike solution into production code. For more on this, see How to TDD the Unknown with a Spike Solution under the heading “Throw it away when you’re done with it”.

Did any of my techniques surprise you? What are some techniques you use for spike solutions? Leave a comment by clicking here.

[This post is part of the series TDD Sample App: The Complete Collection …So Far]

About the Author Jon Reid

Jon is a coach and consultant on iOS Clean Code (Test Driven Development, unit testing, refactoring, design). He's been practicing TDD since 2001. You can learn more about his background, or see what services he can bring to your organization.

follow me on:

Leave a Comment: