It looks like your colleague is misunderstanding "defensive programming" and/or exceptions.
Defensive Programming
Defensive programming is about protecting against certain kinds of errors.
In this case x.parent == null
is an error because your method needs to use x.parent.SomeField
. And if parent
were null, then the value of SomeField
would clearly be invalid. Any calculation with or task performed using an invalid value would yield erroneous and unpredictable results.
So you need to protect against this possibility. A very good way to protect is by throwing a NullPointerException
if you discover that x.parent == null
. The exception will stop you from getting an invalid value from SomeField
. It will stop you doing any calculations or performing any tasks with the invalid value. And it will abort all current work up until the the error is suitably resolved.
Note the exception is not the error; an invalid value in parent
that is the actual error. The exception is in fact a protection mechanism. Exceptions are a defensive programming technique, they're not something to be avoided.
Since C# already throws an exception, you don't actually have to do anything. In fact it turns out that your colleague's effort "in the name of defensive programming", is actually undoing built-in defensive programming provided by the language.
Exceptions
I've noticed many programmers are unduly paranoid about exceptions. An exception itself isn't an error, it's simply reporting the error.
Your colleague says: "the null check makes sure the code doesn't break the application". This suggests he believes that exceptions break applications. They generally don't "break" an entire application.
Exceptions could break an application if poor exception handling puts the application in an inconsistent state. (But this is even more likely if errors are hidden.) They can also break an application if an exception 'escapes' a thread. (Escaping the main thread obviously implies your program has terminated rather ungracefully. But even escaping a child thread is bad enough that the best option for an operating system is to GPF the application.)
Exceptions will however break (abort) the current operation. And this is something they must do. Because if you code a method called DoSomething
which calls DoStep1
; an error in DoStep1
means that DoSomething
cannot do its job properly. There is no point in going on to call DoStep2
.
However, if at some point you can fully resolve a particular error, then by all means: do so. But note the emphasis on "fully resolve"; this doesn't mean just hide the error. Also, just logging the error is usually insufficient to resolve it. It means getting to the point where: if another method calls your method and uses it correctly, the 'resolved error' will not negatively affect the caller's ability to do its job properly. (No matter what that caller may be.)
Perhaps the best example of fully resolving an error is in the main processing loop of an application. Its job is to: wait for a message in the queue, pull the next message off the queue and call appropriate code to process the message. If an exception was raised and not resolved before getting back to the main message loop, it needs to be resolved. Otherwise the exception will escape the main thread and the application will be terminated.
Many languages provide a default exception handler (with a mechanism for the programmer to override/intercept it) in their standard framework. The default handler will usually just show an error message to the user and then swallow the exception.
Why? Because provided you haven't implemented poor exception handling, your program will be in a consistent state. The current message was aborted, and the next message can be processed as if nothing is wrong. You can of course override this handler to:
- Write to log file.
- Send call-stack information for troubleshooting.
- Ignore certain classes of exception. (E.g.
Abort
could imply you don't even need to tell the user, perhaps because you previously displayed a message.)
- etc.
Exception Handling
If you can resolve the error without an exception first being raised, then it is cleaner to do so. However, sometimes the error cannot be resolved where it first appears, or cannot be detected in advance. In these cases an exception should be raised/thrown to report the error, and you resolve it by implementing an exception handler (catch block in C#).
NOTE: Exception handlers server two distinct purposes: First they provide you a place to perform clean-up (or rollback code) specifically because there was an error/exception. Second, they provide a place to resolve an error and to swallow the exception. NB: It's very important in the former case that the exception be re-raised/thrown because it has not been resolved.
In a comment about throwing an exception and handling it, you said: "I wanted to do that but I was told it creates exception handling code everywhere."
This is another misconception. As per the previous side-note you only need exception handling where:
- You can resolve an error, in which case you're done.
- Or where you need to implement rollback code.
The concern may be due to flawed cause-and-effect analysis. You don't need rollback code just because you're throwing exceptions. There are many other reasons that exceptions can be thrown. Rollback code is required because the method needs to perform clean-up if an error occurs. In other words the exception handling code would be required in any case. This suggests the best defence against excessive exception handling is to design in such a way that the need for clean-up on errors is reduced.
So don't "not throw exceptions" to avoid excessive exception handling. I agree excessive exception handling is bad (see design consideration above). But it is far worse to not rollback when you should because you didn't even know there was an error.