『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を続けることで、
バグの入り込む余地が減り、修正や変更に強い、
健康なシステムになっていくのだ。

Permlink


バカの獲物

Copyright © 1905 tko at jitu.org