Rhinoは、Javaプログラムの中で、JavaScriptやRubyといったスクリプト言語のテキストを動的に読み込んで実行するための「スクリプトエンジン」と呼ばれるオープンソースソフトウェアだ。かつてはMozilla Foundationによって配布されていたが、現在ではJavaの実行環境にネイティブに組み込まれている。スクリプト言語から環境内のJavaクラスさえ呼び出せてしまうという優れものだ。
これを用いることで、ビジネスロジックを「仕様」として扱いやすくなる。テーブル仕様書上でスクリプトを書き込める領域を用意しておき、これを仕様翻訳エンジンがタイミングに応じて呼び出すようにすれば良い。図7はテーブル仕様書の一部としてスクリプトを編集しているパネルの例である。
実際、スクリプト言語でビジネスロジックを書くと、ある種の仕様については、日本語よりもプログラミング言語を用いて表現したほうが手っ取り早いことがよく分かる。比較的複雑なビジネスロジックの例を見てほしい(図8)。
これは図7の編集画面上で例示されているスクリプトの全文で、ここに示されたテーブル操作が、仕入振替レコードが追加されたときに自動的に実行される。このロジックを第三者に正確にプログラミングしてもらうための日本語の説明を、想像してみてほしい。日本語で書くよりも、いきなりプログラミング言語で書く方が端的かつ厳密だ。
//////////////// // 変数の定義 // //////////////// var statement_CT010 = session.connection.createStatement(); var statement_CT100 = session.connection.createStatement(); var rtRate = 0.000; var amSiire = 0; var amSiireNaika = 0; var amTax = 0; var amKeijyouUpdated = 0; var sql = ''; var nrSiire = ''; var errorMessage = ''; var dtNend = 0; var dtMSeq = 0; var result_CT010; ///////////////////////////////////////// // 関連する仕入先マスター(CT010)の検索 // ///////////////////////////////////////// sql = 'select AMKEIJYOU, UPDCOUNTER from CT010' + ' where NRTORIHIKISAKI = \'' + HT010_NRTORIHIKISAKI.value + '\''; instance.setProcessLog(sql); result_CT010 = statement_CT010.executeQuery(sql); if (result_CT010.next()) { ////////////////////////////////// // 年度・月度・為替レートの設定 // ////////////////////////////////// dtNend = session.getFYearOfDate(HT010_DTTORIHIKI.value); dtMSeq = session.getMSeqOfDate(HT010_DTTORIHIKI.value); rtRate = session.getAnnualExchangeRate(AT030_KBCURRENCY.value, dtNend, 'TTS'); ////////////////////// // 単価&金額の設定 // ////////////////////// if (HT010_KBSIIREHURIKAE.value == '10') { //10:雑益振替// amSiireNaika = rtRate * HT010_AMTORIHIKI.value * -1; amSiire = HT010_AMTORIHIKI.value * -1; amTax = HT010_AMTAX.value * -1; } if (HT010_KBSIIREHURIKAE.value == '11') { //11:雑損仕入 // amSiireNaika = rtRate * HT010_AMTORIHIKI.value; amSiire = HT010_AMTORIHIKI.value; amTax = HT010_AMTAX.value; } if (HT010_KBSIIREHURIKAE.value == '20') { //20:相殺(売掛金振替)// amSiireNaika = rtRate * HT010_AMTORIHIKI.value * -1; amSiire = HT010_AMTORIHIKI.value * -1; amTax = HT010_AMTAX.value * -1; } if (HT010_KBSIIREHURIKAE.value == '30') { //30:売上原価振替// amSiireNaika = rtRate * HT010_AMTORIHIKI.value; amSiire = HT010_AMTORIHIKI.value; amTax = HT010_AMTAX.value; } /////////////////////////// // 仕入実績(CT100)の追加 // /////////////////////////// nrSiire = session.getNextNumber('NRSIIRE'); sql = 'insert into CT100 (' + 'NRSIIRE, KBAKADEN, KBSIIRE, ' + 'DTSIIRE, NRTORIHIKISAKI, NRSYOHIN, TXSYOHIN, ' + 'SQLOT, PRSIIRE, KBCURRENCY, PRSIIRENAIKA, ' + 'QTSIIRE, KBSIIREUM, AMSIIRE, AMTAX, AMKANZEI, AMSYOGAKARI, ' + 'NRTORIHIKIKANRI, DTCREATE, DTNEND, DTMSEQ, FGVALID) values(' + '\'' + nrSiire + '\', ' + '\'1\', ' + '\'' + HT010_KBSIIREHURIKAE.value + '\', ' + '\'' + HT010_DTTORIHIKI.value + '\', ' + '\'' + HT010_NRTORIHIKISAKI.value + '\', ' + '\'\', ' + '\'' + HT010_TXTEKIYO.value + '\', ' + '0, ' + HT010_AMTORIHIKI.value + ', ' + '\'' + AT030_KBCURRENCY.value + '\', ' + amSiireNaika + ', ' + '1, ' + '\'CNT\', ' + amSiire + ', ' + amTax + ', 0, 0, ' + '\'' + HT010_NRTORIHIKIKANRI.value + '\', ' + 'CURRENT_TIMESTAMP, ' + dtNend + ', ' + dtMSeq + ', \'T\')'; instance.setProcessLog(sql); count = statement_CT100.executeUpdate(sql); if (count != 1) { errorMessage = '仕入実績(CT100)の登録に失敗しました。処理はキャンセルされます。'; } ///////////////////////////////////////// // 仕入先マスター(CT010)の買掛残高更新 // ///////////////////////////////////////// if (errorMessage == '') { amKeijyouUpdated = Number(result_CT010.getInt('AMKEIJYOU')) + Number(amSiire) + Number(amTax); sql = 'update CT010 set' + ' AMKEIJYOU = ' + amKeijyouUpdated + ', UPDCOUNTER = ' + (result_CT010.getInt('UPDCOUNTER') + 1) + ' where NRTORIHIKISAKI = \'' + HT010_NRTORIHIKISAKI.value + '\'' + ' and UPDCOUNTER = ' + result_CT010.getInt('UPDCOUNTER'); instance.setProcessLog(sql); count = statement_CT010.executeUpdate(sql); if (count != 1) { errorMessage = '仕入先マスタ(CT010)の更新に失敗しました。処理はキャンセルされます。'; } } } else { errorMessage = '仕入先マスタが存在しません。処理はキャンセルされます。'; } result_CT010.close(); //////////////////////////// // ステートメントを閉じる // //////////////////////////// statement_CT010.close(); statement_CT100.close(); //////////////// // エラー処理 // //////////////// if (errorMessage != '') { instance.cancelWithMessage(errorMessage); } |
前回、コードで仕様を表すことの難しさを説明したが、ある種の仕様についてはむしろコードで表したほうが良いということだ。すなわち、様式化しやすいアプリケーションロジックの実装については「実行可能な仕様書」のような技術で合理化し、様式化しにくいビジネスロジックについてはコードで丹念かつ端正に記述すれば良い。
結局、全ての仕様をコードで表現しようとすることにも、全てをノンコーディングで済まそうとすることにも無理がある。従って、アプリケーションドライバを用いて業務システムを開発する担当者は、基本的に「設計者」であると同時に「プログラマ」でなければならない。
以上に関して特に重要な点は、「これまで『機能の仕様』と決め付けがちだった要素の多くが、(全てではないが)テーブル側に無理なく移行できてしまう」という事実だ。ちょうど無駄なぜい肉を落とすようなもので、結果的に機能が身軽になって、規定パターンにさらに収まりやすくなる。これまでの機能はビジネスロジックを余分に抱えることで、奇怪なほどに「個性的な太り方」をしていたということだ。
機能からビジネスロジックを切り離すことには、もう1つの効果がある。システムの可読性(読み取りやすさ)が高まるという点だ。
例えば前述した「仕入振替レコード追加時の関連テーブル操作」の仕様は、従来の枠組みでは「仕入振替登録機能の仕様」とみなされるだろう。これをあえて「仕入振替テーブルの拡張定義」とみなすことで、システムの仕様が分かりやすくなる。仕入振替テーブルの仕様を調べるだけで、このテーブルに関する諸問題を一挙に理解できるようになるからだ。
ただ、ビジネスロジックを機能からテーブルへ移行させるという配慮は、見方を変えれば、機能側の仕様が単純になる一方で、テーブル側の仕様がより複雑化しただけの話ではある。にもかかわらず、全体の可読性が高まるのはなぜか? これは従来、無駄に多くの仕様要素が重複したり矛盾したりしながら機能側に配置されていたためだ。
そのようなシステムを理解するのは、生徒1人1人に担任の先生がどんな人かを問うようなものだ。先生に関する情報は先生自身が一番よく知っているので、本人に会って直接確認すればいい。個々のテーブルに依拠する仕様要素があれば、拡張定義としてテーブルの上に置けば良い。たったこれだけの「正規化(各要素をあるべき場所に配置しなおすこと)」で、業務システム全体の可読性は劇的に向上する。
これで業務システムに含まれる各機能が、それぞれ個性的に見えていた原因が少なくとも2つあることが分かったはずだ。1つは正規化されていないDBを処理していたゆえ。もう1つは、機能がビジネスロジックを不必要に抱え込んでいたゆえだ。これらに対処すれば、少なくとも「無駄に個性的な機能」を扱う必要はなくなる。
今回の話をまとめよう。データベースを正規化してテーブル関連を単純な位相の組み合わせにする。同時に、ビジネスロジックを限りなくデータベース側に移行させる。その結果、個々の機能は極めて「うすっぺらでありきたり」なものになる。結果的に、個々の業務システムは「実行可能な仕様書」の集まりとして扱えるようになる。
では、「実行可能な仕様書を書く作業」は、「詳細設計作業」なのだろうか、「プログラミング」なのだろうか。どちらでもいいような問題ではあるのだが、私はそれは「プログラミング」なのだと思う。なぜなら、業務システム開発におけるプログラミングの意味は、実装技術の発展に伴って変わってきたからだ。
かつての「プログラミング」は、コーディングシートと呼ばれる紙の上にコードを手書きする作業のことを指していた。それをコンピュータに入力する作業は「パンチング」と呼ばれていた。入出力系の技術革新が起こって、プログラマ自らがコード入力できるようになった。結果的に「パンチング」はプログラミングに吸収された。アプリケーションドライバのような技術革新が起これば、今度は「詳細設計作業」がプログラミングに吸収される番だ。そう考えれば、前回取り上げた「エクセル方眼紙」も、必要な技術革新が起こるまでの過渡的な必要悪だったのだと合点がいく。
いずれにせよ、アプリケーションドライバによって実装された業務システムは、開発者から見れば「ひとまとまりの仕様情報」にしか見えない。開発時には「仕様書」を新たに書くだけでいいし、保守時には「仕様書」を保守すれば「仕様変更」が完了する。そして、仕様を書いてそのまま実行することがテストであり、それは「仕様の正しさ」をチェックするための作業だ。要するに「正しい仕様書の集まり」が、名実ともに「正しいシステム」となるのである。
実システムに対する認識がこのように変化することによって、開発体制も変化せざるを得なくなる。ごく少数の「設計できるプログラマ」だけで全工程をまかなえるようになる。大げさな管理体制も人海戦術もいらない。従来よりも少ないコストで、ユーザー企業は自社の基幹業務管理システムを開発・維持できるようになる。
また、案件あたりに必要とされる人員も大幅削減されるため、開発者の待遇も改善される。ただし、「要件定義だけやります」とか「設計だけやります」とか「プログラミングだけやります」では勤まらない。必要なメンバー(工数)がごく少ないだけに、開発者はそれらを全てこなせる「多能工」でなければならないからだ。「設計できるプログラマ」とはそういう意味だ。われわれはそのための学びを怠ってはならない。
しかし、システム開発を「設計できるプログラマの独壇場」にするためには、実はまだ決定的に足りないものがある。業種別仕様書ライブラリ(レファレンスモデルライブラリ)だ。これが欠落していることは現在のSI業界の重大かつ恥ずべき欠陥で、余分なシステム化コストを経済社会に押し付ける結果を招いている。
次回、この問題に対処するための、アプリケーションドライバの本当の、そして恐るべきゴールを紹介しよう。
渡辺 幸三(わたなべ こうぞう)
業務システムを専門とするプログラマ。業務システムをデータ構成・機能構成・業務構成の3要素の成り立ちとして捉える「三要素分析法」の提唱者。モデリングツール XEAD Modeler、実装ツール XEAD Driverの開発者。DBC代表。「業務システムのための上流工程入門」、「生産管理・原価管理システムのためのデータモデリング」他の著書がある。ブログは「設計者の発言」。
Copyright © ITmedia, Inc. All Rights Reserved.