Por que main () deve ser curto?

87

Faço programação há mais de 9 anos e, de acordo com o conselho do meu primeiro professor de programação, sempre mantenho minha main()função extremamente curta.

No começo eu não tinha ideia do porquê. Eu apenas obedeci sem entender, para o deleite dos meus professores.

Depois de ganhar experiência, percebi que, se eu projetasse meu código corretamente, uma main()função curta apenas acontecia. Escrever código modularizado e seguir o princípio de responsabilidade única permitiu que meu código fosse projetado em "grupos" e main()serviu como nada mais que um catalisador para colocar o programa em funcionamento.

Avançando há algumas semanas, eu estava olhando o código fonte do Python e encontrei a main()função:

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

...

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

Yay python. main()Função curta == Bom código.

Os professores de programação estavam certos.

Querendo olhar mais fundo, dei uma olhada no Py_Main. Na sua totalidade, é definido da seguinte forma:

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

Bom Deus Todo-Poderoso ... é grande o suficiente para afundar o Titanic.

Parece que o Python fez o truque "Introdução à programação 101" e acabou de mover todo main()o código de uma função diferente, chamando-a de algo muito semelhante ao "main".

Aqui está minha pergunta: este código está muito mal escrito ou há outros motivos para ter uma função principal curta?

Como está agora, não vejo absolutamente nenhuma diferença entre fazer isso e apenas mover o código de Py_Main()volta para main(). Estou errado em pensar isso?

riwalk
fonte
4
não seria melhor para codereviews.stackexchange.com ?
foobar
38
@Luzhin, não. Não estou pedindo a ninguém para revisar o código fonte do Python. Esta é uma questão de programação.
riwalk
3
TBH, metade do código é o processamento de opções, ea qualquer momento o seu programa suporta o lote de opções, e você escrever um processador de costume, isto é o que você acaba fazendo ...
Nim
7
@Star Não, Programmers.SE também é para práticas recomendadas, estilos de codificação etc. Na verdade, é para isso que visito o site.
Mateen Ulhaq
4
@Nim, eu entendo que é o que está fazendo, mas não há nenhuma razão para não escrevê-lo como options = ParseOptionFlags(argc,argv)onde optionsé a structque contém as variáveis Py_BytesWarningFlag, Py_DebugFlagetc ...
riwalk

Respostas:

137

Você não pode exportar mainde uma biblioteca, mas pode exportar Py_Maine qualquer pessoa que utilize essa biblioteca pode "chamar" o Python muitas vezes com argumentos diferentes no mesmo programa. Nesse ponto, pythontorna-se apenas mais um consumidor da biblioteca, pouco mais que um invólucro para a função da biblioteca; chama Py_Mainassim como todo mundo.

Rob Kennedy
fonte
1
Há uma boa resposta.
riwalk
26
Suponho que seja mais preciso dizer que você não pode importá- lo, @Shoosh. O padrão C ++ proíbe chamá-lo de seu próprio código. Além disso, seu vínculo é definido pela implementação. Além disso, retornando de mainchamadas efetivas exit, o que você normalmente não deseja que uma biblioteca faça.
Rob Kennedy
3
@ Codificador, consulte C ++ 03 §3.6.1 / 5: "Uma declaração de retorno maintem o efeito de deixar a função principal ... e chamar exitcom o valor de retorno como argumento". Veja também § 18.3 / 8, que explica que "objetos com duração de armazenamento estático são destruídos" e "todos os fluxos C abertos ... são liberados" quando você chama exit. C99 tem linguagem semelhante.
Rob Kennedy
1
@ Codificador, se as exitfolhas mainsão irrelevantes. Não estamos discutindo o comportamento de exit. Estamos discutindo o comportamento de main. E o comportamento de main inclui o comportamento de exit, o que quer que seja. É isso que torna indesejável importar e ligar main(se isso é possível ou permitido).
Rob Kennedy
3
@ Codificador, se retornar de mainnão tiver o efeito de chamar exitseu compilador, ele não seguirá o padrão. Que o padrão dita tal comportamento de mainprova que não é algo especial sobre ele. O aspecto especial mainé que o retorno dele tem o efeito de chamar exit. ( Como isso depende dos gravadores do compilador. O compilador pode simplesmente inserir código no epílogo da função que destrói objetos estáticos, chama atexitrotinas, libera arquivos e encerra o programa - o que, novamente, não é algo que você deseja em uma biblioteca .)
Rob Kennedy
42

Não é que não maindeva demorar tanto quanto você deve evitar que qualquer função seja muito longa. mainé apenas um caso especial de função. Funções mais longas ficam muito difíceis de grok, diminuem a manutenção e geralmente são mais difíceis de trabalhar. Ao manter as funções (e main) mais curtas, você geralmente melhora a qualidade do seu código.

No seu exemplo, não há nenhum benefício em remover o código main.

Mark B
fonte
9
A palavra de ouro pode ser "reutilização". Um longo mainnão é muito reutilizável.
S.Lott
1
@ S - Essa é uma palavra de ouro. Outro é OMG !!! O TDAH APROVEITOU !!!! ou em termos leigos: legibilidade.
Edward Strange
3
main () também possui algumas restrições que outras funções não possuem.
Martin York
1
Também main () não tem significado real. Seu código deve significar algo para outro programador. Eu uso main para analisar argumentos e é isso - e até delego isso se forem mais do que algumas linhas.
Bill K
@ Bill K: Bom argumento, usar main () apenas para analisar argumentos (e iniciar o restante do programa) também está em conformidade com o princípio de responsabilidade única.
Giorgio
28

Uma razão para main()encurtar envolve testes de unidade. main()é a única função que não pode ser testada em unidade; portanto, faz sentido extrair a maior parte do comportamento para outra classe que pode ser testada em unidade. Isso vai junto com o que você disse

Escrever código modularizado e seguir o princípio de responsabilidade única permitiu que meu código fosse projetado em "grupos", e main () serviu como nada além de um catalisador para executar o programa.

Nota: Eu peguei a ideia daqui .

Chance
fonte
Outra boa. Nunca pensei nesse aspecto.
riwalk
16

Raramente é uma boa ideia maindemorar muito; como em qualquer função (ou método), se for longo, provavelmente você está perdendo oportunidades de refatoração.

No caso específico que você mencionou acima, mainé curto, porque toda essa complexidade é levada em consideração Py_Main; se você quiser que seu código se comporte como um shell python, basta usá-lo sem muita brincadeira. (Tem que ser fatorado assim, porque não funciona bem se você coloca mainuma biblioteca; coisas estranhas acontecem se você o fizer.)

EDIT:
Para esclarecer, mainnão pode estar em uma biblioteca estática porque não possui um link explícito e, portanto, não será vinculado corretamente (a menos que você o coloque em um arquivo de objeto com algo a que se refere, o que é simplesmente horrível !) As bibliotecas compartilhadas geralmente são tratadas como semelhantes (novamente, para evitar confusão), embora em muitas plataformas um fator adicional seja que uma biblioteca compartilhada é apenas um executável sem uma seção de inicialização (da qual mainé apenas a última e mais visível parte). )

Donal Fellows
fonte
1
Em suma, não coloque mainem uma biblioteca. Ou não vai funcionar ou vai confundir você terrivelmente. Mas delegar virtualmente todo o seu trabalho a uma função que está em uma biblioteca, geralmente é sensata.
Donal Fellows
6

Main deve ser curto pelo mesmo motivo que qualquer função deve ser curta. O cérebro humano tem dificuldade em manter grandes quantidades de dados não particionados na memória de uma só vez. Divida-o em partes lógicas para que seja fácil para outros desenvolvedores (assim como você!) Digerir e raciocinar.

E sim, seu exemplo é terrível e difícil de ler, e muito menos manter.

Ed S.
fonte
Sim, eu sempre suspeitei que o código em si era terrível (embora a pergunta fosse sobre a localização do código, não o próprio código). Tenho medo de que minha visão do Python foi inerentemente danificado como resultado ...
riwalk
1
@ stargazer: Não sei se o código em si é terrível, apenas que não está bem organizado para o consumo humano. Dito isto, há muitos códigos "feios" por aí que funcionam bem e têm um ótimo desempenho. A beleza do código não é tudo, embora devamos sempre tentar o nosso melhor para escrever o código mais limpo possível.
Ed S.
meh. Para mim, eles são o mesmo. Código limpo tende a ser mais estável.
precisa
O código não é terrível, principalmente existem casos de switch e o manuseio de várias plataformas. O que exatamente você acha terrível?
Francesco
@Francesco: Desculpe, "Terrível" do ponto de vista de manutenção e legibilidade, não funcional.
Ed S.
1

Algumas pessoas desfrutam de mais de 50 funções que não fazem mais nada, mas fazem uma chamada para outra função. Eu preferiria preferir a função principal normal que faz a lógica do programa principal. Bem estruturado, é claro.

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

Não vejo nenhuma razão para envolver tudo isso dentro de um invólucro.

É puramente um gosto pessoal.

Codificador
fonte
1
Porque o documento é o código. Você pode escrever código dessa maneira sem a necessidade (quase) de escrever comentários. E quando você altera o código, a documentação muda automaticamente :-).
Oliver Weiler
1

É uma prática recomendada manter TODAS as suas funções curtas, não apenas as principais. No entanto, "curto" é subjetivo, depende do tamanho do seu programa e do idioma que você está usando.

Mike Miller
fonte
0

Não há nenhum requisito para mainter qualquer comprimento, além dos padrões de codificação. mainé uma função como qualquer outra e, como tal, sua complexidade deve estar abaixo de 10 (ou seja o que for que seus padrões de codificação digam). É isso aí, qualquer outra coisa é bastante argumentativa.

editar

mainnão deve ser curto. Ou longo. Ele deve incluir a funcionalidade necessária para executar com base em seu design e aderir aos padrões de codificação.

Quanto ao código específico da sua pergunta - sim, é feio.

Quanto à sua segunda pergunta - sim, você está errado . Mover todo esse código de volta para main não permite usá-lo modularmente como uma biblioteca, vinculando Py_Mainde fora.

Agora estou claro?

littleadv
fonte
Não perguntei se pode demorar. Perguntei por que não demoraria muito.
riwalk
"Complexidade abaixo de 10"? Existe uma unidade de medida para isso?
Donal Fellows
@ Stargazer712 O comprimento da função também é normalmente regulado pelos padrões de codificação. É uma questão de legibilidade (e complexidade, geralmente funções longas são ramificadas para que a complexidade esteja muito acima de 20) e, como eu disse - mainnão é diferente de qualquer outra função a esse respeito.
littleadv
@Donal - sim, clique no link.
littleadv
Eu vou ter que diminuir esse voto. Você está perdendo completamente a intenção da pergunta.
riwalk
0

Aqui está um novo motivo pragmático para manter o principal curto do GCC 4.6.1 Changelog :

Na maioria dos destinos com suporte à seção nomeada, as funções usadas apenas na inicialização (construtores estáticos e principal ), as funções usadas apenas na saída e as funções detectadas como frias são colocadas em subseções de segmento de texto separadas . Isso estende o recurso -freorder-functions e é controlado pela mesma opção. O objetivo é melhorar o tempo de inicialização de grandes programas C ++.

Destaque adicionado por mim.

Peter G.
fonte
0

Não presuma que, apenas porque um pouco de software é bom, todo o código por trás desse software é bom. Bom software e bom código não são a mesma coisa e, mesmo quando um bom software é apoiado por um bom código, é inevitável que em um projeto grande haja lugares onde os padrões caem.

É uma boa prática ter uma mainfunção curta , mas esse é realmente apenas um caso especial da regra geral de que é melhor ter funções curtas. As funções curtas são mais fáceis de entender e mais fáceis de depurar, além de serem melhores no tipo de design de "uso único" que torna os programas mais expressivos. mainé, talvez, um lugar mais importante para seguir a regra, já que quem quer entender o programa deve entender, mainenquanto cantos mais obscuros da base de código podem ser visitados com menos frequência.

Mas, a base de código Python não está enviando o código Py_Mainpara o jogo desta regra, mas porque você não pode exportar mainde uma biblioteca nem chamá-lo como uma função.

Jack Aidley
fonte
-1

Existem várias respostas técnicas acima, vamos deixar isso de lado.

Um main deve ser curto porque deve ser uma inicialização. O principal deve instanciar um pequeno número de objetos, geralmente um, que faz o trabalho. Como em qualquer outro lugar, esses objetos devem ser bem projetados, coesos, fracamente acoplados, encapsulados, ...

Embora possa haver razões técnicas para que uma linha principal chame outro método de monstro, em princípio você está correto. Do ponto de vista da engenharia de software, nada foi ganho. Se a escolha for entre uma linha principal chamando um método de monstro e a principal é um método de monstro, o último é fracionariamente menos ruim.

Gannet de concreto
fonte
Você está assumindo que "o código C ++ deve usar objetos e apenas objetos". Isso não é verdade, o C ++ é uma linguagem multiparadigmática e não força tudo em um molde OO, como algumas outras linguagens.
Ben Voigt