Learning how to refactor to reduce the mess is a key skill. I hope it goes without saying, but two important tools to enable confident refactoring is to use version control and to have a fast unit test suite.
Of course, the hard part is knowing what to do that will reduce the mess!
I find that books are a difficult medium through which to learn this, as most of the challenges come from larger codebases than most books are willing to address, and structural problems that are hard to really explain without working knowledge of the code in question.
Personally, one of the places I learned the most was right here on gamedev.net. When I was getting started I found there were several members who introduced me to new ways of thinking about programming. I don't know if a new person today would have the same experience, as the world of programming Q&A is likely irrevocably different now that forums like here compete with Stack Overflow.
Exposure to different ways of writing code will be good for you. However, there is a danger here. If you read the source for frameworks, game engines and some libraries, you might see all sorts of abstraction and generic code and you might think that this is good design. When I was starting out, I was in awe of all the complex code you could write and I strove to replicate that in a codebase that didn't need any of it. Since then, in my experience, I've found that good library design is quite different from good application design. Each abstraction you introduce must deliver a big benefit, as done poorly they can make the code much less clear.
Another challenge is how much to design up front.
My approach is to write small programs that work and grow them into larger programs, rather than think too much about the design up front. This requires occasional mass refactorings once the program reaches a critical level of complexity where the current overall design starts to feel like it is getting in the way. If you've not done this many times, it can seem scary, but with version control and a battery of tests there is little to worry about. This will allow you try out alternative designs and see if they help.
Writing tests can help influence the design of the real code (you may wish to read about "Test Driven Development" for details). Even if you don't go as far as TDD, writing unit tests for your code will force to you interact with your code in small pieces, which should highlight problematic dependencies.
Code that has lots of dependencies is almost universally considered to be more difficult to work with than code that has few or no such dependencies. An ideal to aim for is to have independent "modules" that have minimal knowledge of one another.
For example, one game idea that I have takes place with ships moving between planets in a solar system. Assuming there is no existing library that covers my needs, I could start by writing a gravity based physics simulation module that supports massive objects (stars, planets, moons) and negligible objects (space ships, missiles, space stations, etc) and that will handle the movement and prediction of these objects.
The main thing to remember is that this is a practical skill, you'll get better with experience. Don't compare yourself with people who have likely been programming for a long time, they weren't so good when they were starting out! Rather take inspiration from where they got to, and try use that to motivate yourself to hone your craft.
Good luck!