As anyone who follows me on Twitter or this blog is no doubt sick of hearing, TapNote 1.2 is now available and includes Dropbox sync!
And to help my readers maintain their sanity, that’s all I will say on that subject. Instead, I’d like to take a closer look at why this version—which from a user’s perspective has merely three visible changes (two buttons and a conflict resolution scene they will rarely, if ever, see)—took me nine months to complete.
An app is not a website
Although I have been writing code for years, most of the things that I have worked on to date have been websites. Typically these are projects with a fairly limited scope, or with a scope determined by a third party. Nothing that I have coded from scratch can properly be called a web app, and my contributions to other people’s web apps have mainly consisted of frontend work to refine or extend an existing design.
Developing TapNote has thus been an interesting experience for me. It is the first true application that I have created, and I am finding that applications (particularly applications that you are both designing and developing) are an entirely different beast from the types of web coding that I am familiar with. A lot of the basics are the same, of course (coding and debugging is pretty similar no matter where you do it), but the overall process is much different.
Because I am so new to application development I have as a matter of course been making mistakes. Looking back over my progress on TapNote since I started working on synchronization until now, one thing in particular stands out as the most prominent cause of my long development cycle.
The dream
Every software developer worth their salt has a dream, and it is a dream of the Perfect Solution. The Perfect Solution is elegant, fast, and does everything they need it to do without edging into feature bloat. The Perfect Solution’s code is well-organized, logical, handles every possible error scenario gracefully, and is easy to extend and maintain. The Perfect Solution is not limited by external factors; should an external dependency prevent the Perfect Solution from achieving perfection, an alternative solution will be developed to work with the Perfect Solution instead.
The dream of the Perfect Solution is a difficult dream from which to wake, and judging from my growing experience as a developer is one of the main culprits for brilliant software that is either never begun or never shipped. In our imperfect world, the Perfect Solution requires such a daunting amount of work to achieve that many people give up without even trying, or find themselves endlessly tweaking the same code over and over until they forget that they ever intended to release it for others to enjoy in the first place.
When I started work on synchronization for TapNote, I was dreaming of a perfect solution. Looking back at my commit logs, the consequences are clear: three months without any work early on as I remained overwhelmed by my own ambition; multiple database schema rewrites, each setting progress back as I had to rework my basic logic planning; code that I wrote, removed, rewrote, and removed again across multiple portions of the app. The list goes on.
Eventually, however, I realized that my dreaming was preventing me from actually accomplishing anything, and at that point I finally started to write some workable code, culminating in TapNote 1.2. Needless to say, the sync feature in 1.2 bears little resemblance to my original plans.
Dreaming piecemeal
I don’t believe the problem is my tendency to dream. My desire for perfection is a driving force influencing me to create and develop; odds are good I wouldn’t have started writing TapNote if not for the inadequacies I perceived in other note-taking apps on WebOS coupled with my vision of the perfect solution.
The problem is that the dream we developers perceive, whole and perfect, is ultimately a lie. Aside from possibly Athena, nothing leaps into this world fully-formed and perfect. Whether we are talking about people or programs, everything needs time to mature into its fullest potential.
What I am finding is that the key to using the dream without being overwhelmed is to break it into pieces.
I don’t remember specifically when this happened, but sometime after my three months of inactivity, I finally sat down and admitted to myself that I was never going to be able to include all the features I wanted in version 1.2 and still ship it. Instead, I wrote out a list of the things that were absolutely necessary for synching to be any use whatsoever. Then I implemented the most basic of those features, tested them, and crossed some of the other features off the list (having discovered that I could live without them, whatever I thought originally). As I continued to write code and test it, I found that even the bowdlerized version of the Perfect Solution I was developing was surprisingly useful, and the more I used it the more it felt like the right approach. Additionally, even though I knew they weren’t ultimately going to result in my Perfect Solution being unleashed upon the world, hitting small milestones (like the first successful push/pull sync) was extremely gratifying.
Grand dreams, simple pleasures
Though it is a lesson I continue to have trouble implementing consistently, ultimately coding applications—like many creative endeavors—is about grand dreams and simple pleasures. The dream is what prods you to undertake the project, while the simple pleasures are the small features that you implement day to day that keep you happy and productive. I’m sure this is self-evident for most experienced developers, but before now I didn’t truly understand how integral iteration is to successful application development. Perhaps this is one of those lessons that you have to experience to fully comprehend; I honestly don’t know.
In any case, with the basic groundwork for sync laid out in TapNote, I am now free to take things one feature at a time as piece by piece I work my way closer to perfection. Let’s just hope I don’t get too distracted by dreaming along the way.