I'm self taught, and have been doing this professionally for 20 years. I started by learning the fundamentals, and then I started implementing RFC's myself (for things like web servers, ftp servers, SMTP, etc...). Then I went, and compared my solutions to the open source solutions, and tried to see what concepts I was missing. A reason I think this definitely helped me is because when I was creating my own projects, it's easy to decide not to implement a feature if you get stuck trying to figure out how to implement it, and becomes self limiting. Following a spec is going to be a lot closer to how you will work in the real world, and you aren't allowed to decide to just not include a feature because you get stuck.

The comments about writing lots of code are absolutely true, the more you write, the more mistakes you make, the more bugs you have will increase your experience with edge cases and common issues. It's invaluable.

When it comes to actual tips for the code quality itself, a good aim is to make the code readable, as opposed to understandable. Use longer variable names, and avoid single letter variable names. The goal is to make it obvious what your code is doing, you want to limit the comments to sections of the code that NEED them. Far too many developers get the "Comment all the things" dogma, and this just creates a mess. I see this far far too often:

class Person {

// Returns the first name of the person
function FirstName() {...}

}

This is beyond useless. And remember code will begin to smell, but comments will begin to stink, so you want to make them only when clarification or additional information is required. If you find you have to comment all the time, then you need to work on your code composition and naming.

For flow, you want to make your code work, then refactor it to clean it up. Refactoring should be something you allow time for on any feature implementation. Never move onto another features saying "I'll clean that up later", you won't, or you will eventually because you've created a brittle mess.

Read other peoples code, and run it through a debugger. While I normally eschew debuggers except in fairly rare occasions, they can be invaluable about learning about patterns that you're not familiar with.

Try ditching the IDE. IDE's make your life easy, and they also often make you ignorant as to how your application actually works. Heavy handed namespacing languages like C# and Java are a little trickier to work with this way, but you will learn so much more about your application than you will being spoon fed everything. The reason this is important is something will eventually break, and it's far better to understand those underpinnings when building the app than when something breaks when you're staring down a deadline.

Read books about patterns, languages, etc..., and re-read them after you have been using the techniques for a while. Become familiar with the GoF design patterns, so you understand them, and understand when they're useful. Do not use them as a gospel. Pigeonholing in patterns where they don't belong will create worse code than if you hack together your own more appropriate method. But definitely use them when they fit.

Create an open source side project, and put it up on github. Good code involves interacting with other developers, and even if only 10 people are using your repo, you will learn all about pull-requests and merging, etc...

Teach what you know. You will be amazed at how much better you will understand a lot of concepts once you have to explain how they work. Even if it's just to a rubber duck.

Speaking of rubber ducks, when you get stuck on a problem, or you have a bug that seems illogical and you don't have someone handy to ask for help, explain the issue to a rubber duck. On our team, we'll often call another developer over, and while we explain the problem to them, before we're done explaining, we've already figured out what's wrong. Language uses a different area of the brain, and I suspect that by talking things out, your brain will approach the problem in a way different from the state we use for coding. (this is pure blatant speculation on my part).

Unit test, unit test, unit test. However, ignore coverage metrics, that's a huge waste of time. When you're starting out, write all the tests, but be sure to observe all the instances where the unit test actually identified an issue in the application. You'll always want to unit test your business logic, but avoid testing everything and never test for things that are impossible to occur. Also never test anything but your own code, I've seen fat too many instances of unit tests that simply add a suite of tests to jQuery or some other library. But do assure that you are able to handle bad input, regardless of here it comes from.

Don't be clever. Sure, you can make an easily readable 7 lines of code into a clever one liner that only you understand because you're oh so smart. But don't do it. When you're writing code, you're doing it at the height of your understanding of the system at the time. So even if you're the only eyes that see it, as you move on to other projects, you will no longer be as 'smart' in that codebase. This is especially true when you switch between languages frequently. Beyond that, you probably won't be the only person looking at your code, so write the code (as much as possible) to allow a non-developer to be able to get a sense of what's going on.

Avoid magic strings. What this means is that a string generally shouldn't be used to determine the execution path of code. A simple spelling mistake can be very difficult to detect, and there aren't any tools that can assist. Enums, constants, or named integers are far safer.

Take breaks, get exercise, and don't try to code for every waking hour. If you're in flow, and making real progress this is fine, but if you're just working the hours and aren't making a lot of headway, a good nights sleep will go a lot further than another 4 hours of a fatigued brain. Seriously, sleep at least 8 hours, your ability to learn and remember is entirely based on your REM cycles, when the brain essentially writes from it's cache to permanent storage and cleans up the toxins from the day. Without sufficient sleep, you will not remember what you did, and you will not learn from the mistakes you made and new understandings you would have otherwise acquired.

Never copy and paste from SO or other snippet that you find to solve your problem. Read the example and understand what is going on in the snippet. Then hide the browser and type it in as you understand it to work. I came up with a rule for my first team 15 years ago, and that was to never insert code into a project that you don't understand. If you don't understand it when you add it, you won't understand it any better when that code breaks.