94

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?

Thomas Owens
  • 82,739
riwalk
  • 7,680
  • 4
    wouldn't that be better for http://codereviews.stackexchange.com? – foobar Jun 20 '11 at 21:24
  • 42
    @Luzhin, no. I'm not asking anyone to review Python's source code. This is programming question. – riwalk Jun 20 '11 at 21:25
  • 5
    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. – Mateen Ulhaq Jun 20 '11 at 21:34
  • 5
    @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... – riwalk Jun 20 '11 at 21:35
  • 1
    I wonder what Guido has to say about it. – JAB Jun 20 '11 at 21:40
  • @JAB, I would be curious what he would say. It seems as though it could be written in less of a "top to bottom" fashion. – riwalk Jun 20 '11 at 21:51
  • @Stargazer712, yes you could, but then you've just moved the hairy bits a little bit further along, better to replace with a cleaner options parsing code, but I guess they don't want extra dependencies... – Nim Jun 20 '11 at 22:08
  • @Nim, yes, you could look at it as though you're just, "kicking the can further down the road", but on the other hand you could notice that by doing that, you are doing a quarantine on some ugly code. If you go back and clean it later, great. If not, at least its ugliness is not bleeding into other sections of your code. In addition, if you do this with all aspects of the code, you get an explicit ordering of events (ParseOptionFlags(argc, argv); ExecuteCode(); ..., etc). Its very effective in making your code more readable and maintainable. – riwalk Jun 20 '11 at 22:13
  • 2
    +2 I upvoted this on both SO and Programmers.SE! – Mateen Ulhaq Jun 21 '11 at 01:12

11 Answers11

137

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.

Rob Kennedy
  • 1,014
  • You cannot export main -- why?

    –  Jun 20 '11 at 21:45
  • 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
  • @Rob, no it does not. If it calls exit, you are doing it WRONG. Exit doesn't do any cleanups, it just kills the CRT and process right there. No destructors in main scope are called. The real entry point MainCRTStartup or something initializes heap, does bunch of stuff, then calls main, and upon return calls exit or some-such. – Coder Jun 21 '11 at 08:40
  • @shoosh: main() is a special function, most compilers automatically make it the entry point for an application. If you want to make main() a library function, programs using the library will be forced to use the library's main() instead of their own, which is generally undesirable. Also, you cannot combine two libraries when both of them define main(). In short, defining main() in a library is evil. – tdammers Jun 21 '11 at 09:44
  • 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
  • @Rob, Just verified. New class (classx) with ctor and dtor. Main with body - case1: {classx a;return 0;} and case2: {classx a;exit(0);}. Dtor in case1 is called, dtor in case 2 is NOT called. If you have object deserialization in dtor, it's not doing anything if you call exit! Yes memory is cleaned, but exit kills the process right there, no dtors executed. Tested with VS2010, win32, debug. – Coder Jun 21 '11 at 15:06
  • @Rob, 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." This is true, but exit doesn't leave the main, it terminates process, there is no scope cleanup! Also stack cookie checking will not take place. Main is just a regular function which is wrapped inside the true entry point! See: http://www.catch22.net/tuts/minexe C-runtime and default libs. – Coder Jun 21 '11 at 15:16
  • Now you're doing it wrong, @Coder. Your object has automatic storage duration. The standard says objects with static storage duration are destroyed, so it wasn't a valid test. Your conclusion does not follow from your test. Besides, all you can hope to learn from your test is whether your compiler follows the standard. – Rob Kennedy Jun 21 '11 at 15:18
  • 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
  • @Rob, I would like to see how *nix behaves, but in Windows, main is a very ordinary function, and if you'll export it, I am quite sure it will not exit the application, because exit is called by the wrapping function. Unless you kill the application and process with exit(0), but you can do that from every function, so nothing special about main here. Also it's a good question if exported main is THE main. Also, not that quote says leaving main, so main is gone, and then, who calls exit is not even specified. – Coder Jun 21 '11 at 15:37
  • 3
    @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
  • I tested on gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3. I can call the main from other functions without there being an implicit exit call. – Martin Vilcans Jun 21 '11 at 20:47
  • 1
    C is a little different from C++ in that regard, @Martin. C allows recursive calls to main, so it says that returning from the initial call to main is the same as calling exit. In the scenario we're imagining here, there are two main functions: the host application's, and the one you import from a library. Calling the imported function would be the initial call of that function, so returning from it should terminate your program. (I doubt how many implementations actually work that way, though, since they probably don't expect to export main anyway.) – Rob Kennedy Jun 21 '11 at 21:07
  • @Rob Kennedy Well, I tested it with g++ too and got the same result, but perhaps it doesn't follow the standard. When I read that a return from main should call exit, I assumed that the typical implementation is a library function that calls main, and then exit, i.e. exit(main()). What you're saying makes less sense, but may be right. – Martin Vilcans Jun 21 '11 at 21:40
  • Yes, @Martin, if you called main recursively, then your C++ program was ill-formed, so it wasn't a valid test. As far as I can tell, implementations may indeed simply call exit(main()) — since programs aren't allowed to call main themselves (like your test did), the implementation doesn't really need to worry about inserting special code in the function epilogue just to guarantee that returning from it acts like exit. – Rob Kennedy Jun 21 '11 at 21:48
  • This reminds me of jQuery's return new jQuery.init(a,b) trick. Basically it provides additional functionality by allowing you to override the function later. – William Jun 21 '11 at 21:54
  • That's interesting, @Rob. I didn't know that you are not allowed to call main. Not that it matters because I would never want to in the first place. – Martin Vilcans Jun 22 '11 at 20:52
43

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.

Mark B
  • 751
  • 4
  • 3
  • 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. – Edward Strange Jun 20 '11 at 22:18
  • 4
    main() also has some restrictions that other functions do not have. – Martin York 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
29

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.

Chance
  • 521
15

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).

  • 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
6

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.

Ed Swangren
  • 2,757
  • 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... – riwalk 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 Swangren Jun 20 '11 at 23:07
  • meh. To me, they are one and the same. Clean code tends to be more stable. – riwalk Jun 21 '11 at 02: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 05:27
  • @Francesco: Sorry, "Terrible" from a maintenance and readability perspective, not a functional one. – Ed Swangren Sep 06 '11 at 21:06
1

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.

Coder
  • 6,968
  • 5
  • 38
  • 49
  • 1
    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 09:49
1

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.

0

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.

Jack Aidley
  • 2,953
0

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?

littleadv
  • 4,704
  • 28
  • 26
  • I didn't ask whether it can be long. I asked why shouldn't it be long. – riwalk 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. – riwalk Jun 20 '11 at 21:38
  • @littleadv, actually, if I had to put my finger on it, I'd say that the question was, "Why should main() be short?" Your answer did not help. Suck it up and take it like a man. – riwalk Jun 20 '11 at 21:45
  • @littleadv. Grow up. (I'm done--proceed to whine in whatever way suits your fancy) – riwalk Jun 20 '11 at 21:48
  • @DonalFellows: “Complexity below 10”? Is there a measurement unit for that?" Yes, a common measure is cyclomatic complexity (http://en.wikipedia.org/wiki/Cyclomatic_complexity) – Concrete Gannet Jun 21 '11 at 00:19
0

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.

Peter G.
  • 117
  • 6
-1

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.

  • 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 04:48