“A ship in harbor is safe, but that is not what ships are built for.”
–John A. Shedd (But made famous by Adm. Grace Hopper)
NOTE: This is also published on Medium.
Quick Disclaimer: This is NOT an original idea for an essay. Lots of folks have used this very quote to preface life advice. I’m just using it to talk about my own personal experience and approach. It’s not advice. It’s just me, talking about my favorite subject: myself.
First, There Is the Terror
I don’t know how to do almost every single project I ever take on.
The first phase is usually a nice round of panic and gibbering terror.
“It’s too big! Too ambitious!”
“It’ll never work!”
“I’m too stupid to do this!”
“I don’t even know where to begin!”
“What was I THINKING?”
etc. ad nauseam.
After I’ve “enjoyed” that for a while, I wipe my eyes, blow my nose, roll up my sleeves, and get to work.
This article is about how I manage to get through these new endeavors, and deliver consistently great software, each time, all the while, learning new tech and exploring distant shores.
Behold the Sausage Factory
Those that respect the law and love sausage should watch neither being made.
My process is ghastly. I’ve never done pair programming, but I can’t see my partner being thrilled by my antics.
I call it “driving into the weeds,” or “banging into the guardrails.” I just try stuff, throw up strawmen, make terrible assumptions, skim the docs, create naive, messy exploratory code, mix my model with my controller, and seldom write down a damn thing. I’m constantly googling even the most basic algorithms, and Stack Overflow is my best friend.
Trust me; you’d be horrified.
But I work FAST, and my final products are always impeccable, with rich, usable UI, extremely high quality, inherent localization, flawless polish, great documentation, and very little technical debt.
I was trained as an artist, and before I started my training, I would use a pen, making every line count. I was quite proud of my work.
The first thing my teacher did, was take away my pen, and give me a pencil. He taught me how to “rough in” a sketch, with sloppy, imprecise lines that suggested, rather than defined, shapes. He taught me how to NOT draw in places, and let whitespace do the work for me.
That’s pretty much how I develop software. I “rough in a sketch,” then refine the work, until it is as close to “perfect” as I can get.
I also like to remove as much code as possible. The best code is the code you don’t write. The eraser is my best friend.
Careful Stretching Is Key
One thing that I am careful about, is not putting too much “stretch” in my “stretch goals.” I don’t have a Ph.D, so, unless I plan to license a library that provides Ph.D-level capabilities, or hire a Ph.D, I will think twice about starting a project that depends upon a Ph.D-level capability.
I also need to manage scope. I’m pretty familiar with how much I can do in a certain amount of time, and am pretty good at “off the cuff, big-picture” estimation (comes from years of screwing up). I need to look at my resources and timeframe. I usually assume that my estimate is probably too optimistic by about 50% (conventional wisdom is 100%, but I use 50% if working alone, and 150% if working in a team –NEVER underestimate team overhead).
So…stretch, but stretch carefully.
All My Projects Have A “Sell-By” Date, and Insane Quality
I like to make sure that each project I do has an “exit strategy”; where it’s considered “done.” I’m also totally obsessive about quality in my work. A big reason is that if I do it right the first time, I won’t keep getting sucked back into it to clean up my mess. I want to keep my finished projects in the rearview mirror.
Driving it, is that I plan to bite off more than I can chew in my next project, and I don’t want to be distracted by my last project tapping me on the shoulder, demanding attention.
Every Single Project I Work On Is Designed to Ship
“We are what we repeatedly do. Excellence, then is not an act, but a habit.”
It doesn’t matter whether or not it’s just “experimenting,” or if I have a concrete deliverable venue in mind.
I write ALL of my code as if it will be the subject of an audit and a “white glove” test.
It’s important to learn good habits, and I often find myself rooting back through old codebases in order to find inspiration or snippets for new work. If the code is already “ship quality,” then that’s one less thing that I need to worry about.
I run on a very lean process. Since I’m deliberately running each project on a “stretch” basis, it’s really important that I reduce the problem surface by as much as possible, every chance I get. This is a simple way to do that.
It also helps to keep me focused on the practical, and encourages a “real-world-results-oriented” approach.
If Possible, I Plan to Open-Source My Work
Almost every line of code I’ve written in the last couple of decades has been open-source. I was a manager in my “day job,” so I did open-source development to keep my tech chops up.
I think that it’s important to put the code out there; even if no one ever bothers to look at it, because it forces me to write good code. You always clean the house before guests arrive, so it establishes good habits.
In most of my projects, the meat of the code is really fairly pedestrian. Nothing really earth-shattering or confidential. Nothing to see here, folks. Move along.
Another advantage of open-sourcing your work, is that it can increase the pool of available dependencies, some of which may have coercive licenses.
Before I Begin, I Map Out the Project Into Subsystems
In order to reduce the “overwhelming” nature of my new, scary project, I will generally break it into “development units.” These could be based on functional composition, backend/frontend/API structure, or even “This scary. This not-so-scary.” I draw up my “napkin sketch,” and apply the steps I’ll outline shortly to each subsystem.
I like to keep the schedule and project plan as vague as possible; with refinement coming in a “Just In Time” manner, as the project progresses. I just assume that I’m going to be thrown a lot of curveballs. This is doubly true, if I plan to take on a project that I don’t already know how to do.
I Start By Writing Something That Works; Maybe Not Well
The first thing that I do, is make sure that my data/operation happens correctly. Sometimes, I may do the TDD thing, and write failing tests, but more often, I begin to write empty tests when I start new functionality, with the goal that they be “fleshed out” as I develop the functionality. I think that it’s important to be testing the code actively, as soon as possible.
Most often, I start by writing a test harness, as opposed to unit tests. I find that unit tests can be awful “concrete galoshy,” and I try to avoid that kind of rigidity; especially at the start of the project.
In another post, I mentioned how I “make it up as I go along” in my designs; sometimes, even for fairly complex, heterogenous systems.
So my first goal is to get something delivering the required data or operation, and a way to validate that. My tests “grow up” with my functions. My initial work is usually fairly naive, and not particularly performant. I only worry about performance if that’s a fundamental “first pressing” requirement, where the functionality will fail unless the code is optimized. Optimization is a tricky process that can introduce a lot of strange bugs, and takes a fairly significant amount of work. Also, in many cases, it isn’t actually necessary (I know, I’m going to Hell for saying that, but it’s been my experience that lots of stuff runs fine unoptimized).
One thing that I do implement right away, is the use of SwiftLint, and I set all warnings as errors. I want to be writing clean code, right off the starting line.
I will often also add comments, and set up Jazzy-style headers. Even simple separator bars for function headers are better than nothing. They help it when you visually scan the text file. It’s a good idea to use “// MARK: ” comments to delineate blocks of functionality.
Another couple of things that I find I need to incorporate from the beginning are error-handling, and, if applicable, localization. I shouldn’t wait to add these.
Then I Start to Refine It
Before thinking about doing things like optimizing, I generally start refactoring for “code smell.” I do things like break model code out of controllers, and try to encapsulate dependencies (that means bottlenecking their API, so they can be replaced or refined, later).
As I mentioned earlier, code removal is a big deal. I look for repetitive or redundant functionality that can be refactored into protocols or shared base classes.
Since we are talking Apple, I often try to see if I can get a Swift source file to compile with only “import Foundation”. It’s not critical, but does help to prepare code for reuse, at a later date.
Speaking of “reuse,” I will sometimes look at subsystems in my project, and see if there’s anything that I’d like to break out into a standalone project. If I can do that, it helps that subsystem to be drastically higher quality, and gives me some stuff that can make future projects more stable and faster to develop.
If I do decide to break out a subsystem, I may put the project on hold, and create a separate project for the subsystem. A recent example (as of the time of this writing), is the RVS_PersistentPrefs project, which was extracted from the RVS_MediaServer project. I put the RVS_MediaServer project on hold for about a week, as I created the RVS_PersistentPrefs project. This did mean that the RVS_MediaServer project took longer than it would have, if I had just used the original “embedded,” preferences, but the work paid for itself almost immediately, after I got back to the RVS_MediaServer project. The RVS_IPAddress project was also a “factored out” tool, as was the RVS_ParseXMLDuration project. They came from my ONVIF work.
Optimizing Is Not For Everyone
Once I have the code refactored into functional/good design form, and the documentation and tests are in good shape, I see if optimization is necessary.
“Of course optimization is necessary! Isn’t oxygen necessary?” you say.
Yeah…not really. Especially if you are doing a lot of UI code. Stability, utility and responsiveness are the main factors. “Responsiveness” may be something that is handled by optimization, but I find that revisiting the design is often a better route. Using things like threading, KVO binding, and/or property observing can help an application to be quite snappy; much more so than a zippy inner loop optimization. Standard Occam’s Razor.
For example, if an operation is slowing down UI responsiveness, my first thought is not whether or not I need to optimize the calculation/decision algorithm; it’s whether or not to dispatch that operation to a helper thread. The inner loop may still need to be optimized, but it’s just good practice to thread the time-consuming operation.
I also look for things that get repeated over and over. See if they can be cached or consolidated. Standard low-hanging fruit.
Totally Reduce Concrete Galoshes
This is probably the most important part of my personal process. I wrote about “concrete galoshes,” here. It’s critical to reduce the “ossification coefficient” of the project. These are the things that are written down or that become “petrified.” Some are very much necessary, but many can be deferred.
Personally, I’m very good at keeping things in my head, and building mental models and simulations. It’s just one of those things I’m good at. I’m not so good at a lot of other stuff (for example, I constantly forget even very basic algorithms and practices –quite embarrassing), so I can’t feel too smug, but keeping a complex system architecture in my head is something I do well.
If it stays in my head, then I can move big stuff around; like realizing that my fundamental approach is wrong, and redesigning an area of functionality “on the fly.” This is quite possible for me. I do it frequently.
I Love Learning
The big reason for taking on challenges, is that I have yet to tire of learning. It’s humbling and difficult, but, in my opinion, well worth it.
I Don’t Love Being Patronized
The biggest pain that I encounter, is that I’m constantly a “n00b,” so I’m asking “stupid” questions, and making erroneous assumptions all the time, as I explore and construct mental models. This can often result in me being treated quite badly by folks that are established in the area I’m exploring.
I’ve learned to just bite my tongue/keep my hands off the keyboard, and “let them win.” I am the “interloper,” here. It’s “their” domain, and the chances are good that I’ll just be passing through. It’s only polite to try not to make too big a splash. They have to live there; I usually don’t.
Also, whether or not I like it, they are doing me a favor. They’re helping me to learn new stuff, and that’s really what this is all about. Many of them worked hard to reach whatever position of prominence they occupy, and some may feel as if that justifies setting a high bar for those that come afterwards.
Can’t really argue with that. I have a great recipe for Humble Pie. It goes well with Crow, and Egg-On-Face.
Personally, I feel that it’s good for the character to be routinely in a position where I have to cede to superior expertise. It stinks, being the smartest guy in the room. I’ve spent my entire career surrounded by people much smarter and more accomplished than me. If nothing else, it helps to “normalize” excellence.
It All Comes Together to Keep the Ship Afloat
In my experience, this methodology has always resulted in extremely fast development work, creating astonishingly high-quality deliverables, with documentation and maintainability built-in.
It also helps me to establish a habit of being able to accomplish –and ship– fairly ambitous projects; using as few resources as possible.
But for me, the most important part, is that I have sailed to yet another unknown shore, and discovered something that I did not know beforehand.
I think that’s pretty cool, and makes life more interesting.