There’s a common expression “Better to beg for forgiveness than to ask for permission.” In the world of software engineering, taking this motto to heart can lead to robust and stable software. Far too often, we try to verify every possible prerequisite before performing an action.
For example, when creating a new file, we might first check if it exists, and if it does, if it is writeable. Then if it is not, we check if the parent directory is writeable. Finally, check if the disk has free disk space. This seems like a pretty valid approach at first glance but a million things can go wrong. What if you’re on a UNIX system, and the directory is mounted to a different partition than the central one? Now your code to check disk space has grown more complicated. But still, this can work, right? Well it could still be an invalid file system. What if it’s valid on one file system but not on another? You could have a vast library of code to verify filenames on different platforms, but as soon as one platform starts allowing more generic filenames, your code is overly restrictive.
So, trying to verify that you can create a file before creating it leads to a large library of code to a solution that mostly works. And of course if a process changes directory permissions between verifying that you can create a file and creating the file, your program will still crash. A solution that works 90% of the time and possibly annoys users is possible in this manner with a lot of effort, but obviously not advisable. Then, there is the other solution: try and apologize. Whatever language you are running has some manner of exceptions or failure codes. Try to open the file, and if there is an exception (or an invalid return code), display the error to the user. One simple solution, cross platform, future proof, and beautiful.
This idea of trying an action and diagnosing any errors resulting leads to an important corollary: check results not side-effects. I had to write an installation manager for a suite of software, which ran actual installers as subprocesses. I spent hours attempting to verify return codes, parsing logs, and even timing the installers. Logs ended up being different in different versions of windows, timing was unreliable at best, and return codes were completely useless. In the end, I realized the best solution was staring me in the face. Wait for the installer to exit, and check the registry to see if the software was installed. If it was, then obviously the installer succeeded.
When writing software, just do what you want to do, and deal with the problems that arise. When verifying actions, check for the expected results, not whether or not the actions think they succeeded. These two simple rules will save you many headaches.