I'm one of many, many people who develop software without an academic background in it. (I got a GCSE in Computer Science, but didn't study it at A-Level — sorry, I don't know what the equivalent of those would be in other countries — and my degree was in Mathematics.) But I've been working in IT for two-and-a-half decades now, and still seem to be getting away with it!
I have no doubt that you know your stuff, and have the potential to be a great programmer. You wouldn't have been hired otherwise — and you wouldn't be asking here if you didn't care and want to be better. And those are probably the most important things!
None of us is perfect, and as long as you're aware of that, you're probably going to do fine.
I'll mention a few of the things that I'd tell the 21-year-old me — you can judge whether they apply to you at all. And because I've got pretentious in my old age, I'll use some quotes:
“Programs must be written for people to read, and only incidentally for machines to execute.” — Abelson & Sussman [preface to Structure And Interpretation Of Computer Programs]
It's really important for code to be as clear as possible. (Almost) anyone can write code that runs and does what it's supposed to; but it's really hard to write code that anyone else can read, understand, and work on. And you will be working with other people: you'll need to read and understand their code, and they yours. (And you'll need to read your own code, months or years down the line, so you'll be doing yourself a favour too!)
A more vivid way to put this is:
“Always code as if the guy who ends up maintaining your code
will be a violent psychopath who knows where you live.” — John Woods [comp.lang.c++]
On complexity:
“Everyone knows that debugging is twice as hard as writing
a program in the first place. So if you’re as clever as you
can be when you write it, how will you ever debug it?” — Kernighan & Plauger [The Elements Of Programming Style]
It pays to keep things as simple as you can. Simple code is easier to understand, easier to maintain and extend, and more likely to be correct in the first place. When you know advanced techniques and clever hacks, it's tempting to use them to show off. But resist the temptation!
Sometimes I think the most important — and difficult — task we programmers face is that of fighting complexity. In the face of increasingly complex and demanding requirements and requests, ballooning codebases, and ever-more-complex tools, keeping things simple is a real challenge, but it's vital!
“Be consistent.” — Larry Wall [the perlstyle manpage]
(Yes, you might well find that funny coming from him… :)
Consistency is really import in a codebase. No code is perfect, and the code you inherit will probably have unhelpful indentation and spacing, confusing names, out-of-date techniques, and/or worse. Many of those are worth fixing when you work on that code. But before you do, look around the codebase and see if those things apply everywhere. If so, grit your teeth and leave them alone — and even write new code in the same style. With a large codebase, a uniform style/naming/&c is much easier to read and work on than a patchwork of different styles — even if that one style isn't the best.
(That's not to say you should never look to improve existing code — just do it in a way that your colleagues will approve of.)
And finally, a point I don't have a quote for: always question requirements. Always ask “What's the ultimate goal here? What is this actually trying to achieve?” Because very often, what they've asked for won't actually do all they need it to, or will cause problems elsewhere, or will perform badly, or will add unnecessary complexity, or will be hard to maintain in future, or will prevent other planned improvements, or will take far more work than necessary. It still surprises me how often a bit of investigation can lead you to a different and much better solution.
— As I said, you may already be aware of some or all of these points. In which case, you're part-way to being a great programmer already! 😀