『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