Je programme depuis plus de 9 ans et, selon les conseils de mon premier professeur de programmation, ma main()
fonction est toujours extrêmement courte.
Au début, je ne savais pas pourquoi. J'ai juste obéi sans comprendre, pour le plus grand plaisir de mes professeurs.
Après avoir acquis de l'expérience, je me suis rendu compte que si je concevais correctement mon code, il main()
me suffisait d' avoir une fonction courte . Ecrire du code modularisé et suivre le principe de responsabilité unique a permis de concevoir mon code par "grappes" et de ne main()
servir que de catalyseur pour faire fonctionner le programme.
Il y a quelques semaines, je regardais le code source de Python et j'ai trouvé la main()
fonction:
/* Minimal main program -- everything is loaded from the library */
...
int
main(int argc, char **argv)
{
...
return Py_Main(argc, argv);
}
Yay python. main()
Fonction courte == Bon code.
Les professeurs de programmation avaient raison.
Voulant regarder plus en profondeur, j'ai jeté un coup d'œil à Py_Main. Dans son intégralité, il est défini comme suit:
/* 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;
}
Bon Dieu tout-puissant ... il est assez grand pour couler le Titanic.
Il semble que Python ait utilisé l'astuce "Introduction à la programmation 101" et ait simplement déplacé main()
le code de tout le système vers une fonction différente, appelée quelque chose de très similaire à "principal".
Voici ma question: ce code est-il terriblement écrit ou y a-t-il d'autres raisons d'avoir une fonction principale courte?
En l'état actuel des choses, je ne vois absolument aucune différence entre le faire et simplement Py_Main()
réintégrer le code main()
. Ai-je tort de penser cela?
la source
options = ParseOptionFlags(argc,argv)
oùoptions
est unstruct
qui contient les variablesPy_BytesWarningFlag
,Py_DebugFlag
etc ...Réponses:
Vous ne pouvez pas exporter
main
depuis une bibliothèque, mais vous pouvez exporterPy_Main
. Toute personne utilisant cette bibliothèque peut "appeler" Python plusieurs fois avec des arguments différents dans le même programme. À ce stade,python
devient juste un autre consommateur de la bibliothèque, un peu plus qu'un wrapper pour la fonction de bibliothèque; il appellePy_Main
comme tout le monde.la source
main
appelsexit
, ce que vous ne voulez généralement pas faire par une bibliothèque.main
a pour effet de laisser la fonction principale… et d'appelerexit
avec la valeur de retour comme argument." Voir également les § 18.3 / 8, qui expliquent que "les objets dont la durée de stockage est statique sont détruits" et que "tous les flux C ouverts… sont vidés" lors de l'appelexit
. C99 a un langage similaire.exit
feuillesmain
soient sans importance. Nous ne discutons pas du comportement deexit
. Nous discutons du comportement demain
. Et le comportement demain
inclut le comportement deexit
, quel qu'il soit. C'est ce qui rend indésirable l'importation et l'appelmain
(si cela est même possible ou autorisé).main
n'a pas pour effet d'appelerexit
votre compilateur, celui-ci ne suit pas la norme. Que la norme dicte un tel comportement pourmain
prouve qu'il est quelque chose de spécial à ce sujet. La particularitémain
est que le retour de celui-ci a pour effet d'appelerexit
. ( Comment cela se fait-il aux rédacteurs du compilateur? Le compilateur pourrait simplement insérer du code dans l'épilogue de fonction qui détruit les objets statiques, appelle desatexit
routines, vide les fichiers et termine le programme - ce qui, encore une fois, n'est pas ce que vous voulez dans une bibliothèque .)Ce n'est pas que ça ne
main
devrait pas être long, mais éviter que toute fonction soit trop longue.main
est juste un cas particulier de fonction. Les fonctions plus longues deviennent très difficiles à maîtriser, réduisent la facilité de maintenance et sont généralement plus difficiles à utiliser. En gardant les fonctions (etmain
) plus courtes, vous améliorez généralement la qualité de votre code.Dans votre exemple, supprimer le code ne présente aucun avantage
main
.la source
main
n'est pas très réutilisable.Une des raisons de faire
main()
court implique des tests unitaires.main()
est la seule fonction qui ne peut pas être testée par unité, il est donc logique d'extraire la majorité du comportement dans une autre classe pouvant être testée par unité. Cela va avec ce que vous avez ditNote: j'ai eu l'idée d' ici .
la source
C'est rarement une bonne idée
main
d'être long; comme avec n'importe quelle fonction (ou méthode) si elle est longue, vous manquez probablement des opportunités de refactoring.Dans le cas spécifique que vous avez mentionné ci-dessus, il
main
est court car toute cette complexité est prise en comptePy_Main
; si vous voulez que votre code se comporte comme un shell Python, vous pouvez simplement utiliser ce code sans trop de manipulations. (Cela doit être pris en compte comme ça parce que cela ne fonctionne pas bien si vous mettezmain
dans une bibliothèque; des choses étranges se produisent si vous le faites.)EDIT:
Pour clarifier,
main
ne peut pas être dans une bibliothèque statique car elle n’a pas de lien explicite avec elle et ne sera donc pas liée correctement (sauf si vous colocalisez-le dans un fichier objet avec quelque chose qui est mentionné, ce qui est horrible !) Les bibliothèques partagées sont généralement traitées comme étant similaires (encore une fois, pour éviter toute confusion), bien que sur de nombreuses plates-formes, un facteur supplémentaire réside dans le fait qu’une bibliothèque partagée n’est qu’un exécutable sans section d’amorçage (dontmain
la partie la plus visible est la plus visible). ).la source
main
dans une bibliothèque. Ca ne marchera pas ou ça vous dérangera terriblement. Mais déléguer la quasi-totalité de son travail à une fonction qui se trouve dans une bibliothèque est souvent judicieux.Main doit être court pour la même raison que toute fonction doit être courte. Le cerveau humain a du mal à conserver en mémoire de grandes quantités de données non partitionnées. Découpez-le en morceaux logiques pour qu'il soit facile pour les autres développeurs (ainsi que pour vous-même!) De digérer et de raisonner.
Et oui, votre exemple est terrible et difficile à lire, encore moins à maintenir.
la source
Certaines personnes apprécient plus de 50 fonctions qui ne font rien d’autre, mais qui encapsulent l’appel d’une autre fonction. Je préférerais plutôt la fonction principale normale qui effectue la logique du programme principal. Bien structuré bien sûr.
Je ne vois aucune raison pour laquelle je devrais envelopper quoi que ce soit à l'intérieur d'une enveloppe.
C'est purement un goût personnel.
la source
Il est recommandé de garder TOUTES vos fonctions courtes, pas seulement principales. Cependant, "court" est subjectif, cela dépend de la taille de votre programme et du langage que vous utilisez.
la source
Il n'est pas nécessaire
main
d'être d'une longueur quelconque, autre que les normes de codage.main
est une fonction comme une autre, et en tant que telle, sa complexité devrait être inférieure à 10 (ou quoi que disent vos normes de codage). Ça y est, toute autre chose est plutôt argumentative.modifier
main
ne devrait pas être court. Ou long. Il doit inclure les fonctionnalités qu'il est nécessaire d'exécuter en fonction de votre conception et respecter les normes de codage.En ce qui concerne le code spécifique dans votre question - oui, c'est moche.
En ce qui concerne votre deuxième question - oui, vous avez tort . Remettre tout ce code dans main ne vous permet pas de l'utiliser comme bibliothèque en liant
Py_Main
depuis l'extérieur.Maintenant, je suis clair?
la source
main
n’est pas différent de toute autre fonction à cet égard.Voici une nouvelle raison pragmatique pour rester trop court du Changelog GCC 4.6.1 :
Mise en surbrillance ajoutée par moi.
la source
Ne supposez pas que, simplement parce qu’un logiciel est bon, tout le code qui le sous-tend est bon. Un bon logiciel et un bon code ne sont pas la même chose et même lorsqu'un bon logiciel est soutenu par un bon code, il est inévitable que dans un grand projet, il y ait des endroits où les normes glissent.
C'est une bonne pratique d'avoir une
main
fonction courte , mais il ne s'agit en réalité que d'un cas particulier de la règle générale selon laquelle il est préférable d'avoir des fonctions courtes. Les fonctions courtes sont plus faciles à comprendre et à déboguer, tout en permettant de mieux s'en tenir au type de conception «à usage unique» qui rend les programmes plus expressifs.main
C’est peut-être un lieu plus important de respecter la règle puisque quiconque veut comprendre le programme doit comprendre,main
alors que des recoins plus obscurs de la base de code peuvent être visités moins souvent.Cependant, la base de code Python ne pousse pas le code
Py_Main
pour jouer cette règle, mais parce que vous ne pouvez pas exportermain
depuis une bibliothèque ni l'appeler en tant que fonction.la source
Il y a plusieurs réponses techniques ci-dessus, laissez cela de côté.
Une main devrait être courte parce que ce devrait être un bootstrap. Le principal doit instancier un petit nombre d'objets, souvent un, qui font le travail. Comme partout ailleurs, ces objets doivent être bien conçus, cohérents, faiblement couplés, encapsulés, etc.
Bien qu’il puisse y avoir des raisons techniques à avoir une ligne principale pour appeler une autre méthode monstre, en principe, vous avez raison. Du point de vue du génie logiciel, rien n’a été gagné. Si vous avez le choix entre une ligne principale appelant une méthode monstre, et main étant elle-même une méthode monstre, cette dernière est légèrement moins mauvaise.
la source