C言語で設計って難しい
ここ一ヶ月くらいSDL2をいじっています。前々からゲームエンジンに興味を持ってSDL2をとりあえず触って作って、確かめてみようという風にやっていました。
一ヶ月くらいやるとある程度、勘が働いてきてどういう風に動いているのかが分かってきました。そこで、改めて整理をするためにちょっとした自分用のライブラリを作ろうと思いライブラリ設計をしてみました。
ですが、いざ設計をしようとするなかなかうまく行きませんでした。というのも、仕事でrubyを扱っていてオブジェクト指向が見についていていざ構造化設計をしようとしてもなかなか整理できませんでした。
オブジェクト指向の場合、基本的にはデータと処理がセットになっているイメージです。
class TestString
attr_reader :str
def length
return str.length
end
end
試しに、文字列を扱うTestStringというクラスを作ってみました。このクラスは文字列を扱っていて、文字列の長さを取得できるように鳴っています。
attr_readerにデータの構造を書いておいて、lengthが処理という形になっています。オブジェクト指向はデータと処理がセットになっていることで、処理の名前の意味さえわかっていれば他の人でも使えるようになっています。
ただし、Cは手続き型言語というくくりでオブジェクト指向が使えません。rubyの開発者である「まつもとゆきひろ」さんもrubyを作る前に会社でC言語でオブジェクト指向を扱えるライブラリを作っていましたから、自分で作るしかありません。
ここで問題になるのは、データと処理を一緒にできないということはUML図が書けないということです。私の場合、クラスを考えてそれからそのクラスに対して〜をしてほしいことを考えて設計します。こうすれば、「〜のクラスは××の役割があるから❍❍という処理があればいい」という風に考えることができて小説を書くようにできます。
C言語ではこのような書き方ができないので、いくらUMLで設計をしてもコードに置き換えることがしづらいんです。
そこで、コンポーネントとワーニエ法とUNIXの哲学の考え方を拝借してこれを解決しました。
コンポーネントとワーニエ法とUNIXの哲学とは?
この3つの考え方でなにをしたい?
私は先程「〜のクラスは××の役割があるから❍❍という処理があればいい」という風に設計をしていると言いましたが、この考え方はオリジナルではなくアラン・ケイの考えるオブジェクトの拝借をしました。
すこし前、オブジェクト指向がうまくイメージがつかめなくて、調べている最中にアラン・ケイについての記事を知りました。アラン・ケイはプログラミングに「メッセージング」という考え方を取り入れました。
メッセージングというのは簡単にいうと、データに対して「〜をやって!」という風に頼みごとをして、プログラムを組んでいけば比較的簡単にできるということです。
例えば、レストラン業務を考えてみてください。ここで単純に考えるために以下のような編成で従業員がいるとしましょう
ホールスタッフ:接客をする
シェフスタッフ:料理をつくる
あなたがお客様とすると、まずはじめにホールスタッフに「~という料理をちょうだい」と頼みます。すると、ホールスタッフはそれを受け取ってシェフスタッフに対して「~という料理をつくってちょうだい!」と頼みます。シェフスタッフは~という料理を作ります。作ったら、シェフスタッフがホールスタッフに「~という料理を持ってて!」と頼み、ホールスタッフは料理を持って行きあなたの手元に料理が届きます。
「」にはいるところがメッセージに当たるところです。つまり、頼みごとを考えることをプログラミングの世界に持ち込んだのがアラン・ケイの考えるオブジェクト指向です。
この3つの考え方でアラン・ケイの考えるオブジェクト指向に近づけるのが目的です。
コンポーネントとは?
コンポーネントは特定の処理をしてくれる部品です。レストランでいうところ、各スタッフに当たります。
コンポーネントには特定の役割があって、その役割から外れることはありません。このレストランが完全分業されていているのであれば、シェフがホール業務をすることはできませんし、ホールがシェフ業務をすることもできません。仮にできたとしても、全くホールをやったことがないシェフがお客様にオーダーを取ったら、頼んでない料理が出たりします。ホールがシェフをやったらそれこそカオスです。肉料理を頼んだら、消し炭がでる可能性もあります。
大げさに書いてしまいましたが、プログラミングでもこのようなことが発生します。文字列扱っていたはずなのに、なんか文字列が数字の計算をやっていたら絶対に混乱をします。
「役割を持った部品である」ということだと理解してくれればいいです。
ワーニエ法とは
これはシニアエンジニアの庵から引用します。
集合論に基づき入力データ構造を分析し,データが「いつ,どこで,何回」使われるかをもとに,
連接・選択・反復の制御構造でプログラムを展開する.
これは、要するにデータ構造を分析して、データがどのように使われるかを考えて設計をする手法です。
結構、オブジェクト指向の設計に似ています。
この方法は、ソフトウェア構造化技法の中では、データを階層ごとに考えられています。
動物の分類について
--動物
|
|-鳥類
|-哺乳類
簡単に書いてしまいましたが、このように動物というデータから紐付いて鳥類、哺乳類というデータ構造があるという風に樹形図を作るように設計する手法です。
オブジェクト指向にも犬猫という例え方がありますが、これは継承の考え方に非常に近いのでこれでオブジェクト指向っぽい設計ができます。
UNIXの哲学とは
UNIXの哲学は、UNIXを作る過程で分かったプログラミングの考え方です。
小さいものは美しい。
各プログラムが一つのことをうまくやるようにせよ。
できる限り早く原型(プロトタイプ)を作れ。
効率よりも移植しやすさを選べ。
単純なテキストファイルにデータを格納せよ。
ソフトウェアを梃子(てこ)として利用せよ。
効率と移植性を高めるためにシェルスクリプトを利用せよ。
拘束的なユーザーインターフェースは作るな。
全てのプログラムはフィルタとして振る舞うようにせよ。
UNIXの哲学はとにかくシンプルを大切にすることが根本にあります。なんでもできる機械を作るために、なんでもできるプログラムを作るととは必ず悪手になります。
神クラスがあるがためにメンテナンスがしづらくなったり、改修したくても神クラスが複雑すぎてどこかを切り離せば心臓部が死ぬようなことがあります。
これをなるべく細かく切り分ければ、別のソフトに移植してだめでもだめな部分を改修するだけでいいので作りやすくなります。
UNIXの哲学から考えればシンプルなコードが一番であり、コードの作りがシンプルになって取り回しがしやすくなります。
3つが集まってなにができるか?
先程、C言語はオブジェクト指向のやり方がやりづらいと言いました。その原因はUMLで設計をしてもそれをC言語に置き換えることがこんなことにあります。
今回はKeyboard部分の設計をまずコンポーネントとして作ります。今回はKeyboardに対して以下の事してもらうために作ります。
キーボードの役割
* キーボードのキー入力を検出
* キー入力に合わせて処理を走らせる
とりあえず、簡単にこう書きました。このTODOみたいなものがキーボードの頼みごとです。
このコンポーネントを考えたら、SDL2を置き換えます。
SDL2の場合、Eventが入力管理をしているので、先程のワーニエ法でもう一度分析をします。
--Event
|
|-Window
|
|-Keyboard
このようになっているので、WindowとKeyboardのデータ構造が近い可能性が生まれてきます。
こうなれば、「Keyboardのデータ構造を作れば、Windowのデータ構造も近いから分かる」ということがわかります。
そして、最後にC言語に置き換えるという作業なのですが、先程コンポーネントが役割がわかったので、このコンポーネントの役割に対してUNIXの哲学に従って組めば整合性が取れるソフトが作れる。
「キーボードのキー入力を検出」に対しての処理を書いて、これでも分割しきれていない場合は汎用の処理を書いて対応をする。
終わりに
この方法は、比較的古い知識が多く使われています。アラン・ケイの考え方もSmalltalkが出てきたときくらいの考え方ですし、UNIXの哲学もSmalltalkからすこし前の考え方ですし、ワーニエ法も古いです。
ですが、組み合わせれば設計を今に近づけることはできます。ゲームエンジン自体が結構組み込みに近い処理のやり方に近いし、今のvue.js, reactのようなwebの設計方法では型が合いませんでした。
こうして昔の知識を集約して工夫をすればC言語でも設計ができます。私が設計を重視するのは「私の知らない人でもよめて、開発に参加しやすくしたい。」という考えを元にしています。
コードの読みやすさもそうですが、はじめて参加してくれる人に対して気持ちよく参加してくれればきっと面白いものが生まれやすい環境になると思っています。