Island Life

< 型付けと変更の時定数 | 型論争 >

2013/02/28

システムの非平衡状態

昨日のエントリで、システム中のあるユニットを変更した時に、それに依存する部分を全部一気に変更するのでなく、システムを動かしながら徐々に変更を伝搬してゆく、という運用について触れたんだけど、具体例を書かなかった。というのは現場で出てくる具体例って色々なしがらみに依存して話が難しくなってることが多くて、そこらへん説明するのも冗長だってのと、個別のケースについて何とかしようと思えばどんな道具を使っても「頑張れば何とかなる」ってことが言えちゃうんで。実際の言語の使用感ってのはその「頑張る」をどのくらいに感じるかってところに帰着するんだけど。

それと、ある言語でプログラムする時はその言語で考えるから、その言語で書きづらい事態というのをそもそも想定しないというか、無意識にそういうスポットを避けられるように設計する。なので「こういうのが書きづらいじゃないか」と言われると「確かに素直には書けないけどそもそもそういうのを書きたいと思わないし。どうしても必要なら無理すれば書けるからいいんでない?」って感想になっちゃうことが多い。

そんなわけで、「変更」とか「不平衡」とか聞いても人によってイメージするものが違うんで、話がややこしくなるってのはある。

まあそれでも敢えてひとつ、単純化した例を考えてみよう。

プロジェクトメンバーを管理する。メンバーはidと名前と所属セクションを持つ。

data Section = Research | Production | HR | Management
               deriving (Show)

-- Member id name section
data Member = Member Int String Section
              deriving (Show)

あらゆるシステムがこのデータ型を前提に組まれている。

ところがある時、「複数のセクションに所属するメンバー」というのがありえることがわかった。 つまりMemberの定義は本来こうあるべきだったのだ。

data Member = Member Int String [Section]

これが正しいモデルなので、最終的には全てのコードをこちらに収束させたい。 でもMemberを使ってるシステム全てを一斉にアップデートすることはできない。 一部の、どうしても複数セクションを扱いたいユニットだけには複数セクションを見せて、 それ以外のユニットについては、複数セクションに所属するメンバーは 「そのうちのひとつのセクションだけが見えてればいい」とする。 (一応優先順位があって、最優先のものが見えるようにする。)

さてどうする。

Haskellだとどうするんだろ。MemberEx みたいな別定義にしといて、 既存のコードに対しては MemberEx -> Member とか Member -> MemberEx なアダプタを噛まして対処するのかな。 慣習がよくわからない。

CLOS系なら変える必要があるのは一ヶ所だ。こういう元コードを:

(define-class <member> ()
  (id :init-keyword :id)
  (name :init-keyword :name)
  (section :init-keyword :section)
  )

下のように変えておけば、メンバーが一つのセクションだけに所属すること前提のコードと 複数セクションを見るコードが混在した不平衡状態のまま運用を続けることができる。 change-class メソッドも書いとけば、 動作中のコードにロードして動かしつづけることもできる。

(define-class <member> ()
  (id :init-keyword :id)
  (name :init-keyword :name)
  (sections :init-keyword :sections :init-value '())
  ;; for the backward compatibility - remove after migration is completed
  (section :allocation :virtual
           :slot-ref (^o (car (~ o 'sections)))
           :slot-set! (^[o v] (set! (~ o 'sections)
                                    (cons v (delete v (~ o 'sections))))))
  )

まあ静的型でも「フィールド直接アクセス禁止して常にアクセサ経由にしとけばいいじゃん」とは言える。その意味では程度問題。当初からどのくらいこういった事態を見越して書いとくかって話になる。

それに、「別定義にしてアダプタを噛ませとけば、後から直してゆく時に直すべき箇所が見つけやすいだろう。やっぱり静的型がいい」って考えもあるだろう。

あるいは「sectionとsectionsの関係がill-definedで、こんないいかげんなシステムは既に崩壊してる。このシステムの存在自体がバグである」という立場もありだろう。(自分は「え〜これとそれはone to manyって最初に念押ししといたじゃないですか〜なんでいつの間にかmany to many前提になってるんです〜?」みたいな話がありすぎる環境にいたから、バイアスがかかってると思う。)

結局何がメリットかってのはどの立場を取るかで変わってくるから、「××がメリットなんだ!」という記事は「俺はこういうケースで楽をしたいんだ!」という主張と読むべきなのかもしれない。

ちなみに上のように変えた後で、「いや、やっぱsectionはひとつで良かったわ。戻しといて。」ってなることも、よくある。

Tag: Programming

Past comment(s)

nobsun (2013/03/01 06:18:54):

Expression Problem というのはいつも悩ましいですねぇ.いわゆる静的,動的にかかわらず仕組みとしては単純ではないというのが印象です.

jmuk (2013/03/01 06:53:52):

こういう問題は常にありますね。 で、規模の大きなコードベースではアクセサを定義されているだろうし、そうでないような規模なら全部まるごと直してもそうひどい手間でもない、といったところが直感的な印象ですが、変更箇所は少なくはないでしょう。 でも、 section で常に最初の要素だけを見るような移行コードによって引き起こる問題、みたいなものもありうるかもしれなくて、みたいな、でもどこで誰が古いコードを使っててどこで誰がなんて全部ソースコード検索して調べんのかよ、みたいな反論も思いつくので、結果的にはケースバイケースなのかなあという印象です。

shiro (2013/03/01 09:08:24):

そう、結局ケースバイケースにならざるを得なくて、例えば「sectionのその見方で問題は出ないのか?」なんて完全には見通せないから(そもそも問題の全貌自体、先に行ってみないとわからないから)、どっかで妥協するわけです。その妥協点の選び方で(静的/動的の)好みの違いが出るかもしれない。

Post a comment

Name: