『Unix/Linuxプログラミング 理論と実践』、B.Molay、アスキーより。 原始的なmoreの例が載っている。これをいじる。 /* more01.c - version 0.1 of more * read and print 24 lines then pause for a few special commands */ #include <stdio.h> #define PAGELEN 24 #define LINELEN 512 void do_more(FILE *); int see_more(); int main( int ac , char *av[] ) { FILE *fp; if ( ac == 1 ) do_more( stdin ); else while ( --ac ) if ( (fp = fopen( *++av , "r" )) != NULL ) { do_more( fp ) ; fclose( fp ); } else exit(1); return 0; } void do_more( FILE *fp ) /* * read PAGELEN lines, then call see_more() for further instructions */ { char line[LINELEN]; int num_of_lines = 0; int see_more(), reply; while ( fgets( line, LINELEN, fp ) ){ /* more input */ if ( num_of_lines == PAGELEN ) { /* full screen? */ reply = see_more(); /* y: ask user */ if ( reply == 0 ) /* n: done */ break; num_of_lines -= reply; /* reset count */ } if ( fputs( line, stdout ) == EOF ) /* show line */ exit(1); /* or die */ num_of_lines++; /* count it */ } } int see_more() /* * print message, wait for response, return # of lines to advance * q means no, space means yes, CR means one line */ { int c; printf("\033[7m more? \033[m"); /* reverse on a vt100 */ while( (c=getchar()) != EOF ) /* get response */ { if ( c == 'q' ) /* q -> N */ return 0; if ( c == ' ' ) /* ' ' => next page */ return PAGELEN; /* how many to show */ if ( c == '\n' ) /* Enter key => 1 line */ return 1; } return 0; } まず、いじる前、イの一番にやることはバージョン管理システムに登録するこ と。自分の場合はRCS。 次にやるのは、自分のフォーマッティングにすること。と同時に、不要なコメ ントも消す。コンパイル時の警告も出さないようにする。清掃重要。 /** * more01.c - version 0.1 of more * read and print 24 lines then pause for a few special commands */ #include <stdio.h> #include <stdlib.h> #define PAGELEN 24 #define LINELEN 512 /** * print message, wait for response, return # of lines to advance * q means no, space means yes, CR means one line */ static int see_more(void) { int c; printf("\033[7m more? \033[m"); /* reverse on a vt100 */ while ((c = getchar()) != EOF) { if (c == 'q') return 0; if (c == ' ') return PAGELEN; if (c == '\n') return 1; } return 0; } /** * read PAGELEN lines, then call see_more() for further instructions */ static void do_more(FILE *fp) { char line[LINELEN]; int num_of_lines = 0; int reply; while (fgets(line, LINELEN, fp)) { if (num_of_lines == PAGELEN) { reply = see_more(); if (reply == 0) break; num_of_lines -= reply; } if (fputs(line, stdout) == EOF) exit(1); num_of_lines++; } } int main(int ac , char *av[]) { FILE *fp; if (ac == 1) { do_more(stdin); } else { while (--ac) { if ((fp = fopen(*++av , "r")) != NULL) { do_more(fp); fclose(fp); } else { exit(1); } } } return 0; } コミットしたあと、目につくのがmain。まず、mainの引数の名前を標準的なも のに変えたあと、whileの部分を切り出す。 static void do_more_files(int argc, char *argv[]) { FILE *fp; while (--argc) { if ((fp = fopen(*++argv , "r")) != NULL) { do_more(fp); fclose(fp); } else { exit(1); } } } int main(int argc , char *argv[]) { if (argc == 1) do_more(stdin); else do_more_files(argc, argv); return 0; } しかし、このdo_more_filesのインターフェイスはわかりにくい。なので、わ かりやすいものに変える。 static void do_more_files(char *filenames[], int size) { int i; for (i = 0; i < size; ++i) { FILE *fp; fp = fopen(filenames[i] , "r"); if (!fp) exit(1); do_more(fp); fclose(fp); } } int main(int argc , char *argv[]) { if (argc == 1) do_more(stdin); else do_more_files(argv + 1, argc - 1); return 0; } do_moreとsee_moreには問題があると感じる。 see_moreは仕事が多すぎる。see_moreは、ユーザからの入力をスクロール量に 変換するという仕事をしている。しかし、ユーザからの入力は、実際には『コ マンド』であり、スクロール量ではない。そもそも、see_moreはUI層に位置す るものであり、一方で、スクロール量はロジックやモデルと呼ばれる部分が決 めるべきもの。 ひとまず、see_moreからは『概念的に』コマンドを返すように変える。 enum Command { CMD_QUIT = 0, CMD_LINE = 1, CMD_PAGE = PAGELEN }; /** * print message, wait for response, return # of lines to advance * q means no, space means yes, CR means one line */ static enum Command see_more(void) { int c; printf("\033[7m more? \033[m"); /* reverse on a vt100 */ while ((c = getchar()) != EOF) { if (c == 'q') return CMD_QUIT; if (c == ' ') return CMD_PAGE; if (c == '\n') return CMD_LINE; } return CMD_QUIT; } 次に、do_moreのほうでは、コマンドをスクロール量に変換する。 /** * read PAGELEN lines, then call see_more() for further instructions */ static void do_more(FILE *fp) { char line[LINELEN]; int num_of_lines = 0; while (fgets(line, LINELEN, fp)) { if (num_of_lines == PAGELEN) { enum Command reply; reply = see_more(); if (reply == CMD_LINE) num_of_lines -= 1; else if (reply == CMD_PAGE) num_of_lines -= PAGELEN; else break; } if (fputs(line, stdout) == EOF) exit(1); num_of_lines++; } } しかし、これでもdo_moreにはスッキリしない感じが残る。『指定された行数 だけファイルを読み込んで印字する』という関数があればよさそうだ。 static bool show_text(FILE *fp, int num_of_lines) { int i; for (i = 0; i < num_of_lines; ++i) { char line[LINELEN]; if (!fgets(line, LINELEN, fp)) return false; if (fputs(line, stdout) == EOF) exit(1); } return true; } #include <stdbool.h>を忘れずに。さて、このshow_textを使って、do_moreを 書き換える。 /** * read PAGELEN lines, then call see_more() for further instructions */ static void do_more(FILE *fp) { int num_of_lines = PAGELEN; for (;;) { int reply; if (!show_text(fp, num_of_lines)) return; reply = see_more(); switch (reply) { case CMD_LINE: num_of_lines = 1; break; case CMD_PAGE: num_of_lines = PAGELEN; break; case CMD_QUIT: default: return; } } } 最後にすべてのコードを載せておく。細かいことだが、#defineを消している。 LINELENをリテラルに替えた。 /** * more01.c - version 0.1 of more * read and print 24 lines then pause for a few special commands */ #include <stdbool.h> #include <stdio.h> #include <stdlib.h> enum Command { CMD_QUIT, CMD_LINE, CMD_PAGE }; /** * print message, wait for response, return # of lines to advance * q means no, space means yes, CR means one line */ static enum Command see_more(void) { int c; printf("\033[7m more? \033[m"); /* reverse on a vt100 */ while ((c = getchar()) != EOF) { if (c == 'q') return CMD_QUIT; if (c == ' ') return CMD_PAGE; if (c == '\n') return CMD_LINE; } return CMD_QUIT; } static bool show_text(FILE *fp, int num_of_lines) { int i; for (i = 0; i < num_of_lines; ++i) { char line[512]; if (!fgets(line, sizeof(line), fp)) return false; if (fputs(line, stdout) == EOF) exit(1); } return true; } /** * read PAGELEN lines, then call see_more() for further instructions */ static void do_more(FILE *fp) { const int PAGELEN = 24; int num_of_lines = PAGELEN; for (;;) { enum Command reply; if (!show_text(fp, num_of_lines)) return; reply = see_more(); switch (reply) { case CMD_LINE: num_of_lines = 1; break; case CMD_PAGE: num_of_lines = PAGELEN; break; case CMD_QUIT: default: return; } } } static void do_more_files(char *filenames[], int size) { int i; for (i = 0; i < size; ++i) { FILE *fp; fp = fopen(filenames[i] , "r"); if (!fp) exit(1); do_more(fp); fclose(fp); } } int main(int argc , char *argv[]) { if (argc == 1) do_more(stdin); else do_more_files(argv + 1, argc - 1); return 0; } -- 上のような作業を『リファクタリング』と呼んだりもする。 個人的には『コードを整理する』という言葉を使うほうが 好みではある。 さて、既存の、すでに十分に機能しているコードを、 壊す可能性がありながら、なぜいじくり回すのか。 若者の言葉でいえば『誰得?』というヤツだ。 上のような作業を単純な『コーディング』という見方を していたら、ただの自己マンで終わってしまう。 いじる前と、いじった後では機能は変わらない。変わった のは設計だ。つまり、コードをいじるという行為と設計を よくするという行為が一体となったものがリファクタリングと 呼ばれるものなのだ。 設計という行為と、コーディングという行為がハッキリと 分かれている段階もある。しかし、ある程度コードを 書き進めていくと、新しい発見があり、よりよい設計に 気づくことがある。いや、そのほうがフツウだ。その 『気づき』を無視して、過去の設計にとらわれていると 自縄自縛に陥り、最後に行き着くのは『セカンド・システム 症候群』だ。『新しくゼロから作り直したい』という 衝動を我慢できなくなる。 システムが動いている限り、4S、整理・整頓・清掃・ 清潔はいつまでも続くものなのだ。4Sを続けることで、 バグの入り込む余地が減り、修正や変更に強い、 健康なシステムになっていくのだ。
Copyright © 1905 tko at jitu.org