Why boolean arguments should be avoided - Robert C. Martin (Uncle Bob)
HTML-код
- Опубликовано: 23 ноя 2024
- #cleancode #unclebob #softwaredevelopment #softwaredevelopmenttips #softwareengineering
In this video, Robert C. Martin A.K.A speaks on the types of arguments developers should avoid. He brings to mind reasons why some types of arguments can and often do make the code more difficult to understand, and potential ways to improve.
What do you think?
Source: • Clean Code - Uncle Bob...
About the boolean arguments, other comments seem to be focusing too much on the control flow aspect of the problem ("don't you just end up making the decision somewhere else?"), and not enough on the software design aspects (except myPrzeslaw, he seems to get it). Here's my take.
IN TERMS OF SOFTWARE DESIGN, I can see the following benefits to what Robert C. Martin is suggesting.
Extracting distinct branches into separate functions.
f(..., bool b) { if (b) { f_true(...) } else { f_false(...) } }
Symbolic names. "Never" write function names like this.
Reusability. You may want to re-use the behavior for a specific branch elsewhere, in a place where perhaps the other condition cannot occur. Extract the function allows for code reuse, without tangling the caller with an unnecessary dependency for both cases.
Configurability. "Bubbling up" the interesting decisions for a program is an excellent way to make code easier to understand, maintain and extend. Centralizing a point where program behavior is decided, and segregating it from where the actual actions occur, is a general principle which brings many benefits. See also CQRS, Functional Core + Imperative Shell, Strategy Pattern, ...
Using Enums (not a recommendation by Martin in this video, but a general pattern to avoid boolean arguments)
Understandability. As he said, boolean arguments can be very cryptic reading from the outside. createEmployee("John", 27, true) is fundamentally less understandable than createEmployee("John", 27, Status.EMPLOYED); this is mitigated in languages like Python with keyword arguments. create_employee('John', age=27, is_employed=True)
Extensibility. Perhaps in the future the code might have to deal with more than two cases. Booleans are an inherently restrict data type, with only two valid states (discounting unfortunate languages with null-like values for it). They are perfect for some situations, but not for others. For a more flexible design, Enums are the way. Extra point for backward compatibility when adding Enum states. Some type systems offer ever better extensibility and safety (Rust comes to mind).
General software engineering disclaimers:
* details for a specific situation matter; do not presume everything here is applicable to all cases
* there are still downsides to the suggestions here presented; everything is a trade-off
Was about to say the same. A bool is the easiest type of state and is quite useful in describing the state of a system. In Industrial applications, you have a chain of checks, usually normed, like MainPower = true, FailSafe = false, Input1 = ?, Overheat = false, Enable = true, etc. While some get linked in enums, like idle, start, running, and stop could replace an enable For others it doesn't make sense. A fuse for example is either blown or not.
In your code, there are a few ways to handle this.
Switch case: This type of data is usually aligned, so you can treat it as one variable and program the right behavior for each combination. Code like this reads similar to technical documentation.
If-Tree: This is useful if there is a hierarchy, or behavior is independent from the rest of the data. When there is no MainPower or someone pulled an emergency stop I don't need, any further information to stop the system. (You will need it to do it save). But for overheating for example, while it might be interesting to know what causes heat or how hot it is, there is only so much your system can do. In a car for example the fan behind the intercooler can either run or not, so a bool is all you can do. This is very readable if you think of root-cause patterns.
Queue: If the system gets really complex, you might divide it into subsystems sharing one state of the system. But at this point, you are passing structs and returning a return type.
I really like custom types. John = either Employed or Unemployed or Fan = either On or OFF is less complex than translating it to true and false. And this can get tricky. While overheating and fan = true means it is overheating and the fan is running, Main and EmergencySwitches are active high. This means while there is power and NO emergency the signal gets power and the bit is 1 = true. If any machine has an emergency it pulls the entire signal down so emergency = 0/false means there is an Emergency. If you rename those states it's less complex.
i heard this once -
Readable code is Less Performant but easier to Debug
Less Readable Code is More Performant but Harder to Debug
(Due to Using The Most Efficient Possible Answear instead of Making it the Most Easiest to Edit...)
This must be what it's like for normal people to try to read code..
Read a line, freak out.. read a line, freak out... it sounds absolutely exhausting
I dont think it is cleaner, nor is it more clever, to write two separate nearly identical functions simply because you have invented an arbitrary rule about passing booleans to functions. If this thing happens, process like this. Otherwise process like that. Writing two separate functions because you dont want to pass a boolean is not the solution. Not nearly.
@@armelpeel You are missing the point. If the functions are small and simple enough, you can simply accept two functions, or replace the boolean argument with a type that is more descriptive or extensible in the future. If the function is larger and yet contains some form of boolean-based branching, then there should be sections of that function that can be separated into their own functions. So you don't end up with two almost identical functions. You end up with one function that contains the shared code and the small differences are performed outside that function. By keeping the work of each function simple and focused, you can then reliably create a hierarchy of function calls that will likely not require refactoring due to being so focused. This doesn't matter for one-off one-and-done projects, but in anything that requires longterm maintenance it's a minor win that builds up over time.
You know, it's always Uncle Bob asking me to avoid others - Booleans never asked me yo avoid Uncle Bob, for example. I think I'm starting to see who is the toxic one here.
I identify as Boolean and it’s mean.
True @@TheScottShepard
You must be a strong character to stand that toxicity
@@TheScottShepardnot false
can we have a little more dramatic music?
I was kinda disappointed, was waiting for the swordfight to start at any moment...
@@louen8413 is this why you descended to a comment section?
A bit more noise maybe?
I really felt UBs normative nature required some reinforcing nature. I never felt quite coerced and dictated enough, so by now, I don't even dare to reconsider any more.
@@smort123a bit is a boolean and thus bad as an argument, as I understood
Don't write code! The best code is that you have never written.
The best code is code that _you_ haven't written [hahahah, sorry, I had to]
TRUE
@@nyarlathotep7204 did you just use a boolean?
Your sentence returns a True
@@vitobrxbut there is no return operator. It looks like it returns void.
After years of programming I've come to the conclusion that all programming rules can be summarized as "you shouldn't do things except when you should".
😂 Nice
One should remain especially vigilant around the bad ideas that are known to be sometimes necessary.
Yes, the if(...) inside a function can be avoided by having two functions.
Which leads to the follow up question: Will there be an if(...) outside of the functions? Because if yes, you would just move the additional code to some other place. It would not reduce the complexity or the amount of code.
It's just someone else's problem.
If the boolean argument is usually a constant, make it two functions.
If the boolean argument is usually not constant, keep it as a parameter.
well there would be an if outside for sure to know which function to call lol
reverse = true;
if(reverse){
return reverseTrue();
}else{
return reverseFalse();
}
Exactly what I think
Just write two programs
The thing i like about boolean parameters is that some languages allow optional values that have a default value and in that case you have a default behavior and an edge case
Yeah, true, but that also becomes a refactor hell. When you have thousands of function calls where almost all of them are called without arguments, you need to check all 1,000 to find if there’s that one edge case where it’s called with an argument. Project-wide search helps, but why complicate things when they could be easy? A separate function for the edge case.
@@alexchexes if the control flow is too saturated with that optional Boolean you're basically guaranteed to need to make it a private function that's called by two new public functions without that param. It feels like too much abstraction to me unless you're writing some library or API. Also that optional param is great for passing a debug flag for unit tests.
I hate and like it at the same time, the default value is hell in a big project. Those value should be passed explicitly.
People are missing the point, of *course* you're just moving the boolean, that's the point! There's an old saying "push 'if' up and 'for' down". Your function should either be doing work, or coordinating work, not both. Code that only coordinates other code is usually higher level and more 'volatile' as it changes w/the business. The lower-level code should be as 'context free' as possible as context is the problem the calling code is solving.
I've seen enough code to know that spreading around use cases is dangerous. I mean "determine_nav_solution" can be as simple as a few matrix multiplications, but it has a need for a bunch of booleans - I can think of at least 8 possibilities. Nobody sane would want 256 different functions, with names describing each bool.
Other words function should have only a bunch of assignments, one loop, one switch or one if else operator.
but… coordinating work IS work. This principle can literally not work anywhere unless your "coordination" is completely trivial.
@@ilonachan You have much more learning to do
@@adambickford8720 can only be the response of someone who doesn't work with a real codebase
My function names after this talk be like: copy_files_but_do_not_overwrite_older_files_but_do_override_if_file_is_smaller
EDIT: Guys, it's a fucking joke. The people in the replies trying to explain how this is a bad practice are why programmers have a bad rep for our social skills. Jesus Christ.
😂
Actually I do that too. Good to know that I'm not the only one on the planet.
When you have complex behavior like that, it's probably better to pass it as an enum instead of splitting it into many different functions.
@@saryakan For some reasons this actually reminds the tax-loss harvesting structure that some trackers apply on portfolios with several different cryptocurrencies in it
You probably need to inject a function of when to overwrite instead of booleans
Just write the code that works Boolean parameters or not, single function function or not. Don't worry about that until it becomes a problem, chances are it never will. It is guaranteed 100% you will refactor your code anyway, for some other reason. Premature optimization and overdesigned patterns are a waste of your time.
Trouble is that you probably wont ever see the problem. Its usually only an issue after you are gone and the next guy is asked to work on your code. Then he will be like "I dont see why this problem code is even in there. Do I dare simplify it or will that cause issues Im not aware of"?
@@adamrussell658 If it wasn't an issue while you were there why is it suddenly an issue with the new guy? Maybe it's the new guy issue?
I hate to agree with you, but I do. So much goldplating done on the wrong end.
I think you took the wrong message away from the video. The point of avoiding bool args unless you have good reason to use them isn't about optimizing or anything like that. It's literally just a slightly more advanced/manual linting thing you can do to make functions easier for the next person to understand.
Nobody's going to die if you have bool control flow args, in the same way that nobody will die if you use snake case for your JavaScript variables, or if you cram multiple lines of code into 1 single line. But it can be easier for people's brains to follow your code if you follow some of these simple ideas.
You might then ask how does a bool control flow arg break convention? Well it doesn't really break convention like the other examples I mentioned would, but it's more just a convention to be aware of for making code easier to read by other people. When people see a function called "createPerson", the default assumption is that this function will just simply create a "person" resource whenever called. Control flow args can lead to this expectation being broken. If you instead just write the conditional branches explicitly and use separate functions, it can be easier to read and understand what actions are taken under which circumstance. But again, this is not a hard and fast rule, as there are definitely lots of scenarios where a bool arg makes code easier to understand. It's more just a "when in doubt, do X" type of rule.
@@NerdyStarProductions I think you missed my point. The problem with premature optimization is in the word premature. Everything premature is bad. Including getting rid of Booleans for mythical "readability" as you or someone else "sees" it.
This causes you to overdesign something to avoid imaginary problems in the future.
Tell me which code is harder to refactor:
- Dirty Boolean ridden piece of shit with no architecture
- Crystal clean but WRONG architecture
Dropping the boolian argument and splitting the function in two is a good idea if the called function branches right away. However, in many cases the function follows mostly the same logic (and code) for the two values, the boolian option is there for a slight variation. Splitting such a function into two would double the amount of code and make it less maintainable. Alternatively you could try to use the same code for two different functions by applying metaprogramming, but such solutions can quickly become ugly, especially when done with C macros. From a performance perspective, I have experienced that the compiler often will optimize out the boolean variable and effectively call two different functions depending on the value. This is very useful when the branch is written within a tight loop. Therefore: Strive to write the code for the humans, and let the compiler help write the code for the machine.
I'm no uncle bob, or any expert on his level, but what I've gotten from things like this, don't pass booleans, don't use repositories/use cases, do this, do that, is that it depends where you use it, it's not a strict, you HAVE to do this type of deal, in your example sir, what I would do, is either not abide by this recommendation, and just pass the boolean, or the code that the two functions have in common, I'd just extract into a separate function, that I call from the two variant functions
Eh, I mean your example could be fixed by refactoring all the similar code into a common function, then when branching call two smaller functions that implement the different functionality
- Raises a problem
- Doesn't come up with a solution.
- Leaves
Classic Uncle Bob
Wait what? Didn't he just say to use 2 functions?
@@alexcarrasquillo1you’re still gonna pass that boolean to the other function no?
@preemtimhaxha5309 No! The point is to split the load and add clarity. Instead of having chunky logic in one function based on the state of the book that's passed in, you decide which of two functions to call from the call site. You still need to use the boolean at the call site, but it has a clear intent. Call function A because the bool is true or call function B.
@@preemtimhaxha5309 are you high? dafuq do you mean you will still pass the bolean? If you have toggleSomething(bool), Uncle Bob is suggesting to instead write turnOnSomething() and turnOffSomething(). No need to pass anything if the original only needs a boolean.
@@preemtimhaxha5309No
Of course, why would I want to have 1 function with 3 booleans along, when I could have 8 functions!!!
And the moment I wanted to change 1 aspect of it, now I would change it 8 times on the infinitely long library
If you need to change something 8 times in this scenario, you probably arent using helper-functions when you should
@oli_dev So I have to replace my function with 8 different functions for each case of the booleans, and also the functions shouls be entirely done with helper functions.
The insanity keeps getting better, just as I like
In practice, what I would do in this scenario is use 1 function, but pass keyword-arguments, or create some sort of struct on the stack that contains the booleans.
Something like:
myFunc({
foo: true,
bar: false,
tux: true
})
This way, you get the readibility benefits, AND the benefit of only having 1 function.
(My previous point still stands tho, if you had 8 functions, and you were duplicating code across each, then you are probably not using helper functions when you should)
@oli_dev And finally you realice that this man is out of his mind trying to be controversial with this nonsense
apply responsibility principles and you dont have to add 3 bools in a function in the first place. You wont get this now but one day you will
so the reason is because he doesn't like it
the reason is because it is a smell that your function is actually hiding several other functions within it
@@jameslay6505 aaaand? What is the problem?
Today him say this tomorrow "Avoid to many functions" after that " make your code shorter" after that "express more your code, the problem with those guys is that they have so many things in their head that they ended with a" mess of good ideas " the best code is the code that works properly and do it simple !
"the best code is the code that works properly and do it simple !"
So simple this will idea revolutionize the software industry.
Why didn't anyone ever think of this before it's so simple!
Surely you've solved every businesses software development problems you'll be a trillionaire.
This is such a non problem.
It is problem when you have 6000 lines function
@@lpi3 well maybe the solution is to not have a single 6000 line function in the first place
@@jeremykiel6709 of course. It was not me, who wrote those lines.
Depends... this
if is_outlier:
handle_outlier(data)
is definitely not the same as this
if data.quality == "outlier":
handle_outlier(data)
What Bob's actually arguing against is allowing default named values to be filled by unnamed positional parameters.
In the code example provided, the issue becomes a complete-non issue if the default is named in the call-site:
printName('Olga', 'Puchmajerova')
printName('Olga', 'Puchmajerova', reverse=True)
Parameterizing behavioral changes is a far more compositionally friendly approach than creating parallel implementations as well.
Bob has mischaracterized a language design issue as a coding hygiene issue.
Similarly, output parameters are a similar symptom in *most* cases of not having one of tuples or variants in the language.
I actively use Python in my projects and already forgot that this was an issue in other languages. Keyword arguments are very useful
I think it makes sense to pay attention when using boolean arguments, because there might be a more elegant/better solution - but in a lot of cases, there isn't, and boolean parameters are fine.
Sometimes, passing a strategy/callback (however you wanna call it) instead of a boolean can add a lot of flexibility with minimum overhead. Sometimes, splitting your function down in a few "do" functions and one "coordinate" function makes testing easier and is going to come in handy when you restrucutre your code, add more functionality or change existing one. Especially when you work on library code, it can help getting the desired behaviour with out requiring backwards-incompatible changes upstream.
But there are cases where splitting the code is bullshit, especially if the "do" code is just very small and minor.
In general, most code design nitpicking is mainly useful for library code, where you design APIs that you don't want to break in the future and therefore try to be as flexible as possible.
Still trying to understand the argument against using a boolean in a function.
🤔
It's not so much the boolean. I've seen the problem happen with strings/enums.
It's where you have two separate bits of code that accidentally got put in the same function and the caller has to pass in a "hey run this peice of code" flag. And the different paths are so separate that they didn't need to be in the same function in the first place.
I see that happen a lot becuase people think they are being DRY and they make a bad abstraction
@@Jabberwockybirdi agree. I work with these code regularly, but this has nothing to do with what martin states. If you have a function which ,unironically, does 2 functions, there are much bigger problems in your code base. And neither is having a bs piece of code like doXWhenY. It makes sense only when the two parts have no correlation to each other, like what i stated before. But usually, they are related piece of work, with a common functionality branched out because of a conditional.
A much better way for abstracted away code is use enums, it helps a lot stating Enum.Value instead of true. Ofcourse there are some restrictions but it doesnt limit you to 2 absolute states, which will break in the future when requirements start to dilute in the future.
For example: ReturnPlayerHistoryIfMale and ….Female, i would never in my life accept that code. This highlights the other point too, if 10 years back you used true or false,the entire thing needs to be refactored. Use an enum ReturnPlayerHistory(long playerId, Gender gender) . Makes it much cleaner
For your pattern, your argument is correct. I would rather avoid it, because the meaning of the content of a boolean, is hidden in the variable/argument name.
While an integer like "7" still holds the information, something has been counted here, true or false have absolutly no meaning, enabled=true or disabled=true ? the meaning is hidden in the variable name. So the alternative is to replace boolean with self defined enum {enabled, disabled}, it avoids many confusions, and assigments from one bool variable to another bool variable, who have a completly different meaning. Compiler would not strike on Enabled=Disabled nor Enabled=!Disabled, if everything is just bool.
Readability: absolutely agree. Used named parameters f(reverse=True) or enum values f(order.REVERSE), don't put True or False in your arguments list.
Control flow? I disagree. What if the if code is in the middle of a function, I should write two 90% identical functions with one different block? DRY. What if the test for which branch to take involves more factors than just the boolean? Do I need to say if(boolArgument && isLeapYear() && temperature < 20) every time I decide which of the functions to call? DRY. What if I need to invert the sense of the flag - change one function or n function calls? DRY.
You would refactor to have a base function that does the 90% and then write the two branches as separate functions
I’d say Uncle Bob needs to spend a few more years writing code before he starts advocating universal design patterns. There are use cases when both bools as parameters and output parameters are quite appropriate and the best solution. TryParse, for one instance, is a perfect usage of out parameters.
Depending on the language, returning an optional or a tuple might be more readable. Alternatively, some languages require you to decorate the argument with "out" at call time which at least makes it obvious which arguments might be changed by your function call.
Idk man... I have been tortured by bool args enough to agree with Uncle Bob. In over 90% of the cases they are just bad design.
Bob has been programming while you were in diapers. And he specifically started out with a disclaimer that you don't always avoid bools. Stop avoiding good advice because you get caught up in whataboutism
TryParse is a symptom of the language lacking a good way of unwrapping an Optional (Some/None) type. Just return a nullable and you wouldn't need an out parameter.
@@elijahshadbolt7334Rust mentioned?
If your entire function is a if statement around a boolean argument, then yes, it makes sense to remove that boolean argument, but there are plenty other places where boolean arguments totally make sense (eg. a verbose argument)
Uncle Bob forgets that the passing bool preserves encapsulation,esp in microservices. Suppose you want to get some data and you don't want it to be cached/fetched from cache. If you don't pass bool, then you have to expose two functions.
named arguments , e.g. render(data, cache = false);
Finally, a standup comedian I can relate to!
wtf booleans are literally the easiest things to read
Not if the value is being set by some bit of code that's several pages away or is in a completely different file it isn't.
@@SmallSpoonBrigadebut why is that relevant, when reading it you care about all cases true and false, and so state is irrelevant, and when debuging with any competent editor you already know the state, so the bool reads like a part of the function name.
with bool arguments code runs too fast
The bool thing is simple enough, but boy do I hate passing in a return. When debugging someone else's code having a value changed because it was used as a function input is so easy to miss and not understand why/where it changed.
A small clarification is in order:
Nothing wrong with booleans to guid control flow but try to keep them in a private implementation and expose functions in your API without such argument. THAT, in my opinion is the key.
Booleans can help you with the DRY (Don't Repeat Yourself) approach but that should be shielded - if possible - from the caller.
i feel bad for all the people who had their time wasted for attending this. unless it was advertised as a pure comedy show
Thanks, this vital insight helped me to code Quake 1 rendering engine, in fact it would have been impossible without this magnificent knowledge.
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
Good rule for normal arguments, disagree in the case of kwargs (if your language has it). Obscure options that require a something=True flag to be passed into the function.
Can you imagine how awful it would be to have open functions based on the behavior you want instead of just passing a kwarg? Probably about as awful as having booleans as args!
Yes, oh my gosh. I was looking for ages for this comment. I couldnt agree more
What he wants:
open(file)
open_closefd(file)
😂
Lots of people are missing the major goal of refactoring away the boolean parameter.
You aren't getting rid of the conditional logic.
You are making the purpose of the functions smaller and decoupled rather than hiding two or more district behaviors behind a single function.
The caller still has to know whether they want behavior A or behavior B.
But instead of:
behavior(...., true) vs behavior(...,false)
Where I have to go look up the docs to see which is which...
Why not just have:
behaviorA(...)
behaviorB(...)
And the name tells me the cases.
The OO designer would go a step further.
Make an interface for behavior(...)
Make an A class
Make a B class
And now, in the future, if it wasn't a boolean client decision at all, but actually there was a secret, third thing, we can make a C class and we don't have to go change the signature of behavior(...).
It's still academic nonsense and disconnected from reality.
It's still academic nonsense and disconnected from reality.
formatDoc(bool padTabs, bool useHyperlinks, bool allowNesting) : you are going to write 8 functions now?
@pudicio I would probably use keyword args with defaults to document this configuration pattern if there are two or more flags (if I'm in doing procedural like in python or racket) or use the builder pattern with a setting function for each configuration option ( or configuration record/table) if I'm doing OO or similar.
So no, not 8, since functions aren't the only abstraction in my toolbox. But I would still pick an abstraction so that the caller of my code doesn't deal with "naked boolean values" that they have to memorize the order and meaning of. Nielsen heuristic: recognition rather than recall. Let the interface tell you about itself.
The thing is that usually in this case, you're not doing two totally different behaviors, you're having one slight modification to a longer behavior based on a switch. And you may have more than one switch, which makes breaking things up into multiple functions a nightmare.
I love the solution. Oh wait it isn’t mentioned. That’s rude…
He recommends putting each branch into its own function.
@@GoldenBeholden That just moves the if statement up to wherever the function is called. It doesn't necessarily decrease if statements, it may actually just cause more to happen, because they aren't "allowed" inside the function itself.
@@GoldenBeholdenand what's the point? You'll still have to evaluate a bool and call the appropriate function. And that will happen in a function that has to take a bool as argument or evaluate a bool variable.
So what's the point?
@@euunul @Zocress A well named function makes for more semantic code, while each function can be simplified by just focusing on a single thing. I'm not saying this is always the proper solution, it's just what is suggested in this video.
@@GoldenBeholdenI also do this when I have more than two if-branches
That's a shite reason to not use boolean arguments. Breaking everything up into tiny functions is a composition nightmare, with a code flow that no human can understand. Our worst code is exactly like that. The really good reason is that with multiple arguments of the same type, you'll eventually mix them up. Use type aliases for everything, including booleans and integers, so that you can never mix a "recursive" with "includeDotDirectories", because the function is "search(Recursive(true), IncludeDotDirectories(false))". Our number of bugs from such mistakes has dropped to zero.
Type Aliasing is a chore in most mainstream languages, F# has it greatly done
If all the code is under your control, and the function is not virtual, at least think about it.
Multiple functions is one way to solve it, your specialized boolean-like types, is another way.
(Aliasing is not a well defined term, cause it works mutual in some language, making two different terms interchangable, it doesn't create a new type).
How is it a composition nightmare?
Composition is much simpler when the elements to compose do one single thing each.
@@nekononiaow So you prefer to assemble something from 500 single pieces, rather than from 10 pieces ? Think on a shelf from IKEA. As more parts, as more you have to think on, as more you have to understand, what is good for what.
For people saying 'Great now I have 8 functions instead of one', the video's advice isn't 100% literal and there are different methods for breaking down your booleans. The general principle that should be followed is reducing the complexity and breadth of the work your functions are doing. Maybe you have 3 booleans passed in that would be 8 functions if split as-is, but perhaps one of those is better as an integer and is better utilized in a math expression than a branch. And maybe one of those booleans is logic I want to do sometimes but not always, in which case you can just split that logic off into its own function and call it when you need it. The name of the game is finding the most logical places to divide your code into functions that minimizes the permutations in which one function can do any given thing, such that you can simply read the code from top to bottom and understand the flow without needing to excessively investigate the arguments or inner workings. Boolean arguments are not outright evil, but they are an indicator that your structuring could be better, and that in the near future you may be adding more booleans, or you'll need another function that does the same thing but with some other difference. They bloat heavily and paradoxically can lead to larger, harder to read code.
I don’t understand how not passing the Boolean is functionally better (other than readability).
If you create two versions of the function, then the caller still has to do the if comparison to choose the function. But now you have the added complexity of maintaining two functions with similar code
Boolean arguments have never confused me after I started using inline hints (Microsoft calls it code lens I think). With it turned on the parameter names are displayed (in greyed out not distracting text) right next to the argument. So instead of a random false, it says "is_clean_code_good_code: false" literally a non issue
So it is better to have 2 functions instead, for doing almost the same? Or if I have 2 boolean options, having 4 functions, and creating a dispatching function for the 2 booleans (a total of 5 functions)? It doesn’t add up.
I think what he means is to write simple code in the likes of doThis();
doOtherThing();
Code should be written in a way that its pourpose is clear right at first glance. If you start checking what happens within the if and then the else and then you need to go back to double check previous stuff...then that code is hard to read
Do the dispatch at the caller side. Especially when you pass constants arguments, literally with "true" as argument, you will notice the entire dispatch is meaningless.
You would create code doing "if (true) ...". But you don't realize it, if you hide this in arguments.
you avoid an if statement inside the function, by using an if statement to know which function to call when both operate on the same problem. brilliant.
I didn't really understood the example in video about output arguments, my understanding is that it's like in C or C++ when you pass the pointer (read / write reference) to a function (void f(auto a, auto b, auto& result)) and then collect result from the parameter (int a = 4, b = 2, result = 0; f(a, b, result); std::print(result)) something along this lines. This is indeed really confusing and most of the times it's better nowadays to create a structure from return and the destruct it. (Result f(a, b); ... auto [result1, result2] = f(4, 2))
Linux command flags should all be individual commands for each of their permutations, got it
I code in C/C++ close to the hardware and from my pov bool is the most useless datatype. If I want to save memory, then I work directly with bits. If it should be more readable, I use enums.
Bool in C++ is somewhat limited and odd because of C / C++ doctrine of “everything must be addressable”.
Which in turn, has forced virtually all modern CPUs to support byte level addressing… which is not the most efficient thing, to be honest.
C++ doesn’t do a very good job with booleans.
This is too broad of a rule tho. What if I have a function for which I want to make logging optional. def do_thing(inputs, logging: bool). Am I supposed to duplicate this function only to have logging done in one of the two? Am I supposed to define a length-two enum for the sole purpose of toggling some prints?
What if, instead, I have a function with two branches which are known at runtime? Am I supposed to put ifs before every function call instead of just putting one in the function definition?
Return the string to log instead. This way the caller decides whether to ignore or display it.
Then they can call Log(DoThis()) or simply DoThis(), which is clearer for the reader anyway and gives them control on how to log/display it, which they would not have if that was inside the function.
@nekononiaow logging may appear in different parts of the function and not necessarily in all paths. Also some logger libraries record the location of the current execution frame (line no. and file) so returning the string would invalidate this information
Hot take: don't pass in a bool, especially if it doesn't affect the function's business logic or result, especially if that bool remains unchanged for large stretches of time rather than constantly carrying some regular busywork involved in some specific logic, and especially if this is a pattern that will also repeat in other function(s) as well, moreso if needing to disable logging is only loosely or not at all related to when/how the function is called .
Instead, it's time to make a global flag LOGGING: bool that enforces the desired behavior and the function can just read without passing it in as an argument.
Why? Because it's dead-explicitly predictable what kind of behavior some "if LOGGING == false: enable_logging()" will invoke even if you've never seen the code or the program before, because it is simple as a structure (single value in memory), because it doesn't pollute your function arguments where logging: bool can be ambiguous on functions that return some object: it'll look like you're creating a new object with its logging disabled rather than just not logging for that function's run. An enable_logging() and disable_logging() sandwiching your function call is slightly obtuse but extremely explicit and self-explanatory.
Don't do this if you never need to do this elsewhere though or if you need to switch it on and off so often that the calls to toggle it make the code harder to understand, but I find it odd that you'd hardcore some specific function to just never log, maybe making that stand out more is a good idea? I know it seems simple to just pass in a bool but it's not very intuitive to read: is it even obvious that its effect only lasts for as long as the function itself?
Logging is one of the few things where globals make sense as it's expected to have identical lifetime to the entire program.
no in this example youre good, what you shouldnt do is `def do_thing(inputs, special: bool); if special do special thing; else do normal thing`
For kids in comments who don't get how to make a solution for this. When you pass a boolean into a method, you are basically passing a flag to switch something. What if you need more than ON/OFF? Then you will pass an enum, and what if you have an enum? You will have a switch statment, so kind of more if. You will get to the point when you should just have a switch with small methods that do specified stuff, not huge methods with booleans.
In some cases ofc you can do this, when you are like 100% sure that it will be just ON/OFF like you coding hardware stuff. In other cases Uncle Bob is right and it's pretty straight forward what he says.
use booleans in a way where you only ever want an on/off. if you are using them in more ambiguous ways, you are using booleans wrong and should be using something like an enum instead. when do you ever need more than 2 states for whether a computer is on or off? you dont. if you need more information, such as _why_ the computer is off, then you use a different parameter, not try to encode it in the boolean or replace the boolean completely with the thing that encodes that extra information. it doesnt make sense and it makes more complicated and harder to read code.
To help illustrate the problem, let's go the other extreme - ONLY EVER CREATE ONE FUNCTION and "select" the behavior you want using a combination of flags. So instead of "add(a, b), sub(a, b), email(to, fr, subj, msg), commit_db()... etc" you have "dothing(a, b, c="", d="", is_add=false, is_sub=false, is_email=false, is_commit_db=false)". This is kinda something that really happens in the wild and why Uncle Bob is bringing it up. Imagine how large dothing() is and how confusing it is to work with that function. All the code is sharing scope and might be reusing variables and what not and now if you need to add another feature to this function, well, you have to understand all the other functions it embodies. Instead of a simple 2 min change you've just wasted 2 hours figuring out why the `if (is_commit_db)` branch is using variables that also seem to be used in the `is_email` branch.
I would say that this make sense ONLY IF the function is longer than the screen height.
This boolean can help to divide the code onto smaller pieces. But what about boolean without if?
C#
height = pageSize=="A3" ? 100: 250;
Just imagine how much extra work was made by the teams across the world under the influence of Uncle Bob. He might the single most net negative person in the industry.
That is why pattern matching in function head is the best language feature of elixir. A lot of the time this boolean argument is replaced with guard
Depending on who's reading the same piece of code, you'll have bored, surprised, amused, disappointed, defeated, angry etc, etc people.
I love uncle bob and read his books when I was a kid but let's be honest, he was already outdated 10 years ago
This advice still holds up. I work on modern bloated js frameworks, and they can quickly get out of hand with too many boolean flags
his books are for absolute beginners in an environment that has java only
So many 'experts' that tell us not to do certain things, or to only use certain things. Code however you like. The best code is code that works for you - there's plenty of benefits to use Functional over OOP in certain cases, and there's plenty of reasons to use booleans as parameters, there's even good reason to write difficult to read code and even 'spaghetti code' has a place (job security for instance). There's a place for everything.
haha job security wins. bool arguments are good for the economy 😂
There always will be arguments for and against any rule... sometimes it feels like there are so many of them
So try to follow fewer but important ones... write clear and small functions, use good names, try to make it readable as prose
Pay attention to the code you write instead of making a bunch of code that is proof against bad programming
good.. instead of writing "if" inside that function, you are going to write that "if" inside parent function.. and why just booleans, why not every other parameters, for example used for switch, like enums, write 10 different functions for 10 different enum posibilities.. again you will put that switch inside the parent function
people are like "where do i put the bool then?". keep it in the function it was created! don't PASS it into a function, call two different functions when it gets created. execute control flow where control flow is created, not further down the execution path
yes if bool is a result of an expression
Nah it’s over optimization. Write code that works
How does this advice line up with DRY? If a bool is a simple switch in a function to turn on/off some feature or additional calculation I now create 2 functions that are probably the same code apart from 2 lines or so that he probably thinks is way too long in the first place.
you ask same question as my dumb colleagues. you assume that both code branches are some garbage code, but they are not, most of code is reused. And how bool makes it DRY?
Use helper functions, or better, call one function from inside the other
Don't be dogmatic with DRY.
WET is better than dry
@@Jabberwockybirdnot always but yes in some cases. SOLID principles are not the only ones. anyway, bool arguments has almost nothing to do with DRY but some peiple make wrong conclusions infact avoiding bool arguments can promote DRY
then your functions are spageti code, split them into more
This does not make sense at all, you will still need an if statement somewhere regardless so he is essentially creating 2 methods with an if statement in his recommendation rather than 1 method and an if statement. This would get even worse as you would likely need to make a method that both methods call to avoid repeating code
If the Boolean logic is removed from a function and converted into two function, isn’t that just moving condition to the caller? The caller then has to decide which function to call, placing the burden on it instead of (presumably) the more sensible location?
For web dev maybe. But what if your priority is smaller size of code, or faster code, or both? Hardware sellers love this stuff. Make your code more readable. User should buy a supercomputer for your pretty looking calculator. Because you don’t want to spend time for coding. Spent it for something else.
Some well placed comments solve these issues. It baffles me how few devs actually document their thought process.
while more comments definetely can help, i think i can agree with the video to a certain extent, to at least that code should be better formatted as to make it so that few comments are really needed to be able to understand something
What's the battle music for? It's really annoying.
Disregarding the non-issue bool param, can we talk about passing an object to mutate inside a function? Anyone that's written a single line of C/C++/Rust/Zig will tell you how not only useful that is but literally required. If you want fast code you want to not be creating whole new objects for every function, memory allocation is slow. you want to mutate existing ones. Even JavaScript takes advantage of this. Three.js recently started using more efficient buffers for holding values and a lot of functions let you pass in existing ones to to reduce ops.
Just found out about this channel and I have to say that I wish I didn't. The opinions exposed here are too shallow; lacking the necessary examples to defend your points. They don't seem to be backed by a lot of experience to be honest.
I have experience and I agree with the bool argument. Bob has experience, but I think he presented the problem wrong. An example would have helped.
I avoid booleans, so many bugs are caused by devs misunderstanding them even though they are so simple
I like this approach generally. But it is not absolute. We should not apply it everywhere. We always should think what we are doing. Let’s assume for example that we have a function which do something and it’s main logic it is about 99% of the function. And within this logic, deep inside, we have some condition, depending of the single flag. So what will cost less: not very beautiful solution with passing Bool argument into the function or duplicating 99% of the function to avoid Bool argument usage?
if a bool is the only argument i could agree unless the code would be too long and a function would be descriptive of what is happening.
as for the function only returning an argument. i have no idea why anyone would do that unless its a placeholder for a callback and you do not want to add an if statement to check for a callback.
people add descriptions to functions along with other info with /** to have the IDE elaborate the functionality for a potential callback or a function that has a bool.
if not for that i have not come across other reasons.
Don't pass integers either, just write a function for every possible value
Clearly this guy doesn't write hardware code and only abstracted code. One can't always abstract away memory management.
Doesn't writing two similar functions instead of one, with a boolean passed in, conflict with the DRY principle? How do i decide which which principle to prioritize?
use your head and write code that will make the day better, not worse, for the next person who needs to change it.
Please consult an audio technician to show you how you set the pan, levels and other vital qualities.
Could be better without the over dramatic music. I get it to make it less dry. But it was distracting.
first they say to avoid gotos, then they say to avoid booleans. what next? - avoid c/c++?
rust users incomming 3.. 2.. 1..
Please guys, what's the name of this talk? I'd like to watch it full and not cutet in clips
Thanks
Nonsense.
You probably are thinking of a different example scenario. Or you have never seen the mess that can come from too mamy boolean flags. Or you're just a close minded jerk. Not sure which one
You've literally just lifted the if/else up a level out of the function and then created a second function...
About the second point, I do hate function with output arguments because they are messy to understand and to use... but what other option do we have if there are more than 1 element to return ? Create a structure just for the return type of this function ?
I'm not a genius but my brain can easily read code where it branches based on a boolean. And how do we know this is BOB and not 8O8?
Or use a struct param that explains what the boolean does. Make the boolean a property of the struct.
"Use as few booleans as possible, but not fewer than that."
oh that's just uncle bob. he does that. don't mind him.
True. If there is a boolean in the parameters, odds are that function is not respecting single-responsibility.
Keep it as simple as possible. Multiple ideas shouldnt be condensed into one function unless there is an actual reason to. Thats my opinion. Simple is almost always better.
“Don't organize your code to avoid huge functions, that's rude”
That was funny, I wrote a comment yesterday, really about new programmers today that don't know how to read code. It was deleted some how :D It didn't have bad words or anything. Is uncle bob on the elite team or sumthin?
Nice talk. But this epic music in the background is a bit distracting to me
I wish the sound was clearer
And some people are wondering why code reviews are such a pain on some projects? 😅
maybe true or maybe false XD. But one thing is a fact, many young programmers don't know anything about Boolean Algebra and their maths/calculus abilities are very low. How they understand and solve complex engineering problems with code?
Our senior dev uses int 0/1 is that ok?
It's rude to add music where unnecessary
I sometimes do these booleans in functions. I’ll try avoiding them, because i don’t like them either. But how would I do that if the if statement is buried deeper down? split it into even more functions?
It doesn't matter there are always only two versions of the function, one where it's true and one where it's false.
If you can split the function into more smaller functions it suggests that the original function wasn't a single-responsible one. So yeah, feel free to split them. Unless you see some profit from keeping that bigger multi-responsible one.
Principle of least surprise 👍🙏
If we have two functions, then we are going to have an "if" somewhere before calling them.
And what about boolean properties? like IsEnabled, setEnabled, etc, etc. Frameworks are full of them. All frameworks are bad!
I don't understand the last thing
holy crap... the panning on this video's audio... what the heck?
This is a weird advise. So if I should not pass boolean where do i keep all the logic? In a top entry main() method?
This is referring to situations where the same code is handling two different use-cases. Imagine you have main and a different team on a different application has their own main. Now imagine you each have a different use-case.
You could call Bob's function with like this.
bobsfunland(true), and the other team would call bobsfunland(false).
Or if it were refactored, you could call it like this.
pAR2020UseCase()
And the other team
otherTeamUseCase()
Disagree, at least in my use of JS. If you always run that function conditionally, then you can use the boolean and a guard clause to exit the function without branching. Otherwise, you need to add an if condition each and every time you call the function. Of course, it can default to true, so you don't need to use it at all, or purists can make a higher order function ie. doThing has conditionallyDoThing.
I'll keep doing it.
Don't stop there: remove ALL the arguments. Then we CAN all just get along.
Yes, just let the IF go to the caller function)