2018-11-01

Raspberry Pi3を使ったリアル波形描画(4) オブジェクト指向設計の必要性について考える

組込みソフトウェアの開発で使う言語はいまだにC言語が多いと聞く。

下記の図は,独立行政法人 情報処理推進機構 社会基盤センターが調査した「ソフトウェア開発データ白書 2018-2019」のデータである。

これによると,開発言語がC言語であると解答した組織が108で,C++と解答した組織81をまだ上回っている。ちなみに,一位はJavaの629である。

今どき gcc ですら,c++ は対応済みなので,C言語を使っているのはコンパイラがC++に対応していないという理由ではないと思う。それは「C言語でできるのに,わざわざC++で作り直す,C++を学習し直す必要あるのか?」という単純な問いに答えられないからではないだろうか。

日本人が英語を学習するのと同じで,言語の習得というのはハードルが高く見えるものだ。実際使いこなせるようになるには,それなりに時間もかかる。

だから,「C言語でできるのに,わざわざC++で作り直す,C++を学習し直す必要あるのか?」という問いに対する答えはちゃんと用意しておいた方がよい。

自分なら「ソフトウェアの再利用率を高め,開発効率と品質向上のために必要で,10万行を超える規模のソフトウェアアプリケーションではオブジェクト指向言語を使わないと自転車操業から抜けきれない。」と答える。

実際,同じ機能のアプリケーションソフトウェアをCPUやOSが変わる度に作り直して問題を作り込んでしまっている例を少なからず見るが,作り直す工数も馬鹿にならない上に,リリースしたあとに見つかった不具合の対応がこれまた大変だ。

ソフトウェアは見えにくいから,ソフトウェアアイテム(モジュール)の再利用率も見えにくい。ハードウェアならば,共通部品を使ってコストダウンを図る取り組みが外から見えるのに,ソフトウェアでは過去に作ったことのある機能のソフトウェアモジュールを新たに作り直しても誰も文句を言わない。

そうなってしまう理由はいろいろある。例えばこんな理由だ。
  • その機能のソフトウェアモジュールがすでにあることを知らなかった。
  • ソースコードはあったが,使い方が分からないので新規に作ったほうがいい。
  • 他人の作ったソフトウェアは使いたくない。
  • 新規に作った方が売り上げが上がる。(派遣や請負のソフトウェア開発)
  • 再利用せよという指示がなかった。
  • OSやCPUに依存している箇所があるので使えない。
そもそも,再利用できるようなソフトウェアとして作っていない場合,確かに再利用は難しい。そういう組織は「再利用」という言葉は使わず「流用」という。再利用することを意図して作ったソフトウェアではなく,たまたまそこにあったものを使い回すのが「流用」で,流用レベルでしか使ったことがないのだ。

そういう組織,プロジェクトではコードクローン(同じようなコードがシステムのあちこちに散らばっている状態)が多い。

数千行のソフトウェアで製品が動いていたころ,青春時代を過ごし管理職になった技術者は「ソフトウェアを再利用しなければもう無理」という感覚にならないかもしれない。数十万行,数百万行まで増大してししまったソフトウェアを前に「なぜ,不具合がなくらなないのか」「どうしてソフトウェアエンジニアはミスを繰り返すのか」と自問し続けて負のスパイラルから抜け出せなくなっているのではないだろうか。

その状態を脱却するには,再利用率の高いソフトウェアを作るしかない。資産価値の高いソフトウェアモジュールを再利用できれば,その組織のコアコンピタンスになるし,信頼性の高いソフトウェアモジュールを何回も再利用できれば,開発効率と品質を同時に上げることができる。

C++を体験してみる。Turtle クラスを作成し,オブジェクトを作る

この事例は絶版になってしまった「C++プログラミングスタイル」,1992/8, 山下 浩 (著), 黒羽 裕章 (著), 黒岩 健太郎 (著) を参考にしている。

C++言語を学習するには「基礎からしっかり学ぶC++の教科書」を読むことをお勧めする。これから先は C++言語の概要は知っているという前提で進める。

オブジェクト指向設計でソフトウェアを作成すると,再利用がやりやすいことを 亀のひな形を例にして説明する。

亀の実態がシステムの中で一つしか使われないならば,亀のひな形であるTurtleクラスを作るメリットはあまりないが,システムの中で亀1,亀2 のように複数使用する際には Turtleクラス を作るメリットがある。

また,オブジェクトを使う側の者は,オブジェクトに対して必要な命令を与えて,レスポンスを受けるだけの方が使い勝手が良い。オブジェクトの中身の複雑なアルゴリズムなどは知らない方が,責務を分担できる。責務を分担して,オブジェクトの責任と権限を移譲するという考え方は,大規模・複雑化したシステムでは有効だし,そうしていかないと数十万行以上のソフトウェアシステムではやってられない。

100万行を超えるようなソフトウェアシステムでは「隅々まで把握している技術者がプロジェクトに一人以上いる」という状態を維持できない。

ということで,Turtle クラスを設計してみる。

1.1 Turtle クラスとは
  • X座標,Y座標,向きをクラスの内部(private メンバ)に持つ
  • 外部からは,左を向く,右を向く,指定距離だけ前進することを指示できる。
  • 外部から,現在のX座標,Y座標,向きを尋ねることができる。

Turtle オブジェクトの利用者は,Turtle が内部でどのような動き(処理)をしているのか知らない方が,Turtle を利用しやすい。右を向けとか,左を向けとか,nだけ前進しろとか,今の向きやX,Y座標を聞いたら答えてくれるだけの方が使い勝手がいい。

よしんば,Turtle の中身を知っていると中身をいじりたくなってしまう。これがC言語開発の良くない点だ。

そもそも再利用することを前提に作っていないので,ベターッとソースコードをいじってしまう。さわらなくていいところを隠蔽して,インタフェースだけをアクセスできるようにしようという意図がない。

もちろんC言語でそういった変数や関数の隠蔽は可能だが,そういう意識を持った人だからできることであり,C++を使えば言語上の縛りがあるから,自然とそういった設計になる。

Turtle クラスは ひな形(クラスが持つメンバ変数,メンバ関数が定義されている。このひな形を元にして,オブジェクトを生成(ひな形で定義した変数,関数の領域を確保して,初期化して使えるようにする)する。

同じ性質を持つオブジェクトを複数生成し,それぞれを独自に動かすことができる。
下記の例では, TurtleオブジェクトA と B は別々のタートルとして,動かすことができる。

これは構造体を使って実態を2つ作るのと同じだ。組込みではクラスとオブジェクトは一体一のことが圧倒的に多いからもしれないが,このしくみを使うと意外と簡単におもしろいことができるようになる。

1.2 方向

  0~360° で方向を指定する。






            0°
           ↑
     270° ←  亀   →  90°
                   ↓
           180°

Turtle クラスのソースコード(ヘッダー)ap_turtle.h

#ifndef AP_TURTLE_H
#define AP_TURTLE_H

class Turtle
{
public:
    Turtle();
    void turnleft();                // 左を向く
    void turnright();               // 右を向く
    void forward(int distanse);     // 前進する
    int getXPosition(void);        // 現在のX軸の位置を得る
    int getYPosition(void);        // 現在のY軸の位置を得る
    int getDeraction(void);        // 現在の向きを得る

private:
    int mX;          // X座標
    int mY;          // y座標
    int mD;          // 亀の向き
};

#endif // AP_TURTLE_H

Turtle クラスのソースコード ap_turtle.cpp

#include "ap_turtle.h"

Turtle::Turtle()
{
    mX=0;
    mY=0;
    mD=0;
}

void Turtle::turnleft()
{
    mD=mD-90;
    if (mD < 0) mD = mD+360;
}

void Turtle::turnright()
{
    mD=mD+90;
    if (mD>360) mD=mD-360;
}

void Turtle::forward(int distanse)
{
    if ((mD==0)||(mD==180)) mY=mY+distanse;
    if ((mD==90)||(mD==270)) mX=mX+distanse;
}

int Turtle::getXPosition()
{
    return mX;
}

int Turtle::getYPosition()
{
    return mY;
}

int Turtle::getDeraction()
{
    return mD;
}

Turtle クラスのソースコード main.cpp

#include
#include "ap_turtle.h"
#include
 using namespace std;

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Turtle turtleA;
    Turtle turtleB;

    printf (" Tuttle A Position (X , Y) = ( %3d , %3d ) \n", turtleA.getXPosition(), turtleA.getYPosition());
    printf (" Tuttle B Position (X , Y) = ( %3d , %3d ) \n", turtleB.getXPosition(), turtleB.getYPosition());

    turtleA.forward(10.0);
    turtleB.turnright();
    turtleB.forward(20.0);

    printf (" Tuttle A Position (X , Y) = ( %3d , %3d ) \n", turtleA.getXPosition(), turtleA.getYPosition());
    printf (" Tuttle B Position (X , Y) = ( %3d , %3d ) \n", turtleB.getXPosition(), turtleB.getYPosition());

    return a.exec();
}

サンプルプログラムの解説

  • Turtle クラスのオブジェクト turtleA と turtleB を生成し,現在の位置を表示。
  • turtleA を 10前進させる。
  • turtleB を左に向かせる。(左に90度)
  • turtleB を 20前進させる。
  • turtleA と turtleB の現在の位置を表示。
Turtle クラスの向きの変え方や前進,後退の仕方は Turtle クラス の内部に隠蔽させているので,turtleA や turtleB を動かす側のユーザーは,現在の位置確認と右向け,左向けといくつ前進させるのかだけを指示すればよい。

こうしておけば,そのソフトウェアアイテム(オブジェクト)を使う側の視点と,内部のしくみの視点を分離することができる。

内部のしくみの信頼性が高く,ソフトウェアアイテム(オブジェクト)の使い勝手がよければ,このソフトウェアアイテムは再利用資産となる。

ソフトウェアアイテム(オブジェクト)が複数の製品群に渡って再利用可能な資産となれば,新に作らなければいけないソフトウェアが減り,開発効率が上がる。

だだし,再利用可能な資産となるかどうかは,そのソフトウェアアイテム(オブジェクト)に何をさせるのか,どんな責務を持たせるのか,今後のソフトウェア修正の要求に耐えうるのかといった要件を満足させるくらいの出来でないとダメだ。やみくもに作ったのでは再利用資産にはならない。

そういう意味で Turtle クラス はちょっと工夫すれば,有名なテキストベースのゲームスタートレックゲームに応用できる。

次回は,Turtle クラス を修正して,スタートレックゲームの簡易版を作ってみる。


スタートレックシミュレーションゲーム(Wikipedia)

<スタートレックゲームのオリジナルの動画>
  1. スタートレックゲームのソースコードと解説
  2. MZ-80 でスタートレックゲームを遊んでいる動画
  3. Apple II でスタートレックゲームを遊んでいる動画

0 件のコメント: