Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I've been programming for over 9 years, and according to the advice of my first programming teacher, I always keep my main() function extremely short.

At first I had no idea why. I just obeyed without understanding, much to the delight of my professors.

After gaining experience, I realized that if I designed my code correctly, having a short main() function just sortof happened. Writing modularized code and following the single responsibility principle allowed my code to be designed in "bunches", and main() served as nothing more than a catalyst to get the program running.

Fast forward to a few weeks ago, I was looking at Python's souce code, and I found the main() function:

/* Minimal main program -- everything is loaded from the library */

...

int
main(int argc, char **argv)
{
    ...
    return Py_Main(argc, argv);
}

Yay python. Short main() function == Good code.

Programming teachers were right.

Wanting to look deeper, I took a look at Py_Main. In its entirety, it is defined as follows:

/* Main program */

int
Py_Main(int argc, char **argv)
{
    int c;
    int sts;
    char *command = NULL;
    char *filename = NULL;
    char *module = NULL;
    FILE *fp = stdin;
    char *p;
    int unbuffered = 0;
    int skipfirstline = 0;
    int stdin_is_interactive = 0;
    int help = 0;
    int version = 0;
    int saw_unbuffered_flag = 0;
    PyCompilerFlags cf;

    cf.cf_flags = 0;

    orig_argc = argc;           /* For Py_GetArgcArgv() */
    orig_argv = argv;

#ifdef RISCOS
    Py_RISCOSWimpFlag = 0;
#endif

    PySys_ResetWarnOptions();

    while ((c = _PyOS_GetOpt(argc, argv, PROGRAM_OPTS)) != EOF) {
        if (c == 'c') {
            /* -c is the last option; following arguments
               that look like options are left for the
               command to interpret. */
            command = (char *)malloc(strlen(_PyOS_optarg) + 2);
            if (command == NULL)
                Py_FatalError(
                   "not enough memory to copy -c argument");
            strcpy(command, _PyOS_optarg);
            strcat(command, "\n");
            break;
        }

        if (c == 'm') {
            /* -m is the last option; following arguments
               that look like options are left for the
               module to interpret. */
            module = (char *)malloc(strlen(_PyOS_optarg) + 2);
            if (module == NULL)
                Py_FatalError(
                   "not enough memory to copy -m argument");
            strcpy(module, _PyOS_optarg);
            break;
        }

        switch (c) {
        case 'b':
            Py_BytesWarningFlag++;
            break;

        case 'd':
            Py_DebugFlag++;
            break;

        case '3':
            Py_Py3kWarningFlag++;
            if (!Py_DivisionWarningFlag)
                Py_DivisionWarningFlag = 1;
            break;

        case 'Q':
            if (strcmp(_PyOS_optarg, "old") == 0) {
                Py_DivisionWarningFlag = 0;
                break;
            }
            if (strcmp(_PyOS_optarg, "warn") == 0) {
                Py_DivisionWarningFlag = 1;
                break;
            }
            if (strcmp(_PyOS_optarg, "warnall") == 0) {
                Py_DivisionWarningFlag = 2;
                break;
            }
            if (strcmp(_PyOS_optarg, "new") == 0) {
                /* This only affects __main__ */
                cf.cf_flags |= CO_FUTURE_DIVISION;
                /* And this tells the eval loop to treat
                   BINARY_DIVIDE as BINARY_TRUE_DIVIDE */
                _Py_QnewFlag = 1;
                break;
            }
            fprintf(stderr,
                "-Q option should be `-Qold', "
                "`-Qwarn', `-Qwarnall', or `-Qnew' only\n");
            return usage(2, argv[0]);
            /* NOTREACHED */

        case 'i':
            Py_InspectFlag++;
            Py_InteractiveFlag++;
            break;

        /* case 'J': reserved for Jython */

        case 'O':
            Py_OptimizeFlag++;
            break;

        case 'B':
            Py_DontWriteBytecodeFlag++;
            break;

        case 's':
            Py_NoUserSiteDirectory++;
            break;

        case 'S':
            Py_NoSiteFlag++;
            break;

        case 'E':
            Py_IgnoreEnvironmentFlag++;
            break;

        case 't':
            Py_TabcheckFlag++;
            break;

        case 'u':
            unbuffered++;
            saw_unbuffered_flag = 1;
            break;

        case 'v':
            Py_VerboseFlag++;
            break;

#ifdef RISCOS
        case 'w':
            Py_RISCOSWimpFlag = 1;
            break;
#endif

        case 'x':
            skipfirstline = 1;
            break;

        /* case 'X': reserved for implementation-specific arguments */

        case 'U':
            Py_UnicodeFlag++;
            break;
        case 'h':
        case '?':
            help++;
            break;
        case 'V':
            version++;
            break;

        case 'W':
            PySys_AddWarnOption(_PyOS_optarg);
            break;

        /* This space reserved for other options */

        default:
            return usage(2, argv[0]);
            /*NOTREACHED*/

        }
    }

    if (help)
        return usage(0, argv[0]);

    if (version) {
        fprintf(stderr, "Python %s\n", PY_VERSION);
        return 0;
    }

    if (Py_Py3kWarningFlag && !Py_TabcheckFlag)
        /* -3 implies -t (but not -tt) */
        Py_TabcheckFlag = 1;

    if (!Py_InspectFlag &&
        (p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
        Py_InspectFlag = 1;
    if (!saw_unbuffered_flag &&
        (p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0')
        unbuffered = 1;

    if (!Py_NoUserSiteDirectory &&
        (p = Py_GETENV("PYTHONNOUSERSITE")) && *p != '\0')
        Py_NoUserSiteDirectory = 1;

    if ((p = Py_GETENV("PYTHONWARNINGS")) && *p != '\0') {
        char *buf, *warning;

        buf = (char *)malloc(strlen(p) + 1);
        if (buf == NULL)
            Py_FatalError(
               "not enough memory to copy PYTHONWARNINGS");
        strcpy(buf, p);
        for (warning = strtok(buf, ",");
             warning != NULL;
             warning = strtok(NULL, ","))
            PySys_AddWarnOption(warning);
        free(buf);
    }

    if (command == NULL && module == NULL && _PyOS_optind < argc &&
        strcmp(argv[_PyOS_optind], "-") != 0)
    {
#ifdef __VMS
        filename = decc$translate_vms(argv[_PyOS_optind]);
        if (filename == (char *)0 || filename == (char *)-1)
            filename = argv[_PyOS_optind];

#else
        filename = argv[_PyOS_optind];
#endif
    }

    stdin_is_interactive = Py_FdIsInteractive(stdin, (char *)0);

    if (unbuffered) {
#if defined(MS_WINDOWS) || defined(__CYGWIN__)
        _setmode(fileno(stdin), O_BINARY);
        _setmode(fileno(stdout), O_BINARY);
#endif
#ifdef HAVE_SETVBUF
        setvbuf(stdin,  (char *)NULL, _IONBF, BUFSIZ);
        setvbuf(stdout, (char *)NULL, _IONBF, BUFSIZ);
        setvbuf(stderr, (char *)NULL, _IONBF, BUFSIZ);
#else /* !HAVE_SETVBUF */
        setbuf(stdin,  (char *)NULL);
        setbuf(stdout, (char *)NULL);
        setbuf(stderr, (char *)NULL);
#endif /* !HAVE_SETVBUF */
    }
    else if (Py_InteractiveFlag) {
#ifdef MS_WINDOWS
        /* Doesn't have to have line-buffered -- use unbuffered */
        /* Any set[v]buf(stdin, ...) screws up Tkinter :-( */
        setvbuf(stdout, (char *)NULL, _IONBF, BUFSIZ);
#else /* !MS_WINDOWS */
#ifdef HAVE_SETVBUF
        setvbuf(stdin,  (char *)NULL, _IOLBF, BUFSIZ);
        setvbuf(stdout, (char *)NULL, _IOLBF, BUFSIZ);
#endif /* HAVE_SETVBUF */
#endif /* !MS_WINDOWS */
        /* Leave stderr alone - it should be unbuffered anyway. */
    }
#ifdef __VMS
    else {
        setvbuf (stdout, (char *)NULL, _IOLBF, BUFSIZ);
    }
#endif /* __VMS */

#ifdef __APPLE__
    /* On MacOS X, when the Python interpreter is embedded in an
       application bundle, it gets executed by a bootstrapping script
       that does os.execve() with an argv[0] that's different from the
       actual Python executable. This is needed to keep the Finder happy,
       or rather, to work around Apple's overly strict requirements of
       the process name. However, we still need a usable sys.executable,
       so the actual executable path is passed in an environment variable.
       See Lib/plat-mac/bundlebuiler.py for details about the bootstrap
       script. */
    if ((p = Py_GETENV("PYTHONEXECUTABLE")) && *p != '\0')
        Py_SetProgramName(p);
    else
        Py_SetProgramName(argv[0]);
#else
    Py_SetProgramName(argv[0]);
#endif
    Py_Initialize();

    if (Py_VerboseFlag ||
        (command == NULL && filename == NULL && module == NULL && stdin_is_interactive)) {
        fprintf(stderr, "Python %s on %s\n",
            Py_GetVersion(), Py_GetPlatform());
        if (!Py_NoSiteFlag)
            fprintf(stderr, "%s\n", COPYRIGHT);
    }

    if (command != NULL) {
        /* Backup _PyOS_optind and force sys.argv[0] = '-c' */
        _PyOS_optind--;
        argv[_PyOS_optind] = "-c";
    }

    if (module != NULL) {
        /* Backup _PyOS_optind and force sys.argv[0] = '-c'
           so that PySys_SetArgv correctly sets sys.path[0] to ''
           rather than looking for a file called "-m". See
           tracker issue #8202 for details. */
        _PyOS_optind--;
        argv[_PyOS_optind] = "-c";
    }

    PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind);

    if ((Py_InspectFlag || (command == NULL && filename == NULL && module == NULL)) &&
        isatty(fileno(stdin))) {
        PyObject *v;
        v = PyImport_ImportModule("readline");
        if (v == NULL)
            PyErr_Clear();
        else
            Py_DECREF(v);
    }

    if (command) {
        sts = PyRun_SimpleStringFlags(command, &cf) != 0;
        free(command);
    } else if (module) {
        sts = RunModule(module, 1);
        free(module);
    }
    else {

        if (filename == NULL && stdin_is_interactive) {
            Py_InspectFlag = 0; /* do exit on SystemExit */
            RunStartupFile(&cf);
        }
        /* XXX */

        sts = -1;               /* keep track of whether we've already run __main__ */

        if (filename != NULL) {
            sts = RunMainFromImporter(filename);
        }

        if (sts==-1 && filename!=NULL) {
            if ((fp = fopen(filename, "r")) == NULL) {
                fprintf(stderr, "%s: can't open file '%s': [Errno %d] %s\n",
                    argv[0], filename, errno, strerror(errno));

                return 2;
            }
            else if (skipfirstline) {
                int ch;
                /* Push back first newline so line numbers
                   remain the same */
                while ((ch = getc(fp)) != EOF) {
                    if (ch == '\n') {
                        (void)ungetc(ch, fp);
                        break;
                    }
                }
            }
            {
                /* XXX: does this work on Win/Win64? (see posix_fstat) */
                struct stat sb;
                if (fstat(fileno(fp), &sb) == 0 &&
                    S_ISDIR(sb.st_mode)) {
                    fprintf(stderr, "%s: '%s' is a directory, cannot continue\n", argv[0], filename);
                    fclose(fp);
                    return 1;
                }
            }
        }

        if (sts==-1) {
            /* call pending calls like signal handlers (SIGINT) */
            if (Py_MakePendingCalls() == -1) {
                PyErr_Print();
                sts = 1;
            } else {
                sts = PyRun_AnyFileExFlags(
                    fp,
                    filename == NULL ? "<stdin>" : filename,
                    filename != NULL, &cf) != 0;
            }
        }

    }

    /* Check this environment variable at the end, to give programs the
     * opportunity to set it from Python.
     */
    if (!Py_InspectFlag &&
        (p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
    {
        Py_InspectFlag = 1;
    }

    if (Py_InspectFlag && stdin_is_interactive &&
        (filename != NULL || command != NULL || module != NULL)) {
        Py_InspectFlag = 0;
        /* XXX */
        sts = PyRun_AnyFileFlags(stdin, "<stdin>", &cf) != 0;
    }

    Py_Finalize();
#ifdef RISCOS
    if (Py_RISCOSWimpFlag)
        fprintf(stderr, "\x0cq\x0c"); /* make frontend quit */
#endif

#ifdef __INSURE__
    /* Insure++ is a memory analysis tool that aids in discovering
     * memory leaks and other memory problems.  On Python exit, the
     * interned string dictionary is flagged as being in use at exit
     * (which it is).  Under normal circumstances, this is fine because
     * the memory will be automatically reclaimed by the system.  Under
     * memory debugging, it's a huge source of useless noise, so we
     * trade off slower shutdown for less distraction in the memory
     * reports.  -baw
     */
    _Py_ReleaseInternedStrings();
#endif /* __INSURE__ */

    return sts;
}

Good God Almighty...it is big enough to sink the Titanic.

It seems as though Python did the "Intro to Programming 101" trick and just moved all of main()'s code to a different function called it something very similar to "main".

Here's my question: Is this code terribly written, or are there other reasons to have a short main function?

As it stands right now, I see absolutely no difference between doing this and just moving the code in Py_Main() back into main(). Am I wrong in thinking this?

share|improve this question

migrated from stackoverflow.com Jun 20 '11 at 22:00

This question came from our site for professional and enthusiast programmers.

4  
wouldn't that be better for codereviews.stackexchange.com? –  equality Jun 20 '11 at 21:24
33  
@Luzhin, no. I'm not asking anyone to review Python's source code. This is programming question. –  Stargazer712 Jun 20 '11 at 21:25
3  
TBH, half the code is options processing, and anytime your program supports lot's of options, and you write a custom processor, this is what you end up doing... –  Nim Jun 20 '11 at 21:30
7  
@Star No, Programmers.SE is also for best practices, coding styles, etc. Actually, that's what I visit the site for. –  muntoo Jun 20 '11 at 21:34
4  
@Nim, I understand that is what it is doing, but there is no reason not to write it as options = ParseOptionFlags(argc,argv) where options is a struct that contains the variables Py_BytesWarningFlag, Py_DebugFlag, etc... –  Stargazer712 Jun 20 '11 at 21:35

11 Answers 11

up vote 130 down vote accepted

You cannot export main from a library, but you can export Py_Main, and then anyone using that library can "call" Python many times with different arguments in the same program. At that point, python becomes just another consumer of the library, little more than a wrapper for the library function; it calls Py_Main just like everyone else.

share|improve this answer
1  
There's a good answer. –  Stargazer712 Jun 20 '11 at 21:37
26  
I suppose it might be more accurate to say you can't import it, @Shoosh. The C++ standard forbids calling it from your own code. Besides, its linkage is implementation-defined. Also, returning from main effectively calls exit, which you usually don't want a library to do. –  Rob Kennedy Jun 20 '11 at 21:53
3  
@Coder, see C++03 §3.6.1/5: "A return statement in main has the effect of leaving the main function … and calling exit with the return value as the argument." Also see §18.3/8, which explains that "objects with static storage duration are destroyed" and "all open C streams … are flushed" when you call exit. C99 has similar language. –  Rob Kennedy Jun 21 '11 at 13:58
1  
@Coder, whether exit leaves main is irrelevant. We're not discussing the behavior of exit. We're discussing the behavior of main. And the behavior of main includes the behavior of exit, whatever that may be. That's what makes it undesirable to import and call main (if doing such a thing is even possible or allowed). –  Rob Kennedy Jun 21 '11 at 15:22
2  
@Coder, if returning from main does not have the effect of calling exit on your compiler, then your compiler doesn't follow the standard. That the standard dictates such behavior for main proves that there is something special about it. The special thing about main is that returning from it has the effect of calling exit. (How it does that is up to compiler writers. The compiler could simply insert code in the function epilogue that destroys static objects, calls atexit routines, flushes files, and terminates the program — which, again, isn't something you want in a library.) –  Rob Kennedy Jun 21 '11 at 15:53

It's not that main shouldn't be long so much as you should avoid any function being too long. main is just a special case of function. Longer functions get very hard to grok, decrease maintainability, and are generally tougher to work with. By keeping functions (and main) shorter you generally improve the quality of your code.

In your example there is no benefit at all to moving the code out of main.

share|improve this answer
9  
The golden word may be "reuse". A long main isn't very reusable. –  S.Lott Jun 20 '11 at 22:11
1  
@S - That's one golden word. Another is OMG!!! ADHD JUST KICKED IN!!!! or in layman terms: legibility. –  Crazy Eddie Jun 20 '11 at 22:18
3  
main() also has some restrictions that other functions do not have. –  Loki Astari Jun 20 '11 at 23:33
1  
Also main() has no real meaning. Your code should all mean something to another programmer. I use main to parse arguments and that's it--and I even delegate that if it's more than a few lines. –  Bill K Jun 21 '11 at 22:07
    
@Bill K: Good point, using main() only to parse arguments (and starting the rest of the program) also conforms to the single-responsibility principle. –  Giorgio Mar 17 '13 at 11:41

One reason to make main() short involves unit testing. main() is the one function that cannot be unit tested, so it makes sense to extract the majority of the behavior into another class that can be unit tested. This goes along with what you said

Writing modularized code and following the single responsibility principle allowed my code to be designed in "bunches", and main() served as nothing more than a catalyst to get the program running.

Note: I got the idea from here.

share|improve this answer
    
Another good one. Never thought of that aspect. –  Stargazer712 Jun 20 '11 at 22:46

It's rarely a good idea for main to be long; as with any function (or method) if it's long you're probably missing opportunities for refactoring.

In the specific case you mention above, main is short because all that complexity is factored out into Py_Main; if you want your code to behave like a python shell, you can just use that code without a lot of fiddling around. (It has to be factored like that because it doesn't work well if you put main in a library; odd things happen if you do.)

EDIT:
To clarify, main can't be in a static library because it has no explicit link to it and so won't be linked in correctly (unless you colocate it in an object file with something that is referred to, which is just horrible!) Shared libraries are usually treated as being similar (again, to prevent confusion) though on a lot of platforms an additional factor is that a shared library is just an executable without a bootstrap section (of which main is just the last and most visible part).

share|improve this answer
1  
In short, don't put main in a library. It either won't work or it will confuse you terribly. But delegating virtually all its work to a function that is in a lib, that's often sensible. –  Donal Fellows Jun 20 '11 at 21:53

Main should be short for the same reason that any function should be short. The human brain has a hard time keeping large amounts of unpartitioned data in memory at once. Break it up into logical chunks so that it is easy for other developers (as well as yourself!) to digest and reason about.

And yes, your example is terrible and hard to read, let alone maintain.

share|improve this answer
    
Yes I always suspected that the code itself was terrible (though the question dealt with the placement of the code, not the code itself). I'm afraid that my vision of Python has been inherently damaged as a result... –  Stargazer712 Jun 20 '11 at 22:58
1  
@stargazer: I don't know that the code itself is terrible, just that it is not organized well for human consumption. That said, there is plenty of "ugly" code out there that works well and performs great. Code beauty isn't everything, though we should always try our best to write the cleanest code possible. –  Ed S. Jun 20 '11 at 23:07
    
meh. To me, they are one and the same. Clean code tends to be more stable. –  Stargazer712 Jun 21 '11 at 2:46
    
The code is not terrible, mainly there are switch cases and the handling of multiple platforms. What exactly you find terrible? –  Francesco Jun 21 '11 at 5:27
    
@Francesco: Sorry, "Terrible" from a maintenance and readability perspective, not a functional one. –  Ed S. Sep 6 '11 at 21:06

Some people enjoy 50+ functions that do nothing else, but wrap a call to another function. I would rather prefer normal main function that does the main program logic. Well structured of course.

int main()
{
CheckInstanceCountAndRegister();
InitGlobals();
ProcessCmdParams();
DoInitialization();
ProgramMainLoopOrSomething();
DeInit();
ClearGlobals();
UnregisterInstance();
return 0; //ToMainCRTStartup which cleans heap, etc.
}

I don't see any reason why I should wrap anything of that inside a wrapper.

It's purely a personal taste.

share|improve this answer
    
Because it document's the code. You can write code this way without the need to (almost) ever write comments. And when you change the code, the documentation changes automagically :-). –  Oliver Weiler Jun 28 '11 at 9:49

Its best practice to keep ALL of your functions short, not just main. However "short" is subjective, it depends on the size of your program and the language you are using.

share|improve this answer

There's no requirement for main to be of any length whatsoever, other than coding standards. main is a function as any other, and as such it's complexity should be below 10 (or whatever your coding standards say). That's it, anything else is rather argumentative.

edit

main shouldn't be short. Or long. It should include the functionality it is required to perform based on your design, and adhere to the coding standards.

As to the specific code in your question - yes, it's ugly.

As to your second question - yes, you are wrong. Moving all that code back into main doesn't allow you using it modulary as a library by linking Py_Main from outside.

Now am I clear?

share|improve this answer
    
I didn't ask whether it can be long. I asked why shouldn't it be long. –  Stargazer712 Jun 20 '11 at 21:31
    
“Complexity below 10”? Is there a measurement unit for that? –  Donal Fellows Jun 20 '11 at 21:32
    
@Stargazer712 Function length is usually regulated by the coding standards as well. It's a readability issue (and complexity, usually long functions are branched so that the complexity is way above 20), and as I said - main is no different than any other function in this regard. –  littleadv Jun 20 '11 at 21:33
    
@Donal - yes, click on the link. –  littleadv Jun 20 '11 at 21:33
    
I'm going to have to downvote this one bud. You're completely missing the intent of the question. –  Stargazer712 Jun 20 '11 at 21:38

Here's a new pragmatic reason too keep main short from the GCC 4.6.1 Changelog:

On most targets with named section support, functions used only at startup (static constructors and main), functions used only at exit and functions detected to be cold are placed into separate text segment subsections. This extends the -freorder-functions feature and is controlled by the same switch. The goal is to improve the startup time of large C++ programs.

Highlighting added by me.

share|improve this answer

Do not assume that just because a bit of software is good all of the code behind that software is good. Good software and good code are not the same thing and even where good software is backed by good code, it is inevitable that in a large project there will be places where standards slip.

It is good practice to have a shortmainfunction, but that is really just a special case of the general rule that it is better to have short functions. Short functions are easier to understand and easier to debug as well as being better at sticking to the kind of 'single purpose' design that makes programs more expressive. main is, perhaps, a more important place to stick to the rule since anyone who wants to understand the program must understand main while more obscure corners of the codebase may be visited less often.

But, the Python codebase isn't pushing the code out toPy_Mainin order to game this rule but because you cannot exportmainfrom a library nor call it as a function.

share|improve this answer

There are several technical answers above, lets leave that aside.

A main should be short because it should be a bootstrap. The main should instantiate a small number of objects, often one, which do the work. Like anywhere else, those objects should be well designed, cohesive, loosely coupled, encapsulated, ...

While there might be technical reasons to have a one-line main call another monster method, in principle you are correct. From a software engineering perspective, nothing has been gained. If the choice is between a one line main calling a monster method, and main itself being a monster method, the latter is fractionally less bad.

share|improve this answer
    
You're assuming that "C++ code should use objects and only objects". That's not true, C++ is a multiparadigm language, and doesn't force everything into an OO mold like some other languages. –  Ben Voigt Jun 21 '11 at 4:48

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.