想定と拡張性

Regnessem というソフトウェアがある。

http://regnessem.osdn.jp/

これはインスタントメッセンジャではあるが、より厳密に言うとそれらの基盤となるもので、プラグインで機能を追加できる。 プラグインを大別すればユーザーインターフェイスと通信 (プロトコル) とその他に分けられる。 プラグインは必ずしも通信系のものだけではなく、たとえばノートパソコンの電池残量を通知するようなものもある。

私はとあるウェブチャットを Regnessem を通じてやりとりするためにプラグインを書いたことがあるのだが、そのときに RegnessemAPI に合わせるのに手間がかかった部分がある。 それはログインだ。 私が通信したかったウェブチャットには入室という仕組みがなく、名前と発言を入力すればすぐに発言できるものだった。 何の前触れもなく誰かが発言することがあったのである。

RegnessemAPI はセッション (プロトコルによってはチャンネルとか部屋などといわれる概念) を生成した後にそこへの参加者のアカウントを登録してから実際のメッセージのやりとりが始まるという手順を想定していたので、辻褄を合わせるにはプラグインの側で擬似的なログイン処理をするしかなかった。 発言するのが初めての人がいればその時点でログインしたとみなし、発言がない状態が続けばログアウト扱いとする具合である。

この場合はプラグインの側でどうにか出来たが、若干の無理をしたことにはかわりない。 フレームワークだとかライブラリの想定から外れてしまうと無理をせざるを得ない部分が出てくる。 フレームワークだとかライブラリは拡張性を確保しようとするなら、用途を充分に想定しないといけないな、ということを思った。

Document ID: dac77474f2f7a366afbad4185cd217e7

制度設計とトレードオフ

家庭用の風呂場や便所の扉に錠をかけられるようになっている場合がある。 しかし大抵は外側から錠を開けることが出来るようにもなっている。 十円玉を差し込んでひねるだけで簡単に開く。 これでは錠の役目をはたしていないではないかと思う人もいるかもしれないが、この錠は「使用中に誤って扉を開けないため」のものであって、その機能は充分に果す。 悪意を持って押し入る者を防ぐことを想定したものではない。 そもそも家の中に悪意を持った者がいたらもう駄目だろう。

では家の内部ではなく玄関扉はどうだろう。 玄関の鍵だって複製は簡単だ。 ホームセンターなどで複製を受付けている。 そして複製のために鍵を持ち込むにあたって身分を提示する必要がないし、審査もない。 あまりに簡単なので不安を感じるという意見は何度か目にしたことがある。

どうして身分を確認しないのかというと、持ち込まれた鍵がどこの鍵かを複製業者が知ってしまうと余分に複製しておいて窃盗に用いるということが出来てしまうからだそうだ。 実際にやるかどうかは問題ではなく、何かあったときにいちいち疑いを持たれるのも面倒な話だろう。 また、単純に数の問題でもある。 複製業者に鍵を持ち込む者が悪意を持っていたとして、それで起こる事件は一件に過ぎない。 複製業者が悪意を持てばかなり大きな規模のものになる。 事件の数が少なければ良いというものではないが、多いよりは良い。 そう考えれば鍵の複製を依頼するのに身分証明が不要という制度設計には妥当性があるとわかる。

制度に不安を感じたとき、その制度でなかった場合と比較して考えてみるとよい。 先日は機械の設計におけるトレードオフの話を書いたが、それは制度設計にも言えるわけだ。

Document ID: 142246b01950caab3d15952ccea42617

Chez Scheme で Foreign Interface を使ってみた

しばらく前に Chez Schemeオープンソース化された。

https://github.com/cisco/ChezScheme

Chez Schemeインタプリタ版である Petite Chez Schemeソースコードこそ公開されていなかったものの、以前から無料 (オープンソースではない) で利用できていたが、 Chez Scheme は Petite Chez Scheme よりもかなり高速なようだ。

しかし、私にとっては、速度はあまり重要ではない。 というより、 Petite Chez Scheme で充分すぎる速さだというべきか。 時間がかかる処理をすることがあまりないので劇的に体感する機会がないのだ。 たとえば二分かかる処理が一分になるなら大きな違いだが、二秒の処理が一秒になったからといってそれほどでもない。 (私の関心の外であるというだけで、技術的な高度さはまた別である。)

それよりも私が関心を持ったのは Foreign Interface だ。

http://www.scheme.com/csug8/foreign.html

基本的には Petite Chez Scheme と Chez Scheme とはほぼ互換性があるのだが、 Petite Chez Scheme で利用できなかった Foreign Interface が Chez Scheme では使える。 Foreign Interface は共有オブジェクト (Windows では DLL) の機能を呼び出すもので、 Scheme の外の世界への窓口となる。 処理系が用意しているものでどうしても機能が足りないときには C で書いて Foreign Interface で繋いでしまえば何でも出来る。 Foreign Interface があれば出来ることが一気に増えるわけだ。

そこで私は試しに HTTP 接続をやってみた。 Windows で Wininet を利用するものである。

(import (chezscheme))

(load-shared-object "Wininet.dll")

(define-ftype handle void*)

(define internet-open
  (foreign-procedure __stdcall "InternetOpenW"
                     (wstring unsigned-32 wstring wstring unsigned-32)
                     handle))

(define internet-open-url
  (foreign-procedure __stdcall "InternetOpenUrlW"
                     (handle wstring wstring unsigned-32 unsigned-32 handle)
                     handle))

(define INTERNET_OPEN_TYPE_DIRECT 1)
(define INTERNET_FLAG_RELOAD #x80000000)

(define internet-read-file
  (foreign-procedure __stdcall "InternetReadFile" (handle u8* unsigned-32 u32*)
                     boolean))

(define internet-close-handle
  (foreign-procedure __stdcall "InternetCloseHandle" (handle) boolean))

(define (http-get url bport)
  (let* ((hinternet
          (internet-open "sample" INTERNET_OPEN_TYPE_DIRECT #f #f 0))
         (hfile
          (internet-open-url hinternet url #f 0 INTERNET_FLAG_RELOAD 0)))
    (let ((vec (make-bytevector 1024))
          (vsize (make-bytevector 4)))
      (let loop ((r (internet-read-file hfile vec 1024 vsize)))
        (let ((s (bytevector-u32-ref vsize 0 (endianness little))))
          (unless (zero? s)
            (put-bytevector bport vec 0 s)
            (loop (internet-read-file hfile vec 1024 vsize)))))
      (flush-output-port bport))
    (internet-close-handle hfile)
    (internet-close-handle hinternet)
    (if #f #f)))

(display
 (utf8->string
  (call-with-bytevector-output-port
    (lambda(out)
      (http-get "http://example.com/" out)))))

Foreign Interface を使うときは些細な間違いで簡単にクラッシュするので、汎用的なライブラリを作るときは引数のチェックを厳しくした方が良いと思う。

Document ID: 4469def7a696bf182842bea7e01d5154

品質

私は以前に勤めていた会社で品質管理の仕事をしていた。 そこで私が得た知見を大きく分ければ以下のみっつの要素で説明できる。 機械製品 (または部品) を製造する会社であったが、プログラムやサービスなどについても大まかな考え方は適用できるかもしれない。

予見不能

製品設計の各段階で各部門の担当者を集めてレビューする制度があった。 私も何度も参加したし、指摘することもあった。 しかし、実際に製造が始まってみるととうてい予想できないような問題が発覚するのだ。 複数の要因が絡まった奇跡的な問題が、意外に頻繁に起こったりする。 不可解でも実際に起こるものを起こってないと言い張るわけにもいかない。 事実として起こるのだから。

あるいは、工程に手作業があるとやはりたまには誤りもある。 その誤りがあまりに極端な場合もある。 後から追求する私の立場からは、作業者が急に発狂したとしか解釈しようのないような無茶苦茶なミスというのがあるのである。 それがベテランであっても。 人間というのは無茶苦茶なことをするものなのだという前提で考えなければならない。

要するに、起こりそうな問題を事前に網羅することはとうてい無理だということだ。

物量

事前に予見できない問題をどうやって抑えるのか。 起きた問題に対して改善していくのだ。 それを重ねていくことで品質は上がっていく。 つまり、たくさん作ってたくさん問題を経験することが品質に繋がるのである。 物量は品質なのだ。

これは大量生産品に限ったことではなく、職人技についても言える。 それを制度ではなく個人の技として蓄積していくことを除けばその性質は工業製品とかわりない。 もし工業製品の中にどうしても職人技が必要ならば、その職人が会社から、あるいは業界から離れないように動機 (常識的には充分な金銭) を与えること、次世代を育成することを制度化することで全体としての品質を維持できるだろう。

トレードオフ

問題は少しづつでも解決していけば良くなるが、両立し得ない要素というものがある。

わかりやすい例で言えば資金だ。 何をするにも金がかかる。 資金をかけずに何かをすることは出来ない。 たとえば問題が起きたときの対処に使う資金が充分に小さいならば、あるいは問題が起きる確率が充分に小さいと予測できるのならば問題を放置するというのもひとつの戦略である。 極論すれば隕石が致命的な箇所を貫いていくことだってあるかもしれないのだ。 しかし、地上で使われる機械について隕石への対処など俎上に上げることすら馬鹿馬鹿しいというのはわかるだろう。 ではどのあたりまで考慮すればいいのかというのは目標をどこにおくかということに左右される。

機械製品ではしばしば一箇所に負荷が集中するように、そこが壊れやすいように設計される。 いずれどこかが壊れるならば交換しやすい部品から壊れるようにという設計思想である。 その設計思想が成立するにはその機械の部品が供給され続ける体制を構築する資金とのトレードオフがある。

使い捨てを前提にするならなるべく全ての部品に均等に負荷がかかるようにして長持ちさせるような設計思想も間違いではない。 頻繁にモデルチェンジがあるような機械だったり、修理を受けられない場所で使われるような機械だったりという前提条件があるならば妥当な選択になりえる。

トレードオフの見極めには、前提条件となる目的が定まっていなければならないということだ。

Document ID: b7232642308223ba5e07692b8e23e732

メモリをストリームにする

C++ でプログラミングしているときにメモリをファイルのように扱いたい場合というのがある。 元々実行ファイルとは別のファイルとしていたものを実行ファイルに抱え込む必要ができた場合などだ。

POSIX では fmemopen や open_memstream といった関数でそれが出来るのだそうだ。

しかし、 Windows ではそれを可能とする機能は用意されていないようだ。 低水準のレイヤで出来るのならばその方がよい (その上にどんなライブラリやフレームワークが載っても利用できるので) のだが、用意されていないのならば仕方がない。

仕方がないので C++ の言語機能のレイヤで、つまりはファイルを扱う一般的な方法であるところのストリームのインターフェイスを与えることでファイルと同じように扱う方法を考えた。

// memstream.h
#include <istream>

template <class Ch,class Tr=std::char_traits<Ch> >
class basic_mem_streambuf
  : public std::basic_streambuf<Ch,Tr> {
private:
  const Ch* mem;
  const size_t siz;
  const Ch* cur;
  Ch chbuf;
public:
  basic_mem_streambuf(const Ch* mem, const size_t siz)
    : mem(mem), siz(siz), cur(mem) {
    this->setg(&chbuf, &chbuf+1, &chbuf+1);
  }
protected:
  typename Tr::int_type underflow(void) {
    if(cur >= &mem[siz]) {
      return Tr::eof();
    } else {
      chbuf = *cur++;
      this->setg(&chbuf, &chbuf, &chbuf+1);
      return chbuf;
    }
  }
};

template <class Ch,class Tr=std::char_traits<Ch> >
class basic_mem_stream : public std::basic_istream<Ch,Tr> {
private:
  basic_mem_streambuf<Ch, Tr>* buf;
  basic_mem_stream(void);
public:
  basic_mem_stream(const Ch * mem, typename Tr::int_type siz)
    :std::basic_istream<Ch,Tr>(buf=new basic_mem_streambuf<Ch,Tr>(mem, siz)) {
  }
  ~basic_mem_stream(void) {
    delete buf;
  }
};

typedef basic_mem_stream<char> mem_stream;

以下のようにして、対象となるメモリを指定すればその範囲のデータをストリームとして使える。

#include <cstring>
#include <iostream>
#include "memstream.h"

int main(void) {
  const char* a="123\n456";
  int c,d;
  mem_stream b(a, std::strlen(a));
  b>>c>>d;
  std::cout << c << std::endl << d << std::endl;
}

C++ の入出力まわりは不格好だという批判はあるが、様々なデバイスに対する入出力を抽象化するにはそこそこうまくやっていると思う。

Document ID: d5929030205fda0ad79ae9b71bfbbbc2

invalid use of incomplete type

C++ のプログラムを書いていて、 GCC (5.3.0) ではエラーが報告されるのに Clang では警告すらなくコンパイルできた場合を見付けた。 Watcom C++ や Visual C++ でもコンパイルできるので GCC のバグだろうか? それとも未定義の挙動だろうか?

具体的には以下のコードである。

template<class T>
class foo {
  friend void T::qux(void);
private:
  int baz;
public:
  foo(void) : baz(1){}
};

#include <iostream>

class bar {
public:
  void qux(void) {
    std::cout << data.baz << std::endl;
  }
private:
  foo<bar> data;
};

int main(void) {
  bar x;
  x.qux();
  return 0;
}

エラーの内容は以下のようなものであった。

prog.cc: In instantiation of 'class foo<bar>':
prog.cc:18:12:   required from here
prog.cc:3:15: error: invalid use of incomplete type 'class bar'
   friend void T::qux(void);
               ^
prog.cc:12:7: note: forward declaration of 'class bar'
 class bar {
       ^~~
prog.cc: In member function 'void bar::qux()':
prog.cc:15:23: error: 'int foo<bar>::baz' is private within this context
     std::cout << data.baz << std::endl;
                       ^~~
prog.cc:5:7: note: declared private here
   int baz;
       ^~~

テンプレート引数で与えられた型がこの時点で不完全なのは当たり前のことで、テンプレートっていうのはそういうものだろうと思っていたので、今更それでエラーにされてもなぁという気持ちになる。

Document ID: 816fe8b9c9d96d8c1a313f5a3091702e

工学のバランス感覚

工学 (エンジニアリング) というと工業の学問という印象を持っている人もいるし、狭義にはそういう意味でもあるのだが、実際にはもっと広い意味も持っている。 工学というのは扱う分野というよりはそれが指向するところだ。 工学は科学と対になる概念である。 科学は物事の理屈を解き明かすもので、工学は問題を解決するものだ。 工学における問題解決の道具として科学の知見はおおいに利用するし、工学が積み重ねた問題解決の実績から理屈が見出されることもあるので使う理屈はかなり共通している。 とはいうものの、工学は科学のように厳密な理屈がなくても問題が解決されることが優先だ。 私が持っている制御工学の教科書には以下のような言回しがしばしば現れる。

経験的に次のような値が適当とされている。

私はこれを見て「えっ? 経験則なの?」と思った。 現実には読み取りきれない無数の要因が存在し、それらすべてを厳密に計算することは出来ない場合がある。 あるいは計算可能だとしても計算の手間が効果に見合わないといったこともあるだろう。 結局のところ程度問題なのだ。

例えばあなたが料理をするときにレシピに書いてある分量から 0.01g もずれずに厳密に計量するかというとそんなことはない。 結果的においしく食べられる範囲ならおおざっぱでもいいのだ。 おいしい料理が必要という問題を解決するのに充分ならどんな過程を経てもよい。 しかし、どれくらいおおざっぱでもよいのかというのは多分に感覚的だ。 その感覚というのはこういう風に作ればこういう結果が出来上がるというのを知っているからわかるのである。

そういった、「こうやればこうなるだろうな」という見極めは経験の積み重ねで得るもので、学問としての下地はあるにせよ最終的には肌感覚に頼る泥臭いものなのだ。 しかし、それで充分に問題は解決できるということでもある。 現実の問題解決のためには学問によって積み重ねられた既存の知識を活用しつつもそれに拘泥しないバランス感覚が必要なのである。

Document ID: 9e26670a5690b64eac2ef12933109c40