コードの分割、要約、その実例
コードを如何に分割するのか?
巨大なソースコードを読むために、人はソースコードを分割し、要約するという話をした。
これは計算機科学における「抽象化」の一要素であり、その抽象化を助ける機能が各言語に実装されている。
最も基本的な概念が「サブルーチン」。殆どの言語では関数と呼ばれているものである。
次のPythonのソースコードを読んでほしい。
# 関数の実像は省略
amount = ask_amount()
print_fizzbuzz(amount)
さて、このプログラムはどういったプログラムだろうか?
FizzBuzzの回数を取得して、実行するプログラムのように見える。
# 関数の実像は省略
amount = ask_amount()
nazo_no_shori()
print_fizzbuzz(amount)
ではこのプログラムは?
まあ、明らかに変なのが居るが、FizzBuzzの処理に違いは無さそうだ。
……本当に?
要約として機能していない関数
実はnazo_no_shori()の実装は以下の通りである
def nazo_no_shori() -> None:
global amount
amount = amount + 1
まあ、これは間違いなく酷いコードである。何故酷いコードと言えるのか?
何故なら関数名からどんな処理を行うのかが一切読み取れないからである。関数名というのはその関数が何者なのかを伝える最も重要な情報である。ここを疎かにされては中身を読むほかない。
せめてincrease_amount()とでもしておくべきだったであろう。
そしてamountを引数で受け取らず、直接参照しているのも良くない。そのような関数が存在している可能性がある場合、読まなければならないプログラムの分量が一気に増えてしまう。
関数の中身を見る必要性を減らす
どちらも関数による要約、読み手の認知負荷の軽減が上手く機能しておらず、何の意味もない関数化である。
これを踏まえて手直ししてみよう。
# 関数の実像は省略
amount = ask_amount()
amount = increase(amount)
print_fizzbuzz(amount)
これでプログラムを誤解することは無くなった。この調子で書いていけば良さそうである。
更に要約する
ここでこのプログラムの構造を考えると、FizzBuzzの回数を主軸として処理が連なっている。
ask_amount()、increase()、print_fizzbuzz()、そしてamountは全てFizzBuzz実行のための処理である。まあそのためのプログラムなのだから当然だが。
そこで関数の組み合わせから一歩進み、これらを「FizzBuzzを行うプログラム」と抽象化する事が出来る。
class FizzBuzz:
def __init__(self) -> None:
self.amount = None
def ask_amount(self) -> None: ...
def increase_amount(self) -> None: ...
def print(self) -> None: ...
def run(self) -> None:
self.ask_amount()
self.increase_amount()
self.print()
Pythonのクラスを利用した「FizzBuzzを行うプログラム」の抽象化である。
fizzbuzz_task = FizzBuzz()
fizzbuzz_task.run()
FizzBuzzの実装を見なくても、何となくFizzBuzzを実行するプログラムなんだろうなと読める。
まあ何故かamount+1している事は実行するまで気づかないと思うが。
クラスを要約に使う
関数は「処理」を要約するのに適している。 そしてクラスを使えば「処理」に加えて「データ(状態)」も要約する事が出来る。
今回の例ではamountしか変数が登場しなかったが、より多くの状態を管理するのであればクラス化することが効いてくるだろう。
雑談
ゲームやりたい……積みゲーが多い……
今年出たゲームで言うとSilksongとかはやりたいんですよね。でも腰据えてやりたいというか。
正月に遊べるかなぁ……