We’ve all been there. Your boss tells you to write a “quick and dirty script” to move some data over from another system, help onboard a client who has an unusual setup, or simply process some records that have something wrong with them (often from another one-off script that someone else wrote a while back). You could just write the quick and dirty code and go on with life, however, you also know about several “one-off” scripts that have become semi-permanent residents of your codebase. Odds are also good that these scripts have been a source of frustration and weird errors for most of the time you’ve known about them.
In development, we have to constantly fight the temptation to treat certain parts of our code as being less worthy that the rest. These “second class citizen” pieces of code are considered less important, tested less thoroughly, and are often given much less attention when it is time to refactor or clean up code. They may include reports, testing scripts, build scripts, support tools, one off applications, data import/export scripts, and the like. While it’s easy to rationalize treating these things as less important, it can often come back to bite you. However, due to them being used less often (or less prominently…), you may not be able to always give them the full attention that you would give to the flagship portions of your application. This is especially true of one-off scripts or small short-term applications, because you are told from the outset that they won’t be around for very long.
Thankfully, there are some things you can do to make these kinds of applications and scripts a more sustainable part of your development workflow, for however long you end up keeping them around. If you do it correctly, you may even be able to salvage the script when management “discovers” that it really should be a permanent part of your application. Best of all, these practices can also help ensure that your “one-off” script or application doesn’t end up being a huge maintenance burden for however long you keep it around.
We all have to build and run one-off applications and scripts from time to time. While you can tell yourself that you are only going to need the code for a single instance of a single issue, your own life experience will tell you that this assertion is false. Further, because you are actually touching code in a production environment, you need to go to a lot more effort than you would in a development environment with data that isn’t critical to the business. Finally, you also need to make sure that other people can safely run your process and that they can reason about it in the event that you aren’t there to monitor it.
Keep the code with the main body of your application in the same source control repository. There are several reasons for this.
First, this makes the code accessible to the rest of the team, which means they can run it while you are on vacation if that is required. It also means that other people can review what the code did, which can often help troubleshoot when a weird error occurs due to data modifications that weren’t made in the main codebase.
If you are using a language with strong compile-time checking, this also makes it easier to keep your code up to date with the rest of the system so that it can be used without having to change it as much. This also exposes your code to your test suite, which means that you could make sure that it has tests around it.
Log things at least as well as you do in the main application, if not better.
Because one-off applications and scripts are often run outside the main application, much of your main application instrumentation strategy will not apply here. Also, since the code won’t be run as frequently as your primary application, it is far more likely to contain bugs, inefficiencies, and the like, simply because they haven’t been caught (yet). In a social sense, your temporary application will be blamed for any problems that occur in the main system if it was run recently or touches the area that broke. Logs help you disprove this assertion.
Idempotency is critical
Realistically, you may have to run your application multiple times. This means that any data changes that your application makes should not cause problems on subsequent runs.
Be aware of situations where you are updating records to set their values to their current value; if your audit trailing strategy is not great, you can end up with a lot of noise in your logs. Also be careful of anything that your main system might send out in response to a change (such as notification emails) and make sure those don’t happen, or that you can keep them from happening.
Try to include a rollbackback mechanism
Requirements gathering for one-off applications tends to be sparse and rushed. Its likely that you will have to stop the process, or attempt to undo your changes. Expect it. When rolling back, unless your work is already in a single transaction, make sure that you are rolling back to a correct state before committing.
Break your process into steps that can be executed, stopped, and restarted independently if it takes a while to run.
You will want to verify your work in smaller steps, just to decrease the number of things that can go wrong. If one of your steps is particularly demanding on system resources, you may need to run it off hours. If things are broken into smaller steps, this might mean that you can run some of them in advance, meaning less time stuck in the office after hours.
Prefer queueing mechanisms to loops and separate execution of the queue from filling the queue.
Transient failures are the bane of temporary applications. Try to use structures that mitigate some of the problems. Such mechanisms make it easier to scale if you need to do so, which was likely not considered when you planned the application. Separating the thing that fills the queue from the thing that processes the queue can allow you to fill the queue during business hours while processing it after hours.
Have an audit/simulation mode for your application
Nothing is worse than the feeling of wondering whether something will work. Being able to simulate the work helps. This is especially important if the app lives longer than expected and needs to be run again in the future. Breaking things into steps can help with this, especially when combined with robust rollback mechanisms and/or testing databases.
Allow restriction/configuration of the dataset that you application will work with.
Don’t hardcode things in a temporary applications unless there is no possibility of them changing. The app may get run again on accident. You will likely also find that you either have to repeat a run of your application with a smaller dataset, or a different dataset. Basically, the idea here is to make it relatively easy to re-run the application again later under different circumstances, without code changes.
Allow invocation of your tool from within your application
While it may not seem like it is worth the effort, allowing your one off code to be run from within your application makes it much easier for other team members to do it. This practice also gives you tighter integration with the app, meaning that your code isn’t forgotten during system changes and refactorings. This approach can also make QA efforts around your code much easier.
Allow partial application of application processing results, along with restarts.
Unless your one off requires that the entire system be in maintenance mode for the duration, your code should ensure that each state change leaves the system in a valid state. Basically, you want to avoid transiting an invalid state so that you can avoid errors and can restart safely at will, instead of having major failures and having to run your code at an inopportune time.
One thing that can help with this is the use of staging tables to arrange data before using another call to actually change records. Separating the loading of data from the updating of data can limit the amount of time your app is in an invalid state if you can’t avoid it entirely.
Have a mechanism for providing statistics when the temporary application completes.
You should be able to tell how many records you touched, how long it took to touch them, as well as the count of any side effects. If you can’t, it makes it hard to prove that your code worked, especially if you aren’t the one executing it.
This is also important for subsequent runs. If someone asks how long it takes to do something, being able to give an intelligent answer can keep you out of trouble later. Be sure to include this for rollbacks as well – you want to make sure that you fixed as many records as you messed up.
Tricks of the Trade
Many times in life we have to do something that should only be a one off but ends up becoming a regular routine. It could be something like going to a particular restaurant with a friend one Sunday afternoon for lunch that becomes a regular weekly ritual. This is especially true if you have children, or nieces and nephews, as they love routine and repetition. You may not think much of these sorts of activities in the moment but they have the potential to become something that has lasting meaning. The next time you find yourself having to come up with something or do something that you think is once and done thing in your life consider how what you are doing could become a regular occurrence.