2018年5月26日土曜日

オブジェクト指向とは何か

オブジェクト指向の「オブジェクト」とは「object型」のことではない
私のブログを読んでくださっている方なら、オブジェクト指向言語には慣れ親しんでいるかと思います。でも、「オブジェクト指向」をちゃんと説明できますか?

「オブジェクト指向」の疑問

オブジェクト指向の初学者は、まず最初にオブジェクト指向の3要素を学ぶと思います。
  • カプセル化
  • 継承
  • 多相性(ポリモーフィズム)
言い方に多少の差はあれど、だいたいこんな感じかと思います。
復習しておくと、カプセル化は内部の動作手順を隠蔽し、許可された手法のみを使って操作をできるようにすること、継承はクラス内容を引き継ぎつつ追加の機能を付けたすこと、多相性はメソッドを呼んだ時のふるまい等を実行時の型に合わせて決定することですね。
C++やJava、C#などの代表的なオブジェクト指向言語では、このような要素に対して「クラス」「継承」「メソッドのオーバーライド」などといった具体的な機能が備わっており、これらの要素と言語機能の習得を並行して行うと理解がよく深まるかと思います。

ですが、オブジェクト指向に関する勉強がこれで終わっていないでしょうか。
これはオブジェクト指向の特徴を挙げただけであり、そもそもなぜ「オブジェクト指向」という名前かということには何も触れていません。

そして、そのままオブジェクト指向言語を習得していくと、
  • ありとあらゆるクラス・型の最上位にはobject型がある(すべてのクラス・型はobject型を継承している)
  • ありとあらゆるメソッドは何かしらのクラス(=object型を継承したもの)に属さなければならず、オブジェクトへの操作として定義される
みたいな言語機能を見かけて、「ああ、ありとあらゆる操作がobject型に対する操作だからオブジェクト指向言語なんだな」みたいな納得感を持ってしまっている人も少なからずいるでしょう。私もそうでした。

そもそも、上の箇条書きで出てきた「オブジェクト」って何ですか?クラスのこと?それともインスタンスのことでしょうか。なんとなく意味も分からず、クラスやインスタンスとの違いも明確にしないまま適当に横文字をしゃべっているように見えません?かといって、objectを辞書で引くと「モノ」という訳が出てくるだけで、そんな抽象的な名詞では何も理解が進むことはありません。

だいたい、「オブジェクト言語」ではなく「オブジェクト指向言語」と呼ばれるのもなぜなのでしょうか。上の箇条書き程度の理解では、全然「指向」の意味がわかりませんよね。「指向」すなわち「oriented」を辞書で引くと「~の方向を向いている」とか「~を中心に考える」と言った訳が出てきますが、「オブジェクト」が何かよくわからないのに、そっちを向いているとかそれが中心だとか言われてもわけわかりません。

モジュール化

さて、「オブジェクト指向」とは何かについて紐解いていく前に、ここで一旦「モジュール化」の話をしましょう。

コンピューターが生まれたばかりの頃はごく小規模なプログラムが動いていましたが、コンピューターが発達するにつれて非常に大規模で複雑なプログラムが必要になってきました。
ですが、各プログラムの部分が複雑に絡み合っていては全貌が見えにくくなり、また、複数人での分担してプログラムを書くこともしにくくなります。当然、管理も難しくなって、機能の変更や追加があった時や不具合があった時に太刀打ちできなくなってしまいます。
なので、プログラムを何らかの基準で分割し、整理する必要が出てくるのです。こういうのを一般にモジュール化と言います。

ではどこで分割するのが良いでしょうか。
デイビッド・パーナスは、自身の論文で次のように述べています。
We have tried to demonstrate by these examples that it is almost always incorrect to begin the decomposition of a system into modules on the basis of a flowchart. We propose instead that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others. (Parnas D.L. (December 1972).)

(拙訳)私たちは、フローチャートに基づいてシステムのモジュール化をするのはおおむね不適切だということをこれらの例を用いて示した。その代わりに、難しい設計上の決定や、変更されやすい設計上の決定に基づいて行うことを提案する。これらのモジュールは、それらの決定を他から隠すような設計となる。
フローチャートに基づいてモジュール化するというのは、大きなプログラムの流れをフローチャートで書いてから、その個々の中身をモジュールとして実装する、というような意味のようです。長~い関数を書いてしまう人がその次のステップとしてやってしまいそうなことですね。
モジュール化をするときはそういうやり方をするのではなく、難しいことや変更されやすいことの単位で行うことのほうが効果的だそうです。

では、難しいことって何でしょう。 変わりやすいことって何でしょう。

難しいことや変わりやすいことというのは、往々にして何かの「内部表現」であることが多いです。例えば、内部的にはUTF8を使うのか、Shift-JISを使って処理をするのかとか、リストは配列を使うのか、連結リストを使うのか、などといったことが挙げられます。
なぜならば、やることと言うのはプログラムそのものの存在意味なのでそうそう変わるものではないからです。その実現手段や、内部でのデータ表現の方法はいくらでも変えることができますし、状況が変われば変わり得るでしょう。

そうすると、プログラムを「内」と「外」で分けたくなってくるわけです。内部表現は隠蔽し、外部とのやり取りは定められたインターフェースに則って行う、という形が、モジュール化をする上で大変うまく行く方法だということです。

"モノ視点" で分割する

じゃあどうやって内と外を分けるかと言ったら、1つの方法として、「モノ視点」というのが挙げられるでしょう。

例えば自動車。自動車は運転手から見れば「アクセルペダル」「ブレーキペダル」「シフトレバー」「ハンドル」などの決められたインターフェースがあり、それを外(自動車の駆動機構やエンジン等が入っている場所の外という意味)から操作することで、運転手の意図したとおりに加速、減速、曲がるなどといった動作を出力してくれます。ここで自動車の内部の状態(スロットルがどのくらい開いている、燃料の流量はどれくらい、エンジンの回転数は、温度は、シャフトの回転数は…などなど)は運転手がいちいち気を使う必要が無いですよね。

もうわかったでしょうか。

「オブジェクト指向プログラミング」というのは、「モノを中心に内と外を分けるプログラミング」ということなのです。「オブジェクト」が指しているのはインスタンスでもクラスでもなく、「モノ」なのです。
「モノ」と対になる言葉 として「コト」があります。上記の「フローチャートに基づいて分割する方法」は大まかな処理の流れ(コト)に着目して分割するということですが、オブジェクト指向言語はモノの単位で内外を分割してモジュール化をしようという発想が原点にある言語、という意味なのです。

なので、「オブジェクト指向言語ってどんな言語?」と聞かれたら、「モノ視点で内外を分けてモジュール化する言語」と答えれば良いでしょう。

モノ視点でオブジェクト指向3要素を見る

さて、冒頭で紹介したオブジェクト指向の3要素ですが、決してあれを軽視しているわけではありません。当然、「モノ視点で内外を分けてモジュール化する」という原点からいろいろと考えを深めていくと、それらの3要素に行きつくわけです。

カプセル化

これは「内外に分ける」ということですね。オブジェクト指向ならモノ視点です。
外からインターフェースを介してモノの内部状態を変化させるのがカプセル化です。

継承

これは「モノの分類」に当たります。そもそも「class」という言葉が「分類」ですしね。
例えば、自動車は普通車や大型車などに分類でき、大型車はバスやトラックなどに分類でき、例えばバスならば「自動車の基本機能」+「大型車の追加機能」+「バスの追加機能」みたいな関係性があることが言えます。
モノを階層構造で分類し、その差分を表現するのが継承になります。

多相性(ポリモーフィズム)

これは「分類が同じでも実体は別々になり得る」という意味です。
例えば同じ「ヒト」であっても一人ひとり個性があって違いますよね。その場合、同じ働きかけ(外からの操作)をしてもふるまい方(結果の出力)は異なるでしょう。
分類としてのふるまいではなく、モノそれぞれでふるまうことができるというのが多相性ということになります。

オブジェクト指向以外のモノの見方

さて、ここまで説明してきたオブジェクト指向ですが、欠点が無いわけではありません。

例えばオブジェクト指向を扱ったことがある人ならば、「分類の視点」について悩みを持ったことがあるでしょう。
自動車を大型車、普通車という車種で分類することもできるかもしれませんが、例えばトヨタ車、ホンダ車、日産車…という分類もできるはずです。一つの見方で使うだけならばそれでいいのですが、場合によって見方を変えなければいけないような場合では困ります。
が、多重継承にも様々な問題があり、多重継承構造を認めていないオブジェクト指向言語も少なくありません。

そこで、サブジェクト指向言語というものが開発されました。
「見方によって階層構造が変わってくるから、階層を別に定義できるようにしよう」という発想で、「主題」=「subject」で分割しようという言語です。
Hyper/Jなどという言語があるそうです。
ただ、サブジェクト指向はいろいろな問題があったようです。

そういった背景もあってか、オブジェクト指向のように1つの階層構造をとりあえず作って、様々な階層に横断的に適用させる記述をできるような言語も開発されました。それをアスペクト指向言語と言います。

例えばログの出力コードのような全く階層構造が違うようなクラスに横断的にかかわるようなコードはいろいろな場所に散在しがちです。
こういった関心ごとを分離してまとめるのがアスペクト指向なわけです。

ま、正直この辺りは私はあまり詳しくないのですが。





さて、一通りの説明が終わりましたがいかがでしたでしょうか。

実際にプログラムを書くと、「変わりやすいところを隠蔽」「モノ単位で分割」とか言葉で言うのは簡単でも実際にやるのはなかなか難しいことに気がつくでしょう。ただ、このブログで基本的なオブジェクト指向の考え方はわかっているはずですから、迷ったら原点に立ち返っていろいろと考えていきましょう。

ではでは。

2018年5月23日水曜日

ESP8266にDTRとRTSで自動書き込みをする

ESP8266を搭載したESP-WROOM-02が日本の組み込み市場に出てきてから約3年になりますが、あまりこれに関する記事を見なかったので(少しはあるようです)。
やっぱり組み込み系の記事を見ていると圧倒的にソフト屋さんのブログが多く、ハード屋さんが全然いないんだなって。

ESP8266のリセット回路

ESP8266のリセット回路はこのようになっています。IO0ピンをLにした状態で立ち上げる(リセットする)とブートローダーが起動し、UARTからプログラムが書きこめるようになります。すなわち、IO0と~RESETのスイッチを両方押して、~RESETを離してからIO0を離して書き込みボタンを押すという操作が必要になります。
ですが、書き込みをするたびにスイッチ2つを操作しないといけないのはいささか不便です。可能ならばArduino IDE上で書き込みボタンを押したらそのまま勝手に書き込まれてほしいものです。

そんな誰もが考えることは、ちゃんとSDKの提供者たちも考えてくれていて、その解決策が提供されています。
ズバリ、DTR端子とRTS端子を使います。

シリアル通信のフロー制御

DTR端子とRTS端子を使うと言いましたが、TXDやRXDとは違ってあまり聞きなれない端子です。これはいったい何なのでしょう。

UARTなどのシリアル通信では、送信側が送信したいタイミングでデータの送信をできてしまいます。ですので、受信側が受信する準備ができていない場合、勝手にデータを送信されても取りこぼしてしまいます。そのようなことがあっては不都合な場合、相手方に「受け入れ可能だよ」と言うことを伝える制御を行います。その制御のことをフロー制御と言います。
信号線名 名称 説明
TXD Transmit Data 送信データ
RXD Receive Data 受信データ
TXDを受ける。
DTR Data Terminal Rady 端末準備完了
機器の電源が入りポートが開かれると1になる。
DSR Data Set Ready データセットレディ
DTRを受ける。
RTS Request To Send 送信要求
自分の受信バッファーに十分な空きがあり、データの受け入れが可能にになると1となる。
CTS Clear To Send 送信可能
RTSを受ける。
SG Signal Ground 信号線の接地ライン

フロー制御ありのシリアル通信は、通常このように接続します。DTRで相手にこちらの端末が生きていることを伝え、RTSでデータ受け入れ可能であることを伝えます。
このことから、RTSが1になるときは必ずDTRも1になっているということがわかります。こちらの端末が通信できる状態じゃないのにデータ受け入れ可能になるわけはないですから。
FT232などのUSB-シリアル変換ICにもこのフロー制御用のピンは出ており、ソフトウェアから任意に出力を変更可能です。ただし、FT232ではDTRとRTSの論理が逆転しているので注意が必要です。
FT232R Datasheet - FTDI Chip より引用)

DTRとRTSを使ったリセット回路

さて、これを踏まえてDTRとRTSを使ったリセット回路を作る必要がありますが、大前提として「通常使用時(ESP8266の通常動作時)にシリアル通信をしてもリセット指令が出ない」というのが最低限の要件として挙げられます。
フロー制御を使っていないと言いつつも別用途でそれらの線を結線してしまっているわけですから、例えばPCで汎用ターミナルソフトを使ってESP8266とシリアル通信したときに、そのソフトが気を利かせてフロー制御を行ったがゆえに偶然リセットがかかっては困ります。
そこで先ほどの赤字を思い出してください。RTSが1になるときは必ずDTRも1になっているので(RTS,DTR)=(1,0)は通常のフロー制御ではありえないということになります。
なので、この状態をリセットに割り当てればいいわけです。
その条件をもとにトランジスタ1個で作った回路がこちらになります。FT232に合わせてDTRとRTSの論理を逆転させています。
(~RTS,~DTR)=(0,1)のときのみ~RESETが0になり、他の時は1になります。
そのほか、~RTS=IO0になりますので、~DTR=0にした状態で~RTSを操作することでIO0を変更することもできます。
これで無事にリセットシーケンスが送れるようになりました。めでたしめでたし。

ただ、1つ問題があります。
IO0を出力に使えません。また同じ系統の問題として、手動用のスイッチと共存させることができません。そうすると、RTSとIO0の間に1段トランジスタを挟みたくなりますよね。それならばせっかくなので対称な回路にしてしまいましょう。
これでよく見かける回路になりました。完成です。

真理値表を書くとこんな感じになります。
~RTS ~DTR ~RESET IO0
L L H H
L H L H
H L H L
H H H H
(~RTS,~DTR)=(L,H)のときのみ~RESETがLになり、他のパターンでは~RESET=1となります。そのほか、リセットにかかわらずIO0を制御する方法も確保できており、出力はプルアップとなっているためスイッチとの共存も可能です。
トランジスタ2石で完璧なリセット回路が出来上がりました。

自動書き込みを行う

さて、回路が出来上がったら実際に書き込みます。
Arduino IDEのツールメニューからReset Methodを"nodemcu" (NodeMCU)に指定してあげれば書き込みの直前でDTRとRTSを操作してくれます。
実際に書き込みの時のRESET端子とIO0端子を観察した画像がこちらです。青が~RESET、黄色がIO0です。最初にリセットをかけてから、その入れ替わりでIO0をLにしています。たいていRESET後はクロックが安定するまで(数~十数ms程度)マイコンは立ち上がりませんので、 RESETを終了させるのと同時にIO0をLにしても十分間に合うわけですね。


書き込みが始まるとIO0の黄色線がガタガタ震え始めています。
これは実際にマイコンとデータのやり取りを始めてRTSが変化しているためですね。フロー制御が行われているだけですが、目論見通り~RESETは安定してHを保てています。

これで幸せなESPライフが送れるようになりました。