it-swarm.asia

Main () neden kısa olmalı?

9 yılı aşkın bir süredir program yapıyorum ve ilk programlama öğretmenimin tavsiyelerine göre, main() fonksiyonumu daima çok kısa tutuyorum.

İlk başta neden hakkında hiçbir fikrim yoktu. Anlamadan sadece profesörlerimin zevkine itaat ettim.

Deneyim kazandıktan sonra, kodumu doğru şekilde tasarlarsam, kısa bir main() işlevine sahip olmanın sadece sortof olduğunu fark ettim. Modüler kod yazılması ve tek sorumluluk ilkesinin uygulanması, kodumun "demetlerde" tasarlanmasına izin verdi ve main(), programı çalıştırmak için katalizörden başka bir şey değildi.

Birkaç hafta önce hızlıca, Python'un sos koduna bakıyordum ve main() işlevini buldum:

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

...

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

Yay python. Kısa main() işlevi == İyi kod.

Programlama öğretmenleri haklıydı.

Daha derine bakmak istedim, Py_Main'e baktım. Bütünüyle şu şekilde tanımlanır:

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

Yüce Tanrı ... Titanik'i batırmak için yeterince büyük.

Görünüşe göre Python "Programlamaya Giriş 101" hile yaptı ve sadece main() 'in tüm kodunu "main" olarak adlandırılan çok benzer bir işleve taşıdı .

Benim sorum: Bu kod çok mu yazılmış ya da kısa bir ana fonksiyona sahip olmanın başka nedenleri var mı?

Şu anda olduğu gibi, bunu yapmak ve Py_Main() içindeki kodu tekrar main() içine taşımak arasında kesinlikle hiçbir fark görmüyorum. Bunu düşünmekte yanlış mıyım?

89
riwalk

main dosyasını bir kitaplıktan dışa aktaramazsınız, ancak Py_Main ve daha sonra bu kütüphaneyi kullanan herkes aynı programdaki farklı argümanlarla "= "Python defalarca arayabilir.) Bu noktada python kütüphanenin başka bir tüketicisi olur , kütüphane işlevi için bir sarıcıdan biraz daha fazlası; Py_Main herkes gibi.

138
Rob Kennedy

main çok uzun olmamalı, herhangi bir fonksiyonunun çok uzun olmasını önlemek gerekir. main sadece özel bir işlev örneğidir. Daha uzun fonksiyonların işlenmesi çok zorlaşır, sürdürülebilirliği azaltır ve genellikle çalışmak daha zordur. İşlevleri (ve main) daha kısa tutarak kodunuzun kalitesini genellikle artırırsınız.

Örneğinizde, kodu main dışına taşımanın hiçbir faydası yoktur.

42
Mark B

main() kısa yapmanın bir nedeni birim sınamasıdır. main() birim test edilemeyen bir işlevdir, bu nedenle davranışın çoğunu birim test edilebilen başka bir sınıfa çıkarmak mantıklıdır. Söylediklerinle birlikte gidiyor

Modüler kod yazma ve tek sorumluluk ilkesini takip etme, kodumun "demetlerde" tasarlanmasına izin verdi ve main (), programı çalıştırmak için bir katalizörden başka bir şey değildi.

Not: burada fikrini aldım.

28
Chance

main'nin uzun olması nadiren iyi bir fikirdir; any işlevinde (veya yönteminde) olduğu gibi, uzunsa muhtemelen yeniden düzenleme fırsatlarını kaçırıyorsunuzdur.

Yukarıda bahsettiğiniz özel durumda, main kısadır, çünkü tüm bu karmaşıklık Py_Main; kodunuzun python Kabuk gibi davranmasını istiyorsanız, bu kodu etrafta çok fazla uğraşmadan kullanabilirsiniz. bir kütüphaneye main koydunuz; yaparsanız tuhaf şeyler olur.)

DÜZENLE:
Açıklığa kavuşturmak için, main statik bir kütüphanede olamaz çünkü açık bir bağlantısı yoktur ve bu nedenle doğru bir şekilde bağlanmayacaktır (bir nesne dosyasında bir şeyle konumlandırmazsanız) Paylaşılan kütüphaneler genellikle benzer olarak ele alınır (yine karışıklığı önlemek için), ancak birçok platformda paylaşılan bir kütüphane bootstrap bölümü (main yalnızca son ve en görünür kısımdır).

16
Donal Fellows

Main, herhangi bir fonksiyonun kısa olması için aynı nedenden dolayı kısa olmalıdır. İnsan beyninin büyük miktarlarda bölümlenmemiş verileri bir kerede hafızada tutması zorlaşır. Mantıksal parçalara ayırın, böylece diğer geliştiricilerin (hem de kendiniz!) Sindirmesi ve akıl yürütmesi kolaydır.

Ve evet, örneğiniz bakılmaksızın korkunç ve okunması zor.

6
Ed S.

Bazı insanlar, başka hiçbir şey yapmayan 50'den fazla işlevin tadını çıkarır, ancak başka bir işleve çağrı yapar. Ana program mantığını yapan normal ana işlevi tercih ederim. Tabii ki iyi yapılandırılmış.

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

Bunların hiçbirini bir ambalajın içine sarmam için hiçbir neden göremiyorum.

Tamamen kişisel bir tat.

1
Coder

TÜM işlevlerinizi sadece ana değil, kısa tutmak için en iyi uygulama. Ancak "kısa" özneldir, programınızın boyutuna ve kullandığınız dile bağlıdır.

1
Mike Miller

Sadece biraz yazılım iyi olduğu için, bu yazılımın arkasındaki kodun tamamının iyi olduğunu varsaymayın. İyi yazılım ve iyi kod aynı şey değildir ve iyi yazılımın iyi kodla desteklendiği yerlerde bile, büyük bir projede standartların kaydığı yerler olması kaçınılmazdır.

Kısa bir main işlevine sahip olmak iyi bir uygulamadır, ancak bu, genel kuralın kısa işlevlere sahip olmanın daha iyi olduğu özel bir durumudur. Kısa fonksiyonları anlamak ve hata ayıklamak daha kolay olmasının yanı sıra programları daha anlamlı hale getiren 'tek amaçlı' tasarıma daha iyi uymaktır. main, belki de, kodu anlamak için herkesin main değerini anlaması gerektiğinden, kurala sadık kalmak için daha önemli bir yer olsa da, kod tabanının daha belirsiz köşeleri daha az ziyaret edilebilir.

Ancak, Python kod temeli, bu kuralı oynamak için kodu Py_Main 'A aktarmaz, ancak bir kitaplıktan main veremezsiniz veya bir işlev.

0
Jack Aidley

İşte yeni bir pragmatik sebep de GCC 4.6.1 Changelog :

Adlandırılmış bölüm desteğine sahip çoğu hedefte, yalnızca başlangıçta kullanılan işlevler (statik yapıcılar ve ana ), yalnızca çıkışta kullanılan işlevler ve soğuk olduğu saptanan işlevler ayrı metin segmenti alt bölümlerine yerleştirilir. Bu, -freorder-fonksiyonlar özelliğini genişletir ve aynı anahtar tarafından kontrol edilir. Amaç, büyük C++ programlarının başlangıç ​​zamanını geliştirmektir.

Vurgulama ekledim.

0
Peter G.

main için kodlama standartları dışında herhangi bir uzunlukta olma zorunluluğu yoktur. main diğerleri gibi bir işlevdir ve bu nedenle karmaşıklık 10'un altında olmalıdır (veya kodlama standartlarınız ne derse desin). Bu kadar, başka bir şey oldukça tartışmacı.

düzenlemek

main kısa olmamalıdır. Veya uzun. Tasarımınıza bağlı olarak gerçekleştirilmesi gereken işlevselliği içermeli ve kodlama standartlarına uymalıdır.

Sorunuzdaki özel koda gelince - evet, bu çirkin.

İkinci sorunuzla ilgili olarak - evet, yanılıyorsunuz . Tüm bu kodu tekrar ana konuma taşımak, Py_Main Bağlantısını dışarıdan bağlayarak modüler bir kütüphane olarak kullanmanıza izin vermez.

Şimdi açık mıyım?

0
littleadv