Design - Software Engineering

Purpose of func main()

The purpose of func main() is to translate commandline arguments to application startup state. Once the state is prepared a specific entry function is called. More often than not, logging verbosity is one such state that needs to be configured early on.
Use the builtin flag package to define, document and parse the arguments.

Example CountStars(galaxy string)

Imagine an application that counts the stars in a named galaxy. The main function should then make sure the flags are correct and forward them as arguments to the function doing the actual work. The name of the galaxy would be such a flag and perhaps a verbosity flag for debugging purposes.

func main() {
	galaxy := "milkyway"
		&galaxy, "galaxy",
		galaxy, "name of galaxy to count stars in",
	verbose := false
		&verbose, "verbose",
		verbose, "enables verbose logging",
	CountStars(os.Stdout, galaxy)

Now that you know what the main function should do, let us take a look at how it should be done, apart of the flag definition and argument passing.
First, the cyclomatic complexity of the main function is one. Ie. there is only one path through this program. There are however two exit points, apart from the obvious one flag.Parse() exits if the parsed flags do not match the predefined. The single pathway means that testing the main function is simple. Execute this application with valid flags and all lines are covered, leaving all other code for unittesting.
Also, if you execute the program you would note that second, the order of the flags are sorted in the same way as the help output.

Cyclomatic complexity should be one.
Flag order should match output.


Adhering to the “keep it simple principle” and only doing one thing in each function, works out nicely for the main function as well. One could argue that, if you moved everything inside main into a start function, the flag definitions would also be tested. Think about it for a minute and figure out what exactly you would be testing. If the flag package already makes sure it's functions work as expected the only thing left is testing which flags you have defined. They would need to be updated each time you add or remove a flag which is a sign of a poor test.
You could potentially refactor main and separate the flag definitions into smaller functions for readability but you still wouldn't need to write unittests for them.

Keep main simple, constrain it to only set global startup state before calling the one function that does the actual work.
This works great for services and simpler commands that only do one thing.