1. 29
  1.  

  2. 4

    Avoid package level state

    I really actually like this way of declaring this goal. I’ve heard “no package level variables” before, but there are instances you might actually want to do this (regexp.MustCompile comes to mind).

    This being said, there are still instances where init/package level state are useful (in particular, systems where you can register drivers or plugins, similar to the database/sql package.

    1. 4

      I might be the odd man out, but I really dislike the database/sql interface. Unless I’m missing some hidden advantage of the init() method, I would think it better to have at least a package-level RegisterDriver() function if not a type DriverSet struct{} and a func (ds *DriverSet) RegisterDriver(). Thoughts?

      1. 3

        With vendoring init() and registering a driver by accident twice is a sign imho that the pattern is fraught, I would also rather have a method I have to call and make it explicit in my main()

      2. 2

        There are still instances where init/package level state are useful (in particular, systems where you can register drivers or plugins, similar to the database/sql package.)

        Like weberc2 I consider this an antipattern. It’s strictly better to have a component that takes a driver as a parameter to construction.

        1. 1

          I understand where you’re coming from. In an ideal world I would agree, but I still think this can be a useful pattern if you need things configurable at runtime.

          One of the examples I have of this is my IRC bot - everything is implemented as a plugin and they’re all registered by name. When a bot is started, it takes the list of all the plugins and initializes them based on that list… this would be a pain to do by hand. Similarly, with databases, if you’re supporting more than one, it’s hard to configure at runtime if there are multiple possibilities without a system like this (though you need to special case a number of things as well because of query incompatibilities).

      3. 4

        I have nothing against the 10 values of the post that seem sound to me. But, those values apply to other languages as well (Except maybe the concurrency part.) When I write Python, PHP or JS, I strive to handle errors explicitly, avoid nested blocks, keep things simple, etc.

        But I could say the same thing about the Zen of Python.

        1. 3

          Every time you indent you add another precondition to the programmer’s stack consuming one of the 7 ±2 slots in their short term memory. Avoid control flow that requires deep indentation. Rather than nesting deeply, keep the success path to the left using guard clauses.

          This doesn’t actually reduce the number of control paths the programmer has to consider, it is just an aesthetic adjustment that makes those control paths not reflected in the indentation of the program.

          Personally I’d rather have worse aesthetics, with a completely obvious program branching structure.

          1. 5

            To me it actually makes it simpler to read because it’s easier to make out the exit paths and their conditions.

            1. 1

              I’ve noticed that there is sort of a brain-behavior dichotomy where one group of programmers read programs like a story (I suspect that might be you), while other programmers read programs like a map or diagram (I suspect that might be me). I’m not proposing any value judgements here, but it does seem to be a “different strokes for different folks” kind of situation.

            2. 2

              This doesn’t actually reduce the number of control paths the programmer has to consider,

              I think it does. If you’re at indent level 0, your working state is empty. If you’re at indent level 1, your working state necessarily includes the predicate of the indent. That grows linearly.

              expr // I can consider this expr in isolation
              if cond1 {
                  expr // This expr's context includes cond1
              } else {
                  if cond2 {
                      expr // This expr's context includes cond1 and cond2
                  }
              }
              
              1. 1

                This is my point:

                if cond1: return err
                if cond2: return <value>
                expr // this expr includes both cond1 and cond2
                     // the indentation does not reflect control flow
                
                1. 1

                  I don’t agree. Once line 1 has been parsed and passed, I can throw cond1 out of my working context. Technically it is part of expr’s context but effectively it is not.

            3. 1

              Avoid package level state

              Seek to be explicit, reduce coupling, and spooky action at a distance by providing the dependencies a type needs as fields on that type rather than using package variables.

              I’ve actually started doing the opposite of this in a bunch of cases. I discovered that a lot of my classes/modules were basically being used as singletons so instead of binding functionality to types and requiring instantiation of the type I just keep everything as globals internal to the module and then expose a public interface that encapsulates that module-level-state. If something does require multiple instantiations I can always break it out into a struct/class later. A more detailed explanation of this concept is discussed in Brian Will’s Object-Oriented Programming is Good*. This form of module-oriented-programming doesn’t work in every case, but I have personally found it useful.