I’ve spent enough time dealing with Java programs to see enough ETL’s and other sorts of command-line based apps implemented in Java. And with these sort of programs came with the realization that more often than not developers opt for the “easy” way out and end up implementing command-line parsing by themselves.
This seem to be due to mainly one reason: a lot of developers don’t know of libraries available for parsing command-line arguments; and since Java offers a
main(String .. args) I guess it is very tempting to end up rolling your own code for this. And before you know it, there goes productivity as you end up handcrafting your own solution which then gets difficult to expand, has bugs and takes too much effort to maintain and distracts you from your business logic goals.
I went through a little learning exercise with my teams in MZ recently about introducing Apache Commons CLI in some of our apps so I thought I’d share some of these here. I have put together for this a Github repo here: https://github.com/liviutudor/using-apache-cli
The starting point is no CLI: if you look at the
master branch in this repo this is the standard scenario I have seen before: relying on positional parameters, so 1st parameter is filename, 2nd is encoding and so on: https://github.com/liviutudor/using-apache-cli/blob/master/src/main/java/liv/tudor/App.java#L24
As you can see the idea is simple: assume certain positions in the command line for certain parameters, read them and then validate them and finally proceed to using them. This code is very easy to write however you pay a price in maintainability: if you want to add a parameter or want to get rid of one or change it, this has a knock-on effect on all the indices of the other parameters. Similarly, if you want to make some parameters optional then this has an effect on indices and the code you write for it.
The other problem, also is that your main application code gets “decorated” with a lot of code for parsing and validating the arguments. This typically calls for a refactor and extracting all that code in a class/module of its own.
As such, first step in addressing an application like that for me is to extract this code in a class of its own which just stores the parameters — if you look at branch
config_no_cli that’s exactly what I’ve done: I’ve extracted the parameters processing in
CmdLineParams: https://github.com/liviutudor/using-apache-cli/blob/config_no_cli/src/main/java/liv/tudor/CmdLineParams.java As you can see this is a Java bean which offers more friendly getters for the parameters so it makes it more intuitive when using it in the main app and calling methods such as
getFileName (rather than the cryptic
However, while we have made the code more readable by extracting the command line arguments processing in its own class, this still relies on positional parameters. So for next step I’m finally introducing Apache Commons CLI — look at
using_cli branch: https://github.com/liviutudor/using-apache-cli/blob/using_cli/src/main/java/liv/tudor/CmdLineParams.java We are keeping the same pattern of externalizing the parameters parsing and validation but now add CLI to it.
This allows us to no longer rely on position of parameters, specify if some parameters are optional or not (and let the library bail out if required parameters are missing) and a few other niceties that CLI library provides. The nice thing also about extracting parameters parsing in a separate class is that now we can use any way (and library!) for specifying and parsing and validating command line parameters without affecting the main app and the way it interacts with the parameter parsing — changes for parameter processing are now isolated to the
Conclusion: when faced with refactoring apps which rely on positional parameters, take this 2 step approach:
- Extract parameters parsing into its own module/class
- then proceed to adding CLI into this class