日本ソフトウェア科学会第 32 回大会 (2015 年度) 講演論文集

コールスタックに基づいてクラス拡張の有効範囲を制御するための言語機構の提案
福室 嶺

千葉 滋

クラス拡張の有効範囲を制御するための言語機構を提案する．クラス拡張は既存のクラスに対して元のソースコードを変更することなくメソッドの追加や再定義を行う仕組みで，コードの再利用性を高めることができる一方，クラス拡張がプログラマの意図しない領域で有効になることで誤動作を誘発するなど，危険も大きい．クラス拡張を安全に利用するために，本機構ではプログラマのコードに対する理解度に応じて段階的に有効範囲を拡張できるようにした．これにより，プログラマの理解を超えた領域で不意にクラス拡張が有効になってしまうようなことを回避できる．本機構はクラス拡張を実装するあらゆるプログラミング言語に対して適用可能なアイディアであるが，今回は特に Ruby に対して適用し，まずプロトタイプとして Ruby のライブラリという形での実装を行った．

うな影響を与えるかを測ることは難しい．このような

1 はじめに問題への対策として，クラス拡張の有効範囲を何らか直接ソースコードを書き換えることなく既存のクラの方法で制限する仕組みがこれまで数多く提案されスを拡張することが出来るクラス拡張 (class extensions)てきた．例えば，selector namespaces [10] や，Ruby

は，コードの再利用性を高める手段の 1 つとして幅の refinements，classboxes [2]，method shelters [1]，広く利用されている．クラス拡張に類する言語機構は

method shells [9] 等がある．しかしこれらの手法は，Smalltalk [3] や，Aspect-Oriented Programming [6]，安全性は高いものの制御できる有効範囲が狭すぎて

Context-Oriented Programming [5]，C# [8]，Ruby [7]クラス拡張の有用性を損なってしまっていたり，ある等，様々なプログラミング言語やパラダイムに取りいは逆に有効範囲が広すぎて安全性に不安があると入れられている．Ruby ではオープンクラスという名いったように，未だ十分なものになっていないと我々前で実装されており，特に人気の高い Web フレームは考えている．

ワークである Ruby on Rails [4] では Ruby のビルト

そこで我々は，安全性を損なわない範囲でクラス拡インクラスさえもオープンクラスを用いて積極的に張の有効範囲を制御可能な言語機構 method seals を拡張している．しかしその一方でクラス拡張は意図し提案する．method seals はプログラマが理解度に応ない動作を引き起こしやすい．これは拡張されたクラじて段階的にクラス拡張の有効範囲を広げていくこ

スの振る舞いが，拡張前の振る舞いを前提にしていたとができる．プログラマの理解を超えた範囲で勝手にコードと互換性があるとは限らないからである．完全クラス拡張が有効になることを防げるため，クラス拡な後方互換性を保ってクラス拡張を実装することは常張によってプログラムが不意に破壊されるというように可能とは限らなく，また，利用範囲の広いクラスへなことは起こりにくくなる．の拡張の場合はクラス拡張がプログラム全体にどのよ以降，2 章ではクラス拡張とその問題について述べ，何故クラス拡張の有効範囲を制御する必要がある

Ryo Fukumuro, Shigeru Chiba, 東京大学大学院情報理工学系研究科創造情報学専攻, Dept. of Creative Informatics, Graduate School of Information Science andTechnology, The University of Tokyo.

かを論じ，3 章では我々が提案する method seals の概要を例とともに述べる．4 章では我々が Ruby 上に実装した method seals のプロトタイプについて紹介

12
3
4
5
6
789puts 1/2# => 0class Fixnum
def /( other )quo(other)end
endputs 1/2

図1# => 1/2

オープンクラスによるビルトインクラスの書換え

し，5 章ではその実装に対して行った性能計測につい

1
2
3
4
56
7
8
9101112
13
1415
1617

module ExtFixnum
refine Fixnum dodef /( other )quo( other )
end
end
end
class MyMathusing ExtFixnumdef half( other )other /2
endendputs 1/2
puts MyMath .new.half (1)# => 0# => 1/2て報告する．6 章では関連研究について述べ，7 章では本論文のまとめと今後の課題について述べる．

2 クラス拡張の有効範囲を制御する仕組みの必要性

図2

refinements によるビルトインクラスの書換えうに制御することさえ出来れば問題無い．

Ruby の refinements のように有効範囲をレキシカ

クラス拡張はコードの再利用性を高めるが，既存のルスコープに基づいて決定する手法は安全性は高い

コードを不意に破壊してしまう危険性がある．クラが，拡張対象となるメソッドが間接的に呼び出されス拡張とは既存のクラスを外部から拡張する仕組み

る場合ではクラス拡張を有効にすることは出来ない．

であり，メソッドの追加や再定義を直接コードを書き

図 2 は図 1 を refinements を用いて書きなおしたも換えることなく行える (実装によっては再定義を禁止のである．refinements ではクラス拡張は定義したするものもある)．このような言語機構は C# や Rubyだけでは有効にならず，using メソッドを用いて明をはじめとする様々な言語で提供されている．例えば示的に有効化しなくてはならない．そしてその有効

Ruby ではオープンクラスという名前で提供されてい範囲は using の呼び出しがレキシカルスコープ下でる．オープンクラスはメソッドの追加や再定義を行う参照可能な範囲に限定される．つまり，この例では，ための専用の構文は持たず， 通常のクラス定義と同MyMath クラス内に限定されており，half メソッド様の構文を用い，既に同名のクラスがあった場合はその定義が実際に影響を受けている．16 行目の除算なのクラスに対しての拡張となる．オープンクラスは

ど MyMath の外には一切の影響が無い．一方，図 3 でグローバルに有効になり，一度有効にすると無効にす

は HTMLReader の内部で呼び出しているファイルをることは出来ない．図 1 はオープンクラスによって読み込むための Reader クラスを書換え，テストのた

Ruby のビルトインクラスである Fixnum クラスのメめに一時的に定数を返すようなスタブを作成しよう

ソッドを再定義し，整数型の除算において有理数を返としている．しかし，Reader クラスを直接呼び出しすように変更を加える例である．このような影響範囲た場合は動作するが，HTMLReader 経由で呼び出すとの大きな変更は，自身の書いたプログラム内だけでなReaderStub は有効にならない．これは先に述べたよく，サードパーティライブラリなど外部のコードに影

うに，ReaderStub の有効範囲は using 呼び出しがレ響を及ぼし，正しく動作しなくなる恐れがある．

キシカルスコープ下で参照可能な範囲に限られること不意の挙動を防ぎ，クラス拡張を安全に利用するたによる．すなわち，3 行目の Reader の呼び出しからめにはクラス拡張の有効範囲を適切に制御する仕組

みて 16 行目の using 呼び出しが参照不可能な位置にみを欠かすことは出来ない．図 1 のような危険な例あることに原因がある．間接的に呼び出される場合にでも，そのクラス拡張が適用されることを前提にして一方，Context-Oriented Programming(文脈指向

書かれたコード上でのみクラス拡張が有効になるよプログラミング，COP) [5] のようにクラス拡張の有効1234567891011121314151617181920

class HTMLReader
def read_html (path)r = Reader .new.readparse_html r.read pathend
endmodule ReaderStub
refine Reader do
def read(path)
return "<div >foo </div >"
end
end
end
using ReaderStub
Reader .new.read "dummy "# => "<div >foo </div >"HTMLReader .new. read_html " dummy "# => No such file or directory - dummy

図31234567891011121314151617181920
21

class CSSReader
def read_css ( filepath )parse_css Reader .new.read filepath
endendclass HTMLReader
def read_html ( filepath )parsed = parse_html Reader .new.read( filepath )css_reader = CSSReader .newstyles = parsed . get_css_paths .map do |p|css_reader . read_css p
endparsed . set_style styles
endendwith ReaderStub do# Activate the extension in this block
HTMLReader .new. read_html " dummy "end

refinements によるスタブの作成 (期待通りに動作しない)

範囲を動的に決定する手法では拡張対象のメソッドが

図4

CSS も解釈できるように拡張された HTML パーサ

無効化

間接的に呼ばれた場合でも有効にすることができる

プログラマがコードを読んで理解した上でクラス拡が，意図しないところでも有効化されてしまう危険が張を適用したい部分 (パッケージ)，すなわち “意図しある．“意図しないところ” とは，プログラマがコーたところ” を明示できるようにしたクラス拡張 method

ドを読んでおらず，挙動をよく理解していない部分とseals を提案する．Method seals では，“意図したとそこから間接的に呼ばれるコードのことである．図 3ころ” であれば間接的に呼び出されたメソッドであっの例を拡張し，HTMLReader が内部で CSS の解釈もてもそのクラス拡張は有効であり，逆に “意図したと行うようにし，それを動的スコープで有効化するようころ” と明示しない限りそのパッケージとその先ではにしたものを図 4 に示す．with を用いてクラス拡張クラス拡張は無効になる．このようにパッケージ p とを有効化し，そのブロック内では間接的に呼び出されその先で呼び出されるパッケージでクラス拡張 e をるものも含めて全ての関数呼び出しにおいてクラス無効にすることを “クラス拡張 e についてパッケージ拡張が有効になり，HTMLReader 内部で呼び出されるp を seal する” と呼ぶ．また，クラス拡張 e について

Reader#read は ReaderStub によって書換えられたパッケージ p に付与された seal 属性を剥がすことをものになる．そして同様に CSSReader 内部で呼ばれ“クラス拡張 e についてパッケージ p を unseal する”ている Reader#read に対してもクラス拡張は有効にと呼ぶ．デフォルトでは全てのパッケージは seal さなる．仮に，プログラマが HTMLReader の挙動は理解れており，プログラマはクラス拡張の有効範囲を広げしているが CSSReader の動作については理解しているために自身の理解度と必要性に応じて各パッケージないものとすると，CSSReader 内部の Reader#readを unseal していくことになる．

において拡張が有効になることはプログラマの意図か

Method seals で実際にクラス拡張の有効化及びら外れることになり，致命的なバグを生み出しかねなunseal を行うためには unseal 付き using 宣言を用いい．実際，図 3 で示した ReaderStub は HTMLReader

る．拡張の有効無効を切り替えるアルゴリズムは次ので使われることを前提に実装されており，CSSReader

ようになる．

が解釈できない文字列を返す．

3 意図しない間接呼び出しでのクラス拡張の(a) using 宣言でクラス拡張 e を有効化．宣言を含むパッケージ p で有効になる．

1
2
3using ReaderStub , [ HTMLReader ]Reader .new.read " dummy " # => "<div >foo </div >"HTMLReader .new. read_html " dummy "

図5

12
3
4
5

seal_define Reader , : ReaderStub do
def_method (: read) do"<div >foo </div >"endendMethod seals によるクラス拡張の有効化
図6

プロトタイプ実装におけるクラス拡張の定義(b) パッケージ p から別のパッケージ q のメソッドを呼び出すと，(b1) q が using 宣言の unseal パッケージリス

123

seal_using (: ReaderStub , [ HTMLReader ]) do
HTMLReader .new. read_html " dummy "end

トに含まれていれば q 内部でもクラス拡張 eが有効になる．以降 (b) を繰り返す．

図7

プロトタイプ実装におけるクラス拡張の有効化(b2) q が unseal パッケージリストに含まれていない場合は q 内部とその先に呼び出される

ラス拡張の定義を図 6 に，クラス拡張の有効化を図 7

パッケージではクラス拡張 e が無効になる．に示す．クラス拡張を定義するためには seal define

unseal 付き using 宣言は具体的には例えば図 5 のメソッドを用いる．seal define の第 1 引数には拡張ようになる．これはクラス拡張 ReaderStub を有効対象のクラス，第 2 引数にはクラス拡張の名前をシンにし，さらにそれについて HTMLReader を unseal しボルで渡す．定義したクラス拡張を有効化するためにている．using によってまずは自身を含むパッケーは seal using メソッドを用いる．seal using の第ジでクラス拡張が有効になる (a)．したがって，2 行

1 引数にはクラス拡張の名前，第 2 引数には unseal

目の Reader#read 呼び出しはクラス拡張が適用され対象のクラスをリストで渡す．更にクラス拡張を利用たものになる．次に HTMLReader#read html を呼びするコードをブロックとして渡す．

出すことを考える (b)．今回は HTMLReader は unseal

現在の実装は Ruby 2.0 以降を対象としたライブラ

パッケージリストに含まれているため，HTMLReader

リという形で作成されており，全て Ruby で書かれてでもクラス拡張 ReaderStub が有効になる (b1)．しいる．デバッガやプロファイラのための API である

たがって，図 4 の 9 行目の Reader#read 呼び出しはTracePoint を用いて各メソッド呼び出し時に処理を挟クラス拡張が適用されたものとなる．一方，12 行目

むことで現在のコールパスを取得できるようにしていの CSSReader#read css 呼び出しを考えると，またる．また，3 章で示したアルゴリズムに従ってメソッ別パッケージのメソッド呼び出しになるため，(b) のドディスパッチを行うように拡張対象のメソッドを書処理が繰り返される．今回は unseal パッケージリスき換えている．本来 method seals は refinements のトに CSSReader は含まれていないため，クラス拡張自然な拡張として実現できる (何も unseal しなけれは無効になり，CSSReader 内部の Reader#read 呼びば refinements と同等) ため，実用的にはそのように出しは拡張前のものが呼び出されることになる (b2)．

実装することが望ましいと考えており，今後パフォー

仮に CSSReader が更に別のパッケージのメソッドをマンスの改善とともに Ruby 処理系の拡張によって実呼び出していたとしても，これ以降 (b) の処理は行わ現することを予定している．また，現在は unseal のれない．

対象 (パッケージ) はクラスのみだが，その対象をメソッドやモジュールへ広げることを検討している．

4 Ruby 上のプロトタイプ実装
我々は 3 章で示した method seals の基本的なアイ

5 予備的な性能評価

ディアを Ruby 上にプロトタイプとして実装した．た4 章で述べたとおり，現在のプロトタイプは Rubyだし，現時点では実装上の都合から 3 章で示したも上にライブラリという形で実現されており，デバッガのとは異なるシンタックスを持つ．本実装におけるク向け API を用いて各メソッドコールにイベントハン表1fib(31)

6 関連研究
実装種別

Ruby

時間 (s)

クラス拡張に類する言語機構や関連研究は数多く存

0.363

在する．2 章で述べたように，Ruby [7] のオープンク

Method seals あり (sealed)

4.069

ラスは既存クラスに対して外部からメソッドの追加やMethod seals あり (unsealed)

9.422

再定義が可能だが，破壊的に行われ一度適用すると元に戻すことは出来ない．また，Ruby の refinements

ドラを設定するなど実行性能を考慮した実装にはなっはクラス拡張の有効範囲制御を行うことができるが，ていない．実行性能の改善は目下の課題であり，本章

間接呼び出しで有効にすることは出来ない．Selector

ではその予備実験として行った現実装の性能評価につnamespaces は Modular Smalltalk [10] で導入された

いて報告する．

概念で，クラス拡張をモジュール化し，それを特定の本実験ではフィボナッチ数の計算を行うプログラムスコープにインポートすることでクラス拡張の有効範を method seals を用いて実装し，その実行時間を測

囲をそのスコープ下に限定することができる．Ruby

定した．実験プログラムでは，まず Ruby で methodの refinements によく似ており，間接呼び出しで有効seals を使わずに Fib クラスおよび実際の計算を行うにすることは出来ない．Context-Oriented Program-fib メソッドを作成し，その後で method seals で外ming [5] は実行時に文脈に応じてクラスの振る舞いを部から fib を同じ実装内容で再定義した．これを用い変えることができるプログラミングパラダイムであて，元の Fib クラスを用いて計算した場合と methodる．文脈は動的スコープで有効無効が切り替えられる

seals で再定義を行った場合のそれぞれで計測を行っため，間接呼び出しでも有効だが，全ての文脈についた．更に，method seals を用いた場合は，unseal して各クラスの振る舞いの変更をプログラマが把握するて再定義された fib が呼ばれるケースと unseal せずことは難しく，理解の浅さから意図しない動作を引き元の fib が呼ばれるケースの両方を計測した．これを起こしかねない．Classboxes [2] は classbox というモ行ったのは，コールパス上に seal されているクラス

ジュール内にクラス宣言とクラス拡張を定義することが現れた時点でそれ以降は常に拡張が無効になることができる．ここで定義したクラスやクラス拡張はそのが確定し，その分の処理内容が減ることから unseal

classbox 内だけで有効だが，他の classbox からもイしないケースでは実行時間が短くなることが予想さンポートすることで自身の classbox 内で利用できるれるためである．実験環境はプロセッサが Intel Coreようになる．間接呼び出しでも有効 (この機能を local

i7-4578U 3.00GHz，メモリが 16GB で，Ruby のバーrebindings と呼ぶ) だが，一度インポートしたクラス

ジョンは 2.1.5p273 を用いた．

拡張はその classbox 内で常に有効になるため，プロ

表 1 に fib(31) を計算した結果を示す．提案手法をグラマがその classbox 内の実装詳細をインポートし用いると，Ruby で実装したものに比べて 26 倍，最たものも含めて全て理解していなければ，意図しな終的に拡張前のメソッドが呼ばれる場合でも 11 倍のいクラス拡張の適用が起こりうる．Method shelters

実行時間と，非常に大きなオーバーヘッドがかかるこ[1] は本研究の前身研究の 1 つで，method shelter ととがわかる．これは全てのメソッド呼び出しごとにいうモジュールを導入している．Method shelters は現在実行されている環境が seal されているかどうか

Classboxes によく似ており，method shelter 内にクの判定を行っていることや，拡張対象のメソッドがメラスやクラス拡張を定義したり，他の method shelterソッドディスパッチを行うものへと書き換えられていのものをインポートすることができる．Classboxesることが大きな要因だと思われる．と大きく違う点は，method shelter は内部が exposedchamber と hidden chamber という 2 つの部分に分かれており，どちらにクラスやクラス拡張を定義する

かによって，外部からのインポートを許可するかど有効にするとしたほうが現実的である．提案手法ではうかを制御できるところにある．しかし，どちらのプログラマのコードに対する理解をそのまま明示す

chamber に定義するかはインポート先の実装者に委ることを強制することでより安全にクラス拡張を利ねられているため，やはりインポートする側がイン用できるようにすることを目指した．

ポート先の実装について理解していないと依然とし今後の課題としては，提案手法をより実用的なプロて意図しない挙動を引き起こしうる．Method shells

グラムに対して適用することで基本的なアイディアの[9] は本研究のもう 1 つの前身研究である．Method

有用性を検証する，実用化に向けてパフォーマンスのshells は method shelters が複雑化しすぎたことを踏改善を図る等が考えられる．

まえ，chamber を廃止し，別途 2 種類のインポート方法を用意することでよりシステムを簡素にした．また，インポート元でどのようにインポートするかを制御できるようになったため，安全性が高まった．しかし，単純なシンタックスを獲得した一方で依然として複雑なセマンティクスを持っており，暗黙のうちに複雑なメソッドディスパッチが行われるためやはりプログラマが全体像を把握することは難しく，意図しない挙動を引き起こしうる．本研究はこれを踏まえ，暗黙的な解決を諦めることでより単純で安全なセマンティクスを得ることを目指したものである．

7 まとめ
本研究では，クラス拡張を安全に利用するために，プログラマのコードに対する理解度に応じて段階的に有効範囲を拡張できる言語機構 method seals を提案した．一般に，クラス拡張は既存のクラスを拡張するという性質上，拡張対象のクラスがプログラム上のどこで使われているかを完全に把握した上で利用しなければ不意にプログラムを破壊しかねない．サード
パーティライブラリの利用や多人数による開発を考慮すると，プログラム全体を把握することは難しく，クラス拡張をプログラマが把握している領域だけで

参考文献[ 1 ] Akai, S. and Chiba, S.: Method shelters: avoiding
conflicts among class extensions caused by local rebinding, Proceedings of the 11th annual international conference on Aspect-oriented Software Development, ACM,
2012, pp. 131–142.
[ 2 ] Bergel, A., Ducasse, S., Nierstrasz, O., and Wuyts,
R.: Classboxes: Controlling visibility of class extensions, Computer Languages, Systems &amp; Structures,Vol. 31, No. 3(2005), pp. 107–126.
[ 3 ] Goldberg, A. and Robson, D.: Smalltalk-80: the language and its implementation, Addison-Wesley Longman Publishing Co., Inc., 1983.
[ 4 ] Hansson, D. H.: Ruby on Rails, http://rubyonrails.org/.
[ 5 ] Hirschfeld, R., Costanza, P., and Nierstrasz, O.:
Context-oriented programming, Journal of Object Technology, Vol. 7, No. 3(2008), pp. 125–151.
[ 6 ] Kiczales, G., Lamping, J., Mendhekar, A., Maeda,C., Lopes, C., Loingtier, J.-M., and Irwin, J.: Aspectoriented programming, Springer, 1997.
[ 7 ] Matsumoto, Y.: Ruby Programming Language,
http://www.ruby-lang.org/.
[ 8 ] Microsoft Corporation: C# Language Specification,
https://msdn.microsoft.com/library/ms228593.
[ 9 ] Takeshita, W. and Chiba, S.: Method Shells: avoiding conflicts on destructive class extensions by implicit
context switches, Software Composition, Springer, 2013,
pp. 49–64.
[10] Wirfs-Brock, A. and Wilkerson, B.: A overview ofmodular smalltalk, ACM SIGPLAN Notices, Vol. 23,
No. 11, ACM, 1988, pp. 123–134.