Of all the Gall
I ran into Gall's Law recently, which states: "A complex system that works is invariably found to have evolved from a simple system that worked. The inverse proposition also appears to be true: A complex system designed from scratch never works and cannot be made to work. You have to start over, beginning with a working simple system."
John Gall's pithy statement relates to a software methodology I less pithily call "Get it working and keep it working". (Let's call this "Griffin's Method".) It's something that experienced software developers understand, either intuitively or explicitly. The basis of it is that when you make an incremental improvement - either a change or an addition - you know exactly where to look when it fails, as it often does. So, when building a system from scratch, you want to create the smallest possible system and get that working. Then, you add small pieces which you've gotten working separately; each piece is its own small system.
I first absorbed this principle many years ago when I was learning to program in Pascal. (Pascal? - yes, many years ago.) I had written about 1200 lines of code, which was a lot for me at the time, to simulate the "Hearts" card game. Once it was completed, I eagerly ran it, hoping for it to do great things. It didn't. Problem was, I had no idea how to fix it. So I abandoned it. Lesson learned.
A software guy I once worked with could whip out bug-free software about as fast as anybody I've ever seen. Besides being a very bright guy, he was rigorous about writing module tests for everything he wrote at the same time he wrote the code. I haven't always done that in the past, but having been inspired by his example, I've been doing it more regularly in the last few years. Although writing module tests might seem like extra work, it always turns out to be time well spent.
Module testing doesn't have to be fancy. The simplest form of module testing is to just single-step through all your code when you first create it. In the process, look at the values of variables to see if they make sense. That takes very little time and lets you immediately remove at least the top layer of bugs - highly recommended! For GUI software like ScopeFIR and ScopeDSP, I put breakpoints in all clauses of new code, do things interactively with the GUI to hit the breakpoints, and remove each breakpoint when hit. Then, any remaining breakpoints indicate something that I haven't exercised: try again.
If you're working in a software development process that requires formal module testing, there's simply no excuse to save it for the end of your project. It should be done when you first write your code. When formal module tests are required, I typically use asserts in the early module tests to detect problems, then replace the asserts with formal test outputs at the end of the project to please the authorities.
Some software development methodologies even take module tests a step further by suggesting that you write your module tests before you write your code. Although that sounds like potentially a good idea to me, I'm not sure I'm disciplined enough to do that yet. And it doesn't seem to be a common practice. But maybe you and I should try it sometime to see what happens.