This scenario could have been prevented if you had a good system for regularly testing incremental changes in place. If you haven't read Deadron's article on unit testing in a while, I highly recommend you do so again. It's a good read and it can help remind you why test-driven development is a great way to do things.
For BYOND, Deadron.Test is a great test framework. It will help you feel encouraged to switch to test-driven development with its ease of use and lack of required intervention. With Deadron.Test, it's automated for you such that you just have to make an /obj/test/verb and your test is already ran.
For C++, I wrote a similar testing framework inspired by Deadron.Test. The process for using it is like so:
- Create a new set of header and source files for your test(s).
- Create a new class derived from TestFramework::UnitTest
- Define the public `bool Run(void)` function. A return of 'true' indicates success, a return of 'false' indicates failure. You can use Message(const char *) to supply a message to go along with the failure message.
- Define `const char * Name(void) const` to return a name for your unit test.
- Use the `DefTest(unitTestClass)` macro on your class in the source file.
- To make the unit tests actually run, create a new `TestFramework::Core` instance and call `bool Run()` on it. A return value of true indicates success on all tests, a return value of false indicates a failure. It outputs which tests pass and the test that fails (if one fails).
- Make sure that testing.cpp is compiled last
- Optional: If you wish to create a new variable of some type belonging to your testing class, you can define `void Clean()` to do any necessary clean-up after your test concludes. This can be an easy way to avoid the redundancy of cleaning up in the several scenarios that the test may fail without causing any leaking of any sort.
- Do not put stack-based variables outside of Run(). The way the testing framework is setup, your test class gets constructed inside of a static global (allocated at program startup), which never gets destroyed.
- Create two builds for your application, one to function and one to run the unit tests. I personally use CMake and have it build the normal application without the testing framework then a second version with "TESTVER" defined and my testing framework included. My `main()` is one giant #ifdef/#else/#endif. You can do it however you want, but I do not recommend including the testing framework in a release build.
After months of usage and refining, I have simplified the process of using it as much as possible. After you've got it running tests and you've written a test class or two, it starts to come naturally to you.
Using a test-driven development cycle has saved me from going bald, and it could save your hair too. There have been countless times where I've written up a new piece of code to interact with something else only to find that that something else didn't completely operate the way I wanted it to because of some changes I made else-where. Now that I know how to write unit tests, this happens no more.