I have lost count of the times I have read a call with five positional arguments and had to count commas backwards to work out which one is the timeout and which is the retry count. The fix has been in the catalogue since 1999: introduce a parameter object. Bundle the fields that keep travelling together and give the bundle a name and a type. The call sites stop reading like SQL with the column names removed.

The trigger I use is the third caller. Two call sites passing the same group can be coincidence. A third one tells you the concept has been in the domain all along without anyone naming it. A third caller passing the same group is the point at which the group wants a name. Waiting for a lint warning misses this, because the warning fires on width and the problem is that the group has no name.

The cap is editorial and we set it the same way, max-params at 3. But the rule catches the symptom rather than the cause. A constructor taking four loose strings passes nothing and fails everything, while a four-argument call where three of them are genuinely independent is fine. The rule makes you look.

A parameter object does not automatically improve things. Once a function takes a parameter object, every caller has to build one, and if that object is a bag with no methods you have moved the comma-counting to the construction site. The object should be the place the validation lives, so an invalid one cannot be built. If it is only a struct, you have added a hop.

Naming the object usually turns up a domain word that was not in the codebase before. That part still surprises me. The extraction is mechanical, but the name is a decision. You were avoiding that decision every time you typed that fifth argument.

I run the same reasoning on agents now. An agent handed a five-argument signature will keep adding positional arguments, because the shape it sees is the shape it copies. It cannot count commas backwards either.

The longer version is at https://prickles.org/tenet/parameter-object/S3

      • atzanteol@sh.itjust.works
        link
        fedilink
        English
        arrow-up
        0
        arrow-down
        1
        ·
        3 days ago

        if you forget to assign a value to one of them, since the object constructor will likely assign an unknown default value.

        This bit - in a language that cares about types that should either not be possible or difficult to do by accident.

        • Onno (VK6FLAB)@lemmy.radio
          link
          fedilink
          arrow-up
          0
          ·
          3 days ago

          That depends entirely on how the object is constructed. Plenty of implementations use object.new() and you’re expected to fill in the blanks.

          Besides, even if you have a constructor that takes parameters, it’s essentially the same as calling a function. Get it wrong and stuff breaks.

          • atzanteol@sh.itjust.works
            link
            fedilink
            English
            arrow-up
            0
            arrow-down
            1
            ·
            3 days ago

            That depends entirely on how the object is constructed. Plenty of implementations use object.new() and you’re expected to fill in the blanks.

            That’s because some languages simply do not give a shit about data types. They’re just like “your opinion man” and can even vary at runtime which creates lots of exciting errors as you’re pointing out. Enjoy writing lots of type-checks.

            In a language that does care about types you can control that and don’t worry about it.

            Besides, even if you have a constructor that takes parameters, it’s essentially the same as calling a function. Get it wrong and stuff breaks.

            data class UserRequest { 
               val id: Int,
               val name: String
            }
            

            No guesswork. All values must be provided and none can be ‘null’. Well designed languages help you avoid such problems. Poorly designed languages facilitate it.

            • Onno (VK6FLAB)@lemmy.radio
              link
              fedilink
              arrow-up
              0
              ·
              3 days ago

              How is your code sample for an object any different from a function definition?

              In both cases you need to read the documentation, consult the source, or have an IDE that does.

              • atzanteol@sh.itjust.works
                link
                fedilink
                English
                arrow-up
                0
                arrow-down
                1
                ·
                3 days ago

                I thought it was clear that I was responding to “if you forget to assign a value to one of them, since the object constructor will likely assign an unknown default value.”

                I’m not defending the article.