Make your life easier by using imaginative coding to create clear function contracts, faster prototyping, and better architecture.
The first time I saw imaginative coding used was towards the start of my career when I was required to finish implementing code that a coworker had started. His code was a work-in-progress and non-functional. As I began reading his code, I continued to find references to functions that didn’t exist, often with comments above the function reference that described how the function should work. This was confusing to me at the time. It took me longer than it should have to realize that this was his method of programming, not that a file was missing from the source repository.
Eventually I adopted his coding style, something I think of as “imaginative coding”. I have since learned that imaginative coding helps
- Encourage clear function contracts
- Enable faster prototyping and iteration
- Create better architecture
Clear Function Contracts
Functions with clear contracts are simple to create when you define their purpose before defining their implementation.
Consider a situation where we need to parse and interpret a predefined custom scripting language for an application we are developing. Where do you even start? I enjoy starting at an entry point to the new functionality, and ask myself “How do I imagine this being used?”
Akin to writer’s block, it helps if you simply start writing. Get something written down in your editor, and go from there.
In the code snippet above, I am imagining the scripting language interpretation being encapsulated as a stand-alone library, and a single function being called to run a script. At this stage, I need to think about a few things, some of which are listed below:
- Who will be calling this?
- What parameters do they need to provide?
- How will errors be handled?
- Without planning too far ahead, is there anything I am sure I will want for future expansion, or to make testing easier?
After considering the above questions , I decide that a
Script structure or class should be used to hold information about the script, which should be passed into the
RunScript method. Experience in writing similar code for custom interpreters tells me I may also want to require a
ScriptContext object that describes the environment the script will be executed in (global variables, pre-loaded libraries, etc)
At this point, I am thinking it would make more sense to create a Run method on the Script structure that accepts the script context as a parameter, and to use the RunScript method for logging, output formatting, error handling, and other miscellaneous functionality. This also has me thinking: how do I handle the output of the script? Similarly, is there even a return value? What about script parameters? I decide to update the
RunScript arguments to accept a
*ScriptParam array, and to leave the script output/return value question for later:
Notice how as I think through, and imagine how this will be used, that it is natural to create clear function contracts. I find it much easier to have clear function contracts and purpose when developing using this method, as opposed to developing a large monolithic function or script that I then have to refactor into more appropriate design patterns and architecture. To put it simply, functions with clear contracts are simple to create when you define their purpose before defining their implementation.
Fast prototyping and iteration
As obvious as it sounds, it’s easiest to iterate, rework, and refactor code when there is little or no code to work with
I usually take a step back when I have established a rough code flow to consider the direction the architecture is heading in. After some reflection, I realize I want to add the concept of an interpreter, and that
Script structures are mostly informational and shouldn’t run themselves. I adjust the code accordingly:
Notice how the interpreter parameter is an interface — this makes it easier for multiple implementations of the scripting language interpreter to exist and be swapped out. I also decided I didn’t like the name
ScriptContext and that it should be called a
Scope instead. I then added the
ScriptExecOpts parameter to the interpreter interface’s
Now that I roughly have the execution structure(s) figured out, I would begin writing basic tests to start putting the stubbed-out code through its paces. These tests would later expand to test the fully implemented functions. Writing tests for the stubbed out code will further help me define how I want my code to be used, and will set me up nicely to use test driven development (TDD) while I finish the implementation.
As obvious as it sounds, it’s easiest to iterate, rework, and refactor code when there is little or no code to work with. The refactoring above only dealt with seven lines of code in the previous version of the
RunScript method. Compared to large refactorings I’ve done in the past involving hundreds or thousands of lines of code, this is astoundingly easy.
Better architecture arises when you are able to clearly define what needs to be done, and quickly iterate to “try out” various approaches as you consider how your code will be used and maintained now and in the future. As you gain experience, you will need fewer iterations before you settle on a satisfactory architecture.