Teaching students to hunt for and debug their own errors
Debugging is more a craft, an art, than a science. That's where the pedagogical challenge lies.
That said, here are a handful of tools/techniques I used to varying success:

Model the debugging process. I would project sample working code, break that code, and talk through the error messages (and how to read them). Trying to instill the patience and thoroughness required is harder than explaining the actual errors.
Give students broken code... One of the first review activities I did at the end of a unit involved having students correct broken code. Missing semi-colons, commas in for-loop declarations where semi-colons should be, misspelled function names, errant brackets. This is the opposite of the above, and students should work in both directions: break working code; fix broken code.
...to correct by hand. This is where differentiation can shine. It might take reading printed code to recognize where there is a problem. Take time to fix errors off the screen in order to make them more recognizable on the screen.
Not all errors are created equal. Just because a program runs doesn't mean it's free from bugs. This can be the most pernicious because everything appears to be correct on the surface. The Debugging appendix of Think Java is a great resource for this issue. Having students categorize errors into types further solidifies the understanding of bugs and how to eradicate them.
When in doubt, print yourself a message. While it can lead to messy code as debugging messages get commented out, I'd rather a student have messy, working code with signs of thoughtful debugging than clean code that is broken or incorrect vis-a-vis its spec.

In the end, students need to build their pattern recognition, and that comes with a lot of time and a lot of focused practice. There is also an inevitable connection among debugging skill, programming environment, and language. My students were better at C than Python when it came to fixing their programs. That's due to several factors: C being less forgiving, Python feeling simpler leading to carelessness, and the IDE trying to be helpful with errors as students wrote Python. In C, it took the compilation step to find an error whereas Python would identify warnings immediately, a &quot;feature&quot; that muddied the debugging process.