A Different Type of Optimization

For years I’ve heard the mantra from Apple engineers of not optimizing prematurely, because you never know where your bottlenecks actually are until you profile the running code.

But that’s only one type of optimization.

I, for instance, always optimize for project size.

Expand one of my projects and you’re unlikely to find more than 30 or so class files (counting header and implementation files as one.) Yes, it makes for bigger individual files, but it also makes for a much more easily digestible project tree, and, more importantly, it makes for MUCH less jumping around between files when debugging some complex process.

Over the years I’ve dealt with many applications that send http requests and receive responses. The first thing I do when I inherit one of these projects is to expand the entire project tree to see just what I’m dealing with. Often I find a project consisting of literally hundreds of files, many only a few lines long. One or more class files for every single business object, specialized classes for abstract concepts, multiple classes for implementing a single process, etc…

AAAARRRRGGGGHHHHH!!!!

It has not been unusual for me to see a process flow something like this:

 

User does something that requires calling a web service.

Method 1 in Class A creates a new request object and passes it to Method 2 in Class B.

Method 2 in Class B calls Method 3 in Class B to get info with which to populate the message header.

Method 2 in Class B calls Method 4 in Class C to get the body info for the request.

Method 2 in Class B passes the request to Method 3 in Class D to format the request.

Method 3 in Class D calls Method 5 in Class E to Sign the request.

Method 3 in Class D calls Method 6 in Class E to encode the request.

Method 3 in Class D passes the request to Method 6 in Class F for sending.

 

So we have 6 methods in 6 classes just to send out an http request.

Then there’s the response…

 

Method 1 in Class A gets the response object.

Method 1 in Class A passes the response to Method 2 in Class B to parse.

Method 2 in Class B calls Method 3 in Class C to parse the header.

Method 2 in Class B calls Method 4 in Class C to parse the body.

Method 2 in Class B creates an instance of (business object) Class D, initializing it with the body of the response.

Method 2 in Class B passes the instance of Class D to Method 5 in Class E, which is the final recipient of the result.

 

So we have 5 methods in 5 classes to handle the result of the http request.

All total, that’s 11 methods in 11 Classes to handle an http request/response. Well… up to 11 Classes, as there could be overlap in classes between the send and receive processes.

And this is just the norm. I’ve seen some projects which easily double the number of methods and classes touched in implementing this request/response.

Now, let’s say you need to trace a request/response from start to finish. You’re jumping around to 11 methods in 11 files. If there’s a problem somewhere you’re trying to debug, and you’re in an IDE that keeps a tab open for every file you hit, you now have 11 tabs open for the 11 files you touched while tracing through this process. If bad data is occurring somewhere, you have 11 breakpoints in 11 files to try to find it.

When I have to debug the above, I roll my eyes and breath a sigh of aggravation at the developer who designed it.

Let me compare that to the actual process I use in Snap That! for implementing http request/response:

 

User does something that requires calling a web service.

Method 1 in Class A creates a new instance of Class B .

Method 1 in Class A calls Method 2 in Class B with the data needed for the request.

Method 2 in Class B creates the http request, formats it and sends it.

Framework Method 1 in Class B receives data and builds response.

Framework Method 2 in Class B sends response back to Method 3 in Class A.

Method 3 in Class A parses the response and uses the results.

 

So we have 3 methods (not counting the framework methods required to receive and package data) in 2 classes.

Debugging this process? Simple. Simple. Simple.

The usual objection to doing things the way I just outlined is that it results in unmanageably large source files.

Checking the file for the class I use to process http requests, it has 997 lines (and I put brackets on their own lines and use blank lines liberally for visual clarity) to handle 23 distinct web service calls. I don’t find that to be unmanageably large.

Yes, the two processes aren’t exactly equivalent, but basically there are three things I do that limit the amount of jumping between methods and classes in this process:

 

1) Using one DataManager class that handles all outgoing and incoming data requests and responses.

2) Inlining the code in one method that creates the request, formats the header, formats the body and sends the request.

3) Parsing the response into a framework-native dictionary class instance instead of creating instances of custom business object classes for every response.

 

The moral: separation of concerns, encapsulation and modularity are all fine OO concepts, just don’t overdo it.

 

Tags: , ,

Leave a Reply

You must be logged in to post a comment.