I have been programming for 6 years and I am in high school (I prefer not to disclose my age). I have dabbled in many different languages. Just to list a few: Java, PHP, C++, Python, Autohotkey, Mathematica and many more. I have a very good understanding of the basics of general programming. The only problem is I still create bugs all the time. I think too often. Do you have any hints besides continuing to program that will help me become a better programmer and make less errors?
- Keep your methods small. When methods grow too big, extract units of logic to new methods.
- Name your classes, methods, and variables things that make sense. Never feel undue pressure to abbreviate any of these.
- Use GoF design patterns whenever they make sense. [Here's a good beginner's book.]
- Cover your code with automated unit tests. This will force you to modularize your code and give you confidence when refactoring.
- Read Code Complete 2.
Do some analysis on the nature of the bugs themselves. Are they more logical or are they coding errors?
For the former, a good way is to model the potential use case scenarios with UML or relationship diagrams and then take it up from there.
If you are having more coding errors, do some root cause -- what's causing the error? Global variables? Pointers? Too many if-else statements? Lack of comments in the code? You need to take each of these issues piecemeal and then see how you want to go about it.
Last but not the least, please do read the design patterns book by Gang of Four (google this).
One of the things that helps the most is using asserts. How many times a day do you assume something that can be checked with a simple predicate "can't happen"? If, everytime you run into something like this, you put in an assert to prove at runtime that you're right, buggy code will fail fast and close to the root of the problem. Furthermore, the act of stating your assumptions about the current state explicitly is itself a good exercise.
IMHO asserts are actually more valuable than unit tests. Some things are hard to unit test either because they have so many dependencies (either data or other code), because the algorithm is so complex that it's virtually impossible to figure out what the "right answer" is except by programming an implementation and running the code, or because they're non-deterministic. Unit tests also only test the specific cases that you thought to test. Asserts, on the other hand, don't require you to come up with a specific test case, making them much easier to write and thus more likely to actually get written even when in a hurry. They also see real-world inputs, including cases that you never thought of.
We all create bugs. I do think experience tends to reduce them. After you've made a particular mistake a few times you'll recognize the situations where it occurs and develop techniques to avoid it.
One thing you could do right away to help limit the number of bugs you miss is to start doing more testing - add unit testing to your methodology, or go all the way and start doing Test Driven Development.
As others have said, analyse the mistakes you are making, and work out what they are. Once you know what the mistakes generally are work out how you can ensure that you don't make those mistakes.
But once you know what the mistakes are that you are making you have to make the solution a habit. Mentally rap yourself on the knuckles every time you do what caused the mistake (or don't do the step that ensures you don't make the mistake), and mentally pat yourself on the back when you do implement the solution, until it becomes second nature.
Your habits will determine what you will do when under pressure. If you haven't got good habits you will make errors that you should know not to.
Last of all, every one writes bugs. To quote Edgser Dijkstra,
If debugging is the process of removing bugs from a program, then programming must be the process of putting them in.
There will always be bugs, but striving to avoid them is excellent.
You've enough experience to be trying to avoid this, rather than thinking you're great and will get it right first time every time -- just realising that is excellent.
Whenever you find an error, ask yourself "could I have avoided that? how?"
Look for best practices on stackexchange and elsewhere, understand why they're that way, and follow them (assuming you agree).
Read well-written code, either by working with someone else, looking over a high quality existing code base (hard to find), or reading good coding books (look on programmers or stackoverflow for many decent suggestions on books on how to get good, not just how to get started).
You probably already know this, but turn on compiler warnings and if possible use a static code-checking tool (and for that matter, a decent debugger). For a short program, you can ignore warnings. For a large program, you need zero warnings so that if one shows up you can see if it matters.
Based on your experience and reading, develop a spider-sense for code smells. That is, whenever you're about to do something, recognise if it will be risky if you're distracted, or go away from the code for six months and forget what you're doing. Trivial examples:
Have you hard-coded the length of an array in more than one place, even implicitly by giving it an initialisation list? Don't leave it like that even for a minute, there's great likelihood that one day you'll change one but not the other. Use a const variable or use sizeof to calculate the length. If you change something, you should be able to do so by changing one thing and have it still work.
Have you made a temporary assumption? Did you think "Oh, for testing, it'll always be less than 1k?" That's ok -- but put in an assert, so if you ever exceed that you'll see immediately that you have to go back and make it properly variable sized, rather than getting weird errors you have to debug.
Are you writing code which took you more than a minute to get right? Or borrowed a technique from the web? Put in a comment saying why it's like that. If you're not sure the comment will stay current, put in a date. You can always delete it later if you decide that the usage is sufficiently obvious and is now second nature to you, but the chance is almost certain that if you had to stop and think now, you'll have to stop and think when rereading the code.
Are you writing an if() statement based on a bool variable or function? Change its name -- now -- to reflect the sense of the value. Don't write "if (retval)", write "if (success)" or "if (failure)". It'll seem obvious at the time, but later, you'll massively avoid the risk of getting it the wrong way round without noticing.
You presumably already do this, but ALWAYS use source control before you change ANYTHING. The chances are endless that you'll change something and think "wait, did that break it?" If you can compare before and after, it'll be completely transparent where the problem was.
And more in that vein (including more sophisticated examples like antipatterns in archetecture, not just syntax).
Are you working on projects by yourself? You might try getting involved with a larger-scale project. Fixing other people's bugs is a great way to get exposed to all kinds of errors you might not even think were possible.
Everybody creates bugs every day - the goal is just to find and fix them before they become a big problem.
What helps me, is to always list the ways each section, function can fail in my head.
So then I work harder on variable scoping, form validation, anti-sql injection, writing better and correct sql etc.
I guess over time we create our own mental checklists of what NOT to do.
And over time our code get's better...
One practical way to improve your code quality is to learn a highly abstract functional language like Haskell/Caml/Lisp. Haskell would be my recommendation, though others suggest that it's not a good first functional language.
You don't have to make those your primary languages (unless you want to, of course). Just knowing them is extremely beneficial, even if you find them to be extremely difficult.
Once you get in the groove of thinking about concepts like statelessness and higher order functions, you'll know what kinds of mistakes to look out for when you're writing code in your usual imperative languages.
Obviously design flaws and integration mistakes are harder to avoid, but ever since I learned F# and Haskell, I've nearly completely stopped making small-scale bugs.