DEV Community

Discussion on: In Defense of Defensive Programming

Collapse
 
thescottyjam profile image
theScottyJam

It sort of sounds like if typescript automatically provided runtime assertions with each function, then half of your concerns would be gone (though you still have to do manual checks for things such as distinguishing positive from negative numbers).

All of your arguments in this post seem to hang on the idea that "Every single function is a program", and I'm going to disagree with that root argument.

When you mean every, do you really mean every?

What about functions inside functions?

export function doStuff() {
...
// I assume this function doesn't need to be validated
const result = myArray.map(user => user.name)
...
// nor does this
const extractName = user => user.name
const result2 = myArray.map(extractName)
}

ok, that was a bit silly. But what if we moved them outside? Is our program that much more fragile now?

const extractName = user => user.name

export function doStuff() {
...
const result2 = myArray.map(extractName)
}

All we've done is moved the function to a place where anything in this module can call it, instead of whatever is just inside the doStuff() function. The amount of danger this poses depends on how big the module is - if this module only contained the doStuff() function and the helpers that we pulled out from it, then there's no more danger having them outside the function than inside. It's unclear to me whether or not you find non-param-validating private functions like this bad or not, so let's take it a step further.

Let's say our module name was _shares.js, and the developers on the team understood this naming convention to mean "don't touch this unless you're inside this package" (or maybe we're working in a language like Java which actually has package-private access-modifiers). And now we start exporting our extractName function for the package to use. How bad this is depends on the size of the package. Having this exported utility function in a really small package is less risky than keeping it private within a ginormous module, so a rule like "all exported functions should validate their params" is a bit of an arbitrary boundary.

We can take it to the next step and make it a public export of an internal company library, or another step to make it a public export for the general public.

In all of these steps, the only thing we're doing is changing how many places have access to this function - the more places, the riskier it is to not validate its inputs. So claiming that "all functions should be stand-alone programs" sounds nice in theory, but in practice, no one's going to add parameter validation to every single function (like the anonymous functions used in .map()), and there's no clear cut way to define the line of when it should and shouldn't happen.

And what's the disadvantage to not validating parameters? Bugs in your program are harder to track down.

I guess what I'm getting at is that there's a balance. if few things call your function (which is the story of most functions in a project), then it's better to keep it lean and focus on readability over helpful errors. As its usage grows, people can always retroactively make improvements to it. If your function gets used all over the place, then put some more helpful errors. In some cases, you might have to add a fair amount of code to really generate good error message for the number of ways people may mess up (even using a library like yours), and you might need to write longer, more explanatory error messages too - that kind of verbosity just isn't appropriate in every single function ever.

Other places that benefit from parameter validation include:

  • Some forms of string interpolation (e.g. generating HTML, or an SQL query - these are attack surfaces, and should be heavily validated).
  • Code that's doing persistent changes (e.g. database writes), where it's preferable to have errors happen in advance so that it doesn't leave things in a permanent bad state. In other words, a chunk of code that's needs to perform a single, reliable atomic operation using multiple steps.
  • I'm sure other specific scenarios exist too.