zet

Don’t Be a GetOpt Diseased Zombie, Code Better

I fucking hate what GetOpt has done to the otherwise elegant UNIX command line interface. Just look at gpg for an example of how fucking horrible that dash hell has become. Why? Why did we allow it to get that bad. We had a very functional (UNIX filters) approach from the very beginning and monoliths like gpg violated it in the worst possible way.

We have an opportunity to rethink human-computer interactions from the command line, and guess what, applying the same tenets of good programming apply. This is why Bonzai effectively forces developers to think more about how their tools are going to be used. To think in ways that are consistent with their thinking when they are actually writing code.

For example, consider the hypothetical Go function:

func Foo(whatever ...any) {
   // ...
}

Now imagine passing something like this to it:

Foo("--dry-run", "-n", "mynamespace", "--output=json")

Now imagine the code to process that? It takes a full fucking language scanner and parser just to process what should have been a simple set of parameters. This is why Cobra and even Flags code is so fucking ugly. All because getopt fucked us all. Make no mistake, getopt is a full language all by itself, some monstrous dark grammar that was forced onto a very practical world of simpler interfaces.

KEEP YOUR FUCKING LANGUAGE OFF MY COMMAND LINE!

I get very angry when I think about it. No sane programmer would ever allow one of their peers to create a function like Foo. “But I want it to be flexible” would get you laughed out of the room (or fired). It’s fucking idiotic to make it that broad and non-specific. IT’S THE ANTITHESIS OF GOOD CODING! But, somehow everyone thinks that this insanity is somehow okay when it happens on a command line for a single command. Because the standard for communication and interactions with HUMANS on the command line has been so abused by “not only humans use the command line” that we get toxic monsters like gpg.

The command line was created for humans. Somehow we really forgot this. But, I never forgot, and I’m going to champion command line interactions for humans, without having to give up anything until the day that I die. And I’m not the only one, TJ Holowaychuk agrees (in his own way).

Here’s that exact same thing done more sanely:

var dryrun bool
var namespace,output string

func Foo() {
   // ...
}

// and we would run it

dryrun = true              // step 1
namespace = "mynamespace"  // step 2
output = "json"            // step 3
Foo()                      // step 4

Not very functional, but neither was the other monstrosity. Another way would be to set all of those up front and then pass them into the Foo function. The point is, there are a number of specific, clear steps that change state. This doesn’t jam them all into a single fucking line without knowing which has priority, what if something is repeated, and all that. THAT is how we should think of humans interacting with the shell.

After all, every line on the shell is a line of an ongoing program. So why have a double standard for interactive users who are writing code as they go? Interacting with the shell is coding.

“But what if they want --private?”

Think like a good coder. What would a coder do? Have them set a variable that is observed by the function, or pass a consistent parameter to the function itself.

Technically, we have this with environment variables. In fact, one of the most brilliant decisions ever made from the Go team was GOOS and GOARCH which are provided before calling go build. You can’t even set them from the command line with dashes at all. You must use the stateful environment variables. That is good design. You can change the state for your one execution, or you can keep it on for all your executions. We need more of that, not less.

Another positive move in the right direction is kubectl contexts. You can save different states, which are effectively combinations of arguments that would otherwise be on the command line. You just change your state and all your simple command line interactions use that context. Humans are stateful beings. It’s how we communicate. And it also doesn’t stop our programs from using the same interfaces. Anything a human can do on the command line, so can a program.

Besides, why not look at the command line as a language? By banishing getopt and those horrible dashes, we also promote the creation of our own, human-friendly domain-specific languages. Remember, getopt is a language, a fucking horrible language. People freak out by allowing several arguments on the parameter list to be interpreted differently, but it is rather easy, and human friendly, because we do it all the time. Don’t believe me? How about believing the creator of Express, Stylus, Jade/PUG, and Apex, TJ himself. Look at his Apex log tool and how he created a domain-specific language rather than a horrible getopt monstrosity. TJ is obsessed with good design. His code base is one of the most beautiful things I’ve ever seen. He’s a creative, a photographer, and a crafter of very fine code. He gives a shit about user interactions. He gets it. He understands, and he makes things that don’t have complicated interfaces with dashes.

So have the courage to give up your deformed babies, pot-marked with pustule dashes all over their faces. Think differently about how you would do that entire user interaction. How would you code it? I promise you will find a better way. You’ll find it if you remember to think like a programmer about the command line, and not some exceptional thing that requires every fucking thing be done on a single line (without pipes).

#rant #bonzai #cleancode #coding #programming