おがさわらなるひこのオープンソースとかプログラミングとか印刷技術とか

おがさわらなるひこ @naru0ga が技術系で興味を持ったりなんだりしたことをたまーに書くブログです。最近はてなダイアリー放置しすぎて記事書くたびにはてな記法忘れるのではてなブログに移行しました。

クリエイティブ・コモンズ・ライセンス
特に断りがない場合は、本ブログの筆者によるコンテンツは クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています。

Ubuntu WilyのシステムPython3でLibreOfficeをコントロール

LibreOffice Advent Calendar 2015 17日目というか、Facebookの書き込みを見て、あれ、Ubuntuの場合はシステムPythonでPyUno(PythonからLibreOfficeの外部インタフェースUNOを使えるようにしたバインディング)使えたんじゃなかったっけと思って試してみました、というだけの話。

Pythonとか5行ぐらいしか書いたことないので(ややうそ)、勘違いとかあったら教えてください。

せっかくなので、ちょっとググっていたら見つかった、PyUNOをより簡単に使えるようにしたラッパーライブラリであるunotoolsを試してみます。

まずは必要そうなパッケージをaptでガツガツとインストール*1。すでに入ってるものもあるかもだけど気にしない。

$ sudo apt install libreoffice-script-provider-python python3 python3-uno

これでpython3からPyUNOが呼べることをまず確認します。

$ python3
Python 3.4.3+ (default, Oct 14 2015, 16:03:50) 
[GCC 5.2.1 20151010] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import uno
>>> 

問題なさげですね。

次にunotoolsですが、こいつはpipなライブラリなのでまずはpipを入れねばならんのです。apt searchするとpython3-pipというのがいるので、こいつだ!と思ったのですが、どうも罠っぽい。

qiita.com

あるべき姿がわからんのですが、調べるのもめんどくさいのでsetuptoolsを経由して入れてしまいます。virtualenvとか使う方がいいんだろうなーとか思いつつ。

$ sudo apt install python3-setuptools
$ sudo eazy_install3 pip
$ sudo pip install unotools

これで入ったっぽい。

ではunotools公式サイトの例題をやってみます。

最初にLibreOfficeをポート指定して起動。

soffice --accept='socket,host=localhost,port=8100;urp;StarOffice.Service'

それからpython3を起動し、パッケージをインポートしてコネクションを張り、Writerのインスタンスを起こす。

$ python3
Python 3.4.3+ (default, Oct 14 2015, 16:03:50) 
[GCC 5.2.1 20151010] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from unotools import Socket, connect                                        
>>> from unotools.component.writer import Writer
>>> context = connect(Socket('localhost', 8100))                                
>>> writer = Writer(context)  

で、起こしたWriterインスタンスに対して、「文字列を文章末に追加」する。

>>> writer.set_string_to_end('Hello\n')                                         
>>> writer.set_string_to_end('World\n')

はい、こんな感じでWriterにPythonから出力することができました。

f:id:naruoga:20151216210529p:plain

例のための例で恐縮ですが、unotools良さそうですね。

なお、本家PyUNOも、次期リリースの5.1では「もっとPythonらしいインタフェース」になるというお話が今年のLibreOffice Conferenceでありました。興味がある方は是非発表資料(PDF)をご覧あれ。

明日は誰か書いてくれるかな?それでは!

*1:最近、可能な限りaptコマンドで生きようと思っております。

デザパタを一人でこっそり振り返ろう #5 (Singleton)

なんと前の記事を確認したら3年近く前だよ…… orz

この連載?は、へっぽこプログラマー(厳密には足を洗ったので「元」)のぼくがひょんなきっかけから、Javaプログラマー向けデザインパターンの入門書として有名な:

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

(以下 JDP)を買ったはいいけどなんかやる気なくて放置してたところに、これまた Smalltalk 界隈の人に教えてもらった:

Design Patterns Smalltalk Companion, The (Software Patterns Series)

Design Patterns Smalltalk Companion, The (Software Patterns Series)

(以下DPSC)を買って読んだらむちゃくちゃ面白くて、じゃあ JDP の内容を Smalltalk で実装したあと、DPSC を答え合わせ的に読む、ということをやったら自分の勉強になるし、「動的言語におけるデザインパターンは静的言語のそれと違う」って意味が噛み締められるんじゃないかと思って始めたものです。

過去の記事は次のとおり。

ということで今回は Singleton パターン。言わずもがなではありますが、JDP から引用すると(p.58)、

指定したクラスのインスタンスが絶対に1個しかないことを保証したい

というものですな。これまた同じページから引用すると:

現在のシステム設定を表現したクラス、ウィンドウシステムを表現したいクラスなどが代表的な例

だそうです。ウィンドウシステムって、どのウィンドウもどっかからちゃんと手繰れないといけないんだけど(でないと、例えばマウスクリックとかのイベントを誰に渡せばいいんだかわかんなくなる)、そういうときに「全部のウィンドウの根っこ」とか持ちたくなるわけですね。そういうのをシングルトンで管理するよと。

そんなにインスタンスの作成を抑制したいんだったら、全部クラスメソッドでさばいてインスタンス作んなければいいんじゃね?と思うんですが、まあよくわかんないので教科書を追います。

ひとまず実装

JDP の例を見てみましょう。シングルトンであるクラス Singleton を作ります。

平たく言ってしまえば new を禁止して、代わりに「まだインスタンスがなければ new したものを、そうでなければすでにあるインスタンスを返す」メソッド getInstance を提供するってな感じです。Java の場合は new を禁止するにはコンストラクタ Singleton を private にすればいいわけですが、さて Smalltalk だとどうするか。private なんて Smalltalk にはねーよ。

まあ new 禁止を考えずにクラスの定義をば。


super new については前回 #4 で説明したとおり。ここで Singleton new ってやっちゃうと怒られる。……いや、これ self basicNew のほうがいいね。あとで気づいた。

でも自身の new 潰してないんだよなあ。ということで new にカーソル当てて alt-M でインプリメンタ出してなんかいいのないかなーと見てると、お、こんなんあった。

Bool class>>new
self error: 'You may not create any more Booleans - this is two-valued logic'

ふむ。self に error: で適切なエラーメッセージを投げてやればいいのね。じゃあこんなん?

そうすると「new 潰すと酷いことになるかも知んないけどマジいいの?」って聞かれるけどまあ構わずセーブ。

さてとワークスペース開けて試してみましょう。

a := Singleton getInstance.
b := Singleton getInstance.
a == b[print it]→true

ほい。うまくいったっぽい。

DPSC の解説

さて答え合わせ答え合わせ。DPSC の Singleton パターンは 91 ページから。ふむ。大筋は合ってるっぽい。new は self>>error: で潰せというのも正解でした。

Smalltalk 的にはシングルトンそのもの、あるいはシングルトンっぽいものはたくさん使われてるよって書いてあります。例えば Squeak の場合は Smalltalk 変数というのがいてコイツがグローバルな色々な何かを持ってたりしますが、このクラスである SmalltalkImage というクラスは new は「Smalltalk 使えよ」ってエラーを出すように潰してあります。

ということで、大抵のシングルトンパターンの場合は、システムそれ自体のグローバル変数とかに持っててそれ経由のアクセスをするのが普通なわけです。例えば上の例で示した Smalltalk 変数とかね。で、コイツの初期化はシステムのブートアップにやりますよと。でもシングルトンのオブジェクトってそれこそシステム根幹の情報なわけで、それをグローバル変数に持つってのはどうなのよ? 危なそうに見えるよね? と。

で、答えとしては、グローバル変数じゃなくてシングルトンオブジェクトのクラスに一つのメッセージを用意して、そいつからインスタンス変数にルーティングすればいいじゃんって。オブジェクトが生成されたとき、初期化されたとき、GCによって破棄された時など、クラスなら適切に扱えるじゃんねーと。なるほどね。でも実際のところ Smalltalk の大抵のシングルトンオブジェクトはグローバル変数で管理されてますよと。なんでやねん。

ちょっと英語苦手なんで実は意味読み取れてないんだけど引用:

Design Patterns states that Singletons accessed through global variables are not really example of the Singleton pettern (DP 127). One might argue that other examples are Singletons and they're just not implemented optimally.

デザインパターンではグローバル変数経由でアクセスされるシングルトンを Singleton パターン (DP 127) の実際の例とはしていません。一つ考えられることは、他の例はシングルトンなのですが、最適な実装がされていなかったということです。

つまるところ GoF ではシングルトンオブジェクトをグローバル変数に突っ込むのを「パターン」ではないとしてるけど、それっぽい例があるから「おーこうやって実装するといいねー」って最初実装しちゃったって話? そうなんかな。まあいいや。あ、ここで DP 127 っていうのはいわゆる Gang of Four (GoF) のデザインパターン

のページ 127 にあるパターンって意味です。はい。

とにかく本当はグローバル変数に突っ込むのは良いやり方ではないけど、Smalltalk ではそういう例がいっぱいあるってことはわかった。

シングルトンのバリエーション

シングルトンには persistent (永続)、transient (一時的)、single-active-instance (単独アクティブインスタンス) の三つのバリエーションがあるとのこと。

  • 永続シングルトン - 特定のクラスの唯一のインスタンスであり、なおかつ永遠に変わらないもの。
  • 一時的シングルトン - あるクラスの唯一のインスタンスだけども、インスタンスは変わりうるもの。例えばセッション情報を管理するオブジェクトはシングルトンであるが、セッションが破棄されると破棄され、新規にセッションが起きると再生成される。
  • 単独アクティブインスタンス - あるクラスに対し、アクティブまたは有効であるインスタンスは一つだけだが、インスタンス自体は複数存在するもの。例えば IDE のプロジェクトを管理するクラスがあるとして、IDE複数のプロジェクトをオープンできるのでインスタンス複数存在するけど、実際にプログラマーが弄ってるプロジェクトに対応するプロジェクトだけが有効とかそういう。実際にはシングルトンではないけど、性質的には似てるから一緒に論じますよと。
予約語

Smalltalknil, true, false とかは実は UndefinedObject, True, False という「クラス」のシングルトンオブジェクトとみなせますよと。実際 true をインスペクトすると True ってクラスのオブジェクトだよって言われるしね。

Smalltalk における実装について

Smalltalk 内部で実際に使ってるケースを紹介してくれるところが DPSC のうれしいところ。
GoF では Singleton パターンの要件として「(A)あるクラスがただひとつのインスタンスしか持たないこと」だけでなく、「(B)グローバルにアクセスできる方法を提供すること」を挙げているらしいです。ん? JDPにはその要件はスルーされていたような……。まあいいや。

単一インスタンスの保証

で、(A)はさっき議論したように、Smalltalkの場合はクラス変数 (Java の場合は静的メンバー変数っていうのかな)に作ったインスタンス放り込んでおいて new 禁止ってやり方でいいよねと。で、ついでにこんなふうな実装もありうるねと。これは Visual Smalltalk の UndefinedObject クラス。

UndefinedObject>>new
"レシーバーの新たなインスタンスを作る。このクラスでは単一のインスタンス nil が
存在するため許可されない。"
^self invalidMessage

で、invalidMessage を別途定義することで再利用性を上げると。ただ、VisualWorks とか Squeak とかこういうふうにしてなくて素朴に self>>error: 投げてる処理系も多いよと。

C++とかJavaの場合はnewをプライベートにするだけでできることなんだけど、すでにあるクラスを隠すって概念がないSmalltalkの場合は実行時例外を上げるしか方法がなく、文法レベルでチェックができないのは欠点だよねってことが書いてありました。まあそれは一理あるわなー。

インスタンスへのアクセスの提供

(B) についてですけど、さっきも書いたとおりグローバル変数を使うやり方は乱暴ではあるけど一応機能はする。もっと良いやり方はクラスでプロキシーする方法で、getInstance って名前はいかにも「インスタンスを貰う(貰った側が代入して持つ)」って名前でこれはちょっとよろしくない。ので、名前をcurrentとかに変えれば:

Singleton current someMessage.

みたいに自然に書けるでしょ? というお話でした。

new を潰さない方法ってどう?

よくよく考えてみると、newを潰す必要は別になくて、単にこうすればいいんじゃないかと。

Singleton class>>new
^self current

上の議論により self current はシングルトンオブジェクトを(必要なら内部で new して)返すんだから、これによって常に new で同じものが帰るよねと。

問題は、new って名前が「いかにもインスタンスを生成しそう」って名前なので:

| roadRunner wileECoyote |
roadRunner := SingleToon new.
wileECoyote := SingleToon new.
roadRunner position: 100@100.
wileECoyote position: 200@200.

で、実は roadRunner と wileECoyote が同じオブジェクトだとはお釈迦様でも気づくめえってことですわね。ので、やっぱ new は潰しておくほうが無難かなと。プログラマーが混乱したんじゃ意味ないんで。

クラス階層の中でのシングルトン

「このクラスのサブクラスの中のインスタンスは一個しかあっちゃダメ」みたいなことが欲しいときはどうするかって話ですね。Smalltalk の場合は一瞬で、クラス変数の代わりにクラスインスタンス変数を使えばいいですよと。つまり:

Object subclass: #Singleton
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'DPStudy-Singleton'

Singleton class instanceVariableNames 'Singleton'

とやれば後は同じでいいですね。っていうかクラスインスタンス変数ってこんなふうに使うんだー。

おっと、単なるテキストの引き写しになってる。いかんいかん。
その後も DPSC は「アクセサの名前 current とか default とかってどう決める?」とか、ぼくが最初に書いた「全部クラスメソッドでよくね?」とかそういう話があって、もっと具体的なコード例(データベースのラッパークラス)とかSmalltalkクラスライブラリの中での使用例とかいろいろ書いてあるわけでこれもまためちゃくちゃ面白い。が省略。買って読んで。

さて次回はいつになるかわかりませんがPrototypeパターンだそうです。お楽しみに。

はじめてのLibreOfficeソースコミット

2014.11.05 追記:とても重要な参考資料を紹介するの忘れていたので後ろにくっつけます。見てくださいね。


私は口先野郎なので、「LibreOfficeの日本コミュニティに一番必要なのはコード書く人。オープンソースはコード書く人が正義*1」とか言っていながら、自分はぜんぜんコード書いてなかったんですよね。これでも元はプログラミングでおまんまを食べてた人間として、これはいかんよなーってずっと考えてた。

開発に挑戦することに対してはいろんなハードルがあるだろうけど、LibreOfficeの場合はコードベースも巨大だしコミュニティもいろんな人が関わってるのでそれなりのお作法もある。正直、どこから手を付けていいかわかんないところがあります。で、私的には、もちろん開発そのものの難しさもあるけれども、それ以前に、とにかく自分の修正のパッチを送ってレビュープロセス通してって、人が介在するところに心理的に抵抗があったわけです。英語では丁寧にドキュメント化されてるのだけど、私英語得意じゃないしね。

で、これは誇っていいことだと思うんですが、LibreOfficeにはEasy Hacksという「開発入門者のための誰でも直せそうなバグ」をタグ付けしたものがあって、もうほんっとに簡単なバグ(で、緊急性が高くない奴)とかを元に、修正してコンパイルしてレビューシステムにpushしてレビュー受けて、ってことをできるようになってる。よし、これにチャレンジしようと。

LibreOffic Conference 2014 Bernのときから初めて、ついついサボってたのでこれはイケナイと思い立ち、自分の尻叩きのために関東LibreOfficeHackFest(#1) with 第119回東京エリアDebian勉強会というイベントをやって*2 ようやっと目鼻がつき、こないだ送ったパッチは無事に取り込まれました。

すっごいしょぼい修正で恥ずかしいのですが一応コミッター*3 の仲間入りをしたので、記録として残しておきます。

課題の選定

Easy HacksはAll LibreOffice Easy Hacks by required Skillというページで必要なスキルごとに分類されているんですが、今回は曲がりなりにも昔はそれでご飯食べてたC++の、超簡単レベルな奴からピックアップ。

Bug 43157 - Clean up OSL_ASSERT, DBG_ASSERT, etc.

Bug Descriptionから引用すると:

The assertion/logging functionality from osl/diagnose.h (OSL_TRACE, OSL_ASSERT, OSL_ENSURE, OSL_FAIL) and tools/debug.hxx (DBG_ASSERTWARNING, DBG_ASSERT, DBG_BF_ASSERT, DBG_WARNING, DBG_WARNING1--5, DBG_WARNINGFILE, DBG_ERRORFILE) is obsolete and needs to be cleaned up:

  • To assert invariants of the code (that can only be violated if there are programming errors) use standard C/C++ assert.
  • To log warnings about unusual events (that the code nevertheless needs to handle in some way, like malformed input or I/O failures), use the SAL_WARN... macros from sal/log.h.
  • To log other information useful for debugging, use the SAL_INFO... macros.

See https://wiki.documentfoundation.org/Development#Assertions_and_Logging, the mail thread at http://lists.freedesktop.org/archives/libreoffice/2011-November/020864.html, and the documentation in the sal/log.h header for further information.

要は古くて非推奨になったアサーション・ロギングマクロを、今のスタイルに書き直しましょう、ってだけ。機械的な置換でいけそうだからこれなら自分でもできるやろと。

ビルド環境構築

まずはgitでソースコードをまるっと取って来ます。で、ビルドに必要なライブラリをがっと取ってきて、一回ビルド通します。LibreOfficeのフルビルドは環境にも寄りますけど数時間かかるので、ビルド投入して寝ちゃうのがいいでしょう。

$ git clone git://gerrit.libreoffice.org/core LibreOffice
$ sudo apt-get build-dep libreoffice
$ cd LibreOffice
$ ./autogen.sh --enable-gstreamer --disable-gstreamer-0-10 --with-lang="ALL"
$ make

ビルド終わったらちゃんと動くことを確認します。

$ instdir/programs/swriter &

ヘルプ→LibreOfficeDevについて、を確認。

大丈夫そうですね。

コードの修正

今回の修正は色んなところのソースをお掃除しましょうというネタなので、どこ直してもまあいいわけです。なので直せそうなところをgrepで探しましょう。

豆知識としては、LibreOfficeはビルド時に依存するライブラリのバージョンを固定するために、サーバーにおいてあるtar ballを落としてきてworkdir/UnpackedTarballというところに展開します。その他yaccの生成物とかもあるので、単にgrep -r するとそういうのがいっぱい引っかかってしまうので、git grep使うほうが100倍ぐらい賢いです。
ということで、git grep OSL_ したら、あるわあるわ。どれ直してももちろんよいのですが、一部のソースは特定のconfigureオプションをオンにしないとコンパイルされない(そして、そのconfigureオプションはほとんど誰も使っていない)とかある*4 ので、ちょっとだけ注意が必要です。今回は pyuno/source/module/pyuno.cxx というソースを直すことにしました。変更が2行しかないのでw

ターゲットが決まったらまずは作業ブランチ切りましょう。

git checkout -b work

修正はバグ票にあった他のコミットを見ながら、こんな感じに書き換えればいいのかなと*5

--- a/pyuno/source/module/pyuno.cxx
+++ b/pyuno/source/module/pyuno.cxx
@@ -66,7 +66,7 @@ void PyUNO_del (PyObject* self)
 
 OUString val2str( const void * pVal, typelib_TypeDescriptionReference * pTypeRef , sal_Int32 mode )
 {
-    OSL_ASSERT( pVal );
+    SAL_WARN_IF( !pVal, "pyuno", "pVal != NULL" );
     if (pTypeRef->eTypeClass == typelib_TypeClass_VOID)
         return OUString("void");
 
@@ -124,7 +124,7 @@ OUString val2str( const void * pVal, typelib_TypeDescriptionReference * pTypeRef
         buf.append( "{ " );
         typelib_TypeDescription * pTypeDescr = 0;
         TYPELIB_DANGER_GET( &pTypeDescr, pTypeRef );
-        OSL_ASSERT( pTypeDescr );
+        SAL_WARN_IF( !pTypeDescr, "pyuno", "pTypeDescr != NULL" );
 
         typelib_CompoundTypeDescription * pCompType = (typelib_CompoundTypeDescription *)pTypeDescr;
         sal_Int32 nDescr = pCompType->nMembers;

で、make。フルビルド済ませてあるので、今度はそんなにかからないです。本当はmake checkでユニットテストも通すべき(そもそも、このコードにユニットテスト書くべき)なんですが、私が作業したときのmasterはなぜかmake checkすると通らない(よくあることのようです ^^;)ので、うーんと悩んだ末そのまま。

とりあえず、ローカルの作業ブランチにコミットしておきます。コミットコメントはこんな感じ。バグ修正のときはバグ番号を書くのが決まりです。

fdo#43157 - Clean up OSL_ASSERT, DBG_ASSERT
    
- Clean up OSL_ASSERT

gerritの設定

LibreOfficeでは、Googleが開発したソースコードレビューシステムGerritを使用しています。gerritの持つgitリポジトリにpushすると、その上でdiffを確認しつつ、レビューコメントがついたり質疑応答したりできるシステムです。ビルドbotも走っていて、そのコミットでビルドがぶっ壊れないかも確認してくれます。超便利。MLにパッチ投げて議論して、ってのもいいけど、gerritを使うほうが推奨されてる……はず。

ので、まずはgerritの設定するところから始めます。TDF WikiのGerritの解説ページを見ながら。

最初は https://gerrit.libreoffice.org にてアカウント作成ですが、これは大昔にやったことがあったので省略。Googleアカウントがあれば簡単だった記憶が。で、アカウント設定でgit push用のssh公開鍵を設定しておきます。もちろん専用の鍵ペア作ってもかまいません。

次に、解説ページによると、

You must set your username to match your freenode IRC nick (see 'Username' in your gerrit account settings).

とあるので、freenodeのNick登録しなきゃいけません。IRC技能が低い私はここでしばらく調べまわったのですが、自分 irssi 使ってるので、

/msg NickServ REGISTER <password> youremail@example.com

しといて、

/NETWORK ADD -autosendcmd "/^msg nickserv id <username> <password>;wait 2000" Freenode

でFreenodeのNickとGerritのアカウント名を合わせればいい……のかな。今んとこそうしてます。ただ、irssi常時上げてるわけじゃないし、irssiもnotify上げてくれるわけじゃないので実のところIRCマトモに使えてない。なんかいい方法ないすかね。ま、それはまた別の話。

で、先の解説ページに戻りまして、手動セットアップします。まずは.ssh/configに:

Host logerrit gerrit.libreoffice.org
       IdentityFile /path/to/your/private-key
       User <username>
       Port 29418
       HostName gerrit.libreoffice.org

を追記。秘密鍵のところは、さっきGerritに登録した公開鍵とペアになる鍵を指定しましょう。当たり前ですね。

で、LibreOfficeのソースディレクトリに移動しておいて、

$ ./loggerit test

とやって、"Your gerrit setup was successful!" と表示されればOK。

さてgitにpush先を指定しておきましょう。

$ git config remote.origin.pushurl ssh://logerrit/core

これで準備完了です。

いよいよpush……の前に

なんだかんだで、ソースコードを修正してからココらへんの設定をのたのたやってたので、まる2日ぐらい経ってたのかな?

LibreOfficeは進化が早いソフトウェアなので、2日も経つと当然masterには大量に変更が溜まってる。まあ、ビルド通らなくなるとは思えないけど、もし同じファイルを修正した人がいたりしたら悲しくなるので、念のためmasterを最新にして、自分の変更をその先頭にぶら下げるようにしたい。

私、git力低いので、ホントはこういうときってrebaseでできるのかもなあって思いつつ、悩んだ末masterをpullして最新にした後新たにブランチ切って、そこに作業ブランチの変更をcherry-pickするようにしました。いいのかなあ、こんなやり方で。

$ git checkout master
$ git pull origin master
<ぞろぞろとファイルが降りてくる>
$ git checkuout work
$ git log --oneline | head
<先頭は自分のコミットなのでハッシュ値を見ておく>
$ git checkout -b fdo43157 master
$ git cherry-pick <さっき確認したハッシュ値>
$ make
<ちゃんとmake通ってできたものが動くこと確認>

Gerritにpush

ドキドキしながらpush……してもいいのですが、いきなり投げるのはちょっと怖いので、なんか稽古場があった気がする……って読んだら、

<ローカルブランチ名>:refs/for/master の代わりに <ローカルブランチ名>:refs/drafts/master に投げるとドラフトとしてコミットできるよ

って書いてある。これはドラフトに一旦おいて議論するみたいな使い方をするところなのかなってのも思うのですが、まあ、突っ込んですぐ消すなら大丈夫かな、と……。

で、今の場合は:

$ git push origin fdo43157:refs/drafts/master

すると、「このURLに登録されたよ!」って出るので、そこを見に行くと、draftsにちゃんとコミットがあることが確認できる。

ホントは、このdraftsにadd reviewerしてレビュープロセスに移動するのが正しい使い方らしいのですが、誰をレビュアーにするとかプロセスがわかんなかったので、一度abandonして再度pushすることにしました。いいのかな。不安なので、みなさんはポリシー各自確かめてね。とにかくうりゃっとpush!

$ git push origin fdo43157:refs/for/master

すると、こんな感じで登録されます。あ、これレビューコメント色々もらって修正したやつなので最初とは変わってます。古いパッチも見られますけどね。最初にpushしたのはPatch 1。

pushしてしばらく経つと「ビルドbotが起動したよ」「ビルド成功したよ」とメールが来ます。ほっと一安心。あとは人間のレビュアーの登場を待ちます。

ライセンス宣言

おっと、ライセンス宣言もしないといけません。開発者・貢献者リストのページにあるとおり、開発者メーリングリストに:

All of my past & future contributions to LibreOffice may be
licensed under the MPLv2/LGPLv3+ dual license.

というメールを投げる必要があります。著作権譲渡や契約の締結などは不要です。めっちゃカジュアルですね。
本当は先の開発者リストWikiに自分の名前書かなきゃらしいのですが、このWiki超重要なので間違って編集したら怖いな、いわれたらやろうということで一旦保留*6

レビュー指摘反映

さて、ドキドキしながら待ってたら、コメントがつきました。まずはTakeshi Abeさん*7からの二つのコメント。

  • SAL_WARN_IFの第三引数は「こういう状態だからおかしいよ」ってものをログに残すものだから、条件逆じゃない?
  • SAL_WARN_IFの第二引数に指定するログエリアはinclude/sal/log-areas.doxに定義しないとダメだよ。

二番目の指摘は完全に見落としだったんですが、最初の指摘は……私はなにを考えていたんでしょう。まあ、勘違いとしかいいようがないですね。お恥ずかしや。

次の指摘はバグ#43157のreporterでもあるStephan Bergmannさんによるもの。

  • これ両方とも、NULLだったら後ろ死ぬからワーニングじゃなくてアサートにすべきじゃない?

……むぐ。そりゃそうだ。何を考えていたんでしょうワタクシ。

言い訳するとSAL_WARN_IFとかを定義してるinclude/sal/log.hxxにはアサートに当たるものがなかったのと、同じバグ票に対するこちらのコミットだとOSL_ASSERTをSAL_WARN_IFに書き換えているコードがあったからというのはあるんだけど、それはプログラマー的には思考停止であって言い訳にもならん。あかんわ。

で、間抜けな私はあれ、アサートのマクロってなくない?どれ使えばいいんだろ?と動揺して、日本語メーリングリストで質問したら、「いやいやC++のassert()でいいでしょう」ってCalcハッカーのKohei Yoshidaさんから教えてもらった。……あ。そうだね。そりゃそうだ。というか、バグ票にも " To assert invariants of the code (that can only be violated if there are programming errors) use standard C/C++ assert." って書いてあるじゃんかー。頭悪いな。

ということで、assert() に直した奴を再度コミット。--amend でコミットするとChange-Id:を見て、同じgerritエントリーに登録してくれるとな。素晴らしい。ので、それをpush。えいっ。これがPatch 2。

再びレビュー待ち……そして。

またビルドbotからのメールを受け取ってドキドキしながら待ってたら、「いいみたいだね、でもassert()使うなら #include を明示的にするほうがいいと思うな。この場合は他のヘッダでincludeされてるから問題ないけどね」って指摘をもらった。けど、それは別途直しておくよってことでパッチはacceptされて、無事コミットは取り込まれました。わーい!

http://cgit.freedesktop.org/libreoffice/core/commit/?id=922f2005f34589e60969be3f2bf74e4af58e2e69

感想

  • 仕組みはいろいろ良くできてる。ドキュメントが少し散らかってる感じはあるけど、ちゃんと読めばわかる。
  • Gerritすごく良くできてて感動。
  • みなさん大変親切でありがたや。お世話になりました。
  • 次はもうちょっと機能や不具合に関わる修正に挑戦したいな。



参考資料:Tomofumi Yagiさんの発表資料 (2014.11.05追記)

LibreOfficeへのコミットについては、Tomofumi Yagiさんのスライドが非常に参考になるので紹介しておきます。私が書かなかったOpenGrokなどについても説明されていて、必読です。

*1:あーもちろん、翻訳やアウトリーチ・プロモーションやその他の活動もやっぱり大事なんですよ。わかりやすいように極端な言い方をしてるだけで。

*2:タイトルの通り、東京エリアDebian勉強会の皆さんには大変お世話になりました。またコラボしましょう!

*3:LibreOfficeはgitで管理されており、誰でも1行からコミット可能なので、例えばApache OpenOfficeのように特定のコミッターという権限を持つ人がいるわけではありません。コードが取り込まれれば誰でもコミッターです。

*4:BernのHackNightで挑戦したのがうまく行かなかったのはこれが原因。

*5:あ、間違ってるってのは知ってるので、ちょっと待ってね。

*6:結局は、後述のStephenさんがやってくれました。

*7:LibreOffice日本語チームのメンバーで、アクティブにコミットされている安倍さん。

Ruby初心者がRuby on Rails + Mongoidを試してみた

この記事はMongoDB Advent Calendar 2013の二日目です。

後の人のハードルを下げるために、私はレベルが低い話を。

私の勤務先は中堅SIerでして、そこの小さなチームでつかそうな案件のときはMongoDBを使おうということで、出番を虎視眈々と狙っている次第です。以前は Scala に Play! Framework に MongoDB を組み合わせるという案件をやりまして、そのときのことは↓に書いたりしました。

しかし私の部署の中で最大与党は Ruby on Rails (以下 Rails) で図書館管理アプリを作っているメンバーでして、ここのワークロードが低いときにでもぱっと案件取れるようにするには、Rails で MongoDB 使えますよ! と、説得できないといけません。

しかし Rails といえばなんつっても ORM (Object-Relation Mapper) である Active Record でして、Active Record は ORM というからして、当然 RDBMS を叩くことしか考えられていません。そんなわけで、Active Record 的な I/F を持った ODM (Object-Document Mapper) な Mongoid をちょっとお試ししてみました。

Mongoid とは何か

Mongoid とは何か、ということについては本家のページを参照してください。イントロだけ引用すると:

Mongoid (pronounced mann-goyd) is an Object-Document-Mapper (ODM) for MongoDB written in Ruby.(snip)

The philosophy of Mongoid is to provide a familiar API to Ruby developers who have been using Active Record or Data Mapper, while leveraging the power of MongoDB’s schemaless and performant document-based design, dynamic queries, and atomic modifier operations.

(参考訳)
Mongoid (発音は mann-goyd) はRubyで書かれたMongoDBのオブジェクト・文書マッパー (ODM) です。(略)

Mongoidの哲学はActive RecordやData Mapperに慣れている Ruby 開発者に親しみやすい API を与えつつ、MongoDB のスキーマレスや強力なドキュメントベースデザイン、動的クエリ、アトミックな変更操作などの強みを使えるようにすることです。

このようにMongoidはActive Recordの置き換えを強く意識しているので、Railsエンジニアには使いやすいのではないかと思います。

Mongoidを試す

さすがに実プロダクトのバックエンドを差し替えるほどの Rails スキルはないので、新規プロジェクトで Mongoid を使ったものを作ってみます。私は Rails に詳しくないので新規プロジェクトを作成するのは Rails Tutorial を参考にしました。

rbenv + ruby-build を用いて Rubyは 2.0、Railsは 4.0 を導入した環境で:

rails new mongo_sample_app --skip-active-record

で新規のRailsアプリを生成します。--skip-active-recordがミソ。

そしてGemfileに次の一行を加えます。

gem "mongoid", github: 'mongoid/mongoid'

ここで Github の master を取ってきているのは Mongoid の stable である 3.x 系は Rails4.0 (正確にいうとActive Modelの最新) と依存関係が当たるので gem で普通に入れると非常に古いバージョンが入ってしまうから。これ調べたのちょっと昔 (11月頭ぐらい) なのでもしかしたら今は大丈夫かもしれません。なお、ドキュメントによっては bson_ext という gem が必要と書いてあるものがありますが、この gem は Mongoid 3.x 系で取り込まれたので不要になりました。単に Mongoid だけを指定すればいいです。

次に:

rails g mongoid:config

でconfigファイルを出力。config/mongoid.ymlというファイルが生成されます。開いて中をチェックしてみます。コメントが丁寧に入っているので見ればわかると思うのでここで引き写すことはしませんが、ローカルに MongoDB が動いている環境*1 ならばほぼ素で使えますが、そうでない場合も設定はとても簡単です。今回は深入りしませんけど、Read Preference とか Write Concern などもここで定義できる感じですね。

さてここまでできたら単純に一つモデルを定義して scaffold を生成してみましょう。次のようなモデル User を考えます。

User
nameString
emailString
このモデルおよび scaffold を生成するには以下のようにすれば OK です:

rails g scaffold User name email

なお型指定がないのは、Mongoid はデフォルトの型が文字列と決まっているから。

試しに rails s して localhost:3000/users にアクセスすると、ちゃんとユーザー作成ができるようになります。
ここで注目したいのは

db:migrate が不要なこと。

MongoDB の場合はスキーマ定義も不要であるしデータベースもコレクションにドキュメントをinsertしたところで自動で生成されるので、マイグレーションがいらないのです。正直 Rails のちょっとめんどくさいところってソースいじってるだけじゃ完結しなくて rake コマンド叩かないといけないところだと私は思っているので、これは結構ありがたい気がしてます。カジュアルな MongoDB っぽいなあと。

なお、この段階なら、要素を増やすのももう一度scaffoldの生成を行う (そして、コンフリクトは全部overrideで解決) するだけでよいです。

rails g scaffold User name email nick

さすがに出来合いの View や Controller 使う人はいないと思うのですが、Model だけは上書きしてあとは手書き、というスタイルで、開発途中の要素追加も割と容易なのではないかと思います。なお、View や Controller 側から Model を触るときには当然 Active Model にラップされていますので、Rails に慣れた人なら素直にコーディングできるのではないかな??

そんなわけで「スキーマ定義ができないけれど開発には着手しなければならない」場合、Rails + Mongoid + MongoDB という組み合わせはわりと「アリ」ではないかと思います。


埋め込みドキュメントを使うときとか、あと JOIN をしないといけないときとかについてはまだ試してないですけど、まあそういうことは誰かが書いてくれるでしょう多分。


と、ここまで書いたらモロおんなじ内容が Qiita にあったことに今気づきましたが、知らん顔して投稿することにします。重なっていない部分もあるしね。
あすは key さんですね。よろしくおねがいします!

*1:私の環境は Ubuntu に MongoDB Inc 公式リポジトリを追加してローカルに mongod が動いてます。

デザパタを一人でこっそり振り返ろう #4 (Factory Method)

なんと前の記事を確認したら一年以上前だよ…… orz

ということで誰も読んでないと思いつつも続きを書きますが、へっぽこプログラマのぼくがひょんなきっかけから、Javaプログラマ向けデザインパターンの入門書として有名な:

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

(以下 JDP) を買ったはいいけどなんかやる気なくて放置してたところに、これまたSmalltalk界隈の人に教えてもらった:
Design Patterns Smalltalk Companion, The (Software Patterns Series)

Design Patterns Smalltalk Companion, The (Software Patterns Series)

(以下 DPSC) を買って読んだらむちゃくちゃ面白くて、じゃあ JDP の内容を Smalltalk で実装したあと、DPSC を答え合わせ的に読む、ということをやったら自分の勉強になるし、「動的言語におけるデザインパターンは静的言語のそれと違う」って意味が噛み締められるんじゃないかと思って始めた連載?です。

過去の記事は次のとおり*1

ということで今回は Factory Method パターン。JDP から引用すると(p.46)、

Template Method パターン (中略) をインスタンス生成の場面に適用したもの

なんだそーです。

リフレクションとかの機能がなくて、動的にクラスにメソッドを追加できなくて、文字列をもとにオブジェクトを操作するみたいな作業がニガテな C++Java に比べると、SmalltalkRuby なんかだとあんまり大げさなことやらなくてもいい気がするんですが、はてどうでしょう。

ひとまず実践

えーと、JDP で挙げられている例は、「IDカード」を抽象化したクラスを作るためのフレームワークを提供するってものです。入門書って性格上しょうがないと思うんですが、JDP の例はこういう「例のための例」が多くてちょっと歯がゆいです。
まあ文句をいってないで実装しましょう。

JDP では Java なのでパッケージ機能を使って、framework パッケージと idcard パッケージに分けてますが、これは Smalltalk ならカテゴリーにしましょうか。framework はクラスを「作る」「使う」という抽象概念を定義し、idcard はそれを具象化するパッケージというわけ。

まずは framework 側。カテゴリー名は当たらないように長ーくなってます。通常の Smalltalk の名前付けルールとは違うけど許して。


んで具象側のオブジェクト側はこんな感じ。

出力は手抜きで Transcript に出すので、Transcript を表示させておいて:

hoge := IDCardFactory new.
pro1 := hoge createOf: 'なるひこ'.
pro2 := hoge createOf: 'なるおが'.
pro3 := hoge createOf: 'naru0ga'.
pro1 use.
pro2 use.
pro3 use.

とかやると、

なるひこのカードを作ります。
なるおがのカードを作ります。
naru0gaのカードを作ります。
なるひこのカードを使います。
なるおがのカードを使います。
naru0gaのカードを使います。

てーな感じになるわけです。

super new って大丈夫なん?

で、これを書くときに意外とハマったのが、コンストラクタで値を初期化するよーな書き方。って、今までやったことなかったんかいな自分……。
具体的には、

IDCardFactory class>>new
	^ super new initialize.

IDCardFactory>>initialize
	owners := Bag new.

あたりですね。

C++JavaRuby なんかだとコンストラクタでメンバー変数代入すればいいんで (C++ だと初期化子を使うほうが行儀がいいのか?) 最初は:

IDCardFactory class>>new
	owners := Bag new.

って書いたら怒られた。「クラスインスタンス変数には owners なんてねーよ」って。おうふ。
じゃあってんでインスタンス生成のときに値を初期化しているようなオブジェクトなんかねーかなって考えて、そうだ Complex とかどうしてるんだろって見たら、

Complex class>>new
	^ self real: 0 imaginary: 0.

Complex class>>real: aNumber1 imaginary: aNumber2
	| newComplex |
	newComplex := super new.
	newComplex
		real: aNumber1;
		imaginary: aNumber2.
	^ newComplex.

みたいになってる。ここで real: と imaginary: はどっちも Complex で定義されてるメソッド
んー? super new したものに Complex のメッセージ投げて、なんでうまく動くんだろう?? と思って、ついったに:

って書いたら (ここで Hoge>> は Hoge class>> の間違い)、@abee さんに:と教えてもらった。ふむ。
ということは、super new して得られるオブジェクトはあくまでも super の持つオブジェクト (= IDCardFactory クラス) のインスタンスオブジェクトなんだけど、new を処理するのはその上位 (= Factory -> Object -> ...) ってーことなんやね。なんかややこしいけど、イディオムとして覚えとこ。
まあ結果としては、今回の書き方で正しいらしいです。

このあとの議論も面白かったし、ここ深堀するといろいろ面白そうだけど、主題じゃないのでまたいつの日か。

考察的な何か

で、えーと、とりあえずこの例だと:

  • Factory の時点で「作るために必要なもの (Factory class>>createFor: anOwner の anOwner)」が決まってしまっているのは果たしてどうなのか。ものすごく使いにくいような気がする
  • なんで Register しているのか説明がなんにもないので意味がわかんない。これはこのパターンに必要なのか?

とか思っちゃうわけですよね。

後者のほうはどっちかというとテキストの問題ですな。IDCardFactory クラスで register するのは多分、作ったオブジェクトに対してブロードキャストとか処理の委譲をしたいからだと思うんだけど、JDP だとそういう話はぜんぜん出てこないし、そもそもそういうことしたいなら具象クラスじゃなくて抽象クラスの Factory の方でやらないとマズイ気がするし、教科書としては不要な例じゃないかなーと思うのだけど、まあ、先を読み進むと意味があるのかもしれませんし、そこはあまり追求しますまい。

むしろ前者が辛いですよねー。これって実用できる範囲をとても狭めてしまうと思うんだけど……そこのところ、もうちょっと解説が欲しいすな。例えばウィジェットの生成なんかは Factory をよく使う例だと思うんだけど、ウィジェットの場合、生成時に必要な初期化パラメータって微妙に異なったりしますよね。そういう場合、このパターンでやるのが正解なのか、違うパターンでやるのがいいのかとか、もし Factory Method でやるとしたら Java の場合にはどう実装するのが正解なのかってのが知りたいなーと。

DPSC を読んでみた

さて DPSC の Factory Method パターンは p.63 から。

前にも書いたけど DPSC の場合は「このパターンは Smalltalk のクラスライブラリの中で実際こう使われてるよ」って書いてあることで、ここで例に出されているのはズバリ、MVC (Model-View-Controller)。MVC の元祖は Smalltalk なんですよね。でまぁ、例として挙がってるのは複数の種類 (Text と Drawing) の Document を扱える Editor アプリケーション。これは Editor を Factory、Document を Product として考えれば実現できますよと。

Factory Method で MVC のクラスの管理コストを下げる

MVC の辛いところは、要素が増えてくるととにかくクラスの量が爆発的に増えることですよね。DPSC で挙げられているのは View と Controller の関係なのですが、基本的には View は「違う見え方」に対して一つずつクラスを作るのに対して、Controller は対応するだけ作る必要はなくて上位のビューと共通化できることも多いです (Smalltalk の例であれば View の BooleanWidgetView > ActionButton に対し、Controller は両方とも WidgetController でよい)。となると、両者の「どのビューはどのコントローラを使うか」っていう管理をしないといけないです。MVC の元祖的な Smalltalk-80 の場合は Dictionary で管理してたらしいですが、これはクラスが増えるたびに管理が大変。

ということでこの点について VisualWorks で使っているテクニックが紹介されてます。さっくり引用(p.68)。

View>>getController
  "レシーバーの現在のコントローラーを返す。もしレシーバーのコントローラーが nil だったら
  (通常の場合)、レシーバーのデフォルトコントローラーの初期化されたインスタンスが導入され、
  そのインスタンスを返す。"
  controller == nil
    ifTrue: [self setController: self defaultController].
  ^controller.

んで、defaultController を Factory Method としてみなせば、それぞれの View に対応して defaultController の定義をこんなふうに書けばよくなる。

View>>defaultController
  ^Controller new.

Text>>defaultController
  ^TextController new.

しかしもうひと工夫。どちらの defaultController も「あるクラスに対して new を投げてるだけ」なんだから、これも括り出しちゃえばいいじゃないかと。そんなわけでこんな感じ。

View>>defaultController
  ^defaultControllerClass new.

View>>defaultControllerClass
  "レシーバーのデフォルトコントローラーのクラスを返す。"
  "もし Controller 以外のコントローラを使いたければ、サブクラスは"
  "defaultControllerClass を再定義すること。"
  ^Controller.

Text>>defaultControllerClass
  ^TextController.

……正直、きれいになったとは思うけど、うれしさがさっきの例とくらべてよくわからない自分がいます……。
あ、でも下位クラスでオーパーライドすべきメソッド (逆にいえば、同じコントローラで構わなければオーバーライドしなくてもいい) がわかりやすいのはいいですね。
ここでの defaultControllerClass みたいなメソッドは Constant Method と呼ばれるらしくて Kent Beck の:

ケント・ベックのSmalltalkベストプラクティス・パターン―シンプル・デザインへの宝石集

ケント・ベックのSmalltalkベストプラクティス・パターン―シンプル・デザインへの宝石集

  • 作者: ケントベック,Kent Beck,梅沢真史,皆川誠,小黒直樹,森島みどり
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2003/03
  • メディア: 単行本
  • 購入: 7人 クリック: 94回
  • この商品を含むブログ (55件) を見る
に書かれているそうな。読んでみようかなー。

それから、同様の方法はクラスインスタンス変数を用いてもできますよねという話。さっきの例を書き換えればこんな感じかな?

View class instanceVariableNames: 'defaultControllerClass'.

View>>defaultController
  ^self class defaultControllerClass new.

View class>>initialize
  defaultContorllerClass := Controller.

TextView class>>initialize
  defaultContorllerClass := TextController.

あと p.73 からの「Known Smalltalk Uses」で、豊富な実例が紹介されていてなかなか素敵なのだけども省略。クラス生成だけじゃなくて「外部 (external)」のなにかを呼び出すときに、呼び出すパラメータ文字列を生成する奴なんかも Factory Method パターンで扱えるよってのはふむふむだった。

まとめ

さっきの疑問「インスタンス生成のときに決まった引数を渡さなきゃいけないのって、なんかうれしさがわからない」という件、DPSC では「ただ new するだけ (引数とか取らない) でも嬉しいパターンはたくさんある」ということを提示していて、これなら便利さが朧気ながら分かりますね。MVC の例のように、「あるクラスと関係するクラスを new して関連を保持しておく」みたいな処理はみんなこのパターンになるわけだから。むしろ「工場と製品」アナロジーから離れたほうが広く応用が考えられそうですね。

次回は Singleton。いつになるやらわかりませんが(^^;

*1:タイトルが一貫してないことが発覚したのでこそこそ修正。

デザパタを一人でこっそり振り返ろう #3 (Template Method)

この企画は「動的言語においてはデザパタの多くは意味を成さないか違うパターンになる」というよくある俗説?を体感したくて、自分にとってはNative LanguageじゃないSmalltalkで結城先生のJavaデザパタ本 (以下JDP) をなぞってみるという企画でございます。

さて今日のお題からJDPとしては第2部に入りまして「サブクラスにまかせる」というもの。
んで最初がTemplate Methodパターンと。ふむ。

これって要は「親クラスの外部インターフェースを定義している処理を、より詳細にしたメソッドの単純な呼び出しになるよう分割して、その下の具象クラスに実際の処理を隠蔽する」って奴ですよね。

まずは実践

つーことで、JDPの例を、なるべくJDPを見ないようにSmalltalkで書くと*1、まずは AbstractDisplay は:

んで CharDisplay、StringDisplay*2 はこんな感じかな。


あー分かってます分かってます。String>>open と String>>close の実装が愚直すぎることは。コンストラクタで string が与えられたときにサイズ決まるんで、そのときに文字列作って持っておけばいいんだよね。ちょこちょこ試した感じだと、WriteStream 使えばいいのかなって思うんだけど。こんなふうに。

stream := WriteStream with: '+'.
('hogehoge' size) timesRepeat: [stream nextPut: $-].
stream nextPut: $+.
stream contents. "print it."

まあ、それはいいとして。

例によってワークスペースとトランスクリプト出して、

a := CharDisplay with: 'A'.
a display.

b := StringDisplay with: 'hogehoge'.
b display.

ってやって全選択して do it すると、

<<AAAAA>>
+--------+
|hogehoge|
|hogehoge|
|hogehoge|
|hogehoge|
|hogehoge|
+--------+

まぁ、これで一応、やるべきことはできた……けど、なんだか当たり前だのクラッカーだなぁ。デザパタってのはそゆもんっていえばそゆもんだけど。

とゆことで DPSC へ

では DPSC ではどんなことが書いてあるかというと、ちと見てみましょう。

継承って難しいよね実は?

OOPにおいては継承って意外と難しい概念なんで、SELF って言語だと継承をとっぱらっちゃって、代わりに delegate (前回出てきた「委譲」ですな) という概念を導入しちゃってますよとか何とか。

私のへっぽこ英語でさらっと読んだ範囲と自分の経験からいうと、継承が混乱を生むのはその上位下位の関係でなにを抽象化するのかがごちゃごちゃになるからであって*3、「この継承関係はこういうことを抽象化するの!」ときちんとポリシーさえあれば、やっぱり継承は便利だよねと。

で、Template Method はまさにそういう「ポリシーをきちんと決める」例であって。

abstract なメッセージをどう実現するか

えっと、ここは前回からのちょっとした進歩。

私の Smalltalk はまだぴよぴよなので、前回のエントリでは、Java でいう abstract、C++ でいう pure virtual なメッセージの実装方法がわかんなかったわけ。で、コメントだけ書いて定義空っぽにしておいたのね。

でも、ここではまだ実装しないよ、サブクラスの責務だよ、というときには Object>>subclassResponsibility ってメッセージを self に投げとくと、よきに計らってくれるんだそうです。つか、「サブクラスでオーバーライドしないとダメだよん」って実行時エラー出してくれるだけだけど。

でも、実は前回のやり方も間違いってわけじゃなくて、「別に実装しないんだったら、なにもしないでもいいよ」というインターフェースってのもあるでしょうと。そゆ場合には空っぽの実装おいておけばいいよと。
これはあれだよね、Java だったら abstract にしないで stub にしとけばいいとかそういう話だよね。

クラス階層設計の原則

えーと、Auer って人がクラス階層を定義するんだったらこんなふうにしなさいよって指針を出してるそうです。詳しくは DPSC p.359 を読んでください。一応メモ。

  1. クラスは振る舞いによって定義しなさい。状態によって定義してはいけません。
    つまり最初はクラスの構造 (平たくいえば属性;インスタンス変数とか) を考えちゃダメだよと。
  2. 振る舞いを抽象化された状態を使って実装しなさい。
    逆の言い方をすれば、抽象的な振る舞いを実装するのに必要な状態 (属性) だけを考えて、後のことは後でかんがえろということなんだろね。
  3. メッセージのレイヤを認識しなさい。
    つまり親クラスで実現されるべき振る舞いを、サブクラスによって実装されるべきいくつかの小さなメッセージを呼び出す形で考えとけってことかしら。
  4. 状態変数の特定を後回しにしなさい。
    んっとこれは、親クラスでは処理の多くをサブクラスにお任せするわけだから、その処理に必要な状態を親クラスが持つべきではないよねと。サブクラスでそれぞれの処理を実装するときに、じゃあどういう状態 (属性;インスタンス変数) が必要かを考えなさいよと。

まぁ、おっしゃるとおりで。このとおりにやってれば、Template Method って形に自然になるよねーと。


あと Delegate (委譲) について「なにを委譲するの」って説明とか*4、クラス階層のリファクタリングケーススタディとかあって、面白いんだけど省略して。

めんどくさいメソッドを Template Method で実装する方法

というのが書いてあったのでサラッと紹介。詳しくは DPSC 363-364 を。

  1. 最初はまず実装しちゃえ
  2. それをいくつかのステップに細分化しよう
  3. それぞれのステップをメソッド化しよう
  4. そのメソッドを呼ぶように書き換えよう。
  5. メソッドの中にクラス名やリテラルやその他定数になるものがあったら、「定数を取り出すメソッド (constant method)」に分割しよう
  6. 1〜5を繰り返そう。

多分5が一番ミソっぽいよな。ここで constant method をうまく抽出できれば、それを下位のクラスに追い出すことができるわけだから。

あと Smalltalk で Template Method パターンがどう使われてるかってのが延々続くわけだけど、そしてそれがとても面白いのだけど、私が力尽きたので省略。つかみんな DPSC 買って読め。

まとめ

正直 Template Method はあんまり Smalltalk らしさがないんで、前回の Adapter パターンほどワクワクしなかったんだけど、クラス階層設計のポリシーとか Template Method の抽出のしかたとかの丁寧な説明を読んで、DPSC のテキストとしての良さを再認識した気がします。
JDP だとパターンの説明は分かりやすいんだけど、「ほらね、継承使えば便利なことがあるってことがわかったでしょ。でも、どういうふうに継承を使うかってのは決まりはないんだよねー」とか説明が腰が引けてるんだけど、それって DPSC に比べるとズルいと思う。いや、対象読者層が違うといえばそうなんだけど、さ。
単なる公式集じゃなくて、そこからもう一歩上がった「この公式ってどういう考え方で出てくるの?」ということをちゃんと書いてるのが好感度。ここらへん GoF はどうだったっけかなぁ。


さてお次は Factory Method パターンか……これも楽しみですな。しかし、いつ書けるのやら。

*1:最近ムダにgist使うのがお気に入り。

*2:どうでもいいが、Char と String で名前の縮め方のルールが違うのが気になる……。

*3:私の見た酷いコードの例だと、あるクラスの責務が外部仕様の変更によって大きく変わったときに、それを継承によって表現してて (つまり親が I/F なのはいいとして、子が「旧世代のクラス」「新世代のクラス」で兄弟になってる)、新世代のクラスで I/F が変わっていくと、それに合わせて親を変えなきゃいけなくなって、親の I/F が変わるということはそのサブクラスも I/F を合わせなきゃいけないわけだから、もう使われるはずがない旧世代のクラスもせっせこ stub 書かなきゃいけないという最悪のがあった。

*4:これがけっこう面白いのだけど、紹介は機会があれば。

デザパタを一人でこっそり振り返ろう #2 (Adapter)

シリーズ第2回。今回はAdapterパターン*1

まぁ平たくいっちゃえば「オブジェクト間のインターフェースをコンバートしてやることで、いろんなオブジェクトを統一的に扱うことができるようにしようね」ってもの。まあ「アダプター」だもんね。
インターフェースを使う側を Client、アダプターインターフェースを提供するものを Target、Target の定義するインタフェースを橋渡しするのが Adapter、Adapter から呼ばれて機能を提供するものを Adaptee というようです。

Tailored Pattern

GoF /JDP で説明しているパターンがこれ。

二通りの実装があるよって書いてある。ぐぐれば出てきそうなもんなのでクラス図書いて説明したりはしないけど。

継承方式というのは、Target を抽象クラスにしておいて、Target と Adaptee を多重継承して、Adaptee で定義されているインタフェースを self で使って Adapter がインタフェースを実現するというやり方。

委譲方式というのは、Adapter は具象クラスで、その代わりに内部に Adaptee のインスタンスを持って、そのインスタンスにメッセージを送ることでインターフェースを実現する方法。Adaptee のインスタンスに「この処理お願いっ」ってするから「委譲」っていうんだね。


ということは、「Adapter が、Target に提供すべきインターフェースと、Adaptee の提供するインターフェースをがっちり知っていなければならない」ですよね。

ということで、DPSC ではこれを「Tailored Adapter」と呼んでます。「仕立てアダプター」っつか「オーダーメイドアダプター」とでも言うのか。


んでDPSCでは「Smalltalkには多重継承ないから、委譲でしかできないんだよね」ってことで GoF の例を実装してるけど、せっかくだから JDP の例をやってみませうか。

でも JDP の例ってあんまよくなくて、モノが一個しかないのになんでインターフェースを併せてあげなきゃいけないの? って思うよね。ココらへんはLineとTextという違うインタフェースを持つものを統一的に扱いたいから……っていうGoF/DPSCの例のほうが分かりやすいとは思うけど、動かして結果が見やすいからこういう例なのかな?

まあさておき、ぼくが Smalltalk で愚直に実装するとなるとこうかな。

Banner クラスはこんなんで、

Print クラスはすごくシンプル。

PrintBanner クラスはこんな感じかなぁ。

トランスクリプトワークスペース開いてワークスペースに以下のコードを書いて。

testBanner := Banner has: 'Hello'.
testBanner showWithAsters.
testBanner showWithParens.

Transcript cr.

test := PrintBanner with: testBanner.
test printStrong.
test printWeak.

うりゃっと全選択して do it すればトランスクリプトに、

*Hello*
(Hello)

*Hello*
(Hello)

されますねと。

Traits 使ってみよう

Traits というのは Ruby のモジュール mix-in に近いけど、名前がぶつかったときにエイリアスで逃したりできる、もっと緩い感じのモノ、という理解でいいのかな。
Matz さんが Ruby にも入れたいなーと言っているらしいところを見ると、mix-in とは違ったメリットがあるんでしょう。多分。
Squeak の派生である処理系 Pharo と、Squeak 3.9*2 から入りました。

これを使えば継承方式で実装できるのかなーなんて思った。

ということでさささっと書いてみたっす。

まずは Print を Trait として定義するわけだけど、Smalltalk 的には Trait には T という Prefix を付けるようなので、名前を変えまして。

んで、PrintBanner の定義はこんなふうになるのかな。名前は継承版と被らないように一応変えておいた。

使い方は委譲方式とちょい変わって、

test := PrintBanner2 has: 'Hello'.
test printStrong.
test printWeak.

で、ちゃんと実行できました。ぱちぱち。

そもそも Tailored Adapter っているの?

ぼくがこの説明を JDP で読んで、最初に思ったのは、

なにもこんな大げさなコトやらなくても、

Banner>>printStrong
	self showWithAsters.

Banner>>printWeak
	self showWithParens.

でよくね?

ということだった。

DPSC にもその点については書いてあって、「C++ だったらリコンパイルがいるからこういうやり方はちょっとないけど、Smalltalk だったらやってダメな法はないよねー」って書いてあった。やっぱそうか、そうなんか。

多分 ruby でもこんな感じに書けるんじゃないかな。

class Banner
    def printStrong
        self.showWithAsters
    end
end

Message-Based Pluggable Adapter

さて Tailored Adapter は「呼ぶ方のも呼ばれる方のもインターフェースが明らか」という仮定をおいていた。でも、この仮定が崩れたとしたら?

つまり、設計ができた「あとから」新しい Adaptee ができるとしたら?

例えば MVC パターンでウィジェットが表示用にモデルから値を取り出すときなんかを考えると、ビューのウィジェットが Client でモデルが Adaptee になって、クライアント側は value メッセージで表示用の値をとって value: メッセージでセットするとかなる。ところがモデルになるクラスって自前で作るとは限らなくて他所からの流用だったりライブラリだったりするわけで、そうなるとそれごとにAdapter書かなきゃいけない。そういう種類が違うモデルが出現するたびにこんなに大騒ぎするのめんどくさいですよねえ。

めんどくさいのもそうなんだけど、クラスってのはオブジェクトの「雛形」なんであって、Adaptee の数だけ雛形が存在するのって、ちょっとなんだかなぁ、って思いません? ふつー思いますよね。

そこで Smalltalk で活躍するのがこの Message-Based Pluggable Adapter なのだ。


要はね、Adaptee が新たに登場するたびに Adapter クラスを作るのって大変じゃないですか。
そこで、インスタンスに対して動的に動作を突っ込める Smalltalk の機能を活用して、「Adapter クラスのこのインスタンスはこの Adaptee 用」ってできれば嬉しいじゃないってこと。だとぼくは理解してる。

例えばさっき例に出した MVC の話で言うと、ビューがモデルから値を取る value ってメッセージと、逆にモデルに値を設定する value: ってメッセージが考えられるじゃないですか。で、こいつらの動きを、インスタンスごとに定義できたらクラスが爆発しないでいいよねと。

DPSC の写経をしてもしょうがないので大雑把な構造を書くと、

  • Adapter クラスはコンストラクタで Adaptee のインスタンスを貰っておく。これは以上による Tailored Adaptor と同じですね。
  • Smalltalk には "perform:" ってメッセージがあって、以下の二つは等価なんです。
anObject #hogehoge
= anObject perform: #hogehoge
  • ここで重要なのは、#hogehoge が変数に代入されたものであっても構わないということ。だから例えば getSelector という変数に束縛されているとしたら、以下のように書けるということ。
anObject perform: getSelector
  • ということは selector メッセージを使えば、一般的な Adapter というクラスのインスタンスオブジェクトに対して「value に対するメッセージは何?」という設定ができるってことになるわけです。

例を挙げましょう。細かいクラス定義は省略するとして、あ、anObject と printStrongSelector は MessageAdapter のインスタンス変数ね。

と定義しておけば、

adapter := MessageAdapter on: (Banner has: 'Hello').
adapter setPrintStrong: #showWithAsters.
adapter printStrong. " => *Hello*"

と実行できますよと。おお、すげー。

さらに文字列をシンボルに変えるメッセージ asSymbol を使えば、シンボル一個渡すだけで、getter / setter ど同時に定義できたりする。

この仕組みを使うことで、クラス数の爆発を抑えつつ、MVC なんかでの M と V の結合を柔軟に変動させることができる。うーん、これぞプログラミングの醍醐味という奴ではないかい?

この仕組みはめちゃんこクールだと思う。詳しくは DPSC p.114-115 を読むとよいですよ。

Parameterized Adapter

Message-Based Pluggable Adapter はとてもクールなんだけど、インタフェースがパラメータを要求するとき、それを渡すすべがないという問題? がある。

ということでそんな場合に使うのが Parameterized Adapter。
すごい平たくいってしまうと「getter / setter にブロックを渡してしまう」って Adaper。GoF や JDP における Adapter パターンとははるか遠くに来てしまいましたが、モダンなプログラミング言語なら似たようなことはできると思う。

あまりまとまってないまとめ。

  • GoF や JPD で紹介されている Tailored Adapter は Smalltalk (あるいは他のモダンな言語) でも実装できるけど、追加すべきメソッドが静的に決まるのであれば、既存クラスを直接いぢっちゃても (動的言語の場合) 問題ないので、正直誰得。
  • その点、インスタンスごとにあるオブジェクトのメッセージをどうディスパッチするかを指定できる Message-Based Pluggable Adapter はすごい強力。
    • ということで、Pluggable Adapter は VisualWorks でもりもり使われてる、そうな。


ここで紹介したことがすべてではないので、動的言語においてデザインパターンっってどうあるべきなのかってことに興味がある人は、ぜひ

Design Patterns Smalltalk Companion, The (Software Patterns Series)

Design Patterns Smalltalk Companion, The (Software Patterns Series)

を買って読んでみてくだされ。ぼくですら読めるので、英語であることはまったく障害にならないと信じます。


さて次はなんだっけ。Template Method だっけ。
DPSC 的にはどんな切り口なのか楽しみですね。

*1:なお Iterator と Adapter っていう順番なのは JDP が「この二つはとっても分かりやすいから」ということで選んだらしくて、Iterator については DPSC にも「コレクションのなかのオブジェクトに繰り返し処理を行うことはSmalltalkの再利用のもっとも分かりやすい例だよね」って書いてある。

*2:最初 4 系と書いてましたが、コメント欄で umejava さんから教えていただきました。多謝多謝。