Saya telah pemrograman selama lebih dari 9 tahun, dan menurut saran dari guru pemrograman pertama saya, saya selalu menjaga main()
fungsi saya sangat singkat.
Awalnya saya tidak tahu kenapa. Saya hanya patuh tanpa pengertian, sangat menyenangkan para profesor saya.
Setelah mendapatkan pengalaman, saya menyadari bahwa jika saya mendesain kode saya dengan benar, memiliki main()
fungsi pendek terjadi saja. Menulis kode termodulasi dan mengikuti prinsip tanggung jawab tunggal memungkinkan kode saya dirancang dalam "tandan", dan main()
berfungsi sebagai katalis untuk menjalankan program.
Maju cepat ke beberapa minggu yang lalu, saya melihat kode sumber Python, dan saya menemukan main()
fungsinya:
/* Minimal main program -- everything is loaded from the library */
...
int
main(int argc, char **argv)
{
...
return Py_Main(argc, argv);
}
Yay python. main()
Fungsi pendek == Kode yang baik.
Memprogram guru benar.
Ingin melihat lebih dalam, saya melihat Py_Main. Secara keseluruhan, didefinisikan sebagai berikut:
/* 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;
}
Ya Tuhan Yang Maha Kuasa ... itu cukup besar untuk menenggelamkan Titanic.
Tampaknya seolah-olah Python melakukan trik "Pengenalan Pemrograman 101" dan baru saja memindahkan semua main()
kode ke fungsi yang berbeda, menyebutnya sesuatu yang sangat mirip dengan "utama".
Inilah pertanyaan saya: Apakah kode ini ditulis dengan sangat buruk, atau adakah alasan lain untuk memiliki fungsi utama yang singkat?
Seperti berdiri sekarang, saya sama sekali tidak melihat perbedaan antara melakukan ini dan hanya memindahkan kode Py_Main()
kembali ke main()
. Apakah saya salah dalam memikirkan ini?
sumber
options = ParseOptionFlags(argc,argv)
manaoptions
adalahstruct
yang berisi variabelPy_BytesWarningFlag
,Py_DebugFlag
, dll ...Jawaban:
Anda tidak dapat mengekspor
main
dari perpustakaan, tetapi Anda dapat mengeksporPy_Main
, dan siapa pun yang menggunakan perpustakaan itu dapat "memanggil" Python berkali-kali dengan argumen berbeda dalam program yang sama. Pada saat itu,python
menjadi sekadar konsumen perpustakaan, sedikit lebih dari sekadar pembungkus untuk fungsi perpustakaan; itu panggilanPy_Main
sama seperti orang lain.sumber
main
panggilan efektifexit
, yang biasanya Anda tidak ingin perpustakaan lakukan.main
memiliki efek meninggalkan fungsi utama ... dan memanggilexit
dengan nilai kembali sebagai argumen." Juga lihat §18.3 / 8, yang menjelaskan bahwa "objek dengan durasi penyimpanan statis dihancurkan" dan "semua aliran C yang terbuka ... dibilas" ketika Anda meneleponexit
. C99 memiliki bahasa yang mirip.exit
daunmain
tidak relevan. Kami tidak membahas perilakuexit
. Kami sedang mendiskusikan perilakumain
. Dan perilakumain
termasuk perilakuexit
, apa pun itu. Itulah yang membuatnya tidak diinginkan untuk mengimpor dan meneleponmain
(jika melakukan hal seperti itu bahkan mungkin atau diizinkan).main
tidak memiliki efek memanggilexit
kompiler Anda, maka kompiler Anda tidak mengikuti standar. Bahwa standar mendikte perilaku tersebut untukmain
membuktikan bahwa ada adalah sesuatu yang istimewa tentang hal itu. Hal khusus tentang itumain
adalah bahwa kembali darinya memiliki efek meneleponexit
. ( Cara melakukannya tergantung pada penulis kompiler. Kompilator dapat dengan mudah menyisipkan kode dalam fungsi epilog yang menghancurkan objek statis, memanggilatexit
rutin, mem-flush file, dan menghentikan program - yang, sekali lagi, bukan sesuatu yang Anda inginkan di perpustakaan .)Ini bukan berarti bahwa
main
tidak boleh lama begitu banyak seperti Anda harus menghindari setiap fungsi menjadi terlalu lama.main
hanyalah kasus fungsi khusus. Fungsi yang lebih lama menjadi sangat sulit untuk grok, mengurangi rawatan, dan umumnya lebih sulit untuk dikerjakan. Dengan menjaga fungsi (danmain
) lebih pendek, Anda biasanya meningkatkan kualitas kode Anda.Dalam contoh Anda tidak ada manfaat sama sekali untuk mengeluarkan kode
main
.sumber
main
tidak bisa digunakan kembali.Salah satu alasan untuk membuat
main()
pendek melibatkan pengujian unit.main()
adalah satu fungsi yang tidak bisa diuji unit, jadi masuk akal untuk mengekstrak mayoritas perilaku ke dalam kelas lain yang bisa diuji unit. Ini sejalan dengan apa yang Anda katakanCatatan: Saya mendapat ide dari sini .
sumber
Jarang sekali ide yang bagus untuk
main
menjadi panjang; seperti halnya fungsi (atau metode) apa pun jika itu lama Anda mungkin kehilangan peluang untuk refactoring.Dalam kasus khusus yang Anda sebutkan di atas,
main
singkat karena semua kompleksitas diperhitungkanPy_Main
; jika Anda ingin kode Anda berperilaku seperti shell python, Anda bisa menggunakan kode itu tanpa banyak mengotak-atik. (Itu harus diperhitungkan seperti itu karena tidak berfungsi dengan baik jika Anda menempatkanmain
di perpustakaan; hal-hal aneh terjadi jika Anda melakukannya.)EDIT:
Untuk memperjelas,
main
tidak dapat berada di perpustakaan statis karena tidak memiliki tautan eksplisit ke sana dan karenanya tidak akan ditautkan dengan benar (kecuali Anda menempatkannya di file objek dengan sesuatu yang dirujuk, yang hanya mengerikan !) Pustaka bersama biasanya diperlakukan sama (sekali lagi, untuk mencegah kebingungan) meskipun pada banyak platform faktor tambahan adalah bahwa pustaka bersama hanya dapat dieksekusi tanpa bagian bootstrap (yangmain
hanya merupakan bagian terakhir dan paling terlihat ).sumber
main
di perpustakaan. Entah itu tidak akan berhasil atau itu akan membingungkan Anda. Tapi mendelegasikan hampir semua pekerjaannya ke fungsi yang ada di lib, itu sering masuk akal.Utama harus pendek karena alasan yang sama bahwa fungsi apa pun harus pendek. Otak manusia mengalami kesulitan menyimpan sejumlah besar data yang tidak dipartisi dalam memori sekaligus. Pecah menjadi potongan-potongan logis sehingga mudah bagi pengembang lain (dan juga Anda sendiri!) Untuk dicerna dan dipikirkan.
Dan ya, teladan Anda sangat buruk dan sulit dibaca, apalagi dipertahankan.
sumber
Beberapa orang menikmati 50+ fungsi yang tidak melakukan apa-apa lagi, tetapi membungkus panggilan ke fungsi lain. Saya lebih suka fungsi utama normal yang melakukan logika program utama. Tentu saja terstruktur dengan baik.
Saya tidak melihat alasan mengapa saya harus membungkusnya dengan bungkus.
Ini murni selera pribadi.
sumber
Praktik terbaik untuk menjaga SEMUA fungsi Anda tetap singkat, bukan hanya utama. Namun "pendek" itu subjektif, tergantung pada ukuran program Anda dan bahasa yang Anda gunakan.
sumber
Tidak ada persyaratan
main
apa pun untuk apa pun, selain standar pengkodean.main
adalah fungsi seperti yang lainnya, dan kompleksitasnya harus di bawah 10 (atau apa pun standar pengkodean Anda katakan). Itu saja, yang lain agak argumentatif.sunting
main
seharusnya tidak pendek. Atau panjang. Ini harus mencakup fungsionalitas yang diperlukan untuk dilakukan berdasarkan desain Anda, dan mematuhi standar pengkodean.Mengenai kode spesifik dalam pertanyaan Anda - ya, itu jelek.
Mengenai pertanyaan kedua Anda - ya, Anda salah . Memindahkan semua kode itu kembali ke main tidak memungkinkan Anda menggunakannya modulary sebagai perpustakaan dengan menautkan
Py_Main
dari luar.Sekarang apakah saya jelas?
sumber
main
tidak ada bedanya dengan fungsi lain dalam hal ini.Berikut ini alasan pragmatis baru juga tetap pendek dari GCC 4.6.1 Changelog utama :
Sorotan ditambahkan oleh saya.
sumber
Jangan berasumsi bahwa hanya karena sedikit perangkat lunak baik, semua kode di belakang perangkat lunak itu baik. Perangkat lunak yang baik dan kode yang baik bukan hal yang sama dan bahkan di mana perangkat lunak yang baik didukung oleh kode yang baik, tidak dapat dihindari bahwa dalam proyek besar akan ada tempat di mana standar tergelincir.
Ini adalah praktik yang baik untuk memiliki
main
fungsi pendek , tetapi itu benar-benar hanya kasus khusus dari aturan umum bahwa lebih baik memiliki fungsi pendek. Fungsi pendek lebih mudah dipahami dan lebih mudah untuk di-debug serta lebih baik berpegang pada jenis desain 'tujuan tunggal' yang membuat program lebih ekspresif.main
mungkin merupakan tempat yang lebih penting untuk tetap berpegang pada aturan karena siapa pun yang ingin memahami program harus memahamimain
sementara sudut kode basis yang lebih tidak jelas mungkin lebih jarang dikunjungi.Tapi, basis kode Python tidak mendorong kode untuk
Py_Main
menjalankan aturan ini, tetapi karena Anda tidak bisa mengekspormain
dari perpustakaan atau menyebutnya sebagai fungsi.sumber
Ada beberapa jawaban teknis di atas, mari kita kesampingkan itu.
Main harus pendek karena harus bootstrap. Yang utama harus instantiate sejumlah kecil objek, sering satu, yang melakukan pekerjaan. Seperti di tempat lain, benda-benda itu harus dirancang dengan baik, kohesif, longgar digabungkan, dienkapsulasi, ...
Meskipun mungkin ada alasan teknis untuk memiliki panggilan utama satu-line metode monster lain, pada prinsipnya Anda benar. Dari perspektif rekayasa perangkat lunak, tidak ada yang diperoleh. Jika pilihannya adalah antara satu baris utama yang memanggil metode monster, dan utama itu sendiri adalah metode monster, yang terakhir fraksinya kurang buruk.
sumber