Even though it puts { return null } everywhere in the code, I do believe lined ensured conditionals are easier to read than if statements, and it doesn’t seem to impact performance at all.
Thanks to @brill and @mbeimcik, but our little tests with our team disagree with you. Every time we tested stacked if conditions against ensure-like functions, the if-based code lost. Both when asking team members to tell which code was easier to read and when checking with students how well they understood the code seconds after being presented with it. The ensure is about 3x faster than the if.
The addition of the doCreate-like check functions worsened our tests. It just created another layer of redirection before the dev fully understood the code, which made him/her slower in doing so.
I hope somebody else out there can run similar tests to see if our results are just us or if they match.
I believe this happened for two reasons:
The conditionals are interdependent. Condition2 generally can only be assessed if condition1 is true, condition7 can only run if condition3 is true, otherwise, the app would crash. And evaluating if the code is crashing from if conditionals are harder than from ensures.
The conditionals test for the exact characteristics that match the object they need. It’s like saying if the array has 3 elements, one is hex and the second is base64, then use the ThreeElemHexAndBase64Parser and not the ThreeElemBase64AndHexParser when the two are inverted.
I just wish we didn’t have to spell out the { return null } in each line.
And, for some reason, this code here didn’t do well.
fun visit(): MyObj? {
if (condition1) else return null
if (condition2) else return null
if (condition3) else return null
return MyObj(...)
}
I think the presence of the else makes devs wonder if one of these lines is misindented enough to break the conditions. Which leads to more time reviewing the code.
Hopefully one day we get a language with an extra jump expression on top of break, continue and return. ensure is a bit “pompy”. Maybe require is a good keyword?
it’s one of the reasons, I sometimes miss a good “macro” feature.
Also, if everything would be a function (no statements), everything would become easier.
For your problem, why not combine both worlds.
The main advantage of “ensure” seems to be its descriptive nature.
Ad hoc I would write it like this:
class EnsureResult(val cond: Boolean) {
operator fun plus(cond2: EnsureResult) = EnsureResult(cond && cond2.cond)
operator fun <T> plus(result: () -> T) : T? = if (cond) result() else null
}
fun ensure(test: () -> Boolean) : EnsureResult = EnsureResult(test())
data class MyObj(val text: String)
fun visit(x: String): MyObj? =
ensure { x.contains("a") } +
ensure { x.contains("b") } +
ensure { x.contains("c") } +
{ MyObj("has a,b,c") }
fun main() {
println( "abc: ${visit("abc")}" )
println( "abx: ${visit("abx")}" )
println( "xyz: ${visit("xyz")}" )
}
It’s the same as the already-mentioned ensure solution but require already exists so you don’t have to write a separate ensure method.
Second suggestion: in response to your reservations about this solution:
fun visit(): MyObj? {
if (condition1) else return null
if (condition2) else return null
if (condition3) else return null
return MyObj(...)
}
The ‘else’ immediately after ) seems to be a problem, so how about this:
val ok = Unit
fun visit(): MyObj? {
if (condition1) ok else return null
if (condition2) ok else return null
if (condition3) ok else return null
return MyObj(...)
}
(Instead of ok, use “fine”, “hunky_dory” or whatever you prefer.)
The issue with else’s in returns is that any missing null from there might start cascading the ifs. Any missing else will invert the clause. Any missing return will not exit.
It’s very easy to find/understand the pattern ok else return null when condition1, condition2, and condition3 have the same amount of chars. In real conditions of varying lengths, it becomes a brain exercise to find all the else’s, returns, and nulls.
Adding the OK just adds another thing that maybe or may not be there to confuse the reader.
This should be fixed by the Unused return value checker KEEP, in the sense that the compiler should show clear warnings if you just have a dangling Boolean like that that goes nowhere
Yes, but “trusting the compiler will protect you from bugs” is not a good mindset. Devs know to be skeptical of that and that skepticism decreases the readability of the code.