分割統治(Divide and Conquer)
全体のシステムが決まったら、それらを適切な粒度で構成要素に分割していきます。「分割して統治する(Divide and Conquer)」は複雑なシステムを作成するときの基本です。しかし、システムの構成要素の粒度は、ハードウェアの構成やミドルウェアに何を使うかなどによってさまざまに異なってくるので、一概にこうだ、と言いにくい。適切な粒度にするのが非常に難しくて、センスが問われるところです。
システムを分割した構成要素には「コンポーネント」「モジュール」「サブシステム」など、ニュアンスの異なるいろんな名前がありますが、ここでは「パッケージ」と呼びます。なぜならもちろん、ミドルウェアにROSを使うことを想定しているからです。
ミドルウェアの有用性
システムの構成要素を適切に定義するための枠組みがミドルウェアです。乏しいセンスに頼らなくても、ある程度標準化された仕組みが用意され、システムを分割するときの助けとなります。ROSはロボットシステムを作成するための代表的なミドルウェアです。自分でミドルウェアから作るのは大変なので、まずはROSでシステムを組んでみることをお勧めします。

Making sure you're not a bot!
NodeとTopicとServiceとActionがあって…みたいな話はこの記事ではしません。すでに山ほどWebの記事や書籍があるので、ぜひそちらをご覧ください。大事なのは、以下の点です。
- ROSでのシステムの構成要素はパッケージ単位であること
- 独自のIDL(Interface Definition Language)でパッケージのインターフェースが定義されていること
- 再利用可能なパッケージもすでに誰かが作成してくれている可能性があること
ちょっとしたコツ
以下では、システムをROSパッケージに分割していく際のコツ的なものを紹介します。
パッケージの単機能化と名前
基本的に、パッケージが持つ機能は単機能にすることを目指した方がいいです。「なんでもこなせる十徳ナイフ的なROSパッケージ」は全然意味がありません。ROSパッケージはシステムの中の構成要素として他の人も使うので、機能が冗長でたくさんあると使いにくいです。
そして、パッケージの名前です。名前がめちゃくちゃ重要です。ROSの名前の付け方につては、REP-144という形でしっかり規定されているので、ROSを使う人は必ず目を通してほしい。
ROS Package Naming
ただ、じっさいにはこの文書が完全に守られているわけではないです。たとえば、「パッケージにROSメッセージ/サービス/アクションのいずれかが含まれている場合は、_msgsの接尾辞をつける」というルールはちょっと古くて、新しいROS 2だと「_interfaces」になっていることが多いです。
完全にオレオレな名前を付けるのは避け、ほかのパッケージがどうなっているかも見て、よく考えましょう。一回名前をつけてしまうと、あとで変更するのはけっこう大変です。
そのパッケージは、自分のアプリやロボットに特有なものなのか、それとも汎用的なものなのか。よく考えながらパッケージの機能と名前を決めましょう。ロボット固有のパッケージにはそのロボットの名前を付ける決まりです。ロボットの名前がついてないパッケージは、他のロボットでも流用できるかもしれません。名が体を表すように心がけます。
車輪の再発明を避ける
汎用的な機能のパッケージが欲しい場合、既存のパッケージに使えるものが無いか、できるだけ探しましょう。もともとROSは「車輪の再発明を避ける」がコンセプトでした。同じようなパッケージがあればまず試してみて、機能が足りなければそのパッケージを改良することを考え、それでもダメであれば最後の手段として、自分で新たな汎用パッケージを作成することを考えましょう。
インターフェースの規定
インターフェースも重要です。何が入力で、何が出力で、何が機能なのかを明確に決めることが、分割統治の第一歩です。
パッケージのinterface(.msg, .srv, .actionなど)はそのパッケージが持っているべきでしょうか、それとも別のパッケージにすべきでしょうか?interfaceはその実装とは別なので、私は基本的に別のパッケージにするべきだと思います。たとえば、画像が入力で物体の名前とバウンディングボックスが出力、という、画像による物体認識のインターフェースがあるとします。その実装をするパッケージは古典的な機械学習を使うかもしれないし、YOLO系を使うかもしれないし、VLMを使うかもしれませんが、インターフェースパッケージを同じものにしておけば、実装だけを交換してそのまま試すことができます。
内部状態を持つモジュールを限定する
システムの中で、内部状態を持つパッケージをできるだけ少なくし、特定のパッケージのみが状態を管理することが重要です。なぜなら、内部に状態を持つパッケージは一般にテストやデバッグが非常に難しいからです。状態を持つパッケージは、その状態を外部から見えるようにしておきくと同時に、その状態遷移のログをとれるようにしておきます。
たとえば、ロボットが全体として「待機中」「動作中」「異常」などの状態を持つ場合、それらの状態遷移を明示的に管理する "state_manager" パッケージを用意しましょう。このパッケージがそれらの状態のうち必ず1つを保持し、遷移も記録するようにします。他のパッケージはこの "state_manager" に今の状態を問いあわせれば、パッケージごとに状態を管理する必要がありません。
システム設計に正解はないが…
まだまだあるが
私もさすがにROS歴はそこそこ長い(最初に触ったROSは"electric"だった)ので、ROSのシステム作成ではそれなりにベストプラクティスみたいなものがあることに気づいてきています。まだまだあると思うのですがこのへんで切り上げたいと思います。
さて、ロボットシステムの設計について一通り書いてきましたが、まあまあ一般的であたりまえな範囲の話じゃないかなと私は思っています。ただこれ以降の詳細に関しては絶対の正解はなく、設計者が議論しながら決めるような領域に入っていきます。その際にも、システム設計に関してこれまで述べたような考え方が大枠で合意していれば、有意義な議論になるのではないかと思います(思いたい)。
初出:Note