Even something as simple as Substring which is a method we use all the time and is far more clear than most helper functions I've seen in code bases.
Is it Substring(string, index, length) or Substring(string, indexStart, indexEnd)
What happens when you pass in "abc".Substring(0,4) do you get an exception or "abc"?
What does Substring(0,-1) do? or Substring (-2,-3).
What happens when you call it on null? Sometimes this matters, sometimes it doesn't.
- Does it destructively modify the argument, or return a substring? Or both?
- If it returns a substring, is it a view over the original string, or a fresh substring that doesn't share memory with the original?
- If it returns a fresh substring, how does it do it? Is it smart or dumb about allocations? This almost never matters, except when it does.
- How does it handle multibyte characters? Do locales impact it in any way?
With the languages we have today, a big part of the function contract cannot be explicitly expressed in function signatures. And it only gets worse with more complicated tools of abstraction.
(reads: var_1, var_2; mutates: var_3) {
var_3 = var_1 + var_2
int result_value = var_1 * var_2
} (exports: result_value)
return result_value * 5
There are a couple of newer languages experimenting with concepts like this, Jai being one: https://youtu.be/5Nc68IdNKdg?t=3493In the latest IntelliJ, the IDE will visually hint about mutable, primitive-typed local variables (including method parameters). A good example is a for loop variable (i/j/k). The IDE makes it stand-out. When I write Java, I try to use final everywhere for primitive-typed local variables. (I borrowed this idea from functional programming styles.) The IDE gives me a hint if I accidentally forget to mark something as final.
this is basically a lambda you call instantly.
[&x, y, z] () {
x = y + z;
}();
What I find is that function boundaries have a bunch of hidden assumptions we don't think about.
Especially things like exceptions.
For all these utility functions are you going to check input variables, which means doing it over, over and over again. Catching exceptions everywhere etc?
A function can be used for a 'narrow use case' - but - when it's actually made available to other parts of the system, it needs to be kind of more generalized.
This is the problem.
Is it possible that 'nested functions' could provide a solution? As in, you only call the function once, in the context of some other function, so why not physically put it there?
I can have it's own stack, be tested separately if needed, but it remains exclusive to the context that it is in from a readability perspective - and you don't risk having it used for 'other things'.
You could even have an editor 'collapse' the function into a single line of code, to make the longer algorithm more readable.