He estado programando durante más de 9 años, y de acuerdo con el consejo de mi primer maestro de programación, siempre mantengo mi main()
función extremadamente corta.
Al principio no tenía idea de por qué. Simplemente obedecí sin entender, para deleite de mis profesores.
Después de ganar experiencia, me di cuenta de que si diseñaba mi código correctamente, main()
simplemente tenía una función corta . Escribir código modular y siguiendo el principio de la responsabilidad individual permitió que mi código que se ha diseñado en "racimos", y main()
sirvió como nada más que un catalizador para obtener el programa en ejecución.
Un avance rápido hasta hace unas semanas, estaba mirando el código fuente de Python, y encontré la main()
función:
/* Minimal main program -- everything is loaded from the library */
...
int
main(int argc, char **argv)
{
...
return Py_Main(argc, argv);
}
Yay pitón. main()
Función corta == Buen código.
Los maestros de programación tenían razón.
Queriendo mirar más profundo, eché un vistazo a Py_Main. En su totalidad, se define de la siguiente manera:
/* 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;
}
Buen Dios Todopoderoso ... es lo suficientemente grande como para hundir el Titanic.
Parece que Python hizo el truco "Introducción a la programación 101" y simplemente movió todo main()
el código a una función diferente llamada algo muy similar a "main".
Aquí está mi pregunta: ¿está este código terriblemente escrito, o hay otras razones para tener una función principal corta?
Tal como está ahora, no veo absolutamente ninguna diferencia entre hacer esto y simplemente mover el código Py_Main()
nuevamente main()
. ¿Me equivoco al pensar esto?
fuente
options = ParseOptionFlags(argc,argv)
dondeoptions
es unastruct
que contiene las variablesPy_BytesWarningFlag
,Py_DebugFlag
etc ...Respuestas:
No puede exportar
main
desde una biblioteca, pero puede exportarPy_Main
, y cualquiera que use esa biblioteca puede "llamar" a Python muchas veces con diferentes argumentos en el mismo programa. En ese punto, sepython
convierte en otro consumidor de la biblioteca, poco más que un contenedor para la función de la biblioteca; llamaPy_Main
como todos los demás.fuente
main
llamadas efectivasexit
, lo que generalmente no desea que haga una biblioteca.main
tiene el efecto de abandonar la función principal ... y llamarexit
con el valor de retorno como argumento". Consulte también §18.3 / 8, que explica que "los objetos con una duración de almacenamiento estático se destruyen" y "todos los flujos C abiertos ... se vacían" cuando llamaexit
. C99 tiene un lenguaje similar.exit
hojasmain
son irrelevantes. No estamos discutiendo el comportamiento deexit
. Estamos discutiendo el comportamiento demain
. Y el comportamiento demain
incluye el comportamiento deexit
, sea lo que sea. Eso es lo que hace que no sea deseable importar y llamarmain
(si hacer algo así es posible o permitido).main
no tiene el efecto de llamarexit
a su compilador, entonces su compilador no sigue el estándar. Que la norma dicta tal comportamiento demain
prueba que no es algo especial. Lo especialmain
es que regresar de él tiene el efecto de llamarexit
. (La forma en que lo hace depende de los escritores del compilador. El compilador podría simplemente insertar código en el epílogo de la función que destruye objetos estáticos, llama aatexit
rutinas, descarga archivos y termina el programa, lo cual, nuevamente, no es algo que desee en una biblioteca .)No es que no
main
deba ser tanto tiempo como debe evitar que cualquier función sea demasiado larga.main
es solo un caso especial de función. Las funciones más largas se vuelven muy difíciles de asimilar, disminuyen la capacidad de mantenimiento y, en general, son más difíciles de trabajar. Al mantener las funciones (ymain
) más cortas, generalmente mejora la calidad de su código.En su ejemplo, no hay ningún beneficio para mover el código
main
.fuente
main
no es muy reutilizable.Una razón para
main()
abreviar implica pruebas unitarias.main()
es la única función que no se puede probar por unidad, por lo que tiene sentido extraer la mayoría del comportamiento en otra clase que se puede probar por unidad. Esto va de la mano con lo que dijisteNota: Tengo la idea de aquí .
fuente
Rara vez es una buena idea
main
ser larga; Al igual que con cualquier función (o método), si es largo, probablemente pierda oportunidades para refactorizar.En el caso específico que mencionas anteriormente,
main
es breve porque toda esa complejidad se tiene en cuentaPy_Main
; Si desea que su código se comporte como un shell de Python, puede usar ese código sin mucho esfuerzo. (Se debe tener en cuenta de esa manera porque no funciona bien si se colocamain
en una biblioteca; si lo hace, suceden cosas extrañas).EDITAR:
para aclarar,
main
no puede estar en una biblioteca estática porque no tiene un enlace explícito y, por lo tanto, no se vinculará correctamente (a menos que lo coloque en un archivo de objeto con algo a lo que se hace referencia, lo cual es simplemente horrible !) Las bibliotecas compartidas generalmente se tratan como similares (de nuevo, para evitar confusiones), aunque en muchas plataformas un factor adicional es que una biblioteca compartida es solo un ejecutable sin una sección de arranque (de la cualmain
es solo la última parte y la más visible )fuente
main
en una biblioteca. No funcionará o te confundirá terriblemente. Pero delegar virtualmente todo su trabajo a una función que está en una biblioteca, a menudo es sensato.Main debería ser breve por la misma razón que cualquier función debería ser breve. El cerebro humano tiene dificultades para mantener grandes cantidades de datos no particionados en la memoria a la vez. Divídalo en trozos lógicos para que sea más fácil de digerir y razonar para otros desarrolladores (¡y para usted mismo!).
Y sí, su ejemplo es terrible y difícil de leer, y mucho menos mantener.
fuente
Algunas personas disfrutan de más de 50 funciones que no hacen nada más, pero envuelven una llamada a otra función. Prefiero preferir la función principal normal que hace la lógica del programa principal. Bien estructurado por supuesto.
No veo ninguna razón por la que deba envolver algo de eso dentro de una envoltura.
Es puramente un gusto personal.
fuente
Su mejor práctica es mantener TODAS sus funciones cortas, no solo principales. Sin embargo, "corto" es subjetivo, depende del tamaño de su programa y del idioma que esté utilizando.
fuente
No hay ningún requisito para
main
ser de ninguna longitud, aparte de los estándares de codificación.main
es una función como cualquier otra, y como tal, su complejidad debe ser inferior a 10 (o lo que digan sus estándares de codificación). Eso es todo, cualquier otra cosa es bastante argumentativa.editar
main
No debería ser corto. O largo. Debe incluir la funcionalidad que debe realizar según su diseño y cumplir con los estándares de codificación.En cuanto al código específico en su pregunta, sí, es feo.
En cuanto a su segunda pregunta, sí, está equivocado . Mover todo ese código nuevamente a main no le permite usarlo de manera modular como una biblioteca al vincular
Py_Main
desde afuera.Ahora estoy claro?
fuente
main
no es diferente de ninguna otra función a este respecto.Aquí hay una nueva razón pragmática para mantener el principal corto del Registro de cambios de GCC 4.6.1 :
Destacado agregado por mí.
fuente
No asuma que solo porque un poco de software es bueno, todo el código detrás de ese software es bueno. Un buen software y un buen código no son lo mismo e incluso cuando el buen software está respaldado por un buen código, es inevitable que en un proyecto grande haya lugares donde los estándares se desvanezcan.
Es una buena práctica tener una
main
función corta , pero en realidad es solo un caso especial de la regla general de que es mejor tener funciones cortas. Las funciones cortas son más fáciles de entender y más fáciles de depurar, además de ser mejores para apegarse al tipo de diseño de "propósito único" que hace que los programas sean más expresivos.main
es, quizás, un lugar más importante para cumplir con la regla, ya que cualquier persona que quiera comprender el programa debe comprender,main
mientras que los rincones más oscuros de la base de código pueden ser visitados con menos frecuencia.Pero, la base de código de Python no está empujando el código
Py_Main
para jugar esta regla, sino porque no puede exportarmain
desde una biblioteca ni llamarla como una función.fuente
Hay varias respuestas técnicas anteriores, dejemos eso de lado.
Un main debería ser corto porque debería ser un bootstrap. El principal debe instanciar una pequeña cantidad de objetos, a menudo uno, que hacen el trabajo. Al igual que en cualquier otro lugar, esos objetos deben estar bien diseñados, cohesivos, acoplados libremente, encapsulados, ...
Si bien puede haber razones técnicas para que una línea principal de una línea llame a otro método monstruo, en principio está en lo correcto. Desde una perspectiva de ingeniería de software, no se ha ganado nada. Si la elección es entre una línea principal que llama un método monstruo y la principal es un método monstruo, este último es fraccionalmente menos malo.
fuente