Get rid of boolean function parameters
mortoray.com
mortoray.com
Something like:
void DoSomething(Thing thing, enum logging{WithLogging,NoLogging}){}
Because I'd frequently like to be able to make my parameters more explicit, but the overhead of creating a new enum type seems a tad heavyweight.Edit: Raised it as an issue on GitHub with more details - https://github.com/dotnet/roslyn/issues/3497
let do_something (thing : Thing.t) (logging : [`With_logging | `No_logging]) =
...
Even better, it can even infer the type based on you using it (for example matching on it): let do_something thing logging =
let logging_as_bool =
match logging with
| `With_logging -> true
| `No_logging -> false
in
.... DoSomething(thing, logging: true);Required parameters as positional, and optional as named.
doSomething(req_param_1_value, req_param_2_value, optional_param_1 : value, optional_param2: value);
Another thing to consider: In some cases, it may make more sense to split the function into two versions, one for 'true' and one for 'false'. Then you gain the enforced explicitness. The function could call a private method to share code and pass the Boolean. Clearly that isn't ideal for all situations, but it might help in some situations today.
The author describes exactly this approach and implementation, under the heading "Distinct functions".
calc_formula(a, b, false)
Is unclear but so are `a` and `b` ctx.arc(100, 200, 300, 0, Math.PI*2, false)
or gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 100, 200, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null);
How are those examples any more self documenting? I don't see anything special about boolean anymore than the other arguments. If you really want your code to be clear (and I'm not saying I do this) you'd need to either use a language that allows naming every argument or else put every argument in a variable with a descriptive name var centerX = 100;
var centerY = 200;
var radius = 300;
var arcStartRadians = 0;
var arcEndRadians = Math.PI * 2;
var counterClockwise = false;
ctx.arc(centerX, centerY, radius,
arcStartRadians, arcEndRadians,
counterClockwise);It's way easier to remember what's going on when you come back a year later, and all but the simplest compilers will be smart enough to bash it down to constant parameters if that's possible.
Yeah, more lines of code, but I think readability trumps terseness (within reason). Some functions just use a bunch of parameters and it's not really the extra line to name the parameter that's causing the issue. Also if you're using an editor that does code folding you could just fold that block away.
texImage2D(100, 200, 300,
0 /*start*/, Math.PI*2 /*end*/, /*ctrClockwise*/ false); arcWithCenterX: withCenterY:
or similar? (I vaguely recall this, but can't find any examples online.)Objective-C:
[self frob:param1 withObject:param2];
Swift: self.frob(param1, withObject:param2) self.frob(param1, withObject:param2)
could be written as self.[frob:param1 withObject:param2] float CalcFormula(int a,int b, bool isGain) {..}
var x = CalcFormula(4, 5, isGain: true); function Calc_Formula(A, B : Integer; Is_Gain : Boolean) return Float is ...
X : Float := Calc_Formula(4, 5, Is_Gain => True);That said, Ada is certainly something I want to learn more about at some point.
My current habit is to use comments for non-enforced named params; e.g.,
fetchLatestFoo( long barId, false/*fetchContent*/ );
No compile-time checking, etc., but it still serves the purpose.I agree with the author that with flags in fn-signature, the code becomes tough to reason about at the call-site; but a preferred solution, IMO, is to use "named arguments" or "labeled arguments" in the languages that allow them and/or write proper doc-strings in languages that don't.
For instance in OCaml you'd do (note: specifying types is optional):
let calc_formula (a :int) (b :int) ~(is_gain :bool) :float =
if is_gain then
(* something slightly different *)
(* common stuff *)
You'd call the above as: calc_formula 10 20 ~is_gain:true
Optional arguments allow for a terser call-site syntax at the cost of implicit behaviour: let calc_formula ?(is_gain=true) a b = ...
You could then call the above fn: calc_formula 10 20
calc_formula 10 20 ~is_gain:falseIn my experience, even these situations tend to grow arms and legs pretty quickly, so you end up with a 'verbose' boolean that accepts four values: True, False, "superverbose" and "off". Then you notice that in some places the function is being called with an array as the value for 'verbose', because you're in a language where arrays are implicitly cast to Boolean (I'm looking at you python).
There are too many cases where the optional parameter is not really a boolean, but merely a sum type which coincidentally only has two members at the moment, but is likely to grow to more members.
logger.error("message")
or a log statement with a severity parameter logger.log(LOGLEVEL.ERROR, "message")
That lets you change the capture level based on the situation without the need to recompile your application, on a fine granularity (eg class-level). Getting errors in production? Edit one line of XML to turn up the capture level and go.High-performance logging frameworks like SLF4J handle the first case very intelligently, there's almost no overhead to skipping a logging call. If used wisely with a high-performance logging engine (Log4j2 or Logback) there shouldn't be many calls needed, and except in very narrow situations the performance impact (if you could find one) would be drastically outweighed by the improvements in maintainability. Code quality does have its costs at times - otherwise we'd all be writing assembly. It's not going to be the straw that breaks the camel's back when it comes to performance.
In an enterprise product, sure, I'd include a logging framework, but for smaller projects, more often than not, it may bring in unnecessary complexity.
def calc_formula(a, b, is_gain=False):
...
calc(a, b, is_gain=True) def calc_formula(a, b, *, is_gain=False):
...It takes a good deal of practise to know from experience how wrong that would be. So take my advice and always have a copy of this article under your pillow :)
refunder.Refund(orderId, customerEmail, true, false, payment=="visa", order.shipping == 0, true, true, order.day == "sunday")
Not verbatim, but you get the idea -- and yes, the refund method was even more confusing than then invocation const bool useWayA = true;
int result = foo(a, b, useWayA);
instead of int result = foo(a, b, true);With languages that include full pattern matching support and atom/symbol literals (like Erlang and friends, for example, and like most other functional languages IIRC), you could alternately match on a particular symbol instead of relying on a boolean flag.
class Route
{
const RECURSIVE = true;
const NON_RECURSIVE = false;
public function __construct($pattern, $recursive = Route::NON_RECURSIVE) {
// ...
}
}
$route = new Route($pattern, Route::RECURSIVE); $route = new Route($pattern, $recursive = true);The problem with enums as class members is, they have to be scoped when used. I wish that method invocation included the scope of the class being called on.
if is_gain: calc_formula_is_gain(a, b)
else: calc_formula(a, b)
However a completly agree it makes sense with sin/cos functions.Is it that the information is not helpful to others, because it is not helpful to you?
It's not helpful to anyone who has written more than a few lines of code. Which is why I replied in surprise. HN doesn't let me down vote, so I comment.
I like to think that stuff on here should represent what hackers (coders) would find interesting. Advanced coding, distributed systems, the other wooly things like marketing, people skills etc. I didn't think this fell under the scope of hacker news. It's not news to any hacker.
As for the scope, I think the rest of comments here show that this article is indeed in the scope of Hacker News.