The way to approach this is by noticing two "problems". They both stem from the fact that left to my own devices, my development flow naturally drifts towards a lazy style reminiscent of when I first started programming and didn't know any discipline to make things better. Sometimes that's fine, you can just brute-force it, and indeed that's how many of the things I did got done. An example of this style is not using version control much -- just develop the whole thing until it's ready, then commit it and ship it! Once I have a "base thing" though, I can then more easily apply some discipline, so the two problems I'm now going to highlight come most strongly in the earlier phase.
The first problem I found nicely described by this old blog: Imagination Overrun. The second problem is also described there, Knowing When You're Done.
If you never have had a problem with imagination overrun, you probably won't understand, but if you have, perhaps no further elaboration is necessary? For the sake of attempting to bridge understanding or at least avoiding misunderstanding, imagination overrun is a behavioral issue where you end up spending way too much time on things that don't really matter, for your own pleasure, though sometimes you can trick yourself into a bit of it thinking it'll help others. Sure, you think, this handful of code does what I think it should do, but what if it did more? What if I used different structures? What if I expressed things differently? What if I used a dirty memcpy trick instead of just writing 6 nested for loops? What if I squeezed out some extra performance? What if I minimize lines of code? (I've never been a code golfer but a similar mentality is responsible for it and its converse of excessive-verbosity in the name of clarity.) Maybe I'll just rename these handful of symbols...then a day goes by, and you come back and rename them again. You might bug friends to ask them their opinion -- this is actually a hint at one of the solutions to this problem.
All that and more are signs of the problem. You've let your imagination run wild, and now you're going to satisfy it. The original goal, that you might have already solved, or because you immediately went off into the weeds you're still nowhere near solving, isn't a central thought point. Maybe you've even forgotten, what goal?
For me, I think the biggest win against this tendency is to make sure I'm working with an issue tracker. That is, I discipline myself to only focus on one or maybe two tickets at a time. They may be bugs/features/tests/refactors/whatever, but my goal is to focus on just the one issue. If the work is exploding in complexity (i.e. a Big story-point issue) that's a sign it needs to be broken up, i.e. generate new sub-issues, or at least a sign to apply more disciplined methods of dealing with it such as the Mikado Method. You might even just shelve everything and go work on something simpler for a while, especially if this big thing isn't super important right now.
You'll notice that many good professional environments require checked in code to reference an issue (or with git, at least the merge to an important shared branch needs to reference something). Without it, you get random commits, random changes mixed in with commits, and a lack of focus. You also know what to work on next, even if you haven't done any sort of priority analysis -- you can only either pick a ticket, or make a new ticket and work on that. You can't just go wandering off into fiddling and experimentation. (Maybe you need to, there is a time and place, but make a ticket for it! With a definition of done and maybe even time-boxed!)
An issue tracker isn't a silver-bullet, it just works best for me against this. But obviously abuses can still occur. " Why are you spending 3 days on this bug that looks like it's a quick fix and when it eventually is closed the commit reveals it was a quick fix?" There are many possible answers to such a management concern, unchecked (even by the virtue of having a ticket tracker) "imagination overrun" is one of them. And the more friction or overhead a tracker has the more likely random crap will appear in the commits related to other issues, or commits made without reference to any issue at all.
Another good move against this problem is to write more tests, especially tests written before the code under test is actually complete (I'm not a TDD-ist, write tests when it's convenient). I can satisfy my imagination in a more constrained way just writing some tests, then I get the satisfaction of writing code to make them pass. You'll notice good professional orgs do testing.
The last move is to have code review. Similar to having a friend's advice, a code review is better because you need to come to some resolution with the reviewer, you're not just seeking an opinion. So you and the reviewer need to agree to do approach A, or approach B, or some approach C that they came up with, or agree that it doesn't really matter and just leave what you did the first time (even if you yourself still aren't sure whether one is better than the other). You hash it out -- often it doesn't matter, or you favor a principle of going with the one easiest to change later. The important thing is you got another person involved rather than spending an extra amount of time going back and forth with just yourself. You'll notice good professional orgs mandate code review (even if many code reviews are just ceremonial).
The second problem, knowing when you're done, is so related to the first that it's no surprise it's also attacked by the same three moves. Your issue should have some definition of done criteria -- is it met? You're done. Are there no more issues? Your program is done! Do the tests pass? Done! Has a reviewer signed off? Done! Move on. Without an issue telling me I'm done, I might e.g. be too satisfied that I got a dragon sprite to animate (my goal), and decide to go and make it work for a spritesheet and not just a directory of sprites, or do some 'refactoring', or just sit there watching it... An issue instead helps you see "ok that's done, move on" -- and maybe that means a spritesheet system is next, but only the ticket system should tell you.
Anyway, a single person can make use of issues and tests on their own. Having so clearly and consciously recognized this (mainly the first one) recently I hope not to slip back too much into old habits -- though of course some programs are so simple they really don't deserve these formalities. You'll notice many successful open source projects use all three methods, too, to some degree.
As an individual you might not want the overhead of an issue tracker, but it's worth it, that's my final message here.
(Extra: One of the reasons I like the idea of Fossil is that it includes a ticket tracker as part of your source control. It's not separate, like github issues, or jira, or whatever, which can in theory make for lower overhead and nicer integration and use. In practice though, Fossil is ghetto, and so after an experiment with it (a subject for another blog post) I've gone back to git (and immediately ran into these problems because I stopped doing work against tickets for a while). I don't mind github issues for anything up there, but privately I'm currently happy with an even more ghetto but super-low-overhead issues.md and issues-closed.md file that I just commit with my closing changes.)
Posted on 2021-04-20 by Jach