This post is mirrored on my website.

It is common advice that it isn't worth automating something unless the time saved doing the task is greater than the time required to automate it.

Randall Munroe has two relevant xkcd comics (license):

This comic is the most relevant to this article:

When I talk about automation, I include things like code generation and macros, which automate code writing.

I am going to argue that doing work to automate things may still be worth your time, even if it takes longer than it would have without the automation.

Ethics of unergonomic interfaces

Recently, I have been thinking about user interface ergonomics, or how well an interface, be it textual code, physical hardware, or application UI, is designed around humans.

The goal of automation is typically to increase the ergonomic quality of some interface. Rather than doing a mindless and repetitive task (unergonomic), it is done automatically so one can spend their time doing more valuable work (ergonomic).

Jonathan Blow originally made me see interfaces in this way in an On Doubt interview:

If by spending 10 hours of your time, you can save a typical person using software 1 minute of bad experience, then at some level of usage that number just dwarfs your hours of time, and it becomes an ethical failing to not do that work.

Level editing

I heard an especially good example of this. Some game designers I know spent entire weeks hand-writing 2,000-line XML files. These files determined the basic setup of a level in the game.

They would open the game in a specific world, then hand-copy hundreds of floating point coordinates provided by their mouse pointer. These coordinates would be input (by hand!) to this file to precisely place items.

The file would look something like this, only there would be thousands of unique entries:

<level>
  <building x=1234.23 y=928.2233 z=977.22>Hut</building>
  <building x=928.23 y=110.2233 z=5638.22>Shack</building>
</level>

If an artist wanted a building rotated, they would send the designer a message to have them tweak the value. These are creative people with Master's degrees, typing numbers into a file for days on end. These files could have been generated by the computer---there was no creativity involved in their copying to these files.

This is an example of an unethical interface. One week of programmer time would have easily saved multiple weeks of designer time. Not only that, but the programmer would likely get some amount of enjoyment and satisfaction from creating a good tool.

When developing things which have a human interacting with it, you should consider how much you value your life time, and have empathy for your users' life time.

Effects of automation on creations

Automation not only changes how you do something, but what you will be willing to do.

In the level editing example, designers would be much more willing to create levels if they didn't have to suffer every time they did it. They would spend less time copying numbers to index cards and more time being creative and iterating.

I have spent a large amount of time on a new programming language, Cakelisp. I could have implemented all the software I have written with Cakelisp with existing languages. Thanks to Turing completeness, it follows that for any program I can write in Cakelisp, I could have saved all that up-front time making Cakelisp by writing the program in C instead.

This is too naive. I work on both C and C++ programs professionally. I am very familiar with how to get real things done with those languages. I am also intimately familiar with their limitations. When I worked on projects at home, I became more frustrated with those limitations.

Cakelisp was a revelation because it was my chance to escape from these limitations. I had to pay my time in order to make something I consider better, but it was absolutely worth it. I was now in charge of the interface. I shaped the language, the language didn't shape me.

This resulted in much higher motivation to do my own projects. If my goal is to eventually become self-sufficient by selling my software, doing more projects in a more enjoyable way is valuable.

Macro magic

Modern computer processors are very parallel. A good processor nowadays has sixteen or more cores. However, writing multi-threaded software to exploit these processors remains difficult as ever.

In my File Helper project, I needed to keep the user interface responsive while performing a scan of the entire filesystem. My first implementation used SDL's thread API to create a thread dedicated to file system scanning. I used a heavy-handed mutex to ensure the main thread and the scanning thread never stomped on each other's shared memory.

This system was complicated, fragile, and difficult to change.

Task systems assist the programmer by abstracting work that needs to be done into tasks or jobs. In this example, scanning the filesystem would be considered a task.

The problem is, C doesn't have a great interface to these systems.

Here's an example of the C I would have to write to create and run a task in EnkiTS:

taskScheduler= enkiNewTaskScheduler();
enkiInitTaskScheduler(taskScheduler);

// myLongTask is a function which must match a special signature
enkiTaskSet* myTask = enkiCreateTaskSet(taskScheduler, myLongTask);

enkiParamsTaskSet taskParams = enkiGetParamsTaskSet(myTask);
// How many things need processing
taskParams.setSize= 100;
// How many things should be processed in each task bucket
taskParams.minRange= 10;
enkiSetParamsTaskSet(myTask, taskParams);

// Set task on-complete
enkiCompletionAction* completionAction =
    enkiCreateCompletionAction(taskScheduler,
                               onMyLongTaskComplete);
enkiParamsCompletionAction completionArgs =
    enkiGetParamsCompletionAction(completionAction);
completionArgs.pDependency= enkiGetCompletableFromTaskSet(myTask);
enkiSetParamsCompletionAction(completionAction, completionArgs);

// Start the tasks
enkiAddTaskSet(taskScheduler, myTask);
enkiWaitforAllAndShutdown(taskScheduler);

enkiDeleteTaskSet(taskScheduler, myTask);
enkiDeleteCompletionAction(taskScheduler, completionAction);
enkiDeleteTaskScheduler(taskScheduler);

As you can see, this is a lot worse than a simple function call:

myLongTask();

This friction in the interface changes how often I am willing to use the task system. Additionally, it imposes a maintenance burden simply by virtue of being much more lines of code, which encourages bad habits like copy-pasting.

Cakelisp provides me the opportunity to eliminate this friction via full-power macros, which allow me to do arbitrary compile-time code generation.

This means I get to define the interface. The task system becomes a domain-specific language where I only type what I must:

;; I define a task just like a function, with regular arguments
;; These arguments are automatically structured and de-structured
(def-task treemap-update-state-task (state (* treemap-state))
 (do-the-long-scanning state))

;; Start the task, passing the arguments from the current context (state)
(task-system-execute
 (treemap-update-state-task state)
 ;; This will run after the previous task, on any thread
 (classify-treemap-paths state)
 (treemap-update-state-done :pin-to-main-thread state))

This task-system-execute macro took about one week to implement (it's here). That is a large time investment, but it will continue to pay dividends the more I use it. In fact, I have already used it three times in File Helper, with great success.

This macro not only eliminated the fragile mess that was the hand-rolled file system thread code, it made me move even more things off the main thread.

Another example, this time with tasks that run in parallel:

(task-system-execute
 (export-category-paths copied-entries copied-categories)
 (parallel ;; These tasks can run at the same time
  (open-userdata-system-file-explorer
   open-file-explorer-on-complete)
  (on-export-complete :pin-to-main-thread
                      copied-entries copied-categories)))

There are some drawbacks, of course. This macro adds a small amount of time to the compile phase, and produces code that is likely slower than hand-rolled threading code. However, the amount of time recovered by being more willing to make things threaded should easily make up for its drawbacks.

This tool changes how willing I am to write things which require multi-threading. It changes what kinds of things I even consider making.

Conclusion

Automation not only saves you time, it changes what you do. The ergonomics and interface friction influence how you approach tasks. More ergonomic interfaces give you more time to do more interesting work.