Hatena::ブログ(Diary)

技術日記@kiwanami

2012-02-07

オライリー:プログラミングC# 第4版 メモ

プログラミング C# 第4版を最近スキャンしたのですが、以前読んだ時の付箋がたくさん貼ってありました。読み返してみたら今でも相当面白かったので、メモを淡々と書いてみます。

現在は第6版なので、以下のメモには現在の仕様と異なっている部分があるかもしれません。ご注意ください。

  • p26 値型(value type)と参照型(reference type)の説明
    • →地味に各章への伏線がはってある
  • p28 スタックとヒープ
  • p29 組み込み型同士の変換
    • "キャストとは「おい!コンパイラ!私は自分が何をやってるのかちゃんとわかってるよ」と伝えること"
  • p33 シンボル定数(const
    • シンボル定数という呼び方
  • p36 列挙型の変換 cast
    • 指定しないと0から始まって1ずつ増える。途中指定するとそこから増える。
  • p45 goto / label
    • "goto文は、通常別のcaseにジャンプするときに使われる"
    • サンプルコードが政治
  • p53 空白文字と中括弧
    • コラムで空白文字と中括弧のスタイルについて
    • 趣味の問題であり、VSの設定で調整可能。
  • p55 四則演算演算子
    • "C#は、整数の割算をするとき、小学4年生ぐらいの子供が行うように割ります。" → つまり切り捨て
  • p67 クラスとオブジェクトについて
    • クラスは型であり、同時にカプセル化の手段である
    • "クラスとオブジェクトの違いは、犬という概念と足元に座っている特定の犬との間の違いになぞらえることができます。犬とは何なのかという定義を相手にして、これを取ってこいといいつけて遊ぶことはできません。遊び相手にできるのは特定のインスタンスです。"
    • "プログラミングに関する古い冗談に「電球を交換するために何人のオブジェクト指向プログラマが必要でしょうか?」、「ひとりも要りません。電球に、自身を交換するように指示すればよいのです」というものがあります(「ひとりも要りません。なぜなら、Microsoftが暗闇というものの基準そのものを変えてしまったからです」という答もあります)。"
  • p70 アクセス修飾子
  • p71 アクセス修飾子は明示的に書くべき
    • "明示的に書いておくと、それは考えた結果の決定であるという事が伝わりますし、自己記述的にも出来ます。"
  • p73 VB6 の Dim, New と C# の new
    • VB6では Dim と New を同じ行で使うと性能が悪くなるらしい
  • p73 単純型の既定値
    • char の既定値は '\0'
  • p78 C#の「thisポインタ
    • C#では「thisポインタ」は誤解のもとになるので、正しくは「this参照」を使う
    • "本書ではthis参照というように使い分けるようにしています。言い間違えた個所があれば、それぞれに25セントの罰金チャリティに寄付しましょう。"
  • p80 staticメソッドインスタンスから呼べない
    • Javaとは違う
  • p80 オブジェクトの名前 = ヒープ上の無名のインスタンスへの参照の値に付けられた名前
    • "本書において、オブジェクトの名前という表現をする場合、「ヒープ上の無名のインスタンスへの参照の値につけられた名前」を省略しているということを記憶の隅に置いておいてください。"
  • p85 デストラクタの動作
    • GCの動きを具体的に書いている
  • p87 using文
    • try - catch - finally よりも確実
  • p93 出力用の out, 参照渡し ref の例
    • Javaと同様、基本的に引数は「値渡し」。
    • out や ref を使うことで引数で値を返すことが出来る。
  • p99 set アクセサ
  • p102 readonly の代入ルール
  • p105 クラス階層の説明
    • 本文で陥りがちな悪い例を示して、コラムで自分で批判
    • →これで分かる人は面白いけど、分からない人は混乱しそう
  • p109 overrideキーワード
    • オーバーライドするには override って付ける必要がある
  • p111 既定コンストラクタ自動生成
  • p113 new virtual キーワード
    • new virtual を使うと、基本クラスが同名の派生クラスのメソッドを呼ばないように出来る
    • 規模が大きくなってメソッド名がぶつかる事故が起きて、意図せず基本クラスの動作を破壊することを防ぐらしい。
    • →うーん。余計な迷路を増やしただけにも見えるし、ちょっと気持ち悪い。
  • p122 ボックス化とボックス化解除
    • JavaのAutoboxingのように似た挙動をする違う値に変換されるのではなくて、単にObject型で包まれる。
    • 明示的にボックス化解除が必要。解除の際に null や型が違う場合に備えて try が必要。
    • JavaのAutoboxingは最悪だと思っているので、C#のボックス化はまだまし。
  • p123 C#の入れ子クラス
    • JavaインスタンスにひもづいたインナークラスはC#にはない。あるのは Java でいう static なインナークラスのみ。
  • p127 比較演算子オーバーライド
    • C++では対になる演算子は<か=を用意するだけでよいが、C#では対になる演算子は両方定義する必要がある
  • p137 構造体は値渡し、参照にするにはボックス化が必要
    • →クラスと構造体の使い分けは難しそう
  • p140 構造体はnewしなくても使えるが、推奨されない
    • →何のためにあるのか?
  • p143 C#ではインタフェースに定数が書けない
  • p144 Mix In の話
    • 例のアイスクリーム屋の小話
  • p147 インタフェースでのアクセサ
    • publicしかありえないので、アクセス修飾子を書くとコンパイルエラー
  • p159 as演算子。isでチェックしてキャストする
    • →cast必要なときは便利そう
  • p161 抽象クラスかインタフェース
  • p163 キャスト先の型によって動作が変わる!
    • インスタンスが同じでも、キャスト先のインタフェースの型によって動作を変えることが出来る
    • →サンプルコードは的確で分かりやすく、本文で説明されているメリットは理解できるが、それによるデメリットの方が大きくないのか?ちょっと気持ち悪い。
  • p166 インタフェース名を指定して実装するときはアクセス修飾子を付けられない
  • p168 インタフェース名を指定して実装することで、インタフェースメンバの選択的公開が出来る、クラスのセマンティクスを守る
    • →熟考の上での選択だと思うけど、そんな複雑なことがどうしても必要なことがC#界隈にはあるのだろう。
  • p207 IEnumerable<T>とyield
    • →見た目かなり違うけど、中身はRubyのyieldっぽい
  • p210 ジェネリクスの型の制約
    • →型の制約の書き方はどの言語も大変そう
  • p233 Dictionaryのキーをオブジェクトにするならキーのオブジェクトを変更してはいけない!
  • p236 stringとStringは等価
  • p237 逐語的文字列リテラル
    • 基本的にエスケープされないが、ダブルクオートはエスケープする必要がある
    • JavaEmacs Lispにも欲しい
  • p242 case ignore な比較
    • "ここでは、1行を短くすることで結果の出力が書籍のページにきちんと収まるように、WriteLine()を2回呼出しています。" ??
  • p244 string の == はオーバーライドされているので中身で比較される
    • Javaもそうだったらよかった
  • p249 split の params キーワードの例
  • p263 例外について
    • バグとエラーと例外の区別をつけよう
    • →いい話
  • p267 すべての例外をキャッチする書き方
  • p274 finally ブロックの制限
    • return, break などのジャンプを使うとエラー
  • p311 event キーワード
  • p316 delegate匿名メソッド
    • lambda
  • p318 マルチキャストデリゲートの明示的な実行
    • →ここまでくると、わざわざ言語でサポートする必要があったのか疑問
  • p321 デリゲートの非同期実行
    • →話は面白いけど、多分説明が分かりにくそう
  • p341 コラム 亀は続く
    • スチーブン・ホーキング氏の小話
    • "「あなたはなかなかよくできた子ね。しかし亀は亀の上にいるのですよ。その繰り返しです。」"
  • p367 ADO.NET
  • p397 UIデザイン
    • "筆者はUIを完璧にデザインできるプログラマはいないと考えます。"
  • p400 ASP.NET
    • IsPostBack でポストされたかどうかの判断
    • →うーん。昔だからしょうがないかも。
  • p444 アセンブリセキュリティ境界
    • 2つのアセンブリにまたがって型を定義することはできない。型の参照は出来る。
  • p452 デフォルトではプライベートアセンブリ
    • DLL地獄の解決策
  • p480 マーシャリングの説明
    • 約1ページに渡って、マーシャリングとチャンネルについてスター・トレックで説明
    • "例えば、カーク船長が文明の発展に重大な影響を与えるようなことを伝達しようとするとき、最初のシンクはこれを拒絶します。"
    • Prime Directive の知識が前提!!!!
  • p497 RPCのサンプルコードのコメント
    • "「ただそこに立ち、待つばかりの者もまた主に使えし者である」(ミルトン)"
    • キリスト教の知識が前提。深い。
  • p526 訳者注:デッドロック
  • p553 シングルスレッドでブロックするサーバーソケットのサンプルコードについて
    • "電話を目の前にして、鳴り出すのをいつまでも待っているような状態です。このモデルは1週間に2接続程度のサーバーであれば問題ないでしょうが、実際のアプリケーションとしてはほとんど使い物になりません。"
  • p612 fixed キーワード
    • fixedを使うとGCによって変更されないメモリアドレスのポインタを取得できる

この本はサイ本や詳説正規表現と並んでとても楽しかったです。

C/C++の方面から見るとそんなに不思議ではないけど、Java側から見ると落とし穴が多いと思います。Javaと字面は似てるけど、全く同じのつもりで業務でコード書いてる人(SE PGとかでよく見る)は気を付けた方が良いと思いました。(Lispに比べたら大した違いではないけど。。。)

2011-12-06

Emacs で IPC や RPC (Emacs Advent Calendar jp: 2011)

Emacs Advent Calendar jp: 2011 : ATND 6日目の記事のはずでしたが、手違いでかぶってしまい、いろいろあって遅くなってしまいました。

皆様、乱してしまってすみません。

今回の記事では、EmacsでのIPC(Inter-Process Communication)やRPC(Remote Procedure Call)の方法について、自分の知っている範囲でまとめてみます。IPCは、プログラムの間で通信する仕組みです。RPCは、既に起動しているプログラムの一部(関数サブルーチン)を別のプログラムから呼び出す仕組みです。一般的にIPCの枠にRPCが含まれます。

EmacsにはどんなIPC/RPCの実現方法があるのか、実際の応用例などを5分程度で簡単にまとめてみます。

Emacsの標準IPC手段

以下のようなものがあります。(Emacs 23 以上の場合)

ひと通り揃っていて、大体色々出来ますし、D-Bus以外はプラットフォーム中立の事を考えて作られています。

しかしながら、本格的なプログラミング環境としては細かい制御が出来ず、悔しい思いをすることが多いです。特に、共有ライブラリにリンクして、高機能・高パフォーマンスな通信手段(DB直接続とかmsgpackとか)を使うことが出来ません。

そのため、一般的には別の中継プログラムを作成して、パイプなどで通信してやりたい機能を実現するということが多いです。このあたりの歴史や状況については、 id:m2ym さんの文書 Page not found · GitHub Pages が詳しいです。*1

IPC/RPC いろいろ

Emacsで行われている IPC/RPC の手段について簡単に紹介してみます。

コマンド起動

プログラムを起動し、結果を標準出力などからテキスト等で受け取る方式です。

同期的に呼べば簡単に使えますし、外部プログラムだけでデバッグもできるので楽です。

一方で、外部プログラムや設定がOSや環境に依存して動かないこともあります。が、他の方法よりもはるかに問題解決が楽です。

パイプ・ソケット

バックグラウンドで外部プログラムを起動し、パイプやソケットなどで通信する方法です。

非同期プログラミングが大変ですが、常に起動しているため応答の性能も良く、また外部プログラム側も状態を持つことができるので、出来ることが大幅に増えます。

(2011/12/09 TCPソケットのところにパイプ通信が混じっていたのを修正)

D-Bus

D-BusLinux Desktop 上ではかなりデファクトに近いIPC環境になっています。

1対1の通信だけではなく、必要なプロセスにイベント通知させたり、オブジェクトシリアライズ、サービスのインスペクションなども出来るため、IPCとしてはかなり高機能です。

skype.el でも使っていますが、これを積極的に利用したアプリケーションはほとんど無く、正直 D-BusEmacsでサポートする必要はなかった(もっと他にやることがあった?)のではないかとか思っています。

emacsclient

起動中のEmacsにファイルをオープンさせるような用途が圧倒的ですが、S式を送り込んで評価させたり出来るため、手軽なRPCとして利用できます。

HTTP / Webサービス

最近はインターネット上のサービスと通信する機会が多くなってきました。REST APIに直接アクセスしたり、XML-RPCなどで機能を手軽に呼び出すことが出来ます。

xml-rpc.el のように、標準のurlパッケージを使ってEmacs単独で通信することも出来ますが、るびきちさんも指摘する通り、細かいバグSSLプロキシ経由の通信で問題があるので、自前実装する場合は curl, wget を使う方が堅実だと思います。

Webサービスと通信する場合に問題になるのは認証です。一般的に以下の方法があります。

今後、Webサービスとの通信は増えてくると思いますので、この辺りの環境が整備されてくると便利になりそうです。

EPC に続く

これまでのIPC/RPCはプリミティブすぎる(パイプとかD-Busとか)か、特定の目的のための専用スタック(pymacsとかel4rとか)なので、別のプログラムを気軽に呼び出して使えるような、汎用のRPCスタックがありません。

例えば、EmacsDBに直接接続することが出来ませんが、DBに接続して自由にSQLを発行してデータをやり取りできると、大変夢が広がります。さらに、これがPythonRubyなどの専用スタックではなく、インタフェースは固定のまま、自分の好きな言語で拡張できると大変素敵です。

ということで、現在 EPC(Emacs Procedure Call)なるものをつくっています。YAPC Asia 2011の時にデモしたような、EmacsからPerlを自由に呼んでやりたい放題なことを実現できるようにする予定です。

Emacs - Perl 間はだいたい動くのですが、いきなり CPAN に上げる前に PrePAN でレビューをお願いしたいと思っています。

プロトコルの詳細などのドキュメントが固まり次第、お知らせする予定です。

おわり

本当にすみません。Advent Calendar は小粒なネタで毎日続けることが重要だと思いました。来年も参加するときは気をつけようと思います。

やっぱり1時間ぐらいかかりました。

*1:ちなみに、自分も Emacs 自体が単純に肥大化していくことにはちょっと違和感があります。単純に機能が増えるよりも、パッケージや汎用的な通信手段で簡単に拡張できることに集中したほうがいいと思っています。

2011-12-05

Nginx Inside Memo (カーネル/VM Advent Calendar)

この記事は 500 Internal Server Error : イベント開催支援ツール ATND(アテンド) のために書かれたものです。

一つ前の5日目は @sora_h さんの?柜??_?柜??平台_?柜??777(唯一)官方网站でした。明日は、 @master_q さんの予定です。

nginx の内部の仕組みやモジュールの作り方について簡単に紹介してみます。

最近 nginx のコードを読んでハックを試みました。 nginx のコードについてはほとんどドキュメントがなく、日本語でも見かけなかったため(もし書いている人がいたらすみません)、後から続く人にとって多少でも参考になれるようメモをまとめました。(2011/12/06 デバッグについて少し追記)

全部読みきったわけではないので、コードを読む上でのとりかかりぐらいの情報です。もし、間違いがあれば教えてください。

このメモは stable version である Nginx-1.0.10 のコードを対象にしています。

参考情報

全体アーキテクチャ

各地で説明されているように、クライアントのリクエストをさばくWorkerプロセスと、Workerプロセスを管理して、全体の起動や終了などを管理するMasterプロセスからなる。

WorkerとMaster

WorkerプロセスとMasterプロセスは、UNIXシグナルや共有メモリ、ドメインソケットなど(環境によって違う)で通信する。

各Workerはシングルプロセス*1のシンプルなイベントドリブンな設計になっている。

イベントドリブン動作イメージ (ngx_process_events_and_timers関数)

nginxが受け取るイベントには、ネットワークのソケットの接続開始・終了や、読み出し・書き込み可能状態の変更、シグナルなどの通知などがある。ひとつのプロセスでIOを多重化し、可能な限り処理を非同期に行うことで、各地で評判の高いスループットが実現している。OS(FreeBSDとか)によってはファイルのIOも非同期に行うことができる。

OSや実行環境でのイベント操作の抽象化のために ngx_event_actions_t という構造体が定義されている。この構造体のメンバにイベント操作の関数ポインタ入っている。

ngx_event_actions_t

個別のイベントについては ngx_event_t でイベントの情報と操作がセットで定義されていて、各地の実装で実際のイベント情報が生成される。

コードレイアウトと起動

コードのレイアウトはシンプルで直感的。見たまま。

エントリポイントは /src/core/nginx.c の main 関数。内容はごく普通のデーモンサーバー

この中で cycle という単語がよく出てくる。プロセスサイクルの略だと思われる。大体 worker/master プロセスのメインの処理やデータを表す感じ。

ngx_cycle_t の主なメンバ

高速化の努力

全部読んでないけど、なるほどと思った。

イベントドリブン

全体の設計が徹底的にイベントドリブン。非同期に出来るものであれば、何でもこのアーキテクチャに乗って回すことが出来る。後述のモジュール実装でも出てくる。

独自メモリアロケーション

イベントオブジェクトバッファなど、各地で細かくメモリの割り当て・開放が行われるので、多分かなり効果ありそう。

Rope的バッファ

バッファをリンクリストで連結。イベントドリブンな仕組みと相性が良さそう。

その他自前ライブラリ

やっぱり自前だと速いのかもしれない。

HTTP処理

処理フェーズ状態遷移

HTTPの細かい処理を状態遷移マシンで抽象化

  • 状態一覧 (ngx_http_init_phase_handlers 周辺で定義)
    • POST_READ
    • SERVER_REWRITE
    • FIND_CONFIG
    • REWRITE
    • POST_REWRITE
    • PREACCESS
    • POST_ACCESS
    • TRY_FILES
    • CONTENT
    • LOG

各状態でやるべき処理が明らかになって見通しが良い。ただし、状態遷移マシンの実装はハック的。

フィルター

よくある出力の抽象化。リンクリストで実装。

設定で柔軟に組み合わせられる。

モジュール

各パーツをモジュールという入れ物に入れて、統一的に扱う仕組み。(ここは抽象度が高くて範囲が広く、いたる所 void* だらけで完全に把握できてない。)

ngx_xxx_module_t が各モジュールごとにあり、そこから各モジュールの設定ファイルの解釈、状態(コンテキスト)にアクセスできる。

主なモジュール構造体 ngx_module_t のメンバ(未完成。。。)

設定ファイルの解釈、起動時の初期化、終了時の処理などが統一的に定義されていて、大体どのモジュールも同じような書き方になっている。

モジュールには core, event, http, mail の4種類がある。各モジュールの依存関係の図が欲しい。

モジュール開発について

nginx にはたくさんモジュールがある。が、本家サイトにはモジュール開発の情報がない。

デバッガ

本体やモジュールが落ちる場合はデバッガを使って原因を調査する。Workerを調査する場合は、追跡しやすくするために worker プロセスは1つにしておくほうが良い。

デバッガを使って本体を起動するか、起動中のPIDを調べてアタッチする。Linuxだとgdbやnemiverを使ってやる感じ。

設定ファイルのパースの状況やモジュールの状態がどうなっているかなどがよく分かる。

gdbで起動させる場合は、フォークしたWorkerプロセスをフォローしてもらうために、以下のコマンドを実行しておく。

set follow-fork-mode child

結局やりたかったこと

これまで業務で DRBD などを用いて大容量冗長化のファイルシステムを用いてきたが、ファイル容量のスケールアップが辛く、また運用のコストも高いので、初期コスト低い・簡単運用・スケールアップ可能な分散ファイルシステムを探している。

最近、 MongoDB の分散FSである GridFS に注目している。

nginx-gridfs というモジュールがあり、これを使うと GridFS に直接つなげることが出来る。単純なローカルファイルへのリクエストとのパフォーマンス比較ではもちろん圧倒的に負ける。

しかしながら、他の分散ファイルシステムと比較した場合のパフォーマンスについては自明ではない。そこで、手元で修正したり、ハックしながら調査中。

GridFS との接続が同期通信なので、ここを非同期にするとスループットが上がるのではないかと考えている。もしくはキャッシュをうまくやる。でも C で実装するのは辛いので mighttpd など、他のプランも考えたい。

GlusterFS も調査したい。

その他高速Webサーバー

まとめ

つかれました。

*1:マルチスレッドで動かすことも出来るみたい

2011-07-27

calfw の設計と利用方法

はじめに

前回の記事は calfw が使えるようになるところまででした。今回の記事では calfw をもう少し詳しく説明してみたいと思います。

今回の記事の目標は2点あります。一つはカレンダーの情報源である「情報ソース(cfw:source)」の作り方、もう一つは calfw 自体を他のアプリケーションに組み込む方法です。前者の情報ソースを作れるようになれば、自分でカレンダーに表示する内容を作れるようになります。後者が分かれば、「ここにカレンダーがあるとすごく便利なのに」と思うような場面で calfw を組み込むことが出来るようになります。

calfw の全体のアーキテクチャは MVC になっています。 Emacsアプリケーションでも、ある程度大きなアプリケーションでは OOP のテクニックはよく使われています。 calfw では、それほど複雑なクラス構成は出てきていないため、構造体と名前のルールによるシンプルなモジュール化を使っています。

cfw:source について

cfw:source はカレンダーの情報源を定義するオブジェクトです。 cfw:open-calendar-buffer などのカレンダー構築用関数に :contents-sources の名前付き引数で渡します。以下にコンポーネント構築のコード例を示します。

(cfw:open-calendar-buffer
   :contents-sources
   (list 
    (cfw:org-create-source)  ; org 用のソース生成関数
    (cfw:howm-create-source) ; howm 用のソース生成関数
    (cfw:ical-create-source "ical" ; icalendar 用のソース生成関数
      "https://../basic.ics" "Red")))

ここでは org, howm, icalendar の情報ソースをつくってカレンダーを構築しています。このように、任意の情報源を組み合わせてカレンダーを構築することが出来ます。

この情報ソースオブジェクトを自分で用意できれば、同じようにカレンダーに表示させることが出来ます。

まず、 cfw:source の詳細について簡単に説明します。その後、具体的なソースの作り方を説明します。

cfw:source 詳細

cfw:source は defstruct で定義された構造体です。各スロットの詳細は以下のようです。

名前 説明
name [必須] 情報源の名前。ステータスバーに表示される。
data [必須] 情報源の内容を返す関数。詳細は次の「 cfw:source-data 仕様」で説明。
update [オプション] 情報源の内容を更新する必要があるときに calfw 本体から呼ばれる。
ここでキャッシュクリアなどを行う。
color [オプション] この情報源の色。文字列で指定。
M-x list-colors-display で出てくる色名や、"#abcdef"のような6桁の16進。
主に通常のスケジュール表示用の色。
period-fgcolor [オプション] 期間スケジュールの前景色。省略すると白か黒。
period-bgcolor [オプション] 期間スケジュールの背景色。省略すると color を使う。
opt-face [オプション] face の色以外に指定したい項目。
フォントをイタリックにするとか太字にするとか。
:opt-face '(:weight bold) のように指定。
opt-period-face [オプション] opt-face と同じく、期間スケジュールに適用する face 項目。

name と data が必須であり、 name, date, update 以外は見た目の設定です。

ちなみに、なぜ直接 face を指定するようになってないかというと、 color だけ指定すればあとはうまく calfw が表示してくれることを想定しているからです。まず、 color だけでやってみて、気に入らないところがあれば追加で見た目を細かく調整できるように考えています。

cfw:source-data 仕様とコードサンプル

次に、cfw:source-data の関数の定義や返すべき値について説明します。

cfw:source-data 関数は、 開始日・終了日を受け取って、(日付 . (内容のリスト)) の alist を返す関数です。具体例で書くと以下のようです。(簡単のために、引数の開始日・終了日は使っていません)

;; cfw:source-data の簡単な例
(defun sample-data1 (b e)
  '(
    ((1  1 2011) . ("内容1"))
    ((1 10 2011) . ("内容2" "内容2行目"))
    ))

(cfw:open-calendar-buffer
  :contents-sources
   (list 
     (make-cfw:source
      :name "test1" :data 'sample-data1)))

このコードを scratch バッファなどで実行して2011年1月を表示させると、以下のような画面になります。

単純なスケジュールの例

日付は (list 月 日 年 ) で指定します。この形式は、 calendar.el や orgmode で広く使われている形式です。 Emacs の TIME 型やその他の形式との相互変換については、日付変換まとめ を参照してください。

期間スケジュールは以下のコードのように periods の項目を作って、そこに (list 開始日 終了日 内容) のリストを入れます。*1

;; cfw:source-data 期間スケジュールの例
(defun sample-data2 (b e)
  '(
    ((1  8 2011) . ("内容1"))
     (periods
      ((1 8 2011) (1 9 2011) "期間1")
      ((1 11 2011) (1 12 2011) "次の期間"))
    ))

(cfw:open-calendar-buffer
  :contents-sources
   (list 
     (make-cfw:source
      :name "test2" :data 'sample-data2)))

このコードの結果は以下のようになります。

期間スケジュールの例

以下、もう少し細かい仕様です。

  • 関数引数の開始日・終了日は表示したい期間(両端含む)
  • 関数引数の開始日・終了日の範囲外のものが含まれてもかまわない
  • 描画が必要な度に呼ばれるので、計算に時間がかかる場合はデータをキャッシュしておくべき
  • リスト内は日付順に並んでなくてもよい。periods もリストのどこにあってもよい。重複している場合はまとめられる。
  • 日付内での並び順はデフォルトではアルファベット順
    • コンポーネント構築時の :sorter 引数で、並べ替えの関数を指定できる

ここまでは固定の日付データを使いましたが、実際には何らかのプログラムで結果リストを出力することになると思います。 org, howm, icalendar の実際のコードが参考になると思います。

calfw コンポーネント

次は、 calfw を他のアプリケーションで使う方法について説明します。

calfw はカレンダー表示の方法として次の3つの形態を用意しています。

  • 独立したバッファとして表示
  • リージョン内に挿入
  • テキスト出力

それぞれ簡単に説明します。

バッファ

通常の Emacsアプリと同じように、独立したバッファを作ってカレンダーを表示します。

関数 cfw:open-calendar-buffer を呼ぶと、カレンダー用のバッファを作成し、 switch-to-buffer でバッファを表示します。バッファは cfw:calendar-mode のメジャーモードがセットされ、キーバインドなどもバッファ全体で cfw:calendar-mode-map がセットされます。

ふつうのよくある使い方です。アプリケーション境界バッファ単位であり、ユーザーは通常のバッファ操作を行うことが出来ます。

リージョン

別のアプリケーションバッファに埋め込みたい場合に使います。具体例としては howm のメニューへの埋め込みがあります。

どういうことなのか分かりにくいかもしれないので、 scratch バッファで以下の式を評価してみてください。

;; scratch バッファで以下の式を評価してみる
(cfw:create-calendar-component-region :height 10)

scratch バッファで実行 → そのままカレンダーの操作ができる

アンドゥで元に戻ります。

カレンダーのリージョン以外には影響をほとんど与えず、キーバインドも自前で用意(テキストの keymap プロパティにセットされている)するので、手軽に他のアプリケーションに組み込むことが出来ます。

テキスト

カレンダーが描画された純粋なテキストが欲しい場合に使います。 cfw:get-calendar-text でテキストを返します。

まだ具体的に活用された例はないのですが、外部への export とか、ツールチップ用の文字列準備などで考えています。

「描画先」と「ビュー」

以上、3つの形態を説明しました。これらの見た目や構築方法は全然違いますが、calfw を使うプログラムからは統一的に操作することが出来ます。

今後、これらの形態の違いは「描画先」と呼ぶことにします。似た単語に「ビュー」が出てきますが、これは月や週などの描画方法を指します。分かりにくくてすみません。

オブジェクトの構成

まず、概要をつかむために calfw のオブジェクト達について説明して、その後、各詳細とどう使うのかを説明します。

オブジェクト概要

大きく分けて以下のオブジェクトがあります。

  • 全体の入れ物である「 calfw コンポーネント」 (cfw:component)
  • 全データを管理する「モデル」 (cfw:model)
  • 各カレンダーのデータを定義する「情報ソース」 (cfw:source)
  • 「描画先」の抽象化 (cfw:dest)

UMLのクラス図で描くと以下のようです。*2

オブジェクトの概要図

calfw コンポーネントは MVC の Controller の役目を担い、モデル描画先をつなげて全体を制御します。また、外界とのインタフェースとして各種オブジェクトへのアクセスを提供します。

モデル情報ソースは MVC の Model に当たります。情報の管理とロジックを担当します。

描画先は MVC の V に当たり、描画先を抽象化して切り替えられるような仕組みになっています。

それぞれもう少し詳しく説明します。

全体、見た目の操作 cfw:component

calfw コンポーネントは、全体の統括と見た目の制御等を担当しています。

calfw コンポーネントは以下のデータを保持しています。

  • 描画先 (dest) とモデル (model) への参照
  • 現在選択されている日付 (selected)
  • 現在のビュー (view)
  • 各種フック
    • 描画時 (update-hooks)
    • 選択 (selection-change-hooks)
    • クリック (click-hooks)

操作については以下のような項目があります。

  • 描画先、モデル、所属バッファなどの取得
  • 選択日の取得、更新 (get-selected-date / set-selected-date)
  • ビューの取得、変更 (get-view / set-view)
    • 内容は month, two-weeks, week, day などのシンボル
  • 描画サイズの変更 (resize)
  • 再描画 (update)
  • フックの追加 (add-xxx-hook)

なお、コンポーネント構築後の描画先の変更などは動的に出来ません。

見た目の要となるビューは、今のところただの関数として定義されていて、このコンポーネントのなかで描画担当の関数が振り分けられています。 (cfw:cp-dispatch-view-impl)

calfw コンポーネントのインスタンスは、描画先によって保持される場所が違います。

  • バッファバッファローカル変数 cfw:component
  • リージョン → 範囲内のテキストプロパティ cfw:component
  • テキスト → なし

cfw:cp-get-component 関数を使うと、現在のカーソール位置から適切なインスタンスを取得できます。イベントハンドラなど、ステートレスなロジックはこの方法でインスタンスを取得すると良いと思います。

ステートフルな操作のためには、コンポーネントを使用する側がインスタンスを保持したほうが良いと思います。

カレンダーの内容へのアクセス cfw:model

情報ソースインタフェースで情報の定義を統一し、モデルが情報ソースを束ねて管理します。

主に以下のようなデータを管理します。

  • 内容の情報ソース (contents-sources)
  • 注釈の情報ソース (annotation-sources)
  • 内容のソート関数 (sorter)

モデルは、描画先やビューについての情報は一切持っていません。純粋に、日付や期間に対応する内容だけを管理します。祝日に関しては calendar.el と同様に、 calendar-holiday-list 関数から取得します。

大抵の場合、モデルの内容は構築時に決定されますが、後で動的に情報ソース自体を操作したりするような場合にモデルを経由してアクセスします。

現在の実装では描画処理の過程で、ビュー固有のデータ(いわゆる ViewModel)が付加されます。このあたりの実装は個人的にも微妙な感じがしています。。。

描画先の抽象化 cfw:dest

描画先の操作を抽象化して、ビュー関数が描画先に依存せずに描画できるようにする仕組みです。

描画先は以下のデータや操作を持っています。

  • 描画先バッファ (buffer)
  • 描画範囲取得 (min-func, max-func)
  • サイズ (width, height)
  • 削除関数 (clear-func)
  • 更新関数 (before-update-func, after-update-func)
  • オーバーレイ管理 (select-ol, today-ol)

上の calfw の利用形態のところで少し書いたように、現在の実装ではバッファ、リージョン、テキストがあります。リージョンがやりたくてこの仕組みを作ったと言っても過言ではありません。描画に必要な情報をすべて持っていますので、一つのバッファに複数のカレンダーコンポーネントを入れることも出来ます。 *3

組み込み方針

calfw 活用の方法としては、アプリケーションUIとして使ったり (howmの形態)、日付の入力用として一時的に表示させるということが考えられます。

まず、描画先としてバッファを丸ごと使うか、既にあるバッファに組み込むかという選択があります。切り替えるのは簡単ですので、いろいろ試してみて使いやすい方を採用したらいいと思います。

データの表示は、単純に情報ソースを作れば表示できます。

次に、ユーザーからの入力ですが、日付の選択やクリックなどの単純なアクションは、コンポーネントのフックで簡単に拾うことが出来ます。また、選択された日付は、 cfw:cursor-to-nearest-date か cfw:cursor-to-date で取得できます。現在は、単一の日付しか取得できません。

上記以外のイベント(エンターキーや任意のボタンクリックなど)は、構築時の :custom-map 引数に適当なキーマップオブジェクトを渡すことで取得することが出来ます。さらに細かく調整したい場合は、情報ソースで付加された大抵のテキストプロパティはそのまま表示まで持って行くことが出来ますので、文字単位でキーマップや face を設定することで対応できると思います。

上記のアクションでデータを更新したら、コンポーネントの再描画関数を呼ぶことで表示を更新できます。

これで一通りの入力・表示の操作が出来るのではないかなと思います。以上をまとめると下のような絵になります。

アプリケーションから calfw を使うイメージ

具体的には、 howm 連携のコードが参考になると思います。

今後の予定

以下、 calfw の今後についてのメモです。 orgmode の ML が熱いので、そちらを watch すると良いかもしれません。

  • viewの追加、改善
    • 3,4日表示とか
    • ガントチャートっぽく横長表示とか
    • もっとコンパクトな表示や、オーバーレイでポップアップとか
    • もっとスペースを有効活用できるような表示方法
  • 期間選択
  • 時間の扱い
    • 週表示や日表示
    • 時間の持ち方について検討
  • orgとの連携の強化
  • 他の連携の追加
    • diary とか

*1: ちなみに「(A . (B C) )」は「(A B C)」と同じになります。念のため。

*2: このクラス図はいつもお世話になっている Astah で描いています。データは github のリポジトリに入っています。

*3:この仕組みを拡張して、カレンダー以外の汎用的な「一つのバッファに複数入れて組み合わせることが出来るコンポーネント」の仕組みが出来ないかなということも考えています。ちょうど、近年の Web 上の JavaScript がお行儀よくなって、一つの document 内でも複数の JavaScript コンポーネントやライブラリが同居出来るようになってきた感じにならないかなと、勝手に考えています。

2011-06-01

献本頂きました:良いコードを書く技術

かなり日が経ってしまいましたが、「良いコードを書く技術」の献本をいただきました。

良いコードを書く技術 ?読みやすく保守しやすいプログラミング作法 (WEB+DB PRESS plus)

良いコードを書く技術 ?読みやすく保守しやすいプログラミング作法 (WEB+DB PRESS plus)

個人的には大変いい本だと思っていますので、宣伝もかねて少しコメントしたいと思います。

対象読者

まずは、この本がターゲットとしている対象読者についてです。

ブログなどでよく見るようなWeb系企業だけでなくて、ぜんぜん表に出てこないようなSIerも含めたエンジニアの人口分布を考えてみます。自分のこれまでの経験から、以下のような分布になっているのではないかと考えています。

エンジニア人口の分布(想像):※軸の説明は本文参照

縦軸は技術レベルを表します。

  • A 一人で完結して仕事が出来る人
    • AA 自力でサービスまで作れる、勉強会スピーカー、未踏戦士とか
  • B 中間の人
    • B+ うまく伸ばせば伸びる人
    • B- うまく教えれば人並みに出来る人
  • C ※お察しください

横軸は意識レベルを表します。

  • 1 コードに魂を売った人
  • 2 勉強会にも積極的に出て上を目指したい人
  • 3 プログラミングは嫌いではないが、仕事以外の時間は楽しみたい
  • 4 そもそもプログラミングが嫌い

色が付いているところがエンジニアの人口の分布で、B3あたりにピークがあって、斜めに分布が広がっているイメージです。

なお、他にも独立な軸はいくつかある(例えば環境とか仕事力とか)と思いますので、この図は適当に射影したものだと考えてください。また、いくつかの企業やSIerの現場を見てきた自分の経験をもとにしていますので、もしかしたらみなさんの現場とは違うかもしれません。

この分布図の中で、赤っぽい部分(B3周辺)が対象読者です。

対象読者の分布(想像)

ちなみに、はてなの皆様(この記事を読んでいるような皆さん)は水色のあたりだと思っています。アウトプットし続けたり難しい本や技術に挑戦していて、かなりレベルが高いと思っています。はてなの皆様に限らず、Code Complete(や巻末の参考文献など)を読んだり、Amazonに積極的に書評を書くような人もこのあたりに入ると思います。

なので、この本は、この記事を読んでいるような皆さん自身にとってはかなり物足りない内容のはずです。期待して買ってがっかりされた人もおられるかもしれません。ごめんなさい。

しかしながら、新人教育やメンバーの底上げには丁度良い分量と内容になっていると思います。かなり頑張って計算して内容や章立てを組み立ててありますし、この正味200ページという分量にも意味があります。

先程の分布を仮定するとターゲットとなる読者数が多いため、少しの効果でも人数が多ければ全体的効果が高いと思います。特にエンジニアの人数の多い(何十人以上)組織にお勧めです。

抽象化

この本でもう一つ個人的に重要だと思っているテーマが抽象化です。抽象化には哲学が重要だと思っていて、その抽象化に対する哲学がこの本にはちょっとだけ含まれています。個人的には抽象化については「SICP読め」で終わってしまうのですが、なかなか現実はそうもいきません。

情報系の研究室の方々や関数型言語をやっている人達には考えられないと思いますが、現場(特にSIer)では「プログラマ」を100人集めたとき9割以上の人は再帰を使ったプログラムが書けません。クイックソートはもちろんバブルソートすら書けない人も多いです。そもそも抽象化以前のレベルではありますが、こういう現状はFizzBuzzのテストや迷路の試験などの結果からも裏づけられていると思います。*1

こういう抽象化以前の現状に対して、どのように抽象化とそのパワー、そして各自のスキルの目指すべき方向を伝えられるか、ということがこの本のひとつの目標でした。(自分は書いてないけど)

現場でよく行なわれている「単に似ているところをまとめるだけの抽象化」だけでは限界がある、でも「勤勉な人の繊細なシステム」を作るような人にもなって欲くない。後半ではそういう微妙なラインを狙って章立てをしています。上の図でB2-B3の人達が業務しか経験していないと全く見ることがないであろう(でも現場ですぐに使えそうな感じの)具体例やテクニックを並べています。(自分は書いてないけど)

arton 勤勉中の勤勉なという人が世の中にはいるわけ。勤勉には飽きたらずもっと勤勉な人。そういう人はねいろんなこと勉強するから、色んなこと知ってるわけ。そうすっと、その人のプログラムがちょっと走った後には、なぜか String#length が実際の長さの 100 倍の値が返ってくるとかさ、色んな事が起きそうだよね。

  :

arton Web 2.0 系はβだから良いんだよ。直せば良い。masuidrive さんが一人で一所懸命頑張って作ったシステムっていうのと、勤勉な人たちが 1000 人ぐらいなぜか中国やインドから人を集めて作ったシステムは、繊細さが違うんだよ、繊細さが (笑) あまりにも繊細なものだから、息吹きかけてもやばいわけ。

一同 (笑)

ささだ 角谷さんウケすぎ。

角谷 「勤勉な人たちの作った繊細なシステム」という表現がもう素晴らし過ぎて (笑) 思い当たることがたくさん……。

  :

(※ 引用の強調はkiwanamiの主観です)

この本の中で紹介した具体例を身に付けて欲しいというわけではありません。例示した内容を通して、「抽象化にはいろいろなレベルや方法があって、ちょっとしたテクニックでもすごい生産性が得られて早く家に帰れますよ。でも現場だけでは経験できないことが多いし無闇に使うと火傷するから、たくさん本や情報を得てバランスの良い選択をして欲しい。」ということが伝わればいいなと思っています。(自分は書いてないけど)

そこで興味を持ってもらった人には、ぜひ巻末の参考文献を取っ掛かりにして先に進んで欲しいです。

ということで、この本が多くの現場エンジニアに読まれて、少しでも現場の底上げに役立ってもらえるといいなあと祈念いたしまして、献本の謝辞にかえさせて頂きます。ありがとうございました。

*1:ただし逆に、現場の人はもっと重要な別のエンジニアリングがあります。例えば、業務知識の引き出し方・まとめ方・分割の仕方、部屋のレイアウト・クライアント配置・配線や電源も含めたインフラ整備、データ移行、システム導入計画や切り替えのマネージメント、稼動後の障害対応などなど挙げ始めるときりがありません。もちろん技術的スキル不足からブラックになっている現場も多々あるのですが、開発プロセスやテストコードやドメインなんとかの枝葉以前に、そもそも今から作ろうとしているシステムを一体誰が責任を持って稼動させるのかという視点が抜けているなあと思うことが多いです。そこがちゃんとしていれば多くの問題は解決するはずなのですが。すみません、余談が長すぎました。続きは別エントリでやるかもしれません。