eno writer

007 - "killing it" / setting the pace / visualizing the code

"killing it"

Hello again. It feels like it's been an eternity but it has only been two weeks. I skipped blogging last week because I took three days off to go on a quick getaway with my wife. Unfortunately we both got stomach flu within a few hours of arriving at the hotel. Notwithstanding that, it was still a pretty refreshing break.

Part of me wants to keep this devlog strictly professional and strictly positive. The other part wants to document an authentic account of starting a new venture, warts and all, rather than a whitewashed fairy tale story. There's an aspect of startup culture where you must always be "killing it". It can be very tiresome talking to certain startup founders who won't admit to a single challenge or obstacle in their purported unrelenting success. I'm not sure this is necessarily a good thing for anyone.

At the suggestion of a friend, I recently read The Making of Prince of Persia: Journals 1985-1993. The book is a lightly annotated reproduction of journals that Jordan Mechner kept as he created the hit game Prince of Persia. The honesty of the journal is refreshing. Some days, the entries are awash with unbridled optimism. Jordan gushes over how excited he is for the game and how it will easily be one of the most acclaimed titles of the year. Other days, Jordan laments wasting years of his life making a video game which has little promise for commercial success and frets over the imminent demise of the video game market. I can definitely relate.

Setting the Pace

One of the difficulties of working for yourself is you have to set your own expectations for how hard to work. This is further exacerbated in the earliest stages of a venture when there are no external motivating factors like clients, customers, boards, etc.

One solution would be to set an arbitrary deadline and do whatever it takes to make it happen. If you read the recent Elon Musk biography, you know he is a big fan of this strategy. Clearly it works quite well for him. Of course Elon's lifestyle is a bit extreme. I'm not sure I want to make everything in my life secondary to the development of a word processor. Despite that, I could adapt his strategy to a more balanced lifestyle by setting less ambitious goals with more forgiving deadlines.

For the time being though, I am not planning to set a deadline for the first version of eno. I have two reasons. First, I am not really sure what version 1 of eno looks like, nor do I necessarily know how to make it (yet). So when I say, "I will finish eno v1 by July 1st", that's kind of meaningless. I can easily shift the goal posts of what v1 needs to be to ensure I meet my deadline, regardless of how hard I actually work. I could counteract this by writing down a list of minimum requirements for v1 right now, but there's a high probability that in a month's time these requirements would make no sense. At that point, I'd either have to slavishly adhere to a suboptimal vision of the product or rewrite my requirements - thereby moving the goalposts.

My second reason is that writing high quality code is a process of constant reworking and refactoring. The first code you write is almost never the best solution. As you work more with a problem, you start to understand it in new dimensions. As you layer in more capabilities you often find that several seemingly disparate functions of the software can actually work through a single cohesive component in the code. Taking time for this process is especially important when you are building the foundational layers of a long term software project. Rushing to complete milestones in the face of a deadline creates pressure to skip that extra refactor and instead push towards adding more capabilities no matter the cost. This is not the right choice for eno in these early days. What's more, a strong foundation in the code will allow the project to go orders of magnitude faster in later days. We spent weeks and weeks of developer time at my last startup paying for poor architectural decisions I made in the early days.

The underlying mechanic of Elon's strategy is that it creates pressure to work harder than is comfortable. When you feel tired and you want to end work for the day, that deadline forces you to find the energy to keep going even though it sucks. What's more, is when you can't quit work before the job is done, you start to see with greater clarity what work is actually required to complete the job. Frittering time away on unimportant details becomes much more painful. This is exhibited in multiple anecdotes from the Elon biography where an insane deadline forced the realization that something wasn't as insurmountable a task as it seemed.

If the question is "how hard should I work?" the answer is "harder than I want to." Recognizing this, and being a team of one, I try to apply this analysis hour by hour. When I feel myself thinking it's time to stop, that means it's time to keep going.

Visualizing the Code

In the latest implementation of my text editing engine, paragraphs are stored in a linked list of fragments of text. The data structure is something like this:

const TextNode = struct {
    next: *TextNode
    text: []const u8,
    state: enum{ current, deleted },
}

A single sentence might be spread across several nodes:

Node 0               Node 1.               Node 2
|The quick brown| -> | fox jumped over| -> | the lazy dog.|

I have a function which takes inputs for a replace text range operation and modifies the linked list as needed:

replaceTextRange(first: *TextNode, start: usize, end: usize, text: []const u8) void {
   // iterate through the list of TextNodes and determine what needs to change
   var it = first;
   var text_index: usize = 0;
   while(it) |node| : (it = node.next) {
       const end = text_indx
       if(start > text_index and start < text_index + node.text.len) {
           // the replace range affects this node, do stuff.
       }
       if(node.state == .current) text_index += node.text.len;
   }
}

The actual body of this function is hundreds of lines of code long (there are a lot more complexities to how text works in the engine).

As I started to test this function I began to discover lots of off-by-one type errors. At first I would write a unit test reproducing the error and then just kind of brute force the answer by switching < to <= and adding/subtracting 1 from indices in various places. This approach began to break down as the function became more and more complex.

What I really needed was to be able to see this stuff visually. To do that, I came up with these functions:

fn logRange(start: usize, end: usize) void {
    for (0..start) |_| u.log(" ", .{});
    for (start..end) |_| u.log("█", .{});
    u.log("\n", .{});
}

I can then call this in replaceTextRange to visualize each step of the loop:

replaceTextRange(first: *TextNode, start: u16, end: u16, text: []const u8) void {
   // iterate through the list of TextNodes and determine what needs to change
   var it = first;
   var text_index: usize = 0;
   while(it) |node| : (it = node.next) {
       const end = text_indx
       logRange(start, end);
       logRange(text_index, text_index + node.text.len);
       …
       if(node.state == .current) text_index += node.text.len;
   }
}

Now when I have a failing test, I get printouts like this:

Node 0:
        █████
██████

Node 1:
        █████
      ████

Node 2:
        █████
          █████

Node 3:
        █████
               █████

This helps me understand what's going on: it's obvious that Nodes 1 and 2 should be affected by the replaceRange operation and Node 0 and 3 should have no effect. The nice thing is that the display functions are really simple. This makes it unlikely that the display itself has a bug. I simply need to look at the visualizations and figure out how to get the computer to understand the positioning of the text ranges in the same way that I see them.


If you liked this post, please consider sharing it with a friend.

We also have an RSS feed

#software #startups