web-dev-qa-db-ja.com

main()を短くすべきなのはなぜですか?

私は9年以上プログラミングをしていて、最初のプログラミングの先生のアドバイスによると、私は常にmain()関数を非常に短くしています。

最初はなぜだか分かりませんでした。私は理解せずにただ従っただけで、私の教授達は大いに喜びました。

経験を積んで、私は自分のコードを正しく設計した場合、短いmain()関数が発生することに気づきました。モジュール化されたコードを記述し、単一責任の原則に従うことで、コードを「束」で設計でき、main()は、プログラムを実行するための触媒に過ぎませんでした。

数週間前に早送りし、Pythonのソースコードを見ていたところ、main()関数が見つかりました。

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

...

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

イェーイPython。短いmain() function ==良いコード。

プログラミング教師は正しかった。

もっと深く見たいので、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"と非常によく似ています。

これが私の質問です:このコードはひどく書かれていますか、それとも短いmain関数を持つ他の理由がありますか?

現在のところ、これを行うこととPy_Main()内のコードをmain()に戻すだけの違いはまったくありません。これを考えるのは間違っていますか?

89
riwalk

ライブラリからmainをエクスポートすることはできませんが、Py_Mainをエクスポートできます。そうすると、そのライブラリを使用するすべてのユーザーがPython同じプログラム。その時点で、pythonはライブラリのもう1つのコンシューマになり、ライブラリ関数のラッパーにすぎません。他の人と同じようにPy_Mainを呼び出します。

138
Rob Kennedy

any関数が長すぎることを避けるために、mainを長くする必要はありません。 mainは、関数の特殊なケースです。関数が長くなると、処理が非常に難しくなり、保守性が低下し、一般に操作が難しくなります。関数(およびmain)を短くすることで、一般にコードの品質が向上します。

あなたの例では、コードをmainの外に移動してもまったくメリットがありません。

42
Mark B

main()を短くする1つの理由は、ユニットテストに関係しています。 main()は単体テストできない1つの関数なので、動作の大部分を単体テストできる別のクラスに抽出することは理にかなっています。これはあなたが言ったことと一致します

モジュール化されたコードを記述し、単一の責任の原則に従うことで、コードを「束」で設計することができ、main()はプログラムを実行するための触媒に過ぎませんでした。

注:私は here からアイデアを得ました。

28
Chance

mainを長くすることはめったにありません。 any関数(またはメソッド)と同様に、それが長い場合、おそらくリファクタリングの機会を逃しています。

上記の特定のケースでは、mainは短いです。これは、そのすべての複雑さがPy_Mainに分解されるためです。コードがpythonシェルのように動作するようにしたい場合は、それほどコードをいじる必要なく、そのコードを使用できます。 (ライブラリにmainを配置するとうまく機能しないため、このように因数分解する必要があります。そうすると奇妙なことが起こります。)

編集:
明確にするために、mainは明示的なリンクがないため、静的ライブラリに含めることはできません。したがって、オブジェクトファイルに何かを配置しない限り、正しくリンクされません。多くのプラットフォームでは、共有ライブラリはbootstrapセクション(そのうちのmainは、最後の最も目に見える部分です)。

16
Donal Fellows

Mainは、すべての関数が短くなるのと同じ理由で短くする必要があります。人間の脳は、パーティション化されていない大量のデータを一度にメモリに保持するのに苦労しています。それを論理的なチャンクに分割して、他の開発者(そしてあなた自身!)が簡単に要約して推論できるようにします。

そして、はい、あなたの例はひどくて読むのが難しく、まして維持することはもちろんです。

6
Ed S.

一部の人々は、他には何もしないが別の関数への呼び出しをラップする50以上の関数を楽しんでいます。私はメインプログラムロジックを実行する通常のメイン関数を使用したいと思います。もちろんうまく構成されています。

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

それをラッパーでラップする必要がある理由はわかりません。

それは純粋に個人的な好みです。

1
Coder

メインだけでなく、すべての関数を短くすることをお勧めします。ただし、「短い」とは主観的なものであり、プログラムのサイズと使用している言語によって異なります。

1
Mike Miller

ほんの少しのソフトウェアが良いからと言って、そのソフトウェアの背後にあるすべてのコードが良いと思い込まないでください。優れたソフトウェアと優れたコードは同じものではなく、優れたソフトウェアが優れたコードによって裏付けられている場合でも、大規模なプロジェクトでは標準がずれる場所があることは避けられません。

Short mainfunctionを持つことは良い習慣ですが、これは実際には、短い関数を持つほうがよいという一般的なルールの特別なケースにすぎません。短い関数は理解しやすく、デバッグしやすく、プログラムをより表現力豊かにする一種の「単一目的」の設計にこだわるのに優れています。 mainは、プログラムを理解したい人はmainを理解する必要があるため、おそらくルールに固執するためのより重要な場所です。

しかし、Python codebaseはコードをプッシュしていませんPy_Mainこのルールをゲームするためですが、ライブラリからexport mainすることも、関数として呼び出すこともできないためです。

0
Jack Aidley

新しい実用的な理由もここにあります GCC 4.6.1 Changelog からメインを短くしてください:

名前付きセクションをサポートするほとんどのターゲットでは、起動時にのみ使用される関数(静的コンストラクターとmain)、終了時にのみ使用される関数、およびコールドであることが検出された関数は個別のテキストセグメントサブセクションに配置されます。これは-freorder-functions機能を拡張し、同じスイッチによって制御されます。目標は、大規模なC++プログラムの起動時間を改善することです。

私が追加したハイライト。

0
Peter G.

mainは、コーディング標準以外の長さにする必要はありません。 mainは他の関数と同じであり、そのため complexity は10(またはコーディング標準の規定)未満である必要があります。それだけです。他のことはかなり議論の余地があります。

edit

mainは短くしないでください。または長い。設計に基づいて実行するために必要な機能を含み、コーディング標準に準拠する必要があります。

あなたの質問の特定のコードに関して-はい、それは醜いです。

2番目の質問について-はい、あなたは間違っています。すべてのコードをメインに戻すと、外部からPy_Mainをリンクしてライブラリとしてモジュール化して使用することができなくなります。

今私は明確ですか?

0
littleadv