astah*開発チーム内で「ロボットを使ったオブジェクト指向ワークショップ」を行いました。
目的は「なぜオブジェクト指向を活用すると良いのか、をロボット(NXT)を使って実感すること」です。チェンジビジョン東京本社で、技術研修の開発や講師を担当している久保秋さん(ETロボコン本部審査員でもあります)が、某大学で実際に講義している内容を社内向けにアレンジして開催しました。
以下、ワークショップに参加した松田さんのレポートです。
利用したロボット
上の赤丸:荷物を載せるところ。物を載せたことを検知できる
下の赤丸:バンパー。壁にぶつかったことを検知できる
モータは2つあり、前後に移動することができる
まず始めに、ロボットを動かすサンプルプログラムを見ました。
//タスク1用の関数
void tsk1(VP_INT exinf){
//前進する
ecrobot_set_motor_speed(NXT_PORT_A, 40);
ecrobot_set_motor_speed(NXT_PORT_C, 40);
//タッチセンサの値が押されていない間繰り返す
while(!ecrobot_get_touch_sensor(NXT_PORT_S1)){
}
//後退する
ecrobot_set_motor_speed(NXT_PORT_A, -40);
ecrobot_set_motor_speed(NXT_PORT_C, -40);
//2000ミリ秒待つ
systick_wait_ms(2000);
//止まる
ecrobot_set_motor_speed(NXT_PORT_A, 0);
ecrobot_set_motor_speed(NXT_PORT_C, 0);
//自タスクの終了
ext_tsk();
}
これは、ロボットが「前進してバンパーに物が当たったら、後退して2秒後に止まる」というプログラムです。しかしコメントがなければ何をしたいコードなのか、ロボットを知らない人は分からないでしょう。オブジェクト図は下のようになります。
これをリファクタリングしていきます。C言語の為、オブジェクト指向のように書くのは大変なので、独自ルールとして「クラス名_操作名()」という名前で関数を作ることとします。ロボットは荷物を運ぶようですから、運搬車が走るという関数を作ってみます。
void transporter_run(){
//前進する
ecrobot_set_motor_speed(NXT_PORT_A, 40);
ecrobot_set_motor_speed(NXT_PORT_C, 40);
//タッチセンサの値が押されていない間繰り返す
while(!ecrobot_get_touch_sensor(NXT_PORT_S1)){
}
//後退する
ecrobot_set_motor_speed(NXT_PORT_A, -40);
ecrobot_set_motor_speed(NXT_PORT_C, -40);
//2000ミリ秒待つ
systick_wait_ms(2000);
//止まる
ecrobot_set_motor_speed(NXT_PORT_A, 0);
ecrobot_set_motor_speed(NXT_PORT_C, 0);
}
void tsk1(VP_INT exinf){
transporter_run();
//自タスクの終了
ext_tsk();
}
これで、タスク1では運搬車が走ってタスクを終了することが分かるようになりました。
次に、transporter_run()から前進する部分を抜き出します。
void transporter_forward(){
ecrobot_set_motor_speed(NXT_PORT_A, 40);
ecrobot_set_motor_speed(NXT_PORT_C, 40);
}
void transporter_run(){
transporter_forward();
//タッチセンサの値が押されていない間繰り返す
while(!ecrobot_get_touch_sensor(NXT_PORT_S1)){
}
//後退する
ecrobot_set_motor_speed(NXT_PORT_A, -40);
ecrobot_set_motor_speed(NXT_PORT_C, -40);
//2000ミリ秒待つ
systick_wait_ms(2000);
//止まる
ecrobot_set_motor_speed(NXT_PORT_A, 0);
ecrobot_set_motor_speed(NXT_PORT_C, 0);
}
次に「タッチセンサの値が押されていない間繰り返す」と書かれている部分を関数に抜き出します。このコメントは、ソースコードが何をしているかを書いてあるだけで意味をなしていません。コメントを書いた人に聞くと、「バンパーにものが当たったら」の部分に当たるそうです。バンパーに物が当たるという判定は、タッチセンサが押されたことで分かります。しかし、この関数を使う人は、タッチセンサを意識して命令をしたくありません。バンパーに触れるのを待つという名前で関数を抽出します。
void bumper_wait_to_touch(){
while(!ecrobot_get_touch_sensor(NXT_PORT_S1)){
}
}
void transporter_run(){
transporter_forward();
bumper_wait_to_touch();
//後退する
ecrobot_set_motor_speed(NXT_PORT_A, -40);
ecrobot_set_motor_speed(NXT_PORT_C, -40);
//2000ミリ秒待つ
systick_wait_ms(2000);
//止まる
ecrobot_set_motor_speed(NXT_PORT_A, 0);
ecrobot_set_motor_speed(NXT_PORT_C, 0);
}
この図を見ると、左右のモータへの命令も1つのクラスに委託した方がよさそうです。先ほど作成した
transporter_forward()をdriver_forward()に変更してみます。
この調子で後退する、止まるも抽出していき、transporter_run()は下記のようになります。
void transporter_run(){
driver_forward();
bumper_wait_to_touch();
driver_back();
systick_wait_ms(2000);
driver_stop();
}
これで、ロボットのことをよく知らない人が読んでも、ある程度どのような動きをするか予測できる形になりました。また、今まではセンサの情報しかありませんでしたが、運搬車であること、動くことができること、バンパーが付いていることも分かるようになりました。
「荷物が載ったらライントレースし、荷物を落っことしたら止まり、荷物が載っているときに壁に到達したら止まるという動作をさせる」という課題が出ました。最初から複数のことをこなすのは大変なので、荷物を積んだら走り、ぶつかったら止まるというプログラムを、まずはモデルを書かずに実装してみました。
すると、荷物が載っているときに、何かがバンパーにぶつかっても走り続けてしまいました。
void transporter_run(){
if(carrier_is_loaded()){
driver_forward();
} else if (bumper_is_pushed()){
driver_stop();
}
}
carrier_is_loaded()は、荷物が乗っていることを判定する関数、bumper_is_pushed()は、バンパーが押されていることを判定する関数です。このプログラムでは、荷物が乗っている場合にバンパーの状態を判定することができません。ここで、ステートマシン図を書いてみます。
ステートマシン図では、遷移やその状態の時に何をするのかに着目して書きたいため、最初は状態名をあえて書かないようにすると良いそうです。状態名を先に考えてしまうと、そちらに注目しがちになります。
後に状態名を書きます。
最終的にステートマシン図からできたコードが下記になります。ライントレースは、今回は時間の都合上、省略しました。
enum{WAIT_LOADING,CARRING,END};
int state = WAIT_LOADING;
void transporter_run(){
if(state == WAIT_LOADING){
if(carrier_is_loaded()){
state = CARRING;
return;
}
}
if(state == CARRING){
driver_forward();
if(bumper_is_pushed()){
driver_stop();
state = END;
return;
}
if(!carrier_is_loaded()){
driver_stop();
state = CARRING;
return;
}
}
}
無事、期待通りに走ることができました!
モデルをかくことによって、書きたいことを整理することができました。プログラムを人に見せるときも、図があることで容易に説明することができそうです。
- リファクタリングには、オブジェクト図が有用!
- 見通しの悪いコードは、オブジェクト図と一緒にリファクタリングすると良い名前が見つかる
- 誰(クラス)が何(操作)をするか、ということを意識して名前をつけると良い
- 操作名は、操作の中で何をしているかではなく、使う側にとって分かりやすい名前を考えるべき
- 掃除を人に依頼するとき、依頼する側は「どんな道具を使うか」は意識しない
- ステートマシン図は、状態名よりも「遷移や何をするか」の方が大切
- 条件式を使うプログラムは、ステートマシン図を利用すると考えやすい
- プログラムに加えてモデルがあると、システムを理解しやすい
astah*では、クラス図を使ってオブジェクト図を描くことができます。(参照FAQ:astah*で、オブジェクト図、パッケージ図、ロバストネス図は描画できますか?) 無償版もございますので、ぜひダウンロードしてモデルを描いてみてください。
astah*ダウンロード:http://astah.change-vision.com/ja/download.html
(*1) 今回のワークショップで使用したロボットは、平成22年度文科省専修学校補助事業「産学連携による実践型人材育成事業-専門人材の基盤的教育推進プログラム-」に採択された「産業界と連携した高品質組込みソフトウェア技術者養成プロジェクト」(九州技教育専門学校主催)の演習に利用されたものです。
(*2) 今回のワークショップで利用した教材は、IT人材育成の推進を目的とした、ロボットを活用した教材・研修サービスを提供している株式会社アフレルさんの「モデリングを活用したソースコード改善演習」の教材を一部利用させていただきました。アフレルさん、ありがとうございました。教材について興味のある方は、株式会社アフレルさんへお問合せください。
「ロボットを使ったオブジェクト指向ワークショップ」への1件のフィードバック