Middle of last year, the project I had been working on since 2012 was drawing to an end, and reflecting back on the work I had done, I realized that the quality of the code was incredibly uneven. Some parts were easy to maintain and extend, whereas others were poorly structured and a severe headache to work with.
Having identified this quality issue with my code, I began to contemplate what could be the problem. I re-read all the great books on code quality and the software development process; Clean Code, the Programmatic Programmer, and so forth. It turned into a bit of an obsession.
By the time I identified the common factors in the problematic code, which was mostly down to the code having too many responsibilities, and one instance of using inheritance where aggregation would be better, the quest for a solution to this problem had inadvertently left me a changed man.
Problem solving is something I am very good at. It's in my mixed education I think, both physicists and programmers are tasked with solving problems--it's the very essence of the crafts, I'd argue--and I have one foot in both professions. The crux is that before you can solve a problem, you need to first realize that it's a problem (and it can be solved by you).
My epiphany was that problems with my development process were "problems" like I knew them from problem solving. That is, I could apply myself to them. I've always liked the concept of kaizen, continuous improvement, but the idea of applying it to the craft of programming never occurred to me. But now everything came together very rapidly. I have probably improved more in the last 9 months than I have in the last 4½ years, and I was by no means a bad programmer to begin with.
Over these months, I have made innumerable alterations to almost every aspect of how I work, and most changes are small, but I think among the more drastic changes I did was one to how I write my code.
I used to half-write a function, then half-write a function it called, and so forth, jumping around in the source file like a mad-man; and then when all the compiler errors went away, cross my fingers I didn't make any mistakes when I put everything together. Besides creating one hell of a clutter, this method also tends to introduce a lot of unnecessary bugs. With this method, your code simply isn't very well thought through, because there is no human alive that can keep all that half-finished code fresh in their mind all at once. As a compensation for this, it tends to lead to pretty long functions, and some severely unhealthy complexity.
I've realized that if you start at the top, and make it a rule to always finish your functions before doing anything else, while keeping them short and giving everything descriptive names, calling other functions that haven't yet been written and using fields that aren't defined, you're able to produce much clearer code. This way, you don't need to keep very much in your mind at all. When you move to implement the next function, you just need to look at its name to know what you intended it to do.
Before incorporating this method into how I work, I mostly got things right, but my ambition ended there. My biggest problem was that I never consistently put any thought to what I was doing. I tried to get better, but my attempts at improvements were wildly undirected, so I saw no tangible change in quality. For the most part it isn't very fruitful to try to assimilate solutions to problems that aren't your problems. So even though I read all the right books and blogs, I was pretty stagnant in terms of skill. I thought I was pretty good, but I just had no idea how much better I could become. The solution to re-iterate being to use self-reflection as a tool to identify problems, which you can solve, and use as leverage for greater understanding.
All that said, I'm still learning and improving. The day I stop is the day I am dead.