誰にとっての最適化? - Gコードの場合

デザインはわがまま→思いやり
川崎和男 『ドリームデザイナー

最近はユニバーサルデザイン(UD)という言葉を良く耳にするようになりました。はてなキーワードの説明では、『ユニバーサル=普遍的な、全体の、という言葉が示しているように、「すべての人のためのデザイン」を意味し、年齢や障害の有無などにかかわらず、最初からできるだけ多くの人が利用可能であるようにデザインすること。』となってます。つまり、全ての人にやさしいデザインということになるでしょうか。
この「ユニバーサル」つまり「全ての人に」という部分は非常に重要な考え方だと思いますが、やはり人によって得手不得手がありますので、ものによっては、どうしてもこっちを立てればこっちがたたずといった状態になります。こうなると、どこを立てるか、つまり、どんな評価関数でもって最適化をかけるかということが重要になります。

さて、こんなことを書いているのは最近Gコードの構成についてなるほどーっと思ったのがきっかけです。

Gコードといえばテレビ番組の予約等で使われる数桁の数字。新聞のラテ欄やTV雑誌でおなじみですね。といっても、最近は、EPG搭載のデジタルTVやDVD/HDDレコーダが普及して、あまり使われなくなった感がありますが。さて、Gコード、原理的には、数文字の数字に、番組の開始・終了日時、チャンネル情報を符号化したものになっています。ですので、この数字を対応のVHSビデオやDVDレコーダに入力すると、これら予約録画に必要な情報に変換して、予約録画ができるという仕組みです。

はてさて、このあたりまでは使われたことがある方ならご存知かもしれないのですが、今回私がなるほどと思ったのは、この数字の符号化方法にあります。Gコードの数字列は可変長コードになっているのですが、この数字列、ゴールデンタイムの番組、つまり視聴率の高そうな番組程短くなるように符号化されているのです。いわゆる、エントロピー符号化というやつです。時には、一文字のこともあるそうです(※1)。知ってました?
つまり、Gコードの符号化設計における評価関数はユーザーの予約頻度なんですね。

478850362X で、ここでふと思ったのは、「では、今のGコードの符号化って本当に使われ方に対して最適化されているの?」ということ。実際の仕様は公開されていませんが、個人で解析されている方の情報等をみると、結構こったことになっているようですが、そもそもいろんな趣向を持った人がいるのに、最適化ってどうするのだろうという疑問。
まず単純に考えると、時間帯や番組長、チャンネル等で予約頻度順に符号長も短くなるようにするという方法。つまり、世の中の全予約の平均長を短くするという方針。単純に考えるとこうなるのでしょう。多分Gコードもそういう考え方がもとになっているのだと思います。
しかし、よく考えれば、Gコードを使う人って機会慣れした若者から不慣れなおじいちゃん、おばあちゃんまでいろいろおられるはず。となると、「各番組の予約頻度」×「その番組の視聴者層の機械慣れ具合」といった重み付け後の値でもって最適化を考えないといけないのかとかも考えられます。
いやいや、そもそも機械慣れしているなら少々数字が多くても大丈夫だから、この層は一旦おいておいて、おじいちゃん・おばあちゃん層の番組の最適化を進めるというのもひとつの考え方としてあるだろうとか。。
考え出すとこれがなかなか奥が深い。

プログラミング時には、アルゴリズムの性能指針として、平均オーダーと最悪オーダーが使われます。平均と最悪、どちらが特に重要かは、ユースケースによってかわってきます。この考え方は、ユーザビリティを考える時もこれは同じ。先の例では、全番組予約頻度で符号化長を調整するのは平均値の最適化。おじいちゃん・おばあちゃん優先で最適化するのが最悪値の最適化といえなくもないでしょう。
自分自身ふりかえると、大抵の設計時には、ついつい平均値優先になっていたなぁという気がします。きっと、その方がわかりやすいからでしょう。

冒頭の「デザインはわがまま→思いやり」というのは、デザインディレクター 川崎和男氏の言葉。『考具―考えるための道具、持っていますか?』という本の中で紹介されていたので知ったのですが、とても大好きな言葉です。ユーザビリティは、単純に数学的に最適化できるものではありません。最後のまごころ忘れちゃいけませんね。

※1 2005年6月1日放送のNHKきょうの料理では、一部新聞でGコードが「1」と掲載されたため、同番組の出演者が珍しく「当番組をぜひ見てほしい」と宣伝した事もあるんだそう。

【関連書籍】
誰のためのデザイン?―認知科学者のデザイン原論 (新曜社認知科学選書) ドナルド・A. ノーマン
ユーザビリティエンジニアリング原論―ユーザーのためのインタフェースデザイン ヤコブ・ニールセン
アフォーダンス-新しい認知の理論 佐々木正人
暗号解読 上巻 (1) (新潮文庫) サイモン・シン 青木 薫

【関連記事】
できることが増えると、できないことが増える
プログラム+インターフェース=モジュール
似て非なるインターフェース
選択できない選択肢

【関連リンク】
Gコード解析(analyzing VCR-plus code)
ユニバーサルデザイン - コクヨ
Webデザイン・Webデザイナーのまとめサイト | Webデザインに優れたサイトのリンク集
ヤコブ・ニールセンの考えをまとめたユーザビリティガイドライン
Jakob Nielsen博士のAlertbox
楽天はデジタル家電の新製品を随時ご紹介

環境にやさしいソフトウェア開発

良い結果をもたらす嘘は、不幸をもたらす真実よりいい。
ペルシアの諺
すっかり暑くなってエアコンが恋しい季節になってきました。この季節になると、毎年話題にあがってくるのが省エネや環境問題、エコといった話。去年はクールビズがずいぶんとはやりました(私はもともとスーツ着る機会が少ないので、伝聞状態でしたが)。今年はどうなんでしょう。

427000181Xさて、環境問題というと、少し前に「環境問題はなぜウソがまかり通るのか」という本が話題になっていました。関西のTV番組 たかじんのそこまで言って委員会でとりあげられたのがきっかけで、かなり売れたようです。その内容は、官主導のリサイクル運動が隠してきた非効率性と利益誘導の実態をあばくというもの。例えば、
  • 地球が温暖化しても、海の水位は上昇しない
  • 猛毒に仕立て上げられたダイオキシン
  • 回収されたペットボトルはほとんどがリサイクルされていない
等など。実際に読んではいませんが、TVやレビュー記事なんかをみると衝撃の内容。聞いたことのある話もありますし、ちょうど最近「古紙100%再生紙は環境にやさしいはウソでした」のような話もありましたし、ある程度は真実なのでしょう。でも、この手の本の常として、扇動的で行き過ぎた論調になっている部分もあるのでしょうね。反論されている方もおられます。

『環境問題はなぜウソがまかり通るのか』のウソ

常識をくつがえすような内容というのは読んでいて面白いのですが、手放しで信じるのは、結局危険かなと思います。でもまぁ、今の環境政策には何かしら問題はあるのは間違いなさそうですので、いいきっかけにはなったんじゃないかなとは思います。
ちなみに、この本の内容がどうであれ、環境について何も考えなくていいということはありません。日本が欧米に比べてましであっても、環境が確実に壊されているのはまぎれもない事実でしょう。そんな中で、ちょっとした意識をもって行動するだけでも、それが積み重なれば大きな効果になります。リサイクルなんていわなくても、ちょっと水を節約する、エアコンの温度をしぼるなんてことでも十分です。というのは、自分に言っているのですけどね ^^)

4894714086さて、前ふりだけで終わりそうですが、もともとはソフトウェア開発と環境問題について書こうと思ってました (^^)。ものづくりで環境というと、例えば、省電力だとか材料の選択だとかハードウェア的な話が中心で、ソフトウェアはあまり関係ないのかなと思われることもあります。
しかし、組込み機器等では、ソフトウェアのつくりによって、省エネになったり、材料がへったり、あるいは製品が長持ちしたりということにはつながります。例えば、次のようなもの。
  • メモリ効率のよいプログラム設計により搭載メモリが減る
  • ハードウェアアクセスを最小限にして、必要最低限の電源供給におさえる
  • ソフトの起動速度を向上させて、電源をこまめにきれるようにする
このような製品自体の環境性能を向上させる以外にも
  • 開発プロセスを改善して開発効率をあげる。これで作業時間が短くなり、開発にかかるエネルギーを削減する
  • あるいは、それを実現するようなソフトや製品をつくって、ユーザの活動エネルギーをおさえる
なんていう考え方もありますね。特に、後者はものづくりを通じてユーザの利益を最大化する、と同時に環境にとっての利益も最大化するということで、エンジニアにとっての基本姿勢です。
嫌々仕事にせず、まずはいいものをつくる。これこそ一番のエコ活動なのかもしれませんね。

最後にひとつHPを紹介。去年も紹介したEcotonoha。今年もやっています。素敵です。

【関連記事】
ペーパーレス - 電子化からもう一歩

【関連書籍】
省メモリプログラミング―メモリ制限のあるシステムのためのソフトウェアパターン集 James Noble
組込みソフトウェア開発向けコーディング作法ガイド[C言語版] SEC BOOK
組込み現場の「C」プログラミング―基礎からわかる徹底入門 SESSAME
ハッカーのたのしみ―本物のプログラマはいかにして問題を解くか Henry S. Warren
環境問題はなぜウソがまかり通るのか 武田 邦彦
不都合な真実 アル・ゴア 枝廣淳子

【関連リンク】
Ecotonoha (エコトノハ)

行き場のないエラー - エラー処理とassert

失敗はつまずくことではない。つまずいたままでいることだ。

またまた更新間隔が開いてしまったので、久々の更新です(^^;)。今回は「エラー処理」を取り上げてみます。「エラー処理」という単語もあいまいなのですが、今回とりあげるのは「あるメソッド・関数が本来の処理を達成できなかった場合の処理」といったところです。

エラーを伝える

さて、処理が失敗した時の最も基本的なパターンは、戻り値としてエラー値を返すことです。このような関数・メソッドは数多くあります。例えば、POSIX系のclose()関数をみてみましょう。
int close(int fd);
これはエラーで0以外の値が返ります。このように、エラーか否かを戻り値で返すものの他に、本来の戻り値がとり得ない値をエラーの意味として返すものもあります。例えば、POSIX系のopen()関数です。
int open(const char *pathname, int flags, mode_t mode);
この関数は戻り値としてfd(file descriptor)を返しますが、エラー時は-1を返す仕様です。

どちらにせよ、この関数を使用する側としては、戻り値を確認することによって、エラーが起こったか否かを確認することができます。

エラーを処理する

さて、大半の関数がこのようにエラーを戻り値で通知してくれるわけですが、そのようなエラー値を見つけた場合、一体どうすればいいのでしょうか。大抵の場合、次の3種類に分けられるでしょう。
  • 1. エラー処理としてユーザーへの通知や、代替処理、後始末などを行う。
  • 2. エラーを無視する。
  • 3. 処理をそこで止める。

1は最も基本的なパターンです。ファイルがオープンできないといった場合であれば、ファイル名が違う(あるいは存在しない)とか、パーミッションが不適切だとかいう場合がありますので、この場合はユーザー(又は上位モジュール)に通知する必要があるでしょう。場合によっては、ネットワーク越しであればリトライ等も必要かもしれません。書き込み中のエラーならファイルをクローズするなどの後処理も必要でしょう。
しかし、実際にこのようなエラー処理が行われるのはごく一部です。大抵の場合は、2の「無視」に落ちることが多いと思います。

大抵の関数はエラーを返してくれるのですが、実際全ての関数ごとにエラー処理をするのは難しい、あるいは無理がある場合がほとんどです。例えば、バグでしかエラーになりえないものに対して、適切なエラー処理を与えるというのは不可能でしょう(全てのprintfのエラー見てる人なんていませんよね)。それに、全ての関数毎にエラーを処理しようとすると、次のようなすさまじい入れ子ができあがりかねません。
  /* 処理A, B, C, Dを行う */
  if (func_A(x, y) == 0) {
      ... 
      if (func_B(x) == 0) {
          ...
          if (func_C(y) == 0) {
              ...
              if (func_D(x, y) == 0) {
                  ...
open()のようなI/Oのための関数は、途中でエラーが発生する場合もあると思いますが、論理的な関数の場合は、最初の関数が成功すれば、あとは絶対成功するはずという場合もあります。このような場合であれば、確かにエラー処理は必要ありませんし、しようもありません。しかし、エラー処理が必要ないというのと、エラーをチェックしなくて良いというのは違います。つまり、単純に無視していいとは限りません。そこで、3の「処理を止める」というのが登場します。

見てみぬふりをしない

次の例を見て下さい。
...
pthread_mutex_lock(&mutex)
shared_variable ++;
pthread_mutex_unlock(&mutex)
...
いたって単純なmutexでの排他制御コードです。ここでmutex_lockはエラーを返す可能性がありますが、エラーは確認されていません。mutex_lockがエラーになるのは、初期化忘れや、同じスレッドでの二重取得等ですが、自身での本来、このような例でエラーが確認されない理由は、それは外的要因ではなく、単なるバグです(trylockと違い、busyならまたされるだけ)。よって、ユーザーに通知するという必要もありませんし、エラー処理で復帰できる望みも薄そうです。では、どうするか。このコード例では特にエラーを見ずに無視していますが、これでいいのでしょうか。

プログラムにバグがなければ、自スレッドから再度mutexをとってみたり、初期化されていない構造体を渡したりということは発生しません。しかし、開発中にはこのような不正規な処理のために、よくデッドロックを起こしてしまうことがあります。しかも、このようなバグは、タイミング依存になったりして、なかなか発見されません。
しかし、この例の場合、関数(pthread_mutex_lock())のエラーを見ていれば、すぐに分かったはずなのです。

よって、ここは以下のようにすべきでしょう。
...
ret = pthread_mutex_lock(&mutex)
assert(ret == 0);
shared_variable ++;
ret = pthread_mutex_unlock(&mutex)
assert(ret == 0);
...
assertにひっかけてやることで、おかしな動作をした場合、すぐに 検出できます。
バグというのは、発生地点から離れれば離れるほど解析が難しくなりますので、このように発生時点で捕まえるというのは非常に重要です。
なお、標準のassertマクロなら、リリース時に消すこともできまし、検出時に行数も出してくれますので、リリース物の効率にも影響を与えません。

ライブラリの中心でエラーを叫ぶ

さて、上記の例では、エラー値を見て処理を止めていました。しかし、エラーを起こしてはいけないのはここ部分のコードだけではないでしょう。mutex_lockを使用しているコード全般に言えることです。であれば、mutex_lockしている方ではなく、mutex_lockの方でassertにひっかけてやる方がいいのではないかということも考えられます。

その通りです。pthread_mutex_lockの例で言えば、エラーを返す前に、abort()するようなコードを入れておけば、不正な処理が行われた段階でプログラムは停止します。デバッガを繋ぐなりしてあれば、バックトレースでどこが問題かもすぐ解析できます。

しかし、この方法はライブラリのソースコードがない場合はそうはいきません。また、自分がライブラリを作成する側の場合、ライブラリ内で勝手にabortするのは、常に期待値とはいえません。自分がライブラリ作成者である場合は、ライブラリ使用者のために、エラー時の挙動を選択できるようにしておいてやるというのもいいでしょう。例えば、
  • コンパイル時にエラーを返す前の処理(ログを出力、abort、HOOK関数を呼ぶ等)を選択できるようにする
  • 環境変数やAPIでエラーを返す前の処理を選択できるようにする
といったことが考えられます
エラー通知の重要な役割の一つは、上位モジュールの間違いを指摘することです。そして、その目的を達成するためには、単なるエラー通知だけでなく、ログやabort,フック関数の呼び出し等とうまく組み合わせることによって、その効果は何倍も高まります。

例外スローの場合

以上では、関数リターンとしてエラー値を返すというパターンで説明しましたが、C++やJavaの例外スローも同じようなことが言えます。例外の場合、
try {
  doAction();
} catch (...) {
  /* 無視 */
} 
として、とりあえずterminateで落ちるのだけを防ぐといったコードを時々見かけます。しかし、これはエラーを無視して突き進み、事態をどんどん悪化させることにつながりかねません。例外もエラー同様に、どこでどのように処理すべきかということを明確にした上で、設計しないと行き場のない例外が氾濫することになります。

せっかくのエラー通知。しっかりと耳を傾けてあげましょう。

4873115930 【関連書籍】
デバッグルール - 9つの原則、54のヒント David J.Agans
組込み開発者におくるMISRA‐C―組込みプログラミングの高信頼化ガイド MISRA‐C研究会
C++プログラミングの処方箋―ひと味違うコードを書くための99の鉄則 Stepehn C. Dewhurst
Cプログラミングの落とし穴 A.コーニグ
組込みソフトウェア開発における品質向上の勧め―コーディング編 情報処理推進機構 SEC
Effective C++ 【改訂第2版】 Scott Meyers
組込みプレス Vol.3

プログラム+インターフェース=モジュール

アルゴリズム + データ構造 = プログラム
Algorithms + Data Structures = Programs
N.ヴィルト
最近しばらく関数やライブラリのインターフェースについての雑感を書いてきましたが、プログラムにおけるインターフェースの位置付けに関して面白い記事があったのでその紹介と感想を。

その記事と言うのは、Rogue Engineer's Diary/やさぐれ日記さんの「「アルゴリズム+データ構造=プログラム」? 本当に?」という記事。その内容は、前回の記事でも紹介したN.Wirth氏著の古典的名著のタイトルでもあり、プログラミングにおける格言とも言える「アルゴリズム+データ構造=プログラム」という考え方には、「インターフェース」の観点が抜けているんじゃないか、「アルゴリズム+データ構造+インターフェース=プログラム」とするべきじゃないか、といった内容。

なるほど。うまい表現です。でも、個人的には、もうちょっと表現を変えて次のような表現にした方がしっくりきます。
  • アルゴリズム+データ構造=プログラム
  • プログラム+インターフェース=モジュール
プログラムはルーチン、モジュールはライブラリとも言い換えられるかもしれません。つまり、個々の処理を行うプログラム(ルーチン)にインターフェースを与えてやることで、人間が理解しやすい、再利用性のあるモジュール(ライブラリ)になる、というイメージです。目的の処理を実行する「プログラム」を書くだけではなく、それを次回に再利用できる「モジュール」に仕上げるための、インターフェース設計。「+インターフェース」によって、コンピュータが処理する「プログラム」を、人間が利用する「モジュール」へ変化させる。

考え方をの単純な言葉で表現してみるというのは、考え方が整理されて気持ちがいいですね。

【関連記事】
似て非なるインターフェース
関数のユーザビリティ
選択できない選択肢

【関連リンク】
「アルゴリズム+データ構造=プログラム」? 本当に?(Rogue Engineer's Diary/やさぐれ日記)

【関連書籍】
オブジェクト指向における再利用のためのデザインパターン
Javaの格言―より良いオブジェクト設計のためのパターンと定石 Nigel Warren
Algorithms + Data Structures = Programs Niklaus Wirth
珠玉のプログラミング―本質を見抜いたアルゴリズムとデータ構造 - Jon Bentley

似て非なるインターフェース

外見というものは一番ひどい偽りであるかもしれない。
シェークスピア
引き続き、プログラミングにおける関数(ライブラリ)インターフェースの話を。

今までに生み出されてきたプログラムの数は数知れず。さらにこうしている今も世の中では無数のソースコードが生み出されています。とはいえ、そのうちのほとんどはそれほど新規性のあるものではないでしょう。細かい違いはあれど、基本的なロジックは既にどこかで作られたものものを焼き直し、組み合わせているだけです。

アルゴリズム+データ構造=プログラム」と言えば、Pascalの設計者であるNiklauth Wirth氏によって書かれた古典的名著ですが、まさに言い得て妙。「アルゴリズム」と「データ構造」はまさにプログラムの基本構成要素であり、定石とも言えるパターンがあります。クイックソートや二分探索のアルゴリズムや、リスト構造や配列、ハッシュ等のデータ構造。それらの計算オーダー、メモリ効率。。。プログラミングのテキストには必ず出てきます。テキスト上で眺めている間は退屈なものですが、基本的なパターンを知っておくことは実際にプログラムを書く上では重要です。このようなテキストにあるような基本パターン以外にも、既存のソースコードからは、パターン化されたデータ構造やアルゴリズムを数多く見ることが出来ます。

さて、アルゴリズムやデータ構造に加えて、プログラミング、特にモジュール設計において重要なのがインターフェース設計。このインターフェースについても全てを一から考えるようなケースはほとんどなく、大抵の場合、リファレンスとなる前例があるでしょう。これも一種の定石のようなものです。

しかし、このインターフェースについては、アルゴリズムやデータ構造にはない注意点があります。それは、アルゴリズムやデータ構造がコンピュータを対象としたものであるのに対し、インターフェースはそれを使う人間を対象にしているという点です。使う対象の差は、変化に対する追従性に表れます。
アルゴリズムやデータ構造を変えたとしてもコンピュータならすぐ追従してくれます。しかし、例えば関数のインターフェースをちょっと変えても、それを使う人間はすぐに適応してはくれません。慣れ親しんだインターフェースであるほどそうです。人間の思い込みというものはそうすぐには変わりません。

ちょっと例を出しましょう。今、ファイルアクセスを行うライブラリの設計を行っているとします。POSIXのopen/close/read/writeは標準的なインターフェースと言えるので知っている人も多い。リファレンスとしては良いでしょう。 でもここで一つ問題が。このopenのflagsには、ファイルが存在しないときは作成するというO_CREATというフラグがあります。O_CREATEではなくO_CREATです。E一文字くらい省略するなよと何人の人が思ったことでしょう。ということで今回はO_CREATはO_CREATEと変えたとします。
さて、ここで何が起こるでしょうか。大抵のユーザはI/Fを一瞥して、「あぁ、POSIXと同じだな」と思い込んで、詳細な仕様をわざわざ見ずに使うでしょう。となると、ファイルを作成したいときは思わずO_CREATと指定してしまいます。この場合は、コンパイルでエラーになるだけなので対した被害はありませんが、実行時でしか気がつかないような機能的な違いだと非常にやっかいです。
これがPOSIXのopen()と似ても似つかないインターフェースならユーザーはCREATEと指定してくれる可能性が高いでしょう。「似て非なる」という点が混乱のもとになっているのです。

アルゴリズムやデータ構造といったコンピュータを相手にする部分は、小さな工夫を積み重ねることで効果は上がるでしょう。しかしインターフェースのような人間が使うものは小さな変化を積み重ねていては、かえって混乱を招くだけです。「似て非なる」はコンピュータには別々できても人間には分かり難いものです。キーボードのQWERTY配列は人間工学的に見て優れているとは言えませんが、そうは分かっていてもなかなか変更されないのも良く似た理由と考えられるでしょう。

一から新たなインターフェースを設計する際には、単純に最も良いと思われるものを目指せば良いでしょう。しかし、それが何かしら基になるものを改善するようなものである場合は、改善後のインターフェースに対する利便性等だけでなく、変化に対する移行性、時には互換性といったものに対する配慮も必要でしょう。

【関連記事】
関数のユーザビリティ
選択できない選択肢

【関連リンク】
入力法および表記法のヒューマン・インタフェース学入門(QWERTY成立の解説等)

【関連書籍】
Algorithms + Data Structures = Programs Niklaus Wirth
アルゴリズムとデータ構造 - N.Wirth
珠玉のプログラミング―本質を見抜いたアルゴリズムとデータ構造 - Jon Bentley
プログラミング作法 - Brian Kernighan

選択できない選択肢

あなたの実力以上に有徳であろうとするな。
できそうもないことを己に要求するな。
プログラミングにおいて最も重要なもののひとつ。「美しさ」。 「美しい」ソースコードと「読みやすい」ソースコードとは必ずしも一致するのものではありませんが、美しいソースコードは可読性や拡張性が高く、保守性にも優れています。美しいコードを書こうという努力は、決して自己満足に終わるものではありません。

さて、「美しい」といってしまうとあまりにも漠然としているのですが、今回とりあげるのはその中の「統一性」です。
インターフェース設計においては、複数の機能をひとつのインターフェースで実現しようとすることがあります。しかし、もともと複数の機能ですから、機能によって必要とするパラメータが微妙に異なったり、操作対象によって使えたり使えなかたったりする場合がでてくることがあります。
よく言われるオブジェクト指向のアンチパターンにfat interfaceと呼ばれるものがあります。一部の子クラスでのみ有効な機能も全てスーパークラスのインターフェースに詰め込んでしまい、スーパークラスのインターフェースが膨れ上がる。おまけに、実オブジェクトのクラスによって使えるインターフェースと使えないインターフェースまで出てきてしまう。fat interfaceの名の通り、インターフェースの数が膨大になるもの勿論問題なのですが、それ以上に、使えるか使えないか一見して分からないというのは、使う側にとっては非常に問題です。
他の例としては、いろんな機能要求に答えるために、複数の引数を持ったり、フラグ型引数を持つもの。例としては、POSIXのopen()関数。この関数では、flagsにはこれとこれが同時指定可能とか、このflagsにはこのフラグとこのフラグは同時指定可能とか、modeはflagsがこの値のときだけ有効とか、関数の形や型定義からは判断できない仕様ができてきしまっています。

誤解のないように書いておくと、ひとつの関数やインターフェースに複数の機能をバインドしてしまうのはいけないことだと言っているわけではありません。インターフェースの統一は非常に重要な観点です。上記の例で問題なのでは、インターフェース中に「選択できない選択肢」があることです。仕様を確認すれば分かるというのはなしです。そもそも、選べないものは見せるべきではありません。

インターフェースを統一・共通化しようとした際に、上記のような「選択できない選択肢」が登場しそうな場合には、本当に共通化するべきか再考するべきです。そもそも共通項のない機能であれば無理して共通化する必要はありません。共通項が多い場合や、相乗効果が得られそうな場合には統一する 価値があるかもしれません。先のopen関数の場合、多少不要な引数が現れることもありますが、readとwriteのオープン処理が共通化されているために、read/writeモードでファイルをオープンするといったこともできていますので、それほど悪い例とは思いません。

本当に「美しい」コードは、見た目のエレガントさだけではなく、機能性・理解性も優れているものです。

【関連記事】
関数のユーザビリティ

【関連書籍】
アンチパターン―ソフトウェア危篤患者の救出
J2EEアンチパターン
サーバーサイドJavaアンチパターン
Code Complete第2版―完全なプログラミングを目指して

関数のユーザビリティ

しかし,単純なものでも説明がいるような道具があるとするなら,これは,よいインターフェイスを持った道具ではないのだ。
J・J・ギブソン 『アフォーダンス理論』
改めて述べるまでもなく、IT関連技術の発展は目覚しいものがあります。PCを例にとれば、処理速度や容量・解像度のような定量的な尺度で計れる性能については、大抵のユーザーの需要はすでに満たしているのではないでしょうか。このような性能面での機能が満たされれば、次にユーザーが求めるのは「機能性」、そして「使い勝手」です。
最近はこの「使い勝手」を決定付けるものとして、ユーザーインターフェース(User Interfase:UI)に関する議論が活発になっています。GUIを始めとするUI設計に関する記事や書籍もよく目にしますし、日用品や家電製品でもユニバーサルデザインを前面に押し出した製品をよく見かけるようにもなってきました。

ところでこのUIに関する議論は、プログラミングにおける関数やクラスの設計も同じことです。関数やクラスが含まれるライブラリの使用者によっては、インターフェースのみが重要であって、実装には興味がないのです。ここでいうインターフェースとは、ヘッダファイルに直接現れるような関数やクラスの宣言のみではなく、処理内容の外部仕様も含みます。

一般的にUIというと機械と人間を媒介するものを指すので、関数やクラスのインターフェースはUIとは少し違うように思えるかもしれません。しかし、実際にそのライブラリを使うのが別のあるプログラムであるとしても、それを使わせようとプログラミングするのはプログラマたる人間です。そういう観点で考えれば、ライブラリ設計にもUI設計の考え方が当てはまるのではないでしょうか。

例えば、最近よく耳にする「アフォーダンス(affordance)」。物体の持つ属性(形、色、材質、etc.)が、物体自身をどう取り扱ったら良いかについてのメッセージをユーザに対して発している、とする考え方です。Webアプリケーションであれば、リンクをボタンの形にすることで、これは押すものだとユーザーに訴える。
プログラミングの例にあてはめると、関数の名前や引数を見ただけで、その仕様がわかるようにする。例えば、is_A()という名前の関数名のようにis_xxx()という名前によって、関数がbool値を返すということがわかるといった感じです。

その他、昨今のUIの議論からプログラミングの設計について考えさせられるところは多いです。詳しく書き出すと長くなりそうなので、改めて追々書いていきたいと思います。

【関連記事】
変数の命名規則

【関連書籍】
誰のためのデザイン?―認知科学者のデザイン原論
インターフェイスの街角―本当に使いやすいユーザー・インターフェイスの極意
アフォーダンス-新しい認知の理論

warningに気を配る

忠告には用心せよ。この忠告に対してもだ。
Beware of advice - even this.

プログラムを組んでいるとどうしてもちょっとしたミスタイプをしてしまいます。変数名や関数名の間違いのようなものであれば、コンパイルエラーになってすぐ修正すれば終わりです。しかし、中にはコンパイルが通ってしまうというものもあり、実行時にバグを引き起こしてしまうことがあります。

例えば、よくある==と=の間違い。
if (flag = 1) {
  ...
}
val = 1は1を返す式として評価されるので、C/C++上の文法は満たしていますが、大抵の場合、これは、「flag == 1」の間違いでしょう。 これは、普段から「1 == flag」と定数値の方を左辺に持ってくるくせをつけているとある程度防げます。とはいえ、この方法は両辺が変数であったりすると使えないのと、可読性が低くなりがちなので敬遠されることもあります。

もうひとつよくあるミスタイプ別の例をあげると、switch分のdefaultのタイプミスです。
switch (char_type) {
case ALPHA:
  printf("this is alphavet\n");
  break;
case NUM:
  printf("this is numeric\n");
  break;
defualt:
  printf("this is unknown character\n");
  break;
}
defaultとかくべきところをdefualtと書いてしまっています。一見、コンパイルエラーになりそうな気もしますが、実際は、defualtはラベルとして認識されるため文法的にはエラーになりません。もちろん、動作は期待通りにはいきませんので、当然バグになります。

さて、これらのちょっとしたスペルミスはよくあるバグの原因ではありますが、コンパイラのwarningをチェックしていれば大抵気付くようなものです。大抵のコンパイラであれば、上記のような記述は文法的には正しいとは言え、ミスタイプの可能性が高いものとしてwarningを出してくれます。よって、コンパイル時にwarningに気を使うくせをつけておけば、すぐに修正して終わるようなものなのです。

プロジェクトによっては、コーディング規約で全てのwarningをなくすようにというところもあると思いますが、そうでなくても、普段からコンパイラからの警告には耳を傾けるようにしましょう。

【関連記事】
使用しない仮引数

【関連リンク】
C/C++バグ防止: defaultのスペルミスに気をつけろ!
明解プログラミングのすすめ (Cプログラミングの秘訣)

【関連書籍】
ライティングソリッドコード―バグのないプログラミングを目指して スティーブ・マグワイア
C++プログラミングの処方箋―ひと味違うコードを書くための99の鉄則
プログラミング作法 ブライアン・カーニハン

何故コメントを書くのか

悪態のプログラマさんの「もっとコメント論」の記事を見て、ひさびさにコメントに関する記事でも書いてみようかと思います。

以前「省コメントのススメ」という記事を書きました。コメントを書きまくる前に、コメント無しでも分かるようなソースを書きましょうという主旨で書いたつもりです。

しかし、コメントは少なければ少ないほど良いと思っているわけでは決してありません。本当に必要なコメントを書くべきで、むやみやたらに書くものではないという意味です。

例えば、私は次のようなセパレータコメントを使います。
////////////////////////////////////////////////////
/// @breif セパレータコメントクラス
///
/// プログラムを読みやすくするための罫線のような役割のコメント  
class SeparatorComment : public Comment {
{
  ...
}
セパレータコメントと呼んでいるのは、「//////」とスラッシュが罫線のようになっている部分のことです。このようなセパレータは賛否両論あって無駄だという人もいます。コーディング規約で禁止しているものも見かけたことさえあります。 確かに最近のエディタでは、コメント部分の色分けもされますし、関数単位の検索も用意なため、派手なセパレータは要らないだろうというのも分かります。私もあまりに派手にデコレートされたソースは敬遠したくなります。しかし、要所要所に入ったセパレータコメントは可読性を向上させる効果があると思っています。適切な段組のようなもので、いわゆる「見出し」の一種ということになるでしょうか。 このようなソースコードの整形は、コメントでしかできないものですので、コードの処理を理解する上では直接役に立たないとしても決して無駄とは言えないでしょう。

上記のセパレータコメントに関しては私の意見ですが、反対意見をお持ちの方もおられるでしょう。その他のコメントに関しても、ひとつひとつ取り上げれば、人によって賛否両論あると思います。とはいえ、正解がないからといってどんなコメントでも良いというものでもありません。

「何のためにコメントを書くのか」

まずはこれを考えることが肝心です。英語コメントなどはコーディング規約だからというのも理由のひとつかもしれませんが、そのコーディング規約に本当に適切かということに立ち返って考えるべきです。同じようなモジュールに見えても、誰がどのように開発・保守・再利用されるのかによって、適切なコメントの基準というのは変わってきます。

統一感というのも可読性を考えると重要なので、コメントの書き方もある程度コーディング規約で統一することも必要かもしれません。しかし、ただルールだからといって書いていたのでは、適切なコメントを書くことはできないでしょう。

コメントは考え出すと宗教論になりがちですが、「どう書くのか」にこだわるのではなく「何のために書くのか」を考えることが先決でしょう。

【関連記事】
省コメントのススメ
英語のコメント

バグを潜伏させない工夫 - バグをいかに目立たせるか

とにかく目立て
Be visible
リチャード・ブランソン

一般的にバグの発生箇所と発現箇所が離れれていると、デバッグは難しくなり、時間もかかりがちです。今回のお話は、このような「バグの潜伏」を抑制し、「バグ」にいち早く気付かせるための実装上の工夫についてです。

ライブラリ設計において、何らかのオブジェクトにアクセスするIDを定義することがあります。例えば次のような関数定義があったとします。
file_id_t open_file(const char* path, int flags);
ssize_t   read_file(file_id_t id, char* buf, size_t size);
ssize_t   write_file(file_id_t id, const char* buf, size_t size);
off_t     seek_file(file_id_t id, off_t offset, int whence);
int       close_file(file_id id);
fileをオープンする関数によって返されたID(file_id_t型)が返され、以後のファイルアクセスはこのIDを介しての操作になっています。Linux等POSIX系システムで開発されている方はお気づきかと思いますが、上記の関数はPOSIXのI/O関連のsystem callのopen()/read()/write()/lseek()とほぼ同じ形をしています。POSIXでは、file_id_tに相当するのはint型のfile descriptor(fd)になります。POSIX系システムでは、ファイル以外にもsocketやデバイスドライバ等も同様のインターフェースでアクセスされます。POSIXで言えば、プロセスIDやユーザID等のIDもインターフェースに登場します

このようにopen等の関数で一旦捜査対象のIDを取得し、その後はそのIDで操作すると言うインターフェースは様々なところで見られます。このようなIDを用いたインターフェースの利点は何なのでしょうか?全てのインターフェースにファイル名等の文字列をそのまま用いる方法ではだめなのでしょうか?あるいは、IDではなくファイルオブジェクトへのポインタを返すというのでは駄目なのでしょうか?
IDを用いたインターフェースの利点は次のようなものが考えられます。
  • int型等の単純な型を用いることにより、対象の発見コストが低い
    (int型の比較と文字列方のstrcmpをイメージしてみて下さい)
  • IDの有効範囲を適切に設定すれば、プロセス間、システム間のやりとりが可能。
    ポインタでは同一メモリ空間内でしかやりとりできない。
上記の理由などにより、インターフェースにIDを利用するというのは一種の常套手段です。

前振りが長くなりましたが、今回の話題は「IDにどのような値を用いるべきか」という点です。
多くの場合、IDは「特定のオブジェクトを一意に指すもの」として定義される程度で、その値が具体的にどのような値を持つかという点については外部仕様として定義されないことも多いかと思います。IDはマジックナンバーとして利用されるものですので、利用する側が特定の値を期待してはいけません。そう考えると、IDの値について定義しないというのは順当だと考えられます。
外部仕様としてはIDの値について具体的な定義はしないものとしたとしても、実装側としてはどのような値を持たせるべきでしょうか。外部仕様を満たすだけであれば、何でも良いでしょう。例えば、
  • 0から順番にインクリメントした値を振っていく
  • 利用されなくなったIDは再利用する
というルールがまず思い浮かびます。Linux等のfile descriptorはこのような実装です。しかし、このような実装にはいくつかの欠点があります。それは、
  • 0は偶然利用される可能性が高い
  • IDの再利用は、IDの取得/解放シーケンスにバグがあった場合に、複雑なバグをまねきやすい
というものです。つまり、偶然それらしい値が指定されることで、中途半端な動作をまねき、バグが発見されにくくなるというものです。これらは、ライブラリ使用側が埋め込んだバグによって起こる問題で、ライブラリ作成者に責任はありません。しかし、IDの値をちょっと工夫してやれば、ライブラリ使用者にもっと早い段階でバグに気付くチャンスを与えることができます。例えば、次のようなルールを導入してみます。
  • 特殊な値、(0,1は~1(0xffffffff)等)はIDとして使用しない
  • IDは(極力)再利用しない
  • IDは1インクリメントで生成せず、2や3インクリメントなどで生成する
これによって、0や0xffffffffといった偶然入ってしまいがちなIDは指定されたば段階で間違いを指摘することが出来ます。また、IDの再利用をしないことで、シーケンスバグによってバグが複雑化することをさけることができます。最後の「1インクリメントでID生成しない」というのは、IDを配列の添え字のようなインデックス値と同様にインクリメントして使用してしまっているコードでの偶発動作を防ぎます。
どれも、偶然適当な値が入ることによって中途半端に動作が継続し、バグの発見が遅れたり、解析の手間が増えることを防ぐ効果があります。

と、ここまでIDの値という点で書きましたが、話の趣旨は次の二点に集約されます。
  • 外部仕様に出ない部分でも、ちょっとした工夫をするかしないかでデバッグ効率は大きく変わる
  • バグを含むコードであっても「偶然」や「なんとなく」で動作を継続させるとデバッグが難しくなる。 バグのある地点にできるだけ近い地点でバグに気付かせることで、デバッグ効率は上がる

ライブラリ設計において、機能性、拡張性、柔軟性等を考えることは勿論ですが、開発時にバグが混入し難い、バグ混入しても発見しやすい工夫といったことも頭に入れて置きたいですね。

【関連記事】
デバッグ指向のススメ

質問する前に調べる

質問をする前に、まず自分で調べてみる。質問をする時の最低限のマナーです。
トラブルシューティングやHOWTOであれば、掲示板やMLの過去ログ、マニュアルやFAQをまず見ます。キーワードが分からないのであれば検索エンジンで十分な情報が得られることが多いでしょう。

とはいえ、実際は、「調べれば簡単に分かる」ような質問をされることも多々あります()。そんな時、「次からはまず調べてからお願いしますね」とやんわり(でもない気がしますが)と伝えてくれるサービスが百式さんで紹介されていました。

Google It, You Moron (http://www.googleityoumoron.com/)

質問を受けたら、ここで生成されるアドレスをメールで送ってあげましょう。例えば、「volatileって何ですか?」と質問された場合、次のリンクを教えてあげます。
http://www.googleityoumoron.com/?go=volatile
"I will use Google before asking stupid questions."「次からはしょうもない質問をする前にはGoogleを使います」という表示の後、Googleの検索結果が表示されます(^^)。日本語が通らないのはちょっと残念です。

答えを教えるのだけではなく、答えの探し方をさりげなく教える。
効果の程は怪しいですが、その考え方、それに遊び心はいいですね。

【関連記事】
検索力
volatileで最適化を抑制する
【関連リンク】
百式 - 先人の知恵を伝える (Google It, You Moron)


自分もすることがあります。要反省 (^^;)

マルチスレッドで書くべきか

マルチスレッドにするとソフトは速くなるのか?(笑門来福)

最近では「ハイパースレッディング」や「マルチコア」というキーワードが「高速化」というキーワードとともに登場する機会が多いので、そのような誤解が一部にはあるのかもしれません。分かっている人にとっては当たり前の話ですが、「マルチスレッド=高速化」ではありません。

マルチスレッド処理あるいはマルチプロセス処理とは、複数のスレッドもしくはプロセスを並行して動作させることによって、複数の処理を行なうことです。並行処理といっても、単一CPUのシステムでは、実際の処理はシーケンシャルに実行されるため、CPU処理は全く高速化されません。それどころか、マルチスレッド動作にはそれなりオーバーヘッドが伴うため、CPUの計算量だけとればむしろ増大する方向です。

では、なぜマルチスレッドプログラミングを行うのでしょう。ひとつは、I/O等の待ち時間の間に他のスレッドの実行を行うことで処理効率やレスポンス性を改善するためです。シングルスレッドでも書けないことはないのですが、スイッチのタイミングを自分で意識して書く必要があり大変です。このような処理はOSにまかせるべきですし、何より可読性・保守性があがります。
また、マルチコアになれば、本当の意味での並列実効で処理効率があがる可能性もあります。

但し、マルチスレッドには同期や排他等の問題がありますので、プログラミングする上での注意点もたくさんあります。マルチスレッド(マルチタスク)のプログラムというのは、組み込み系では日常的ですが、全く書いたことのない方もおられるでしょう。このあたりの話はまた別の機会に。

【関連書籍】
実践マルチスレッドプログラミング
Javaスレッド完全制覇
Pスレッドプログラミング
人気エントリ
最近の記事
本のおすすめ

4274065979

4844337858

482228493X

4904807057

4873114799


最近のコメント
Links
プロフィール
  • Author:proger
  • 組み込み関係で仕事してます
ブログ内検索