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?
fonte
options = ParseOptionFlags(argc,argv)
ondeoptions
é astruct
que contém as variáveisPy_BytesWarningFlag
,Py_DebugFlag
etc ...Respostas:
Você não pode exportar
main
de uma biblioteca, mas pode exportarPy_Main
e qualquer pessoa que utilize essa biblioteca pode "chamar" o Python muitas vezes com argumentos diferentes no mesmo programa. Nesse ponto,python
torna-se apenas mais um consumidor da biblioteca, pouco mais que um invólucro para a função da biblioteca; chamaPy_Main
assim como todo mundo.fonte
main
chamadas efetivasexit
, o que você normalmente não deseja que uma biblioteca faça.main
tem o efeito de deixar a função principal ... e chamarexit
com 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ê chamaexit
. C99 tem linguagem semelhante.exit
folhasmain
são irrelevantes. Não estamos discutindo o comportamento deexit
. Estamos discutindo o comportamento demain
. E o comportamento demain
inclui o comportamento deexit
, o que quer que seja. É isso que torna indesejável importar e ligarmain
(se isso é possível ou permitido).main
não tiver o efeito de chamarexit
seu compilador, ele não seguirá o padrão. Que o padrão dita tal comportamento demain
prova que não é algo especial sobre ele. O aspecto especialmain
é que o retorno dele tem o efeito de chamarexit
. ( 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, chamaatexit
rotinas, libera arquivos e encerra o programa - o que, novamente, não é algo que você deseja em uma biblioteca .)Não é que não
main
deva 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 (emain
) 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
.fonte
main
não é muito reutilizável.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ê disseNota: Eu peguei a ideia daqui .
fonte
Raramente é uma boa ideia
main
demorar 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çãoPy_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ê colocamain
uma biblioteca; coisas estranhas acontecem se você o fizer.)EDIT:
Para esclarecer,
main
nã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 qualmain
é apenas a última e mais visível parte). )fonte
main
em 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.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.
fonte
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.
Não vejo nenhuma razão para envolver tudo isso dentro de um invólucro.
É puramente um gosto pessoal.
fonte
É 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.
fonte
Não há nenhum requisito para
main
ter 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
main
nã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_Main
de fora.Agora estou claro?
fonte
main
não é diferente de qualquer outra função a esse respeito.Aqui está um novo motivo pragmático para manter o principal curto do GCC 4.6.1 Changelog :
Destaque adicionado por mim.
fonte
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
main
funçã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,main
enquanto 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_Main
para o jogo desta regra, mas porque você não pode exportarmain
de uma biblioteca nem chamá-lo como uma função.fonte
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.
fonte