品質

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

予見不能

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

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

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

物量

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

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

トレードオフ

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

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

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

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

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

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

optional はコンテナか?

C++ に提案されている機能の内で optional という型がある。 従来は何らかの値を返す関数が失敗した場合にその値がポインタならヌルポインタを、整数なら -1 を返すなどといった「無効値」を使っていたわけだが、正常な値としてヌルポインタや -1 を返す可能性がある関数では別の方法が必要であるし、何より無効であることを確認する方法が一貫していないのはうんざりする。 そういった問題を解決するのが optional である。

optional はまだ仕様に入ったわけではないのだが、 Boost::optional を基礎にした提案であり、 Boost ユーザの中には積極的に使っている人もいるようだ。 最近の MinGW を導入していれば std::experimental::optional という名前で入っている。 公式に提案されている optionalBoost::optional は仕様が異なるので注意を要する。

さて、この optional であるが、 0 個または 1 個の値を入れられるコンテナであるという解釈があるようだ。 他の言語ではそういう解釈を持つものもあるらしい。

私は最近の C++ 事情をあまり追っていないので久々に少し遊んでみようと std::experimental::optionalイテレータを定義してみた。

// optional_iterator.h

template<class T>
class optional_iterator {
private:
  std::experimental::optional<T>* object;
public:
  optional_iterator& operator++(void) {
    return object==nullptr ? *this : (object=nullptr, *this);
  }
  optional_iterator operator++(int) {
    optional_iterator<T> temp = *this;
    this->object = nullptr;
    return optional_iterator<T>(temp);
  }
  optional_iterator(std::experimental::optional<T>& o)
    : object(o==std::experimental::nullopt ? nullptr : &o) {
  }
  optional_iterator() : object(nullptr) {
  }
  bool operator==(optional_iterator x) {
    return object == x.object;
  }
  bool operator!=(optional_iterator x) {
    return !(object == x.object);
  }
  T operator*(void) {
    return object ? **object : T();
  }
};

namespace std {
  template<class T>
  optional_iterator<T> begin(std::experimental::optional<T>& opt) {
    return optional_iterator<T>(opt);
  }

  template<class T>
  optional_iterator<T> end(std::experimental::optional<T>& opt) {
    return optional_iterator<T>();
  }
}

以下のような要領で使える。

#include <experimental/optional>
#include <iterator>
#include <iostream>
#include "optional_iterator.h"

int main(void) {
  std::experimental::optional<int> o1=1;
  for(auto i :o1) std::cout << i <<std::endl; // 1 が表示される
  std::experimental::optional<int> o2;
  for(auto i :o2) std::cout << i <<std::endl; // 表示されない
}

Document ID: 6d8f20d090b1289ff2a744aaa2f04e55

こんな夢を見た「七人ミサキ」

こんな夢を見た。

ある革命活動のリーダーに身の危険が迫っているらしい。 彼は革命を放り出して逃げようとしていたが、その前に影武者を用意した。 それが私だ。

私は整形させられ、薬物を使った洗脳で最初から真実リーダー自身だったかのように思い込まされた。 その処置を行なった人物は、その処置のことを「精神サーフェイサー」と言っていた。

リーダーと同じように振舞い、同じように思考して決めた私の次の行動は「逃げよう。 その前に影武者を用意しよう」ということだった。

Document ID: afd956466985625f4803c8fa0cab6c69

Chicken で transcript-on

先日は Gauche 上に transcript-on / transcript-off を実装した。

プログラミング言語 Scheme の処理系として特に人気のあるもののひとつである CHICKEN ではどうだろうかと試してみたところ同じような要領で書けた。 .csirc に以下を書いておけば利用できるようになる。

(define (%transcript-off)
  (error "Transcript mode not yet."))

(define transcript-off %transcript-off)

(define (transcript-on filename)
  (unless (eq? transcript-off %transcript-off)
    (error "Already in transcript mode."))
  (let ((log-port (open-output-file filename))
        (org-printer ##sys#repl-print-hook)
        (org-reader ##sys#repl-read-hook)
        (org-prompter ##sys#read-prompt-hook))
    (set! ##sys#repl-print-hook
          (lambda(x port)
            (org-printer x port)
            (org-printer x log-port)))
    (set! ##sys#repl-read-hook
          (lambda()
            (let ((obj (read)))
              (display obj log-port)
              (newline log-port)
              obj)))
    (set! ##sys#read-prompt-hook
          (lambda()
            (org-prompter)
            (display ((repl-prompt)) log-port)))
    (set! transcript-off
          (lambda()
            (set! transcript-off %transcript-off)
            (set! ##sys#repl-read-hook org-reader)
            (set! ##sys#repl-print-hook org-printer)
            (set! ##sys#read-prompt-hook org-prompter)
            (close-output-port log-port)
            (if #f #t)))
    (if #f #t)))

ドキュメントを読まずに書いているのでこれが CHICKEN 的にまっとうな方法かどうかわからないし、将来のバージョンの CHICKEN でも使えるかわからないが、変数名に hook と付いているからにはフックに使っていいんだろうと安直に判断した。

Document ID: 9d9afc27c1a84423c4ed3f02739cdbbf