ストリーム(ARM/PRU間通信やシリアル通信)のポイントと「最悪なバグ」

どんなプログラミング言語でも、データ受け渡しの基本は「ストリーム」です。
今回は、ストリームの考え方と「最悪なバグ」を生み出さないためのコツに触れます。

データ通信の基本形「ストリーム」と注意点

get/put系やwrite/read系など多くの入出力インターフェースは(ARM/PRU間通信のrpmsgも)1byte~任意長のデータを送受信することができます。
送信側が任意のデータをどんどん流し込み、受け側が好きなタイミングで引き出せます。
このようにデータを流す仕組みを、ストリーム型と言います。
(一方、適当な塊にしてデータ交換する仕組みはパケット型と言います。スマホでおなじみですね。「パケ」放題とか)
受け側の都合で「受け取りたいときに受け取れる」という点が大きなメリットですね。

2台以上の装置がデータの交換を行う場合、それぞれの都合があるので、データ交換のタイミングをピッタリ合わせることはできません。
「日本時間10:02:03.4に送信するから」「分かった!何もしないでまってる!ずっと!」などと言うわけにはいかないのです。
システムが止まってしまいますから。
1台の中で走る2つのプログラムの間でデータ交換を行う場合も同様です。
そこで、一方が送信したデータをバッファにためておいて、受信側が任意のタイミングで取りに行く、という方法を取ります。
適当にバッファに放り込むのではなく、順番を守って入れていきます。
取り出すときも、送られてきた順に従って、取り出していきます。
これがストリームです。

ただし、注意点があります。
送信したものが取り出されない内にどんどん送信してしまうと、あふれてしまう、という点です。
また、送信したものが長時間放置された後、取り出されるケースも考えられます。

もう一つ、見落としがちな注意点があります。
送信側が意図した長さ/区切りで、受け取ってもらえない可能性がある、というものです。
送信側が10文字のデータと20文字のデータを送信した場合、受け取り側のタイミングによっては、30文字まとめて取り出すかもしれないし、最悪、15文字ずつ取り出すかもしれません。
これは困りものです。

LINEやSNSなどでメッセージをやり取りするときも、切りの良いところで送信するように気遣ったり、
送信ボタンやEnterを押したときに送信される仕組みだったりしますよね。
これが、もし、文の途中で送信されて途切れたりしたら…読みにくいですよね。

そうならないようにするために、データの区切りが分かるように、ルールを決めておく必要があります。
文字を送る場合は、改行文字(Enter)を区切り文字にするケースが多いですね。
でも、これはあまり良い方法ではありません。

例えば、プログラムAとプログラムBが通信するケースで考えてみましょう。
通信を開始した後、プログラムBを一時停止、または再起動した場合。
プログラムBが止まっていた間にAが送ったデータはどうなってしまうでしょうか?
プログラムAが送信している最中にプログラムBが起動して初期化処理を行った場合、つくりによっては、送信データの前半部分が欠け、後半部分だけがプログラムBに渡る、という可能性が出てきます。

後半部分の最後に改行文字が付いているのでOK、と判断してしまったら…まずいことになりそうです。

従って、区切りを示すものはデータの最初と最後につけるのがベターです。

最悪なバグ

さて、実際には、これらの問題は滅多に発生しないものです。

滅多にないから、無視できる…でしょうか?
そんなことはありません。
こういった、滅多に発生しない問題、稀にしか発生しない問題こそが、絶対起きてほしくない時に発生するのです。
そして、こういった問題は後から直そうと思っても、なかなか発見することができません。
そう、これこそが「最悪なバグ」です。

「発生率の低いバグ」「後から見つけるのが困難なバグ」
これらを生み出さないように、設計段階で気づく、それが優れたプログラマーです。
多くのプログラマーは何度も痛い目に遭い、これら「最悪のバグ」を恐れ、敏感になって行きます。
だから、そんなバグが生まれにくいコードを書くようになり、隙の無い仕様を書くようになります。
そうして実力のあるプログラマーに成長するのです。

実力者と初心者の違い

実力のあるプログラマーは、工程の前半に時間を割き、後半はコンパクトにまとめます。
初心者は、工程の前半よりも、後半が間延びします。

ここで言う前半とは「設計」のこと。後半は「デバッグ」のことです。
初心者は(設計にもそれなりに時間がかかりますが)デバッグの時間が長いです。
長い上に、どのくらい時間がかかるのか予想できません。

実力者は、設計段階で時間を掛け、デバッグは順調に終わります。
場合によっては、プロジェクト開始時点の予想よりも、設計に時間がかかるかもしれません。
しかし、その場合は、設計の途中の段階でわかるので、プロジェクトの工程の見直しが可能です。
しかし、デバッグ段階で、「予定の期間を過ぎてもまだ終わらない」「あとどれくらいで終わるかわからなくなった」
などということになると、工程の見直しどころではありません。
プロジェクトの成功すら危ぶまれることになります。

初心者は設計とデバッグの割合が5:5くらい、
実力者は設計とデバッグの割合が9:1くらい?

実力者は「設計」の内訳も変わって来ます。
仕様設計や検討の割合と、コーディングの割合が変わり、徐々にコーディング時間が減ります。

コツ1

見落としがちなバグを減らすために、あいまいなものを残さない、ということが大事です。
自分で書いた部分は、見直したり、テストコードを仕込んだりして昇華すればよいでしょう。

目を背けてはならないのは、自分が書いた部分ではないコードに対する確認です。
ライブラリや関数を用いるなら、その仕様、例外時の挙動などを把握しなければなりません。
既存コードを流用するなら、流用元との違いと、それに対する差異を抑えなければなりません。
それができないなら、使わない、という選択も考えるべきでしょう。
何なら、自分で書いてしまったほうが良い、という場合もあります。
実際、私は何度もそうしてきました。
より重要なシーンでは、自分でライブラリの代わりの関数を書き、更に、既存ライブラリのソースを参照して忘れ物がないように補完する、ということまでやったこともあります。
…まぁ、そこまでしなくても良いかもしれませんが、「実績のあるライブラリ(あるいは既存ソース)使えば大丈夫」というのは非常に危険な考えであることは確かです。

コツ2

多くのプログラマーは何度も痛い目にあって成長します。
最初から「最悪なバグ」に気づける天才はいません。たぶん。

ですので、「痛い目」の規模を抑えましょう。
製品やプログラムをリリースしてから、市場で発生…これが最悪ケースですね。
こんなことを繰り返していたら成長する前に退場になります。
そうなる前の段階で、問題に気づけるように、いろいろ工夫しましょう。

デバッグの時、評価テストの時、テスト内容が単調にならないように心がけましょう。
なかなか再現しないバグでも「あの人が触るとなぜか再現する」というケースがあります。
なぜか?
性格? いじわるな?
たぶんそうではなく。
先入観が無い、あるいは先入観を取り除いて臨んでいるから。

設計サイドの思考の人間は、無意識にウィークポイントを避ける傾向にあります。
例えば、私。「テレビのリモコンのボタンを連打」とかできません。
怖いから。
普通は怖いとか思いませんよね?
そういうことです。

だから私は、自分で評価テストをやるときもありますが、バグを見つけられるとは思ってません。
実際、滅多にないです。
人の作ったもののバグは頻繁に見つけますけどね。

だから、仕込みましょう。プログラムの中に。

ささやかな例外もスルーせず、盛大に警告を出すように。警告が出せないところならエラーを返して、上位層で警告出す。

「動作不良にはならないと思うけど、意図した範囲外の値が発生する可能性があるかも」と思ったら、警告が出るようにしましょう。

警告は見落としてしまう、と言う場合は、エラーに格上げしてしまえば発生時点でコンパイルが止まるので確実です。

実際、警告を見落としてしまうことは少なくありません。
動作確認中「何か妙だな」と思ってコンパイル時のメッセージを見直してみると「しっかり警告が出てた」ということもしばしば。
大きなプログラムを組んでいるときはメッセージが多いので、特に。

基本方針は、
デバッグ段階までは、些細なものも逃さないために、ちょっとしたことでもプログラムが止まり、絶対気づけるようにする。
しかし、リリースした後は、なにがなんでもシステムが止まらないように、些細な問題が起こっても大丈夫なように作る。

本当に重要なところを設計するときは、手を抜かず、ここまでやっています。

止まっちゃいけないものを作る場合。
仕様設計、コーディングの要所要所で、例外が起こっても、これだけは動く様に、あるいは自動復旧できるように考える。作る。
その上で、ログには盛大に警告を記す。
デバッグ段階では、警告を吐き出した段階でプログラムが停止するようにしておいて、いやでも気づくようにしておく。
そして、その後の工程からは停止コードを外しておく。

毎回こんなことやってたら心身共に持ちませんが。
工数もかかるし。

まずは、デバッグ工数が延びないように、設計をしっかりやる。
あいまいなところや、漏れがないように。