This article is part number 5 of the CLI design series.


As the Wikipedia puts it in its Command-line interface page:

A command-line option or simply option (also known as a flag or switch) modifies the operation of a command; the effect is determined by the command’s program.

Yet, many developers abuse flags to do many unrelated things and therefore end up providing a non-standard and confusing user interface. Let’s take a look at some specific use cases, both bad and good.

Bad: Requiring a flag

As mentioned above, a synonym for flag is option. As the definition of the word goes, an option is a thing that may be chosen, and therefore cannot be required. In other words: forcing the user to provide one or more options is wrong. For example, consider this interface definition:

do_backup --mode=incremental|full

In this case, we are asking the user to always provide a --mode flag to select the behavior of the backup utility. Apparently, providing this information is required because the developer did not offer a sane default (maybe because it doesn’t make sense to have one). Because the user is forced to specify a backup mode, the interface could better be exposed as:

do_backup incremental|full

The flag did not provide any value, and this new syntax is equally usable.

Alternatively, if --mode had had a default of incremental, the flag would have become optional and therefore the original interface with an optional flag would have been good.

Bad: Using flags as named arguments

As an extension to the previous use case, some interfaces choose to make use of flags as named arguments. Named arguments are not necessarily a bad thing: they allow gathering various data in an unspecified order and make command invocations self-explanatory. However, abusing flags to implement named arguments is the wrong thing to do unless all of the arguments are optional. Consider the following interface definition:

add_user --user=name --group=name [--shell=path] [--uid=id]

Note that both --user and group are required, and as mentioned above that’s not the meaning a flag is supposed to convey. However, it’s understandable that the developer chose to expose this interface, because it shows some consistency with the rest of the possible arguments (--shell and --uid).

Better ways to implement this interface would be:

add_user [--shell=path] [--uid=id] user=name group=name
add_user user=name group=name [shell=path] [uid=id]
add_user [--shell=path] [--uid=id] user_name group_name

As you can see, both the user and the group, which are required, are now either positional arguments or named arguments not disguised as flags. The other data can be collected either via flags or via other named arguments.

Bad: Using flags to select subcommands

Very frequently, CLI tools act as “dispatchers” for various different actions. Consider, for example, Git (or CVS, or Subversion, or even ifconfig). The git tool implements a consistent interface to launch actions such as adding a file to the index, checking in a change or pulling revisions from a remote server. The operations these actions perform are not related other than for the fact that they are part of Git and, in fact, each of these actions is implemented as a separate binary. That’s all good.

However, it’s completely wrong to expose such different actions as flags. If you do so, you are forcing the user to have to specify a flag that is part of the subset of actions, and that is not an optional thing to do. Here goes an example of a tool that can be considered broken:

config [--verbose] [--override] --set key1=value1 [.. keyN=valueN]
config [--verbose] --unset key1 [.. keyN]

This is wrong. The first problem of this interface is that the user is required to specify either --set or --unset, and requiring a flag is something that is not correct semantically as we have already seen above. The second problem of this interface is that the choice of the given flag affects the syntax of the positional arguments: when --set is provided, the arguments are of the form key=value and when --unset is provided the arguments are of the form key.

These two problems hint at an alternative interface for this utility; an interface based on subcommands. The correct usage for this interface should be:

config [--verbose] set [--override] key1=value1 [.. keyN=valueN]
config [--verbose] unset key1 [.. keyN]

It is very important to notice one detail here: by converting the action selection to a subcommand, we are effectively breaking the structure of the command line in two parts: first, a block of options that apply to all subcommands, and a block of options that only apply to individual commands. Having to think about this separation will enlighten the way you implement the various options and how they are handled in code because, for example, --override should only be accepted in the case of set.

Good: Using flags to tune the behavior of the tool

If you want to conditionally expose part of the functionality of a tool, using flags is the right thing to do. Specific cases in which flags are right are: enabling debugging features, selecting whether the output should be colored or not, specifying the number of columns in the printed data, raising the verbosity level, etc.

Good: Using flags to provide optional data

Another case in which flags are perfectly valid are those in which you want to let the user provide additional data that is not required and that has reasonable defaults. Specific examples of this are: path to the configuration file to use, selection of the specific debugging level, etc.

That’s it!

Yup. There are no more cases in which flags are valid. You can only use flags to either fine-tune the behavior of the tool (or subcommand) and/or to tweak the value of built-in settings. Any other uses of flags are most likely invalid and you should reconsider the design of your tool.

Comments from the original Blogger-hosted post: