1. 67
  1.  

  2. 24

    Key phrase for further research: Boolean blindness.

    1. 6

      Less directly related, but still somewhat applicable: Primitive Obsession.

    2. 20

      … or, instead of thinking of types as a solution to everything you may be lucky to use a language with named arguments, so you could

      fetch(account_id, disabled=True, history=True, details=True)
      

      … and in cases where you already have named variables with values you could avoid duplicating names by calling this same function as

      fetch(account_id, disabled, history, details)
      

      What I’m saying is, language matters.

      1. 6

        Language does matter, but your example is still an instance of boolean blindness. What intuition should I have about the truthiness of history or details?

        1. 2

          It’s a question of better naming. I just copied names from the article for a quick example.

          1. 4

            Ok, but also in your example you have connascence of position, which is worse than connascence of name. There’s not much stopping someone (other than just being really careful!) from calling the function with the arguments in the wrong order, because the constructors inhabit the types of almost all the arguments.

            1. 3

              I did not understand that, sorry. But for the record, Python allows you to pass named arguments in any order. Don’t know if that has anything to do with what you’re saying.

              1. 3

                I mean it’s very easy for the caller to confuse the position of the arguments, and if the types in your function’s signature have very loose semantics, your program will compile but it will still be wrong.

                Compare the following:

                -- error prone
                fetch "abc123" True False True
                
                -- better
                fetch (AccountId "abc123") IncludeDisabled WithHistory WithDetails
                

                But for the record, Python allows you to pass named arguments in any order

                I think we’re arguing different things in that case. It’s like the difference between (in JS)

                foo(true, false)
                

                and

                foo({ someField: true, someOtherField: false })
                
        2. 5

          Good point! If you even luckier, your language would support keyword only arguments. E.g. in Python you can declare

          def fetch(account_id, *, disabled, history, details)
          

          That would forbid calling it like fetch(acc_id, True, False, False)

          1. 1

            I wouldn’t do that, actually. Because it would prevent my second example from existing, and you’ll end up with silly stuff like disabled=disable, history=history, .... Overall, I prefer an API to not overly restrict usage choices. Because a consumer is almost always in the better position to judge what’s useful and what’s not than the author of the API. And mis-uses are better prevented with good naming, documentation and code reviews.

            1. 5

              But otherwise there’s nothing ensuring the args are passed in the right order. Even if you name your variables nicely, fetch(account_id, history, details, disabled) runs without any warnings, but is all wrong.

              1. 0

                But otherwise there’s nothing ensuring the args are passed in the right order.

                Tests are.

                1. 4

                  That’s a cop-out. If you assume there’s always adequate test coverage for everything, then no code can be wrong.

                  Of course it’d be nice to have tests for every argument of every invocation of every function, but let’s be honest: it’s not going to happen often. In terms of required discipline and effort vs correctness achieved, named arguments or typed enums solve that better.

                  1. 1

                    I look at it differently :-) Type checking is a cop-out, where people make testing so hard that they don’t do it and have to rely on types to provide semblance of correctness. “If it compiles, it works”, something I hear too often.

                    it’d be nice to have tests for every argument of every invocation of every function

                    This is trying to emulate type checking with testing, which is the wrong way to do it. But we are way, way off topic here, and I can’t hope to elaborate enough details in the middle of a comment thread. May be I’ll work myself enough to blog a series on it some time…

          2. 3

            Aren’t named arguments enums that carry a value? ;)

            1. 2

              Absolutely. This style is an excellent alternative, in languages where it’s practical.

              1. 1

                I don’t like keyword arguments; I would much rather have a “bag of flags” and write:

                fetch(account_id, FETCH_DISABLED|FETCH_HISTORY|FETCH_DETAILS)
                

                One of the biggest problems with keyword arguments is that the temptation to add “just another keyword” argument is very high, so someone might have this:

                def GetReportData(selector, fields=None):
                  if not fields: fields = self.DEFAULT_FIELDS
                

                and get this crazy idea to try extending the API:

                def GetReportData(selector, fields=None, start=None, end=None):
                  if not fields: fields = self.DEFAULT_FIELDS
                  if not start: start = datetime.min
                  if not end: end = datetime.utcnow()
                

                and never see the bug! Surely this isn’t all the fault of keyword arguments (and especially so in this case), but designing your API to be clear and useful without relying on keyword arguments also prevents this problem (and problems like it) from being introduced in the first place.

              2. 12

                I know this one is likely to be controversial, so I should say that I agree with it. This practice is suggested in Google’s C++ style guide, I’ve coded with it, and it’s made my code more robust and maintainable. The cost of maintenance far exceeds the cost of writing code to begin with; this is a simple thing with concrete benefits and it’s hard for me to see a justification for not doing it.

                (Edit: syntax issue with the link)

                1. 6

                  Also, for c++ I find strong type aliases work pretty well, e.g.

                  using IncludeDisabled = NamedType<bool, struct IncludeDisabledTag>;
                  // caller uses:
                  fetch(accountId, IncludeDisabled{true}, ...)
                  // caller can't just pass true or false because of explicit constructor
                  

                  wouldn’t handle the “compatibility matrix” scenario, but works well when you need simple flags.

                  You can also add cast operator to NamedType, that would make it transparent in if statements.

                  1. 1

                    Sadly strong typedefs can cause performance degradations - wrapping int/float in a struct might require compiler (due to ABI requirements) to pass such values differently to calee.

                2. 9

                  Also, if you have more than 2 booleans in a function parameter list, you should reconsider.

                  1. 1

                    I read somewhere the advice that a record (in Haskell, which makes it quite similar to a function) should never have two fields with the same type.

                    1. 4

                      Ouch, this would make quite a few things…impractical. But I guess I’m not thinking haskell-enough? (Or at all, since I know almost nothing about it.

                      1. 5

                        It’s cheap and easy to define new types in Haskell, so it’s not impractical at all. In fact, it’s better to err on the side of adding a bunch of types to your code in Haskell, because Haskell is not a safe language by default — you need to wield it properly to add all that safety.

                        I’ll write an example to give you something more tangible.

                        Let’s say you’re writing some program that uses some geometry. Maybe it’s a game and you need to do collision detection. Maybe it’s a website where people can design extensions for their real estate. I’ll keep the example short to illustrate, but remember it’s only to illustrate, and that this principle works more as it scales up, and that we must avoid thinking “oh well this is only two fields; it’s so small a real programmer would never make this mistake”.

                        You could define the record like this:

                        data Plane = Plane Int Int
                        

                        And this would seem clear enough. Need a rectangle? Make one with Plane 10 20.

                        …Or was it Plane 20 10?

                        It takes one person on a team to get the arguments in the wrong order, and even though this compiles, it will be wrong, and it will likely make your business’ product do weird things at some point.

                        Alternatively, you could write it like this:

                        newtype Width = Width Int
                        newtype Height = Height Int
                        
                        data Plane = Plane Width Height
                        

                        And now anyone wishing to construct this type must be explicit about the values they’re constructing it with.

                        Plane 10 20                    -- won't compile
                        Plane (Height 20) (Width 10)   -- won't compile
                        Plane (Width 10) (Height 20)   -- it works!
                        

                        n.b. I said “record” above, but arguably Haskell doesn’t have records. It may be better to think of records as normal datatypes with bundled accessor functions. If you write

                        data Plane = Plane { planeWidth :: Width, planeHeight :: Height }
                        

                        then you can construct this type with either of the following

                        plane1 = Plane { planeWidth = Width 10, planeHeight = Height 20 }
                        
                        plane2 = Plane (Width 10) (Height 20)
                        
                        1. 0

                          It takes one person on a team to get the arguments in the wrong order, and even though this compiles, it will be wrong, and it will likely make your business’ product do weird things at some point.

                          This is such a strange argument to me. I couldn’t imagine getting width and height swapped and not immediately noticing upon, you know, running the program.

                          1. 6

                            I’ve had a couple situations where I’ve done this, but swapped them in two different places such that the bug isn’t visible yet.

                            Two weeks later, I re-use one of the methods, get the arguments the right way around this time, and hit the bug.

                            1. 3

                              Perhaps you missed the part where I tried to preempt your depressingly predictable comment.

                              but remember it’s only to illustrate, and that this principle works more as it scales up, and that we must avoid thinking “oh well this is only two fields; it’s so small a real programmer would never make this mistake”.

                              1. 0

                                I read that and I stand by my statement. Confusing values of the same type between two different places is the sort of thing that tends to cause major and obvious behavioral problems that are caught by even the most cursory of verification/validation, be it static or dynamic. Catching errors earlier is nice and all, but in all my years of engineering, I can’t think of one example of this class of bug that wasn’t trivially caught and corrected.

                                1. 3

                                  Confusing values of the same type between two different places is the sort of thing that tends to cause major and obvious behavioral problems that are caught by even the most cursory of verification/validation, be it static or dynamic

                                  Oh really.

                                  🙄

                                  1. 1

                                    Can’t open that link. Is this some kind of meta commentary on the fact that the link bugs out with “too many redirects”?

                                    1. 3

                                      It’s a CNN link to an article from 1999 titled “Metric mishap caused loss of NASA orbiter”. The events described center around one engineering team using English measurements while another used metric.

                                      1. 1

                                        Confusing two values with the same meaning but different encodings is very different from confusing two values with different meanings but the same encoding.

                                        The former is a much easier mistake to make and, because the two encodings can have significant overlap, much more difficult to detect.

                                        1. 1

                                          I certainly take your point, although it doesn’t change my overall view on the subject. I was mostly describing the contents of the link to help out, not to weigh in on this disagreement. I just felt like I should respond to say that rather than leaving you to make assumptions.

                    2. 6

                      Swift nailed it with contextual enums.

                      fetch(url, FetchOptions::History)fetch(url, .History)

                      1. 1

                        Do you have a link? I couldn’t find what I needed in the Swift enumerations docs, or by googling “contextual enums”.

                        1. 4

                          The best source I could find after a little searching is this Swift Evolution proposal that became part of the language in Swift 3. It’s specifically about requiring periods before the names of enum cases, but it alludes to the fact that you can often use case names without needing to include the type name:

                          Swift infers the enclosing type for a case on a developer’s behalf when the use is unambiguously of a single enumeration type. Inference enables you to craft switch statements like this:

                          switch Coin() {
                          case .heads: print("Heads")
                          case .tails: print("Tails")
                          }
                          
                          1. 3

                            In Swift docs it’s introduced as a “shorter dot syntax”: https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html

                        2. 4

                          Revealing intentions. Enum, a module, a namespace, something. In Ruby, I’ve abused modules for this and it’s not super great but I’d still do it because it reads really nice.

                          class User
                            module Enabled; end
                            module Disabled; end
                          
                            attr_reader :account_state
                          
                            def initialize
                              @account_state = Enabled
                            end
                          
                            def enable!
                              @account_state = Enabled
                            end
                          
                            def disable!
                              @account_state = Disabled
                            end
                          end
                          
                          # main - but write tests in real world  ;)
                          user = User.new
                          if user.account_state == User::Enabled
                            puts "User is active."
                            user.disable!
                          end
                          
                          puts user.account_state
                          

                          When you run it:

                          User is active.
                          User::Disabled
                          

                          You can use enums, namespaces in other languages to do the same thing. Like I said, I don’t super love this and don’t do this as a rule. But instead of using true/false when I have states, sometimes I do it like this (in non-Ruby too). And it’s dynamically typed so you need to test it which is neither a pro/con. I just don’t like the namespace ;end bit. Note that this isn’t constants because there’s no value being stored. It’s purely namespace and intention revealing. I think that’s kind of neat.

                          1. 2

                            Rather than module Enabled; end you could also use Enabled = Module.new.

                            1. 2

                              Wouldn’t :enabled and :disabled be more idiomatic Ruby?

                              1. 2

                                I’d say so. You could still expose the User::Enabled and User::Disabled constants with those as the values.

                                The use of “sentinel modules” for values is an odd choice and I can’t see any real benefits (comparisons might even be marginally more expensive with this implementation?).

                                Also worth noting that you should always consider looking at things like ActsAsStateMachine rather than hand-rolling your own, of course.

                                1. 1

                                  Yes, that’s more common. But there’s no safety of symbols scattered everywhere (of course there’s no safety with modules scattered either). With symbols, you’d have to go find the possible/expected values but I think having the namespace has a chance of getting some editor autocomplete to work outside the file. There’s really not a guarantee which is why testing is so valued/polished. I don’t always do this module trick. I think it just reveals intention because :disabled becomes a module with a namespace like User::Disabled.

                                  Other languages have this stuff more formalized and checked. Pattern matching with enums is pretty great (to me).

                              2. 6

                                There’s a next step as well http://degoes.net/articles/destroy-all-ifs

                                1. 6

                                  Remember kids, enums spread diseases: https://codecraft.co/2012/10/29/how-enums-spread-disease-and-how-to-cure-it/.

                                  As for booleans, I generally hold the view that if your module has more than one boolean, you’ve implemented an implicit FSM with a bunch of invalid states. Make it explicit!

                                  1. 4

                                    Maybe it wasn’t widely supported at the time, but I’d use enum class members instead of the C preprocessor for this. Something like this in C++:

                                    class VehicleType {
                                    public:
                                      int maximum_speed();
                                      bool slides_easily();
                                      FuelConsumption fuel_consumption();
                                      enum { Car, MotorCycle, Truck, Semi }
                                    }
                                    

                                    Not only because I really don’t like using macros, but also because it makes sense to be able to easily switch to make it a non-enum class.

                                    1. 3

                                      Enum classes are a great solution to the problem, when they are available in your language.

                                      1. 3

                                        If we’re talking about the same thing, in Kotlin these are “sealed classes” and in Rust they’re just “enums”. In both of these cases, the different variants of the enum can have different structures, because they’re classes instead of just instances. (But they can also be singleton classes.)

                                        (Compare to Java, where all the variants of the enum have the same structure, because they’re just instances of the same class.)

                                        1. 2

                                          I wasn’t even talking about having data-carrying variants. I was just talking about the ability to associate methods with your enum, mostly lookup tables. In Rust, you can do this with an associated impl block:

                                          #[derive(Clone, Copy, Debug, Eq, PartialEq)]
                                          enum VehicleType { Car, MotorCycle, Truck, Semi }
                                          impl VehicleType {
                                            fn maximum_speed(self) -> i32 {
                                              match self {
                                                VehicleType::Car => 8,
                                                VehicleType::MotorCycle => 4,
                                                VehicleType::Truck => 8,
                                                VehicleType::Semi => 4,
                                              }
                                            }
                                            fn slides_easily(self) -> bool { ... }
                                            fn fuel_consuption(self) -> FuelConsumption { ... }
                                          }
                                          

                                          The C++ is actually really close to this, even though it’s technically an enum embedded in a class. What C++ doesn’t have is enums as tagged unions. C++ has a form of tagged unions in variant, but it’s not quite the same.

                                          1. 2

                                            When using the terms for algebraic data types that’s basically a sum of products.

                                      2. 4

                                        That article is about a very different use of enum than this article talks about. Enums used as flags are pretty harmless.

                                      3. 3

                                        At work, in our projects that use Ruby, we require all boolean parameters be specified using keyword arguments.

                                        We experimented with using two separate, explicitly named symbols, but it required effort to discover what those exact values were, and removed the nicety of using simple if statements when the decision was truly boolean. Also considered was using a symbol or false (and symbol or nil), leveraging truthiness for conditionals in truly binary situations, but it still lacked the communicative power of a keyword argument, for similar reasons to this article. In some ways it was worse, because depending on the frame of mind of the person writing code, the symbol could be phrased to represent something in an “off” state, which makes the false or nil version a weird code version of a double-negative.

                                        Massively expanding use of keyword arguments felt more verbose at first, and sometimes needlessly so, but became second nature over time.

                                        I now count them among a list of unconditionally good ideas in language design, along with first-class functions and pattern matching.

                                        1. 2

                                          The main drawback is the lack of a lightweight syntax in most languages.

                                          1. 2

                                            I won’t argue that booleans are better than enums. However, the reason we keep gravitating to booleans is that they are lightweight, even though they end up costing more later. Btw: that cost isn’t just to the author, it’s to everyone else who has to remember which enum we’re using, where it’s defined, etc. If it’s a sufficient pervasive enum, that’s ok, but there’s a gap where the enum doesn’t represent a concept with a clearly defined home.

                                            In some cases, you almost want to define the enum inline. Here’s a hypothetical syntax. It’s probably not actually good, but I want that level of simplicity.

                                            function fetch(int accountId, ::IncludeDisabled, ::History, {::Shallow, ::Full, ::IncludeRelations}) {
                                            }
                                            
                                            // includes details, doesn't include history or disabled accounts
                                            fetch(0)
                                            
                                            // includes historical records, excludes disabled accounts, only ids/links
                                            fetch(0, ::History, ::Shallow)
                                            // ditto, but includes normal data
                                            fetch(0, ::Full)
                                            // ditto, but includes some kind of related data
                                            fetch(0, ::IncludeRelations)
                                            
                                            1. 2

                                              ‘Inline enums’ are a great idea :) Their generalisation - anonymous sum types - is often called a ‘variant’ if you’re interested in reading up about it.

                                            2. 2

                                              Like using enums in most languages, unfortunately I think that Python’s enums are….. not great. Instead, I’ve opted for making dataclasses where each “enum variant” implements methods based on needs.

                                              It’s super not great coming from a functional programming background, but lack of pattern matching, destructuring or things like that just make enum usage a bit miserable in Python IMO.

                                              1. 2

                                                Yep. In D, the standard library helps:

                                                import std.typecons;
                                                void api(Flag!"useSpaces" useSpaces);
                                                void client() {
                                                    api(Yes.useSpaces);
                                                }
                                                
                                                1. 1

                                                  Not all situations are the same, sometimes bool is all you need and it’s better to keep things simple.

                                                  One example of a bool being swapped out for a different value is “Expired”; you can represent that as a timestamp of when, which then also includes the binary state of expired or not.

                                                  Overall though, sweeping generalizations around some facet of programming aesthetics are incredibly counterproductive and the time is better spent understanding the trade offs of the different tools a language gives you.

                                                  1. 3

                                                    Meh, normally, I’d use

                                                    enum CacheState { Fresh, Expired }
                                                    

                                                    Yeah, it’s a bit more complicated to branch on it, but it’s more robust for all the reasons given in the OP.

                                                    1. 1

                                                      So, part of this is I code day to day in a language that doesn’t support enums particularly well (golang), but part of it also is to replace a boolean you just created another type and two values of it, which you need to be able to parse and marshal, and generally keep track of and refactor later.

                                                      IMHO that’s better just left as a bool, and typically you just structure it such that the default value (typically false) is also the intended default for the intent of the field.

                                                      i.e. TLSSkipVerify bool where the default is to verify, set true to skip verification.

                                                    2. 2

                                                      Overall though, sweeping generalizations around some facet of programming aesthetics are incredibly counterproductive and the time is better spent understanding the trade offs of the different tools a language gives you.

                                                      This is recognized in the second paragraph (and the first one is just 3 words):

                                                      With any blanket statements like this, there are always exceptions. Though in general, I believe the use of enums is often a better choice compared to boolean, unless you really need to squeeze your data into one single physical bit.

                                                    3. 1

                                                      I started not using boolean params after reading about it in Clean Code and haven’t looked back. http://www.informit.com/articles/article.aspx?p=1392524

                                                      1. 1

                                                        I find it even more tricky with strings, when you have 2 or more of them in function params… as you can’t just use enums then… unless you have named function args, one possible solution can be to use inline structs if your language allows them; but this can be often unwieldy unfortunately.

                                                        I recently stumbled upon this in Go in the area of the Kubernetes API, where you often have to pass a Namespace+Name pair around. The official library has a NamespacedName struct to help, but using it makes the invocations awfully long and noisy, which kinda discourages the use: types.NamespacedName{Namespace: "foo", Name: "bar"}. If I were designing the API, I’d shorten it, probably at least to: types.NN{Namespace: "foo", Name: "bar"}, such that it’d more closely resemble named params. Hm; this leads me to an idea: maybe I’ll just introduce an alias in my codebase… #thankslobsters! :)