Ich programmiere seit über 9 Jahren und gemäß dem Rat meines ersten Programmierlehrers halte ich meine main()
Funktion immer extrem kurz.
Anfangs hatte ich keine Ahnung warum. Ich habe nur ohne Verständnis gehorcht, sehr zur Freude meiner Professoren.
Nachdem ich Erfahrung main()
gesammelt hatte , stellte ich fest, dass es bei korrektem Design meines Codes zu einer Art Kurzfunktion kam. Durch das Schreiben von modularisiertem Code und das Befolgen des Grundsatzes der Einzelverantwortung konnte mein Code in "Blöcken" entworfen werden und main()
diente lediglich als Katalysator, um das Programm zum Laufen zu bringen.
Vor ein paar Wochen habe ich mir den Quellcode von Python angesehen und die folgende main()
Funktion gefunden:
/* Minimal main program -- everything is loaded from the library */
...
int
main(int argc, char **argv)
{
...
return Py_Main(argc, argv);
}
Ja, Python. Kurzfunktion main()
== Guter Code.
Programmierlehrer hatten recht.
Um genauer hinzuschauen, habe ich mir Py_Main angesehen. In seiner Gesamtheit ist es wie folgt definiert:
/* 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;
}
Guter Gott, Allmächtiger ... es ist groß genug, um die Titanic zu versenken.
Es scheint, als hätte Python den Trick "Intro to Programming 101" ausgeführt und den gesamten main()
Code auf eine andere Funktion verschoben , die "main" sehr ähnlich ist.
Ist hier meine Frage: Ist dieser Code schrecklich geschrieben, oder gibt es andere Gründe, eine kurze Hauptfunktion zu haben?
Im Moment sehe ich absolut keinen Unterschied, ob ich das mache oder den Code einfach Py_Main()
wieder hineinschiebe main()
. Liege ich falsch, wenn ich das denke?
quelle
options = ParseOptionFlags(argc,argv)
wooptions
einestruct
, die die Variablen enthältPy_BytesWarningFlag
,Py_DebugFlag
usw ...Antworten:
Sie können nicht
main
aus einer Bibliothek exportieren , aber Sie können exportierenPy_Main
, und dann kann jeder, der diese Bibliothek verwendet, Python mit verschiedenen Argumenten im selben Programm mehrmals "aufrufen". An diesem Punktpython
wird nur ein weiterer Verbraucher der Bibliothek, kaum mehr als ein Wrapper für die Bibliotheksfunktion; es ruftPy_Main
genau wie alle anderen.quelle
main
effektiven Aufrufenexit
, die normalerweise von einer Bibliothek nicht ausgeführt werden sollen.main
bewirkt, dass die Hauptfunktion verlassen wird ... undexit
der Rückgabewert als Argument verwendet wird." Siehe auch §18.3 / 8, in dem erklärt wird, dass "Objekte mit statischer Speicherdauer zerstört werden" und "alle offenen C-Streams ... gelöscht werden", wenn Sie anrufenexit
. C99 hat eine ähnliche Sprache.exit
Blättermain
sind irrelevant. Wir diskutieren nicht das Verhalten vonexit
. Wir diskutieren das Verhalten vonmain
. Und das Verhalten vonmain
schließt das Verhalten von einexit
, was auch immer das sein mag. Das macht es unerwünscht zu importieren und anzurufenmain
(wenn so etwas überhaupt möglich oder erlaubt ist).main
nicht den Effekt hat, dassexit
Ihr Compiler aufgerufen wird, folgt Ihr Compiler nicht dem Standard. Dass der Standard ein solches Verhalten diktiert fürmain
beweist , dass es ist etwas Besonderes. Das Besondere daranmain
ist, dass die Rückkehr von dort den Effekt hat, anzurufenexit
. ( Wie das geht, liegt bei den Compiler-Autoren. Der Compiler könnte einfach Code in den Funktionsepilog einfügen, der statische Objekte zerstört,atexit
Routinen aufruft , Dateien löscht und das Programm beendet - was wiederum nicht in einer Bibliothek gewünscht ist .)Es ist nicht so, dass
main
es nicht so lange dauern sollte, sondern Sie sollten vermeiden, dass eine Funktion zu lang ist.main
ist nur ein spezieller Funktionsfall. Längere Funktionen sind sehr schwer zu fassen, beeinträchtigen die Wartbarkeit und sind im Allgemeinen schwieriger zu bearbeiten. Wenn Sie die Funktionen (undmain
) kürzer halten, verbessern Sie im Allgemeinen die Qualität Ihres Codes.In Ihrem Beispiel hat es überhaupt keinen Vorteil, den Code zu entfernen
main
.quelle
main
ist nicht sehr wiederverwendbar.Ein Grund für eine
main()
Verkürzung ist das Testen von Einheiten.main()
Da diese Funktion nicht Unit-getestet werden kann, ist es sinnvoll, den größten Teil des Verhaltens in eine andere Klasse zu extrahieren, die Unit-getestet werden kann. Das stimmt mit dem überein, was Sie gesagt habenHinweis: Ich habe die Idee von hier .
quelle
Es ist selten eine gute Idee
main
, lange zu sein. Wie bei jeder Funktion (oder Methode), bei der es sich um eine lange Zeit handelt, verpassen Sie wahrscheinlich Möglichkeiten zur Umgestaltung.In dem speziellen Fall, den Sie oben erwähnt haben,
main
ist es kurz, weil all diese Komplexität berücksichtigt wirdPy_Main
. Wenn Sie möchten, dass sich Ihr Code wie eine Python-Shell verhält, können Sie diesen Code einfach verwenden, ohne viel herumzuspielen. (Es muss so berücksichtigt werden, weil es nicht gut funktioniert, wenn Siemain
eine Bibliothek anlegen . Seltsame Dinge passieren, wenn Sie dies tun.)BEARBEITEN:
Zur Verdeutlichung:
main
Kann nicht in einer statischen Bibliothek vorhanden sein, da keine explizite Verknüpfung vorhanden ist und daher nicht korrekt eingebunden wird (es sei denn, Sie ordnen sie in einer Objektdatei mit etwas an, auf das verwiesen wird, was einfach schrecklich ist !) Gemeinsam genutzte Bibliotheken werden normalerweise als ähnlich behandelt (um Verwirrung zu vermeiden). Auf vielen Plattformen ist ein zusätzlicher Faktor, dass eine gemeinsam genutzte Bibliothek nur eine ausführbare Datei ohne einen Bootstrap-Abschnitt ist (von denenmain
nur der letzte und sichtbarste Teil ist) ).quelle
main
in eine Bibliothek stecken. Entweder wird es nicht funktionieren oder es wird Sie schrecklich verwirren. Aber es ist oft sinnvoll, praktisch alle Arbeiten an eine Funktion zu delegieren, die sich in einer Bibliothek befindet.Die Hauptleitung sollte aus dem gleichen Grund kurz sein, aus dem auch jede Funktion kurz sein sollte. Dem menschlichen Gehirn fällt es schwer, große Mengen nicht partitionierter Daten gleichzeitig im Speicher zu halten. Teilen Sie es in logische Teile auf, damit andere Entwickler (und auch Sie selbst!) Es leicht verstehen und überlegen können.
Und ja, Ihr Beispiel ist schrecklich und schwer zu lesen, geschweige denn zu pflegen.
quelle
Einige Leute genießen mehr als 50 Funktionen, die nichts anderes tun, als einen Aufruf einer anderen Funktion zu beenden. Ich würde eher eine normale Hauptfunktion vorziehen, die die Hauptprogrammlogik ausführt. Natürlich gut strukturiert.
Ich sehe keinen Grund, warum ich irgendetwas davon in einen Umschlag packen sollte.
Es ist nur ein persönlicher Geschmack.
quelle
Es wird empfohlen, ALLE Funktionen kurz zu halten, nicht nur die wichtigsten. "Short" ist jedoch subjektiv, es hängt von der Größe Ihres Programms und der Sprache ab, die Sie verwenden.
quelle
Es ist nicht erforderlich
main
, dass Sie eine andere Länge als die Codierungsstandards haben.main
ist eine Funktion wie jede andere, und als solche sollte ihre Komplexität unter 10 liegen (oder was auch immer Ihre Codierungsstandards aussagen). Das war's auch schon, alles andere ist eher umstritten.bearbeiten
main
sollte nicht kurz sein. Oder lange. Es sollte die Funktionen enthalten, die für die Ausführung auf der Grundlage Ihres Designs erforderlich sind, und die Kodierungsstandards einhalten.Bezüglich des spezifischen Codes in Ihrer Frage - ja, es ist hässlich.
Bezüglich Ihrer zweiten Frage - ja, Sie liegen falsch . Wenn Sie den gesamten Code zurück in main verschieben, können Sie ihn nicht als Bibliothek verwenden, indem Sie
Py_Main
von außen verlinken .Jetzt bin ich klar?
quelle
main
in dieser Hinsicht nicht von jeder anderen Funktion.Hier ist ein neuer pragmatischer Grund, um die Hauptsache aus dem GCC 4.6.1-Changelog kurz zu halten :
Hervorhebung von mir hinzugefügt.
quelle
Gehen Sie nicht davon aus, dass der gesamte Code hinter dieser Software gut ist, nur weil ein bisschen Software gut ist. Gute Software und guter Code sind nicht dasselbe, und selbst wenn gute Software von gutem Code unterstützt wird, ist es unvermeidlich, dass es in einem großen Projekt Stellen gibt, an denen Standards ins Wanken geraten.
Es ist eine gute Praxis, eine Kurzfunktion zu haben
main
, aber das ist eigentlich nur ein Sonderfall der allgemeinen Regel, dass es besser ist, Kurzfunktionen zu haben. Kurze Funktionen sind leichter zu verstehen und leichter zu debuggen sowie besser in der Lage, sich an das Design für einen einzigen Zweck zu halten, das Programme ausdrucksvoller macht.main
ist vielleicht ein wichtigerer Ort, um sich an die Regel zu halten, da jeder, der das Programm verstehen will, verstehen muss,main
während undurchsichtigere Ecken der Codebasis möglicherweise weniger häufig besucht werden.Die Python-Codebasis schiebt den Code jedoch nicht nach außen,
Py_Main
um diese Regel abzuspielen, sondern weil Sie ihn wedermain
aus einer Bibliothek exportieren noch als Funktion aufrufen können.quelle
Es gibt mehrere technische Antworten oben, lassen Sie das beiseite.
Ein Main sollte kurz sein, da es ein Bootstrap sein sollte. Das Hauptfenster sollte eine kleine Anzahl von Objekten instanziieren, oft eines, die die Arbeit erledigen. Wie überall sollten diese Objekte gut gestaltet, zusammenhängend, lose gekoppelt, eingekapselt, ... sein.
Es kann zwar technische Gründe geben, eine einzeilige Hauptmethode als eine andere Monstermethode zu bezeichnen, aber im Prinzip haben Sie recht. Aus Sicht der Softwareentwicklung wurde nichts erreicht. Wenn die Wahl zwischen einer einzeiligen Hauptmethode, die eine Monstermethode aufruft, und der Hauptmethode selbst, die eine Monstermethode ist, ist die letztere weniger schlecht.
quelle