Python
FizzBuzz
勉強会
python3
モブプログラミング

FizzBuzzをテーマにしたモブプログラミング社内勉強会を開催しました

株式会社アイリッジの@aimofです。アイリッジ社内で(主に若手向け)モブプログラミング勉強会を主催したのでご報告がてらまとめておきます。今回はプロダクションコードではなく、一般的なコード(FizzBuzz)を使用しました。この記事はアイリッジ社内で共有した文書のリライトになります。

勉強会の目的

社内向けの目的

  • 若手同士が集まって研鑽する場が欲しかった
  • コードの書き方、知見、文化を若手が学ぶのに役立つから
  • 新しめ(と言ってもそこまでではないが)の手法を試すことで知見が広がるから
  • 技術広報的に話題になっていることをやるのは役立つから

個人的な目的

  • 私がアイリッジに転職したてだったので、社内の文化や他の社員のレベル感を知りたかった
  • モブプログラミングやってみたかった
  • とりあえず勉強会が開きたかった

モブプログラミングの雰囲気

出入り自由にしたので変動はあったもののおおよそ4~5人で推移し2時間実施しました。最初から最後まで参加していたメンバーが3人です。

ドライバー以外の全員であーでもないこーでもないと頭を付き合わせて考える形になるため、勘違いや間違いにすぐ気付けるのがよかったです。(修正されないバグはあったけど:後述)。

メンバー同士が一体感を持って取り組めたかと思います。

テーマ

今回用意したテーマがこちらです。

完成するたびに要件が追加されていくFizzBuzz

基本ルール

1から100までの数について、

  • 3と5の倍数である時はFizzBuzzを出力する
  • 3の倍数だが5の倍数でない時はFizzを出力する
  • 3の倍数でないが5の倍数である時はBuzzを出力する
  • 3の倍数でも5の倍数でもない時はその数字を出力する

要件の追加について

あらかじめ用意した要件から一枚を引き、その要件を追加していく。

今回は、箱とペンを置いて自由に要件を書いてもらうことにしました。基本的な要件から難しいものまで様々なものが集まりました。

結果報告

ここからは実際のコードを書きながら、雰囲気をお伝えします。

普通のFizzBuzz

for i in range(1, 101):
    if i % (3 * 5) == 0:
        print("FizzBuzz")
    elif i % 3 == 0:
        print("Fizz")
    elif i % 5 == 0:
        print("Buzz")
    else:
        print(i)

解説不要?の普通のFizzBuzzです。

要件1:ソースコード内に小文字のfを使わない

断念しました。

ネタ的に入っていたのを一発で引きました。if, def, forなど基本的な構文が制限されるため非常に辛い。さっさと次へ。

あとで、 社内で一つの解を教えていただきました。(python2.7です)

i = 1
while i <= 100:
    print "Fizz" * (not i % 3) + "Buzz" * (not i % 5) or i
    i = i + 1

要件2:直近10回の出力に0~9のうち6種類以上の数字が含まれたら、okと出力する

def has_six_different_num(values):
    char_set = set()
    for value in values:
        if type(value) is int:
            for char in str(value):
                char_set.add(char)
    if 6 <= len(char_set):
        return True
    else:
        return False

values = list()
for i in range(1, 101):
    value = ""
    if i % (3 * 5) == 0:
        value = "FizzBuzz"
    elif i % 3 == 0:
        value = "Fizz"
    elif i % 5 == 0:
        value = "Buzz"
    else:
        value = i
    values.append(value)
    if len(values) > 10:
        del values[0]
    if has_six_different_num(values):
        value = "ok"
        values[-1] = "ok"
    print(value)

随分と長くなりました。すでにリファクタリングしがいのあるコードになっています。forを二重に回しているのがいかにもいやらしいなぁなどと言われそうなコードを書いていたら……

要件3:高速化。実行速度半分が目安

二重のfor文を是正しろと言われているかのような要件を引きました。
このタイミングの良さには思わずみんな苦笑してしまいました。

結果がこちら。

def has_six_different_num(values):
    return 7 <= len(set(list("".join(values))))


values = list()
for i in range(1, 30001):
    value = ""
    if i % (3 * 5) == 0:
        print("FizzBuzz")
    elif i % 3 == 0:
        print("Fizz")
    elif i % 5 == 0:
        print("Buzz")
    else:
        value = i
    values.append(str(value))
    if has_six_different_num(values[-10:]):
        value = "ok"
        values[-1] = "ok" # <-ここがバグってる
    if value:
        print(value)

随分とスッキリしました。100回で高速化は難しいので、出力する数字が30000個に増えました。およそ、0.044 秒から0.028秒台まで速くなりました。

7 <= len(set(list("".join(values))))
この部分非常に勉強になりました。

空文字列を含む7種類のcharが使われている場合にはtrueを返します。

実はこのコードバグってます。コメントで書いたところです。ここは空文字列を代入しないと、okの二文字が数字の判定に扱われることになります。

要件4:出力を逆順にする

def has_six_different_num(values):
    return 7 <= len(set(list("".join(values))))


values = list()
for i in range(30000, 0, -1):
    value = ""
    if i % (3 * 5) == 0:
        print("FizzBuzz")
    elif i % 3 == 0:
        print("Fizz")
    elif i % 5 == 0:
        print("Buzz")
    else:
        value = i
    values.append(str(value))
    if has_six_different_num(values[-10:]):
        value = "ok"
        values[-1] = "ok"
    if value:
        print(value)

rangeを変えるだけ。バグにはまだ気づきません。

要件5:Runtime Errorが発生した場合rm -Rf ~を実行する

私の都合で無しになりました。社用パソコンですし……。ちなみにこの後Runtime Errorは一度も起こりませんでした!

このコマンドは決して実行しないでください。ホームディレクトリ以下が削除されます。

要件6:7の倍数の時はFoo、11の倍数の時はBarを出力する。二つ以上が主力される場合の表示順は、Fizz, Buzz, Foo, Barとする。

そろそろ慣れてきたのでドライバーがモブの発言から察すスピードが早くなりました。消したいなと言った直後にその行が消えてるている、みたいな。

この要件のコードは保存し忘れたので次項でまとめて。

要件7:標準入力で自然数を受け取り、その数までの結果をまとめてJson配列で返すようにする

import json


num = int(input())


def has_six_different_num(values):
    return 7 <= len(set(list("".join(values))))


values = list()
output_values = list()
for i in range(num, 0, -1):
    value = ""
    if i % 3 == 0:
        value += "Fizz"
    if i % 5 == 0:
        value += "Buzz"
    if i % 7 == 0:
        value += "Foo"
    if i % 11 == 0:
        value += "Bar"
    if not value:
        value = i
    if type(value) is str:
        values.append("")
    else:
        values.append(str(value))
    if has_six_different_num(values[-10:]):
        value = "ok"
        values[-1] = ""
    output_values.append(value)
print(json.dumps(output_values))

今回の最終成果です。バグも直りました。

まとめと感想

モブプログラミングの勉強会(お試し会)ということでしたが、思っていた以上に楽しめたというのが正直なところです。今回はイベント性が強めなコードを書いたためみんなでワイワイ、という感じが普通以上に強かったかと思います。

ただ、次があったらプロダクションコードで行いたい、というフィードバックもいただきました。アイリッジ社内で困っているコードを用意して勉強会の材料にするのも楽しいですね。(そうしたらQiitaでコードを公開できないのが問題)

モブプログラミングのメリットとして感じたこと

  • 技術力が高い人の知識が共有される
  • 他の人が何を考えてプログラミングしているかが明らかになり、新しい知見に触れられる
  • 自分の考えを発言することで思考の整理につながり、勘違いに速く気がつく
  • 他の人とすり合わせを行うことで自分勝手なコードを減らすことにつながる

もともと掲げていた目的以外に、自分自身の思考の整理につながり適当なことができない点が大きなメリットでした。

その他まとめ

「要件が増えていく」という点が今回はよかったように思います。小さく開発することと、リファクタリングの重要性が高いことが理由です。

アイリッジ社内でさらに面白い勉強会をしていけたら良いと個人的には考えています。面白い勉強会ができたらまたQiitaで公開させていただきます!