基幹システムの保守で感じたこと
大規模システムの保守をすると開発のときにはのっぺりしていて見えない「でこぼこ」が見えるようになる、と上司が言っていた。
生産管理(受注、納期回答、出荷を中心に)の保守に1年強携わる中で感じたことを、とりとめはないけど書いてみる。
説明できること
「なんでこの納期になっているのか?」というような問い合わせがひっきりなしに来る。
システムの自動引当を疑われているわけだけど、実は得意先から納期変更が入っていたことにユーザが気が付いていなかったり。
ユーザがシステムを見るだけで判断できるようにしておくべきだけど、せめて説明できるようにしたい。
データ容量とのトレードオフで1トランザクションデータの履歴(ex. 受注から売上まで)をどこまでの粒度で追えるようにしておくか、時点時点での状況(ex. 在庫が引き当たっていたのか、生産計画が引き当たっていたのか)をどのように判断できるようにしておくか。
また他システムや他サブシステムとのIFをバックアップしておくこと。
ファイル連携であれば単純にファイルをバックアップすればよいけど、IFテーブルに書き込むのであればどのタイミングでバックアップテーブル(またはファイル)に移送するか、WebサービスでHTTPで飛んでくる場合のパラメタ、直接メソッドを呼び出すのであればその引数のログなど。
これらがまずは泥臭くとも後から参照できること、次のステップとしては簡便に検索できること。
影響調査のしやすさ
マスタの設定値を1つ変えたらどういう業務影響があるかと問われるだけで、影響調査で何日もかかったりした。
項目レベルのCRUD表相当の何かが、メンテナンスコストの低い仕組みで存在しており、最新化されていると信頼できればハッピー。
最終はソースをgrepするわけだけど、並行開発などで最新のソースが散在していてどこをgrepすればよいか分からなかったりした日には地獄。
明示ロック
パフォーマンス上の制約でトランザクション分離レベルをSERIALIZABLEにできない場合、明示ロックを使用することになる。
この場合、デッドロックを回避するために、システム全体で共通のロック順序に従ってテーブルをロックしていくと思われる。
ここで問題になるのが、共通関数的なもの。ロック順序に従ってロックをするためには、共通関数の呼び出し元が、共通関数内でアクセスしているテーブルを把握して、呼び出し前に正しい順序でロックをかける必要がある。
これは、共通関数に処理を隠蔽することと相反する。
影響調査にも同じ問題がある。共通関数内で引数の区分などを判断してCRUDするテーブルを切り替えている場合、特定のテーブルや項目という観点で見ると結局どの機能から更新されているか追いかけるのが困難になる。
取り消しをどこまでシステム化するか
業務システムは取消と開発コストのトレードオフ(だと思う)。
開発時はコスト削減でこれ以上の取り消しは不要にすると決めても、実運用ではあらゆるオペレーションミスが発生する。結局データパッチを当てるのだけど、監査上の問題もあるし、そもそも影響範囲が大きすぎてデータパッチを当てるのも難しい。
締め処理後にも遡って処理したいという要望は必ず出てきて、「締め」とは何かという気分にもなる。
取消機能を開発する場合は、データ源泉で取り消し入力して伝播させるのが大原則。
Excel問題
業務システムはExcelと切っても切り離せない。どれだけUIを工夫しても、複数行を一括で処理する際にExcelのコピペには適わない。
Webシステムであれば、Excel出力→Excel上でユーザが編集→Excelファイルをアップロードして取り込むという機能を作る、ExcelをHTTPクライアントとして使うなどが考えられる。
その際、Web画面のロジックを流用して手軽にExcel機能を実装できることが重要。たとえば画面入力とExcel取込でValidationの実装箇所が異なっており、結果として二重実装になったりすると辛い。
マスタ
ERPパッケージではより顕著なのだろうけど、マスタは際限なく肥大化していく。
例えば、工場と得意先の組み合わせで梱包ラベルの出力内容を設定するとする。
ある工場では全体で共通なので得意先ALLで設定したい、別の工場は得意先をグルーピングして設定したい、特定の品種だけ特別な設定をしたい、はじめての得意先に出荷する場合は特別な設定を適用したいなどの要件が発生する。
さらには、どのトランザクションデータからマスタ変更を適用するかという課題もある。
適用開始・終了で管理するにしても、各トランザクションの受注日で切り替えるのか出荷日で切り替えるのか、また日中に変更するが即時反映せずに夜間バッチ先頭で反映するなどの要件があったりする。
結果として、ユーザもどのようにマスタ設定したらよいか分からず、どの部署がメンテナンスするかも不明確になり、また単純なSQLではマスタ設定値を取得できず調査も大変なマスタが完成する。
一括改定(組織変更、他工場への生産移管、単価改定、品種のくくり方の変更など)も大変で、どれだけのマスタを改定すればよいのか、どのようにトランザクションデータに反映させるのか、複雑になる一方。
本番のデータベースに対して、引数を指定して共通関数(更新がないことが前提)を呼び出せる仕組みがあると便利。
どこまでシステムを業務に寄せるのか、業務をシステムに寄せるのかという問題でもあるのだろうけど。
汎用テーブル
どのシステムにも存在するであろう汎用テーブル的なもの。
- ID、種別区分+項目1、項目2、…、項目10みたいなテーブル項目
- だいたいは構造が別途管理されている(ex. 種別:工場では、項目1が工場コードで半角英数、項目2が工場名で制約なし、項目3以降が工場ごとの設定)
そして構造情報を使って、共通のメンテナンス機能を開発して、メンテナンスする。
マスタメンテナンス機能の機能数(= 開発コスト)を削減するために生まれてくるケースが多いと思われる。メタ情報を使って共通機能でメンテナンスするのであれば、複数テーブルを1機能でメンテナンスすればよいようにも思うけど、汎用テーブルが各テーブルに切り出され、数件しかレコードのないテーブルが数百増えたりするのも辛く、難しい。
せめてシステム的な設定(または、データがないと動かない)と、ユーザの設定(または、データがなくとも動く)は別テーブルにしておく方がよい気がする。
テスト環境の構築容易性
特定時点のプログラム一式とデータを使って、テスト環境を簡便に構築できることは重要。
データのマスキング、本番系にメールやデータ連携が流れないようにする設定変更、誤操作しないように画面の背景色を変えるなど、スクリプトに組み込んでおく。
データをダンプするために、ファイルや過去履歴など極端に容量の大きいテーブルは別スキーマにしておく方が簡便。
本番のデータ一式を持ってきて、デバック実行するしか調査のしようがないケースもある。
オーナーの明確化
それぞれの機能、共通機能、テーブルなどのオーナーが明確かつ簡便に判断できないと辛い。壁の向こうにボールを投げておしまいはダメだけど、でもどのチームに聞いたらよいか分からないとどうしようもない。あと名前大事。
ドキュメント
ここの開発テーマに応じて、有用な資料は作られていく。一方、新しい要件が追加されたときに、過去の資料に翻って反映するのはなおざりになり、結果として生き字引が「ここはこの資料を見て、ここはこっちが最新」というパターンになる。
最新化対象の資料を増やすことは、保守コストを上げることなので難しいけれど、どのドキュメントを構成管理するかは明確にしておく必要がある。
フロー、概念図、処理の目的や概要、各項目の業務的な意味など、ソースに表れない論理的なドキュメントは必要。
一方、やはりプログラム設計書は作らない方がよいと思う。どこまでいっても設計書と実装の完全一致は難しい。
ソースを読めない人もいるのは分からないでもない。でも実装を求めるわけではないので、拒否反応さえ解除できれば、Excel設計書を何ファイルも開いてシートを移動しながら読み解くよりは、IDEでF3とかでジャンプしながらデバック実行する方が効率がよいと思う。
プロセスを軽くする人
保守運用ではどうしても事故は起こる。事故が起こると原因を分析して対策を立てる。
そもそも初期構築時の大規模開発ならではの重厚なプロセスが残っていて、そこに事故が起きるたびにチェックリストが追加されていったりすると、大げさではなく身動きが取れなくなってしまう。
時間が経てば、そもそも何のためにそのチェック項目があるのか誰も知らない状態になり、誰も整理できなくなり、最終的にはすべてが平坦におざなりになる。
似たような話で、プログラムでいえば、新機能は拠点ごとに導入していくケースが多いため、何らかのShip Flagのような仕組みでコントロールすることになる。全拠点への導入が終わってもShip FlagのIF文が残り続けることで、ロジックはどんどん複雑になっていく。
プログラムだけではなく、プロセスもリファクタリングが必要。