Bomb-proofing your code with robust programming
Bugs are a fact of life in software. Sometimes they spring to life because of simple omissions or typos. Those are easy to fix. Sometime bugs arise from mysterious outliers, throwing errors that should never be called. These often come from fragile code.
The solution is what's known as robust programming. Also called defensive programming, robust code anticipates problems, abstracts any values returned to exposed functions, and makes debugging a simple process.
Here's the difference between robust and fragile code.
Expects Stupid Inputs
Robust code doesn't assume that any caller has read the manual, understands how to produce correct data inputs, or knows how code is supposed to work. That means they may be passing the wrong data types, malformed data, or things that don't make sense. When it catches something like this, it produces understandable error messages and stops this bad data from propagating through the system.
Just because you're working on internal code that will never interact with external users or methods doesn't mean you shouldn't expect the caller to be an misinformed. We forget what we did a week later, neglect internal documentation, and assume function by method names. Everybody can be misinformed sometimes, so make sure every piece of code is ready for that.
Trusts No One
The flip side of this is that robust code does not trust any piece of information that it does not create. Always assume somebody has it out for you and is going to break your code. Always assume that any function that you call could fail or send back unexpected results. If someone throws a bomb at your methods, they should be able to handle it elegantly enough to not blow up the whole shebang.
This is the flip side of the stupid inputs protection. It's not that they don't know what they are doing, it's that they are looking for ways to break your program. A lot of malicious code uses a buffer overflow exploit, where data exceeding the boundaries of a memory buffer are written to nearby memory areas, sometimes executing arbitrary code. But you can prevent it by checking that a data structure is within the bounds that it claims to be. Essentially, you are asking all incoming data for their identification papers to make sure they are what they claim to be.
Hides Your Cards
Public methods and fields and global variables are two great ways to introduce secret bugs into your code. That's because any other method could have access to this data. So those variables could be changed at any time and in a way that doesn't make sense, especially with multi-threading and multiple cores.
Keeping the important stuff private helps you control what happens to any data in your software. Especially if you are relying on that data. If you are creating an API, you will probably need some sort of public get and set routines; that can't be avoided. But by having only one way in and one way out for data, you can control and track data changes and head bugs off.
Expects Things to Change
We all know code within any large project will change. What you may not expect is file structures or standard inputs or output to change. If you assume they stay the same, then you are asking for trouble. What if you or someone like you overrides it or modifies it? Cue explosions.
You should do this for data structures and objects that you create as well. As a secondary benefit, this makes your code easier to maintain and update. For methods where you return data, this means not passing data structures or pointers to objects if you can help. If the structure that you pass has to change, you might have just broken a lot of code.
Making your code robust -- that is, defensive -- ensures your software will have less bugs and less security holes. Trusting things to just work or for static operation will only lead to heartbreak.