ทำไมหลัก () ควรสั้น


87

ฉันได้เขียนโปรแกรมมานานกว่า 9 ปีและตามคำแนะนำของครูผู้สอนโปรแกรมคนแรกของฉันฉันมักจะทำให้main()ฟังก์ชั่นของฉันสั้นมาก

ตอนแรกฉันไม่รู้ว่าทำไม ฉันแค่เชื่อฟังโดยไม่เข้าใจมากนักเพื่อความสุขใจของอาจารย์

หลังจากได้รับประสบการณ์ฉันรู้ว่าถ้าฉันออกแบบรหัสของฉันอย่างถูกต้องมีmain()ฟังก์ชั่นสั้น ๆแค่เกิดขึ้น การเขียนโค้ดที่ทำให้เป็นโมดูลและทำตามหลักการความรับผิดชอบเดียวทำให้รหัสของฉันถูกออกแบบเป็น "ช่อ" และmain()ทำหน้าที่เป็นตัวเร่งปฏิกิริยาเพื่อให้โปรแกรมทำงาน

กรอไปข้างหน้าเมื่อไม่กี่สัปดาห์ที่ผ่านมาฉันมองไปที่โค้ด souce ของ Python และฉันพบmain()ฟังก์ชัน:

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

...

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

งูหลามเย็ด main()ฟังก์ชั่นสั้น== รหัสที่ดี

ครูการเขียนโปรแกรมถูกต้อง

อยากดูลึกกว่านี้ฉันลองดู Py_Main ครบถ้วนถูกกำหนดไว้ดังนี้:

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

พระเจ้าผู้ยิ่งใหญ่ผู้ทรงอำนาจ ... มันใหญ่พอที่จะจมเรือไททานิค

ดูเหมือนว่า Python จะทำเคล็ดลับ "Intro to Programming 101" และเพิ่งย้ายmain()รหัสของทั้งหมดไปยังฟังก์ชั่นที่แตกต่างกันเรียกว่ามันคล้ายกับ "main"

นี่คือคำถามของฉัน: รหัสนี้เขียนชะมัดหรือมีเหตุผลอื่นที่จะมีฟังก์ชั่นหลักสั้น ๆ ?

ขณะที่มันยืนตอนนี้ผมเห็นอย่างแตกต่างระหว่างการทำเช่นนี้และเพียงแค่การย้ายโค้ดในไม่กลับเข้ามาPy_Main() main()ฉันคิดผิดหรือเปล่า


4
จะไม่ดีกว่าสำหรับcodereviews.stackexchange.comหรือ
foobar

38
@Luzhin ไม่ ฉันไม่ได้ขอให้ใครตรวจสอบซอร์สโค้ดของ Python นี่คือคำถามการเขียนโปรแกรม
riwalk

3
TBH ครึ่งรหัสเป็นตัวเลือกในการประมวลผลและตลอดเวลาที่โปรแกรมของคุณสนับสนุนจำนวนมากของตัวเลือกและคุณเขียนหน่วยประมวลผลที่กำหนดเองนี้เป็นสิ่งที่คุณจะสิ้นสุดการทำ ...
สะเดา

7
@Star ไม่โปรแกรมเมอร์ยังเป็นแนวทางปฏิบัติที่ดีที่สุดรูปแบบการเข้ารหัส ฯลฯ ที่จริงแล้วนั่นคือสิ่งที่ฉันเยี่ยมชมเว็บไซต์สำหรับ
Mateen Ulhaq

4
@Nim ฉันเข้าใจว่าเป็นสิ่งที่ทำ แต่มีเหตุผลที่จะไม่เขียนมันไม่มีoptions = ParseOptionFlags(argc,argv)ที่optionsเป็นstructที่มีตัวแปรPy_BytesWarningFlag, Py_DebugFlagฯลฯ ...
riwalk

คำตอบ:


137

คุณไม่สามารถส่งออกmainจากห้องสมุด แต่คุณสามารถส่งออกPy_Mainแล้วทุกคนที่ใช้ห้องสมุดนั้นสามารถ "เรียก" Python หลายครั้งด้วยข้อโต้แย้งที่แตกต่างกันในโปรแกรมเดียวกัน ณ จุดนี้pythonกลายเป็นเพียงผู้บริโภคของห้องสมุดมากกว่าเพียงแค่ wrapper สำหรับฟังก์ชั่นห้องสมุด มันเรียกPy_Mainเหมือนคนอื่น ๆ


1
มีคำตอบที่ดี
riwalk

26
ฉันคิดว่ามันอาจจะแม่นยำกว่าที่จะบอกว่าคุณไม่สามารถนำเข้า @Shoosh มาตรฐาน C ++ ห้ามการเรียกจากรหัสของคุณเอง นอกจากนี้การเชื่อมโยงของมันคือการกำหนดการใช้งาน นอกจากนี้การกลับมาจากการmainโทรอย่างมีประสิทธิภาพexitซึ่งโดยปกติคุณไม่ต้องการให้ห้องสมุดทำ
Rob Kennedy

3
@Coder ดู C ++ 03 §3.6.1 / 5: "คำสั่ง return ในmainมีผลต่อการออกจากฟังก์ชั่นหลัก ... และการเรียกexitด้วยค่าส่งคืนเป็นอาร์กิวเมนต์" ยังเห็น§18.3 / 8 ซึ่งอธิบายว่า "วัตถุที่มีระยะเวลาการจัดเก็บข้อมูลแบบคงที่จะถูกทำลาย" และ "ทั้งหมดที่เปิด C ลำธาร ... จะล้าง" exitเมื่อคุณเรียก C99 มีภาษาที่คล้ายกัน
Rob Kennedy

1
@ Code ไม่ว่าexitใบไม้mainจะไม่เกี่ยวข้องก็ตาม exitเราไม่ได้พูดคุยเกี่ยวกับการทำงานของ mainเรากำลังคุยกันเรื่องการทำงานของ และพฤติกรรมของmain รวมถึงพฤติกรรมของexitสิ่งที่อาจเป็น นั่นคือสิ่งที่ทำให้ไม่ต้องการนำเข้าและโทรmain(ถ้าทำสิ่งนี้เป็นไปได้หรืออนุญาต)
Rob Kennedy

3
@Coder หากการส่งคืนจากmainไม่มีผลของการเรียกexitคอมไพเลอร์ของคุณคอมไพเลอร์ของคุณจะไม่เป็นไปตามมาตรฐาน ว่ามาตรฐานการสั่งพฤติกรรมดังกล่าวเพื่อmainพิสูจน์ให้เห็นว่ามีเป็นบางสิ่งที่พิเศษเกี่ยวกับเรื่องนี้ สิ่งที่พิเศษเกี่ยวกับคือการที่กลับมาจากการที่มันมีผลกระทบของการเรียกmain exit( วิธีที่มันไม่ว่าจะขึ้นอยู่กับ compiler นักเขียนคอมไพเลอร์ก็สามารถแทรกโค้ดในบทส่งท้ายฟังก์ชั่นที่ทำลายวัตถุคงโทร. atexitประจำวูบวาบไฟล์และยุติโปรแกรม - ซึ่งอีกครั้งไม่ได้เป็นสิ่งที่คุณต้องการในห้องสมุด .)
Rob Kennedy

42

ไม่ใช่ว่าไม่mainควรนานเท่าที่คุณควรหลีกเลี่ยงฟังก์ชั่นใด ๆที่ยาวเกินไป mainเป็นเพียงฟังก์ชั่นพิเศษ ฟังก์ชั่นที่ยาวขึ้นจะยากต่อการควงลดการบำรุงรักษาและโดยทั่วไปจะยากกว่าในการทำงานด้วย โดยการทำให้ฟังก์ชั่น (และmain) สั้นลงคุณจะต้องปรับปรุงคุณภาพรหัสของคุณ

mainในตัวอย่างของคุณมีผลประโยชน์ที่ทุกคนที่จะย้ายโค้ดออกจาก


9
คำทองคำอาจเป็น "นำมาใช้ซ้ำ" ความยาวmainไม่สามารถใช้ซ้ำได้มาก
S.Lott

1
@S - นั่นเป็นคำทองคำหนึ่งคำ อีกอย่างคือ OMG !!! สมาธิสั้นเพียงแค่เข้าไปใน !!!! หรือในแง่คนธรรมดา: ความชัดเจน
Edward Strange

3
main () มีข้อ จำกัด บางอย่างที่ฟังก์ชั่นอื่นไม่มี
Martin York

1
หลักยัง () ไม่มีความหมายที่แท้จริง รหัสของคุณควรมีความหมายกับโปรแกรมเมอร์อื่น ฉันใช้ main เพื่อแยกอาร์กิวเมนต์และมัน - และฉันยังมอบหมายว่าถ้ามันมากกว่าสองสามบรรทัด
Bill K

@ Bill K: ข้อดีคือใช้ main () เพื่อแยกอาร์กิวเมนต์ (และเริ่มโปรแกรมที่เหลือ) เพื่อให้สอดคล้องกับหลักการความรับผิดชอบเดี่ยว
Giorgio

28

เหตุผลข้อหนึ่งที่ทำให้main()การทดสอบนั้นสั้น main()เป็นฟังก์ชั่นหนึ่งที่ไม่สามารถทดสอบหน่วยได้ดังนั้นจึงเหมาะสมที่จะแยกพฤติกรรมส่วนใหญ่ออกเป็นคลาสอื่นที่สามารถทดสอบหน่วยได้ สิ่งนี้สอดคล้องกับสิ่งที่คุณพูด

การเขียนโค้ดที่ทำให้เป็นโมดูลและทำตามหลักการความรับผิดชอบเดียวทำให้รหัสของฉันถูกออกแบบใน "ช่อ" และ main () ทำหน้าที่เป็นอะไรมากกว่าตัวเร่งปฏิกิริยาเพื่อให้โปรแกรมทำงาน

หมายเหตุ: ผมมีความคิดที่ได้จากที่นี่


อีกหนึ่งที่ดี ไม่เคยคิดถึงแง่มุมนั้น
riwalk

16

มันเป็นความคิดที่ดีที่mainจะอยู่ได้นาน เช่นเดียวกับฟังก์ชั่นใด ๆ (หรือวิธีการ) หากใช้เวลานานคุณอาจพลาดโอกาสในการปรับโครงสร้างใหม่

ในกรณีที่เฉพาะเจาะจงที่คุณกล่าวถึงข้างต้นmainเป็นระยะสั้นเพราะทุกสิ่งที่ซับซ้อนเป็นปัจจัยออกไปPy_Main; หากคุณต้องการให้โค้ดทำงานเหมือนเปลือกงูหลามคุณก็สามารถใช้รหัสนั้นได้โดยไม่ต้องเล่นซอ (ต้องแยกตัวประกอบแบบนั้นเพราะมันใช้งานไม่ได้ถ้าคุณใส่mainในห้องสมุดสิ่งแปลก ๆ เกิดขึ้นถ้าคุณทำ)

แก้ไข:
เพื่อชี้แจงmainไม่สามารถอยู่ในห้องสมุดคงเพราะมันไม่มีการเชื่อมโยงอย่างชัดเจนและจะไม่ถูกเชื่อมโยงอย่างถูกต้อง (เว้นแต่คุณจะ colocate ในไฟล์วัตถุด้วยสิ่งที่เรียกว่าซึ่งน่ากลัวเพียง !) ไลบรารีที่ใช้ร่วมกันมักจะถือว่าคล้ายกัน (อีกครั้งเพื่อป้องกันความสับสน) แม้ว่าในแพลตฟอร์มจำนวนมากปัจจัยเพิ่มเติมคือไลบรารีที่ใช้ร่วมกันเป็นเพียงไฟล์ปฏิบัติการที่ไม่มีส่วนบู๊ตสแตรป (ซึ่งmainเป็นเพียงส่วนสุดท้าย )


1
ในระยะสั้นอย่าใส่mainในห้องสมุด มันอาจจะไม่ทำงานหรือจะทำให้คุณสับสนอย่างมาก แต่มอบหมายงานแทบทั้งหมดให้กับฟังก์ชั่นที่อยู่ใน lib ซึ่งมักจะสมเหตุสมผล
Donal Fellows

6

หลักควรสั้นด้วยเหตุผลเดียวกับที่ฟังก์ชันใด ๆ ควรสั้น สมองมนุษย์มีช่วงเวลาที่ยากลำบากในการเก็บข้อมูลจำนวนมากในหน่วยความจำทันที แยกมันออกเป็นส่วน ๆ แบบลอจิคัลเพื่อให้ง่ายสำหรับนักพัฒนาคนอื่น ๆ (รวมถึงตัวคุณเอง!) ในการย่อยและเหตุผลเกี่ยวกับ

และใช่ตัวอย่างของคุณแย่มากและอ่านยาก


ใช่ฉันสงสัยอยู่เสมอว่าตัวรหัสนั้นแย่มาก (แม้ว่าคำถามจะเกี่ยวข้องกับการวางตำแหน่งของรหัส ฉันกลัวว่าวิสัยทัศน์ของฉันของงูใหญ่ได้รับความเสียหายโดยเนื้อแท้เป็นผล ...
riwalk

1
@stargazer: ฉันไม่รู้ว่ารหัสนั้นแย่มากเพียงเพราะมันไม่ได้มีการจัดการที่ดีสำหรับการบริโภคของมนุษย์ ที่กล่าวว่ามีรหัส "น่าเกลียด" มากมายที่ทำงานได้ดีและทำงานได้ดี ความงามของรหัสไม่ใช่ทุกสิ่งแม้ว่าเราควรพยายามอย่างเต็มที่เพื่อเขียนรหัสที่สะอาดที่สุดเท่าที่จะทำได้
Ed S.

Meh สำหรับฉันพวกเขาเป็นหนึ่งเดียวกัน รหัสสะอาดมีแนวโน้มที่จะมีเสถียรภาพมากขึ้น
riwalk

รหัสไม่น่ากลัวส่วนใหญ่จะมีตัวเรือนสวิตช์และการจัดการหลายแพลตฟอร์ม สิ่งที่คุณพบว่าน่ากลัว?
ฟรานเชสโก

@ ฟรานเชสโก้: ขออภัย "แย่มาก" จากมุมมองการบำรุงรักษาและการอ่านไม่ใช่การทำงาน
Ed S.

1

บางคนสนุกกับฟังก์ชั่น 50+ ที่ทำอะไรอย่างอื่น แต่ตัดสายไปยังฟังก์ชั่นอื่น ฉันต้องการฟังก์ชั่นหลักปกติที่ใช้ตรรกะของโปรแกรมหลัก มีโครงสร้างที่ดีแน่นอน

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

ฉันไม่เห็นเหตุผลใด ๆ ว่าทำไมฉันควรห่อสิ่งนั้นไว้ในเสื้อคลุม

มันเป็นรสนิยมส่วนตัวอย่างหมดจด


1
เพราะมันเป็นเอกสารของรหัส คุณสามารถเขียนโค้ดด้วยวิธีนี้โดยไม่จำเป็นต้อง (เกือบ) เคยเขียนความคิดเห็น และเมื่อคุณเปลี่ยนรหัสเอกสารจะเปลี่ยนโดยอัตโนมัติ :-)
Oliver Weiler

1

วิธีปฏิบัติที่ดีที่สุดเพื่อให้ฟังก์ชั่นทั้งหมดของคุณสั้นไม่ใช่แค่หลัก อย่างไรก็ตาม "สั้น" เป็นอัตนัยมันขึ้นอยู่กับขนาดของโปรแกรมและภาษาที่คุณใช้


0

ไม่จำเป็นmainต้องมีความยาวใด ๆ นอกเหนือจากมาตรฐานการเข้ารหัส mainเป็นฟังก์ชั่นอื่น ๆ และเป็นความซับซ้อนที่ควรจะต่ำกว่า 10 (หรือสิ่งที่มาตรฐานการเข้ารหัสของคุณพูด) นั่นคือสิ่งอื่นใดค่อนข้างโต้แย้ง

แก้ไข

mainไม่ควรสั้น หรือยาว ควรรวมฟังก์ชันการทำงานที่จำเป็นในการทำงานตามการออกแบบของคุณและเป็นไปตามมาตรฐานการเข้ารหัส

สำหรับรหัสเฉพาะในคำถามของคุณ - ใช่มันน่าเกลียด

เป็นคำถามที่สองของคุณ - ใช่คุณมีความผิด การย้ายรหัสนั้นกลับไปยังหลักไม่อนุญาตให้คุณใช้งานเป็นโมดูลโดยการลิงก์Py_Mainจากภายนอก

ตอนนี้ฉันชัดเจน


ผมไม่ได้ถามว่ามันสามารถจะยาว ฉันถามว่าทำไมไม่ควรยาว
riwalk

“ ความซับซ้อนต่ำกว่า 10”? มีหน่วยการวัดสำหรับสิ่งนั้นหรือไม่?
Donal Fellows

@ Stargazer712 ความยาวฟังก์ชั่นมักจะถูกควบคุมโดยมาตรฐานการเข้ารหัสเช่นกัน มันเป็นปัญหาการอ่านได้ (และความซับซ้อนมักจะมีการแยกฟังก์ชันยาวเพื่อให้ความซับซ้อนนั้นสูงกว่า 20) และอย่างที่ฉันพูด - mainไม่แตกต่างจากฟังก์ชั่นอื่น ๆ ในเรื่องนี้
littleadv

@ Donal - ใช่คลิกที่ลิงค์
littleadv

ฉันจะต้องลงคะแนนหนึ่งหน่อนี้ คุณพลาดจุดประสงค์ของคำถามไปโดยสิ้นเชิง
riwalk

0

นี่คือเหตุผลในทางปฏิบัติใหม่ให้สั้นเกินไปหลักจากGCC 4.6.1 การเปลี่ยนแปลง :

กับเป้าหมายมากที่สุดด้วยการสนับสนุนส่วนชื่อฟังก์ชั่นใช้เฉพาะที่เริ่มต้น (ก่อสร้างแบบคงที่และ หลัก ) ฟังก์ชั่นใช้เฉพาะที่ประตูทางออกและฟังก์ชั่นการตรวจพบจะหนาวจะ วางลงที่แยกต่างหากส่วนย่อยส่วนข้อความ สิ่งนี้เป็นการขยายคุณสมบัติ -freorder-functions และถูกควบคุมโดยสวิตช์เดียวกัน เป้าหมายคือการปรับปรุงเวลาเริ่มต้นของโปรแกรม C ++ ขนาดใหญ่

เพิ่มการเน้นโดยฉัน


0

อย่าคิดว่าเพียงเพราะซอฟต์แวร์เป็นสิ่งที่ดีรหัสทั้งหมดที่อยู่เบื้องหลังซอฟต์แวร์นั้นดี ซอฟต์แวร์ที่ดีและรหัสที่ดีนั้นไม่เหมือนกันและแม้ว่าซอฟต์แวร์ที่ดีจะได้รับการสนับสนุนด้วยรหัสที่ดีก็เป็นสิ่งที่หลีกเลี่ยงไม่ได้ที่ในโครงการขนาดใหญ่

มันเป็นการปฏิบัติที่ดีที่จะมีmainฟังก์ชั่นสั้น ๆแต่นั่นเป็นเพียงกรณีพิเศษของกฎทั่วไปที่ดีกว่าที่จะมีฟังก์ชั่นสั้น ๆ ฟังก์ชั่นสั้น ๆ นั้นง่ายต่อการเข้าใจและง่ายต่อการดีบักรวมถึงการยึดติดกับการออกแบบ 'จุดประสงค์เดียว' ที่ทำให้โปรแกรมแสดงออกได้ดีขึ้น mainอาจเป็นสถานที่ที่สำคัญกว่าในการยึดติดกับกฎเนื่องจากใครก็ตามที่ต้องการทำความเข้าใจกับโปรแกรมจะต้องเข้าใจmainในขณะที่มุมที่คลุมเครือมากกว่าของ codebase อาจมีผู้เยี่ยมชมน้อยกว่า

แต่ฐานข้อมูลไพ ธ อนไม่ได้ผลักรหัสออกมาPy_Mainเพื่อเล่นกฏนี้ แต่เนื่องจากคุณไม่สามารถส่งออกmainจากห้องสมุดหรือเรียกมันว่าเป็นฟังก์ชั่น


-1

มีคำตอบทางเทคนิคหลายประการข้างต้นให้เว้นไว้

หลักควรสั้นเพราะควรเป็น bootstrap หลักควรยกตัวอย่างวัตถุจำนวนน้อยซึ่งมักเป็นวัตถุหนึ่งที่ทำงาน เช่นเดียวกับที่อื่น ๆ วัตถุเหล่านั้นควรได้รับการออกแบบมาเป็นอย่างดีมีความกลมกลืนเชื่อมกันอย่างอิสระห่อหุ้ม ...

ในขณะที่อาจมีเหตุผลทางเทคนิคที่จะมีวิธีการโทรหนึ่งบรรทัดหลักมอนสเตอร์อื่นในหลักการที่คุณถูกต้อง จากมุมมองของวิศวกรรมซอฟต์แวร์ไม่มีอะไรได้รับ หากตัวเลือกอยู่ระหว่างหลักสายหนึ่งเรียกวิธีมอนสเตอร์และตัวหลักเป็นวิธีมอนสเตอร์ตัวเลือกหลังจะไม่ดีเพียงเล็กน้อย


คุณกำลังสมมติว่า "รหัส C ++ ควรใช้วัตถุและวัตถุเท่านั้น" ไม่เป็นความจริงเลย C ++ เป็นภาษาแบบหลายจุดและไม่ได้บังคับให้ทุกอย่างกลายเป็นรูป OO เหมือนกับภาษาอื่น ๆ
Ben Voigt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.