As tools programmers and tech artists, we are responsible for the efficiency of our designers and artists. And most tools programmers and TA’s I’ve worked with take this very seriously, and are generally very clever, so very few things can stand in their way when they are determined to speed up a developer’s workflow. Most commonly, such speedups are achieved by the automation of repetitive tasks.
But we are also responsible for the quality of our codebase. ”Simplicity” of code and systems is commonly accepted as an ideal all coders should strive for.
Everything should be made as simple as possible, but not simpler.
And here is my problem. Automation increases complexity and reduces simplicity.
Consider the following diagram, which could represent a single workflow with many steps. Each Step represents some unique concept or block of code or logic that exists in a pipeline- for example, exporting the content, format conversion, writing a metadata file, assigning a material, and importing into game. Right now, the user performs each one manually.
Obviously we can do better- we can half the number of steps if we write some code to automatically, say, launch an exe to process the just-exported content, and we can automatically write the metadata file on import.
Once this is in the wild, we realize we can automate the whole thing! So on export, we do everything, and it even imports the content into game. Great! But of course we still need to support some manual intervention for things that don’t ‘fit in.’
There’s a problem here, though. A big one. The code has essentially remained the same- so even though the user’s experience is simpler (which is always the goal!), the way we got there was to add more complexity into the codebase. Because here’s the thing about automation: Automation relies on inference. And inferring things in code is notoriously difficult and brittle. We have basically all the same code we had when we started (though I’m sure we fixed and introduced some new bugs), except we have now effectively doubled the connections between the components, and each connection is brittle. How much of your automation relies on naming, folder structures, globals (environment variables and singletons are globals too), or any number of circumstances that are now built into your codebase? Likewise, if you merely added buttons to create automation, the additional complexity there is obvious. All the old stuff is still in place, you’ve just created another UI and code path on top of it that is either using it or also accessing the same internals.
That is not what we should strive for.
This, instead, is what we should strive for:
This isn’t always possible- but I’ve seen enough pipelines to know that it is probably possible on most pipelines at your studio, and definitely possible on some. It should always be our goal- that every time we want to ‘automate’ what the user does, we instead say “how can I reduce the complexity of the code so nothing needs to worry about this.” This is how you identify automation that increases complexity versus refactorings that reduce complexity: when your change simplifies the codebase (this is open to interpretation but I’d imagine you can judge this pretty easily), and ‘automates’ previously manual parts of the pipeline, that is no longer automation- you have done an excellent refactoring that has reduced complexity and it is not automation (at least not actually- the users are free to call it what they want).
It isn’t always possible. More commonly it would be possible but not without a substantial refactoring somewhere (maybe not even your code). Sometimes, it is just moving the complexity around rather than removing it.
These things are fine! The important thing is that you are now really thinking about your codebase. The goal isn’t to reduce the complexity of your codebase in a day, it is to ensure you are only adding valuable complexity and that you have identified opportunities to reduce complexity.
It’s not very difficult to identify when we are adding excess complexity when automating, or when we are simplifying.
If you have simple configuration needs, such as choosing two options or files, see if you can infer that setup instead from what the user chooses to do (such as providing him two choices, rather than one configurable one).
In contrast to that, prefer upfront configuration to inference if the configuration adds significant power and simplifies the code.
If common use cases no longer fit into the scope of the tool’s effective workflow, refactor the tool. Do not start adding ‘mini-UI’s that support these additional use cases, or you will end up with a complex and confusing mess.
Always present the minimum possible set of options to the user that allows her to get her job done effectively.
As a corollary, if the code behind your simpler UI becomes significantly more complex when simplifying the actual UI, it is likely your system can be streamlined overall. The lower the ratio of UI options to code behind, the better.
All too often I see tools programmers and technical artists automating processes by building new layers of code on existing systems. Coders should always look for ways of simplifying the overall system in code (or moving the complexity out and abstracting it into another system) as a way to achieve a streamlined workflow for the user, rather than building automated workflows and adding complexity and coupling to the existing code.
I intentionally didn’t provide precise examples or anecdotes, but I will gladly provide personal examples and observations in the comments. Thanks.
Any intelligent fool can make things bigger, more complex, and more violent. It takes a touch of genius – and a lot of courage – to move in the opposite direction.