If your software crashes on bad user input, that's an error in your software.
By the way, there is distinction to make between library code that should exception/assert/abort on error and application code that should verify input and display an error to the user.
I've worked for very large companies whose massive codebases are littered with defensive checks that have piled up over many years. Invariably, I find that this is because, when the initial wave of defensive checks inevitably broke down because they guard against something that itself changes behavior again, more defensive checks were added.
All of this could have been avoided if those companies had just created a culture, from the beginning, where you force the upstream data provider to fix its issues rather than coding around them by allowing code to fail-fast rather than gracefully.
Now companies are beginning to introduce integration and unit testing across the board and type systems (again, to ensure internal consistency for systems so that other systems can depend on them) and the cruft and defensiveness is slowly going away.
To repeat the old cliché, the best defense is a good offense :-)
>had just created a culture, from the beginning...
Were it such an easy thing...
Here's how I've experienced such things:
I write naive code. Application hard fails. Test environment down - everyone cross with me.
Me: Yo upstream - fix your data please.
...no response.
So I raise a ticket to get data source fixed - send it to the backlog.
Me: Yo delivery - can we get this ticket prioritised, assigned and put into a sprint?
Delivery: Yeah... nah - cause y'know... delivery.
Me: Yo leadership - can we get a decision taken on whether or not upstream should fix their data, or whether we should just program defensively? If the former - can someone tell delivery to tell upstream?
...no response.
At this point - I give up. Write my little defensive check and admonish myself to do so consistently from now on. Commit, push... go home - sleep like babe.
By the way, there is distinction to make between library code that should exception/assert/abort on error and application code that should verify input and display an error to the user.