gotoだらけのFizzBuzz


概要

goto不要論の話をちょくちょく耳にするのですが、
実際goto乱用するとどれくらい読みづらくなるのか気になりました。

ということで、至って一般的なFizzBuzzをgotoを乱用して実装してみよう、と考えた次第です。

通常のFizzBuzz

C言語でFizzBuzzを実装すると普通はこんな感じになるかと思います。
僕は普段C言語を書かないので、今回の記事のためだけにC言語の勉強をしました。
(何やってんだ...)

C言語の勉強をしたのは学生のころなので、もはやすべて記憶の彼方です。

#include <stdio.h>
#include <string.h>

int main(void) {
  const int MAX = 100;
  int i = 1;
  char text[256] = "";

  while (1) {
    if (i % 3 == 0) strcat(text, "Fizz");
    if (i % 5 == 0) strcat(text, "Buzz");
    if (text[0] == '\0') sprintf(text, "%d", i);
    printf("%s\n", text);
    text[0] = '\0';

    if (MAX <= i) {
      break;
    }
    i++;
  }

  return 0;
}

よくあるFizzBuzzだと思います。

よしとけばいいのにこれをgotoだけで実装して見ようと思います。

FizzBuzz with goto

gotoしたものがこちらになります。

#include <stdio.h>
#include <string.h>

int main(void) {
  const int MAX = 100;
  int i = 1;
  char text[256];

loop:
  goto append_fizz; append_fizz_end:
  goto append_buzz; append_buzz_end:
  goto set_number; set_number_end:
  goto print; print_end:
  goto clear_text; clear_text_end:
  goto goto_end; goto_end_end:
  goto increment_counter; increment_counter_end:
  goto loop;

append_fizz:
  if (i % 3 == 0) strcat(text, "Fizz");
  goto append_fizz_end;

append_buzz:
  if (i % 5 == 0) strcat(text, "Buzz");
  goto append_buzz_end;

set_number:
  if (text[0] == '\0') sprintf(text, "%d", i);
  goto set_number_end;

print:
  printf("%s\n", text);
  goto print_end;

clear_text:
  text[0] = '\0';
  goto clear_text_end;

goto_end:
  if (MAX < i) goto end;
  goto goto_end_end;

increment_counter:
  i++;
  goto increment_counter_end;

end:
  return 0;
}

なんだこれは...(困惑)

すべての処理をgotoのブロックにしてしまって、goto -> XXX_end でもとのブロックに戻ってくるという実装にしました。
ループもforのカウンタ変数を使うのではなく、gotoのジャンプ先でカウンタ変数を更新して戻ってきます。

見るからにつらい。

ここまで極端な実装することは無い(と思いたい)ですが、こういうのが乱用されると混乱しそうですね。

まだgotoできる

これで実装終わって満足したんですが、 strcat 関数があることに気づきました。
strcat 関数を呼ぶためだけに string.h ヘッダをinlcudeしてるんですよね。
これ、gotoにできるな、と気づきました。

ということで、さらにgotoすることにしました。

FizzBuzz with goto 2

さらにgotoしたものがこちらになります。

#include <stdio.h>

#define MAX_SIZE 256

int main(void) {
  const int MAX = 100;
  int i = 1;
  int j = 0;
  int k = 0;
  char text1[MAX_SIZE];
  char text2[MAX_SIZE];
  int append_type;
  int unused_var;

loop:
  goto append_fizz; append_fizz_end:
  goto append_buzz; append_buzz_end:
  goto set_number; set_number_end:
  goto print; print_end:
  goto clear_text; clear_text_end:
  goto goto_end; goto_end_end:
  goto increment_counter; increment_counter_end:
  goto loop;

append_fizz:
  if (0 == i % 3) {
    text2[0] = 'F';
    text2[1] = 'i';
    text2[2] = 'z';
    text2[3] = 'z';
    text2[4] = '\0';
    append_type = 1;
    goto strcat; strcat_append_fizz_end:
    unused_var = 1;
  }
  goto append_fizz_end;

append_buzz:
  if (0 == i % 5) {
    text2[0] = 'B';
    text2[1] = 'u';
    text2[2] = 'z';
    text2[3] = 'z';
    text2[4] = '\0';
    append_type = 2;
    goto strcat; strcat_append_buzz_end:
    unused_var = 1;
  }
  goto append_buzz_end;

set_number:
  if ('\0' == text1[0]) sprintf(text1, "%d", i);
  goto set_number_end;

print:
  printf("%s\n", text1);
  goto print_end;

clear_text:
  text1[0] = '\0';
  goto clear_text_end;

goto_end:
  if (MAX < i) goto end;
  goto goto_end_end;

increment_counter:
  i++;
  goto increment_counter_end;

increment_counter_j:
  j++;
  goto increment_counter_j_end;

increment_counter_k:
  k++;
  goto increment_counter_k_end;

strcat:
  j = 0;
strcat_loop_j:
  if ('\0' == text1[j]) {
    k = 0;
  strcat_loop_k:
    text1[j+k] = text2[k];
    goto strcat_break; strcat_break_end:
    goto increment_counter_k; increment_counter_k_end:
    goto strcat_loop_k;
    unused_var = 1;
  }
  goto increment_counter_j; increment_counter_j_end:
  goto strcat_loop_j;

strcat_loop_break:
  if (1 == append_type) goto strcat_append_fizz_end;
  if (2 == append_type) goto strcat_append_buzz_end;

strcat_break:
  if ('\0' == text2[k]) goto strcat_loop_break;
  goto strcat_break_end;

end:
  return 0;
}

なんだこれは...(困惑)

コードにつらみしか無い。
読むのが辛い。
実装するのも辛かった。

gotoから変数を参照するために、関数先頭で使う変数を全部宣言してgoto先でループカウンタ変数のjとkを更新して回ったり、
条件分岐の最後の文がlabelで終了できないので、コンパイルを通すためだけに unused_var = 1 だけしたりと、もはややけくそ。

一応コンパイルできますし、動きます。

⟩ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

⟩ gcc goto4.c

⟩ ./a.out | head -n 20

2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

ループ変数 i が 1 のときだけ違う文字が格納されちゃってるのですが、
もうこの実装を頑張って直すのが辛くなったので諦めました。

まとめ

  • goto乱用、ダメ、絶対