Sane defaults over Exceptions
Part of our work is defensive programming. A lot of webdevelopment is centered around taking input from a customer, processing it in some way and returning output. Since the source of input is out of our control we are used to writing all kinds of guards. Is this an integer (in case of a dynamically typed language), is it greater than zero, is it smaller than RANDOM_THRESHOLD, etc.
What I see a lot is people using exceptions or even intentional fatal errors for this. I think the reasoning is that since the application will always generate the correct values, the only thing to worry about is malicious attempts and we can show those people 404's or error pages without feeling remorse.
I didn't think much of this, but I've seen a major drawback lately while working on a site that is a bit bigger than I was used to. With over half a million visitors a week and lots of scrapers, bots and other stuff visiting, these exceptions and fatal errors clog up logging quite a bit. Not to the point that we can't handle the volume, but it generates false positives in monitoring channels and it is something we do not want to act upon anyway.
So while I'm happy to see some defensive programming I would be even happier if exceptional situations would be silently resolved to default situations, simply compare the first and the second example:
// Great! if ($limit <= 0) { throw new SorryLimitTooLow(); } if ($offset < 0) { throw new SorryOffsetTooLow(); } // Better! if ($limit <= 0) { $limit = 10; } if ($offset < 0) { $offset = 0; }
If the application makes an error we won't bother the user with it and we will notice during testing (Right?). If the user or a scraper tampers with urls or whatever, we won't bother ourselves with it in our monitoring channels. Everybody wins :)
Comments
-
Blog \o/
-
It's been a busy few months, but I need to put stuff out there more again yeah :p
-
"if the application makes an error we won't bother the user with it and we will notice during testing (Right?)" - the reality is, we won't notice, or won't understand the error cause it's hidden. I'm not arguing against hiding recoverable errors from the user, but if they come from a bug in the application, I think they should be logged at least.
Validating user input is a different topic and should be handled early. -
I think there's a happy medium to be found. Generally, I throw exceptions 'by default': clearly the user (user may not be a human!) has asked for something which there's no way for me to be able to second-guess what they want.
But in the case you mentioned (limit & offset, I assume to get N records from a list, eg news items) then you can second guess it.
Offset = -1; this probably still means "at the start of the list".
Limit = 0 - this probably means "no limit", although we might say that actually the person wants the maximum number you're willing to let them have (RANDOM_THRESHOLD)
-
One could also just advise against throwing Exceptions on bad user input. You know you can't trust the user, so is it really that 'exceptional' when a user requests your application to do something it can't.
I wouldn't throw an Exception like in your example in the first place inside an controller or validation method (assuming MVC). But at any level deeper than my controller I certainly wouldn't want to have fallback behavior. If I call a specific class method and pass it the wrong parameters (originated from user input) I would want an Exception to be thrown. Getting clogged up logs from those Exceptions? Then prevent them by falling back to default values inside your controller.
Let me know what you think about this. I didn't read any details about the context of that fallback behavior.
-
I agree, it is not exceptional to see users entering incorrect data. So by default an exception is sort of strange. I would still use defaults in the called code and not in the calling code, simply since the calling might be spread over more than one location.
If you really don't want to see this in your called code, maybe a query object (in case of a repository) or some other abstraction can arrange for the defaults to be set. This way you won't duplicate the defaults all over and it won't pollute the called code.
-
I agree with Postel's Law, that we should be liberal in what we accept and conservative in what we produce. The challenge is that by failing loudly we can improve the code that calls ours. But I think there is a happy medium.
PHP has significantly improved
assert()
in PHP 7. If we sprinkleassert()
s liberally through our code, we can verify that the inputs are what we expect. Then, when deploying to production, settingzend.assertions
to-1
will skip theassert()
call completely so that we don't trigger and can use sane defaults.In fact research into assertions have found that they are inversely related to bug density.
-
My family members always say that I am killing my time here
at web, but I know I am getting know-how all the time by reading such pleasant articles.