it-swarm.dev

Warum sollte main () kurz sein?

Ich programmiere seit über 9 Jahren und nach dem Rat meines ersten Programmierlehrers halte ich meine main() - Funktion immer extrem kurz.

Anfangs hatte ich keine Ahnung warum. Ich habe nur ohne Verständnis gehorcht, sehr zur Freude meiner Professoren.

Nachdem ich Erfahrung gesammelt hatte, stellte ich fest, dass, wenn ich meinen Code richtig entwarf, eine kurze main() - Funktion einfach irgendwie passierte. Durch das Schreiben von modularisiertem Code und das Befolgen des Einzelverantwortungsprinzips konnte mein Code in "Bündeln" entworfen werden, und main() diente lediglich als Katalysator, um das Programm zum Laufen zu bringen.

Vor ein paar Wochen habe ich mir Pythons Quellcode angesehen und die Funktion main() gefunden:

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

...

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

Yay Python. Kurze main() function == Guter Code.

Programmierlehrer hatten recht.

Um genauer hinzuschauen, habe ich mir Py_Main angesehen. In seiner Gesamtheit ist es wie folgt definiert:

/* 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;
}

Guter Gott, der Allmächtige ... es ist groß genug, um die Titanic zu versenken.

Es scheint, als ob Python hat den Trick "Einführung in die Programmierung 101" ausgeführt und einfach den gesamten Code von main() in eine andere Funktion verschoben, die als "main" bezeichnet wird.

Hier ist meine Frage: Ist dieser Code schrecklich geschrieben oder gibt es andere Gründe, eine kurze Hauptfunktion zu haben?

So wie es jetzt aussieht, sehe ich absolut keinen Unterschied zwischen dem Tun und dem Verschieben des Codes in Py_Main() zurück in main(). Bin ich falsch darin zu denken?

89
riwalk

Sie können main nicht aus einer Bibliothek exportieren, aber Sie können Py_Main, und dann kann jeder, der diese Bibliothek verwendet, Python viele Male mit unterschiedlichen Argumenten im selben Programm "" aufrufen ". Zu diesem Zeitpunkt wird python nur ein weiterer Konsument der Bibliothek , kaum mehr als ein Wrapper für die Bibliotheksfunktion, der Py_Main genau wie jeder andere.

138
Rob Kennedy

Es ist nicht so, dass main nicht so lang sein sollte, wie Sie vermeiden sollten, dass die Funktion any zu lang ist. main ist nur ein Sonderfall der Funktion. Längere Funktionen sind sehr schwer zu bearbeiten, verringern die Wartbarkeit und sind im Allgemeinen schwieriger zu bearbeiten. Indem Sie die Funktionen (und main) kürzer halten, verbessern Sie im Allgemeinen die Qualität Ihres Codes.

In Ihrem Beispiel hat das Verschieben des Codes aus main überhaupt keinen Vorteil.

42
Mark B

Ein Grund, main() kurz zu machen, ist das Testen von Einheiten. main() ist die eine Funktion, die nicht einheitlich getestet werden kann. Daher ist es sinnvoll, den größten Teil des Verhaltens in eine andere Klasse zu extrahieren, die einheitlich getestet werden kann. Dies stimmt mit dem überein, was Sie gesagt haben

Durch das Schreiben von modularisiertem Code und das Befolgen des Einzelverantwortungsprinzips konnte mein Code in "Bündeln" entworfen werden, und main () diente lediglich als Katalysator, um das Programm zum Laufen zu bringen.

Hinweis: Ich habe die Idee von hier .

28
Chance

Es ist selten eine gute Idee, wenn main lang ist; Wie bei any function (oder method), wenn es lang ist, verpassen Sie wahrscheinlich Möglichkeiten zum Refactoring.

In dem oben erwähnten speziellen Fall ist main kurz, da all diese Komplexität in Py_Main Berücksichtigt wird. Wenn Sie möchten, dass sich Ihr Code wie eine python Shell verhält, können Sie diesen Code einfach verwenden, ohne viel herumzuspielen. (Es muss so berücksichtigt werden, weil es nicht gut funktioniert, wenn Sie main in eine Bibliothek stellen; seltsame Dinge passieren, wenn Sie dies tun.)

BEARBEITEN:
Zur Verdeutlichung kann main nicht in einer statischen Bibliothek enthalten sein, da keine explizite Verknüpfung besteht und daher nicht korrekt verknüpft wird (es sei denn, Sie sortieren sie in einer Objektdatei mit etwas das wird erwähnt, was einfach schrecklich ist!) Freigegebene Bibliotheken werden normalerweise als ähnlich behandelt (um Verwirrung zu vermeiden), obwohl auf vielen Plattformen ein zusätzlicher Faktor darin besteht, dass eine gemeinsam genutzte Bibliothek nur eine ausführbare Datei ohne bootstrap Abschnitt (von dem main nur der letzte und sichtbarste Teil ist).

16
Donal Fellows

Main sollte aus dem gleichen Grund kurz sein, aus dem jede Funktion kurz sein sollte. Dem menschlichen Gehirn fällt es schwer, große Mengen nicht partitionierter Daten gleichzeitig im Speicher zu halten. Teilen Sie es in logische Teile auf, damit andere Entwickler (und Sie selbst!) Es leicht verstehen und darüber nachdenken können.

Und ja, Ihr Beispiel ist schrecklich und schwer zu lesen, geschweige denn zu pflegen.

6
Ed S.

Einige Leute genießen mehr als 50 Funktionen, die nichts anderes tun, als einen Aufruf einer anderen Funktion zu beenden. Ich würde lieber eine normale Hauptfunktion bevorzugen, die die Hauptprogrammlogik ausführt. Natürlich gut strukturiert.

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

Ich sehe keinen Grund, warum ich etwas davon in eine Hülle einwickeln sollte.

Es ist ein rein persönlicher Geschmack.

1
Coder

Es wird empfohlen, ALLE Funktionen kurz zu halten, nicht nur die Hauptfunktionen. Wie kurz "subjektiv" auch sein mag, es hängt von der Größe Ihres Programms und der von Ihnen verwendeten Sprache ab.

1
Mike Miller

Gehen Sie nicht davon aus, dass der gesamte Code hinter dieser Software gut ist, nur weil ein bisschen Software gut ist. Gute Software und guter Code sind nicht dasselbe, und selbst wenn gute Software durch guten Code unterstützt wird, ist es unvermeidlich, dass es in einem großen Projekt Orte gibt, an denen Standards verrutschen.

Es ist eine gute Praxis, eine kurze mainFunktion zu haben, aber das ist wirklich nur ein Sonderfall der allgemeinen Regel, dass es besser ist, kurze Funktionen zu haben. Kurze Funktionen sind leichter zu verstehen und leichter zu debuggen und können sich besser an die Art von "Single Purpose" -Design halten, das Programme ausdrucksvoller macht. main ist vielleicht ein wichtigerer Ort, um sich an die Regel zu halten, da jeder, der das Programm verstehen will, main verstehen muss, während dunkelere Ecken der Codebasis weniger häufig besucht werden.

Aber die Python Codebasis schiebt den Code nicht nachPy_MainUm diese Regel zu spielen, aber weil Sie main nicht aus einer Bibliothek exportieren oder als Funktion aufrufen können.

0
Jack Aidley

Hier ist auch ein neuer pragmatischer Grund, um das GCC 4.6.1 Changelog kurz zu halten:

Bei den meisten Zielen mit Unterstützung für benannte Abschnitte werden Funktionen verwendet, die nur beim Start verwendet werden (statische Konstruktoren und main ), Funktionen, die nur beim Beenden verwendet werden, und Funktionen, die als kalt erkannt werden in separaten Textsegment-Unterabschnitten platziert. Dies erweitert die Funktion -freorder-Funktionen und wird von demselben Schalter gesteuert. Ziel ist es, die Startzeit großer C++ - Programme zu verbessern.

Hervorhebung von mir hinzugefügt.

0
Peter G.

Es ist nicht erforderlich, dass main eine beliebige Länge hat, außer Codierungsstandards. main ist eine Funktion wie jede andere und als solche Komplexität sollte unter 10 liegen (oder was auch immer Ihre Codierungsstandards sagen). Das war's, alles andere ist eher argumentativ.

edit

main sollte nicht kurz sein. Oder lang. Es sollte die Funktionalität enthalten, die für die Ausführung basierend auf Ihrem Design erforderlich ist, und die Codierungsstandards einhalten.

Was den spezifischen Code in Ihrer Frage betrifft - ja, es ist hässlich.

In Bezug auf Ihre zweite Frage - ja, Sie liegen falsch . Wenn Sie den gesamten Code wieder in main verschieben, können Sie ihn nicht modular als Bibliothek verwenden, indem Sie Py_Main Von außen verknüpfen.

Jetzt bin ich klar?

0
littleadv