Defensive programming is generally defined using terms such as 'impossible', one definition on the c2 wiki has Defend against the impossible because the impossible will happen. In addition there is also the idea to do the right thing. Such a definition is obviously (and intentionally) vague. The question is, what is the right thing? That's always situation specific and it can be a difficult part in programming. That's what I find interesting here, the programmer can easily make happen whatever we want, but it's difficult to decide what we should want. That's difficult to follow so let's try an example.
We have an application with string inputs. Those string inputs can be validated against patterns. Because the validation happens both on the front-end (web) client (written in Elm) and the backend server code, we need a way to pass the validation patterns between the two. Now ideally we would do some code generation here so that at compile time we can check that we definitely have the validation patterns for all the string inputs that we use on the front-end. But let's assume that we cannot do that for some reason. Instead, the backend passes (in program flags) a dictionary mapping a string-input-key to a validation pattern. So when you output a string input, you might do something like:
1let 2 matchesWholeString s regex = 3 ... 4 5 mPattern = 6 Dict.get "email" model.inputValidationPatterns 7 |> Maybe.andThen Regex.fromString 8 9 10 isValidInput = 11 case mPattern of 12 Nothing -> 13 ... WHAT NOW .. 14 Just pattern -> 15 matchesWholeString model.emailInput pattern 16 17 patternError = 18 case isValidInput of 19 True -> 20 Html.text "" 21 False -> 22 Html.span 23 [ Attributes.class "error" ] 24 [ Html.text "Your input is invalid" ] 25 26 submitButton = 27 case isValidInput of 28 False -> 29 Html.text "" 30 True -> 31 Html.button 32 [ Msg.SubmitEmail |> Events.onClick ] 33 [ Html.text "Submit" ] 34 35in 36Html.div 37  38 [ Html.input 39 [ Attributes.value model.emailInput ] 40  41 , patternError 42 , submitButton 43 ]
So great Elm is forcing us to defensively program and deal with the 'impossible' situation in which the key for the input type is not in the set of validation patterns provided by the backend. The question is, what should we do? What should go in the
WHAT NOW part?
We have basically two options, we can either throw up our hands and say, okay in that case no input is valid. Or we could throw up our hands and say okay all inputs are valid (both options involve throwing up our hands).
# Option One
1 2 isValidInput = 3 case mPattern of 4 Nothing -> 5 False 6
# Option Two
1 2 isValidInput = 3 case mPattern of 4 Nothing -> 5 True 6
# Conflicting desires
It is useful to take a step back and decide what is desirable behaviour. In this case we would want:
- The user to be able to submit valid input
- The user not be able to submit invalid input
- To be notified of bugs quickly so that we can fix them.
Using option one, the user will not be able to subit valid input, but we will surely be notified of this bug pretty quickly. Using option two, the user will be able to submit invalid input, that might be okay if we have some server side validation anyway, but it means we will not be notified about this bug soon.
# Half-way house
We can combine these two strategies. In the case that there is no validation pattern available, we can output a strange error message, but still allow for the user to submit input. This will satisfy both desires 1 and 3, but not 2:
1let 2 matchesWholeString s regex = 3 ... 4 5 mPattern = 6 Dict.get "email" model.inputValidationPatterns 7 |> Maybe.andThen Regex.fromString 8 9 10 isValidInput = 11 case mPattern of 12 Nothing -> 13 True -- So the input is seen as valid 14 Just pattern -> 15 matchesWholeString model.emailInput pattern 16 17 patternError = 18 case isValidInput of 19 True -> 20 Html.text "" 21 False -> 22 Html.span 23 [ Attributes.class "error" ] 24 [ Html.text "Your input is invalid" ] 25 26 systemError = 27 case mPattern of 28 Nothing -> 29 Html.span 30 [ Attributes.class "system-error" ] 31 [ Html.text "There is an internal error relating ..." ] 32 33 submitButton = 34 case isValidInput of 35 False -> 36 Html.text "" 37 True -> 38 Html.button 39 [ Msg.SubmitEmail |> Events.onClick ] 40 [ Html.text "Submit" ] 41 42in 43Html.div 44  45 [ Html.input 46 [ Attributes.value model.emailInput ] 47  48 , patternError 49 , submitButton 50 , systemError 51 ]
Obviously it depends on the particular situation, but by writing down the desires we can arrive at the least bad solution. In this case we were more interested in allowing valid input, rather than in disallowing invalid input, other situations may of course be different. The point here is that when accounting for impossible situations the 'correct' thing to do is not always obvious. Often being alerted to the problem is a strongly desired attribute.