For example: If you have a function called "CreatePath()", make sure all that function does is create a path and nothing else. Make sure you test it such that it can handle any combination of parameters and corner cases such that it has exactly ZERO (0) bugs. "CreatePath" cannot call "CreateUnit" or any other functions that might dilute and increase its complexity.
2. Don't be afraid to refactor and rewrite parts of your code.
As you add more logic to your code, refactor is inevitable. Don't wait to refactor until the last step. One cause that leads to spaghetti code is people are afraid to create new bugs that they keep monkey-patching on top of what was originally a perfectly fine codebase. I have been seeing this so many times in my professional life doing code review. People are so afraid modifying an existing function to make implementation less complex, and would rather put another layer of complexity.
Me: "You can just edit that one function to accept an extra parameter, and it will be easier."
Them: "Well, I am not sure what it does and I don't want to break it" (or some other lame excuse like how he has to update the tests)
Me: "But now you just duplicated the code, and next developer will be confused which function to use"
3. Write Automated Tests
Test, test, test your code, and write a separate program to test your code. In all my professional life, nothing have given me better confidence in my code until I have written automated tests for my code. Sure, it's an annoying thing to do when you write your tests for the first time (ah geez, another shit to worry about), but the confidence you get from running one single command and have ALL your code tested and validated in less than a minute is just too good to pass up.
Follow #1 above to make it easier to write your unit tests. Follow #1 above to make it easier to write functional tests. If your code is a spaghetti, your tests will be a spaghetti too, and it will annoy the heck out of you.
4. Document Your Code
Another thing that most developers avoid (after writing tests) is writing documentation. You HAVE TO write documentation to your code. It sorts of gives your code that final conclusive stamp of approval and seal the envelope. Writing documentation puts your mind into a 3rd person perspective. I have found so many design flaws when writing documentation. For example, let's say your CreatePath() function is badly written that it internally calls CreateUnit() and CreateEnemy(). Writing tests will make you aware of this, and writing documentation will make you reconsider what kind of parameters to accept to avoid calling CreateUnit() and CreateEnemy().
If tests make you aware about the internal working of your code, documentation makes you aware of how components in your code play with each other. Writing documentation is a form of rubber ducky programming. You are trying to explain to some other (imaginary or not) person who is clueless about how your code works.
#3 and #4 are your worst enemies. Somehow developers avoid them like plague thinking they have godlike coding ability that no tests is ever required, and no documentation is ever necessary. "Code should be self-documenting anyway" , I always laugh at that statement.