障害の発生原因の切り分けのポイント

テストや本番運用で障害が発生した場合、既知の障害等で原因が明らかな場合を除き、対応のために原因を調査する必要があります。
原因を調査する上ではどこに原因があるのかの切り分けが必要になります。
以下では、切り分け作業を行う上でのポイントを順を追って説明します。

1.障害が発生した状況を保全する

障害が発生したデータやその状況を記録したログは後の調査で使いますので、誤って更新したり削除したりしないように保全する必要があります。
テスト中であればそのデータを用いたテストは中断する必要がありますし、場合によってはコピーして別の場所に補完する必要があります。

2.期待される値との比較を行う

データやログを見て、設計上期待される値と異なる点を探していきます。
コーディングの単純な誤りであれば、大抵の場合は、データが期待値と異なるようになった箇所やログの出力内容から原因を特定できます。
場合によっては、その期待値自体が合っているのかどうか、設計や要件の確認・再検討が必要になる場合もあります。

3.障害発生手順を確立させる

ログの出力内容が不足している場合や原因が込み入っている場合は、データやログを見ただけでは原因がわからない場合があります。
その場合は、障害が発生した時と同じ手順で障害の再現を確認します。
障害が再現すれば、障害を発生させる手順が確立されたということになります。
厄介なのは障害が再現しなかった場合で、一意キー(顧客番号や受付番号の類)が障害発生時と異なることに注目して一意キーと結びつくデータを洗い出す、ランダムに処理が変わる箇所がないか(乱数を使っている箇所や振り分け先がランダムなロードバランサー等に注目する)、という観点で再現しなかった理由を調査し、障害発生手順を確立させます。
どうしても確立できない場合は、ハード障害である可能性を視野に、障害発生時の状況をまとめてハードウェアを提供するベンダーに確認する、というアクションを起こす必要がある場合もあります。
(私の経験上、「太陽フレアでビットが入れ替わってしまった」という冗談のような原因を告げられたこともあります)

4.再現手順を元に開発環境で原因調査を行う

再現手順が確立したら、開発環境で再現手順を試し、徐々に原因を絞り込んでいきます。
もし環境を変えたことで障害が再現しなくなった場合、環境問題である可能性が出てきます。
(例えば、マスタデータの不備、機器の構成の違いに起因する問題、等)
障害が再現する場合、開発環境ではデバッグを入れて変数の内容を表示することができますので、それをログの代わりにして原因調査を進めることができます。
また、開発環境ではデータも自由に書き変えられますので、データを少しずつ書き変えて挙動を確かめることでも、原因調査を進められます。


いかがでしたでしょうか。

障害調査は経験則で語られることが多いので、私自身の経験を基に文章にしてみました。
もし参考になれば幸いです。

カプセル化によるルールの強制

オブジェクト指向を適用すると、ソースコードの重複した記述を排除でき、生産性や品質を向上することができます。
それとは別に、他の開発者にルールを強制できるメリットもあります。
カプセル化を例にして説明するのがわかりやすいので、今回はカプセル化を例に挙げてルールを強制するメリットを書いていきます。


例えば、自分は商品の金額を計算するPriceCalcクラスを作成していて、他の開発者はPriceCalcクラスを利用するUserクラスを作成しているとします。
自分としては「このロジックで金額計算して欲しい」というのがあるのですが、他の開発者にそのロジックの実装をさせるようにしてしまうと、意図が上手く伝わらなかった時に誤ったロジックで計算されてしまいます。
この状況をソースコードで表すと下記のような形になり、PriceCalcクラスは計算に必要な変数をまとめるだけ、PriceCalc側でその変数を参照して独自に業務ロジックを実装するような形となります。


それでは困るので、クラス変数に付随する業務ロジックもPriceCalcクラス側に実装することにします。
変数とメソッドをひとまとまりにして一つのクラスとして提供することで、Userクラス側ではメソッドを呼び出すだけであるべき業務ロジックに基づいた計算を行うことができ、Userクラス側で業務ロジックを意識する必要が無くなります。

しかし、メンバ変数がpublicだと、Userクラス側でその変数を参照して独自に業務ロジックを実装することもできてしまいます。
自分としてはそれでは困るので、「変数を参照して独自に業務ロジックを実装しないでください」と伝えるのですが、それが上手く伝わらないと独自に業務ロジックを実装されてしまう可能性があります。
自分一人でプログラムを作っている場合は全ての変数をpublicにした方が便利だったりもしますが、複数人で開発している場合は意思疎通がうまく行かなかった場合に意図通りではないコーディングをされてしまうリスクがあります。


それを防ぐためには、変数のアクセスレベルをprivate等にして、変数に自由にアクセスできないようにするのが有効です。
こうすることで、変数を参照して独自に業務ロジックを実装しようとされた場合に、コンパイルエラーとして機械的に失敗させることができます。
これが、カプセル化によるルールの強制であり、意思疎通がうまく行かなかった場合に意図通りではないコーディングをされるリスクを減らすことができます。


いかがでしたでしょうか。

一人で開発する時と複数人で開発する時では、気を付けるべきポイントが変わることがあります。
オブジェクト指向の考え方の一つである「カプセル化」も、単なる慣習として捉えるのではなく、複数人で開発する場合にルールを規定するものとして捉えることで、実務で有効に使えるのではないかと思います。

プログラム上の変数やロジックの意味を現実のビジネスルールと合わせることの重要性

通常、初回リリースの時点では、ソースコードは現実のビジネスルールを反映したものになっています。
(なっているべきです)

そして、障害対応や保守開発でソースコードを修正する際は、将来の保守性を犠牲にしないために、ビジネスルールに沿った修正を行うべきです。
ビジネスルールに沿わない修正を行ってしまうと、ソースコードの解読が困難になったり、後々新たなバグが生まれやすくなったりします。


例えば、麺・スープが入った器に具材を入れて料理を完成させるシステムを開発しているとします。
取り扱う料理は以下の通りとします。

また、

という処理が存在するとします。

ある日、「みそラーメンにはこしょうではなく七味を入れたい」という要望が入り、システムを改修するとします。

ここで、「みそラーメンの分類をうどんにする」という修正を行うべきではありません。
この修正でも要望に応えることはできますが、ビジネスルール上はみそラーメンはうどんに分類するべきではないので、このような修正を行うと保守性が低下します。
システムの実装を良く知らない人がソースコードを読んだ場合、「みそラーメンはラーメンである」という予想を立ててソースコードを読むことになるので、「実装上はみそラーメンはうどんである」となっていると、ソースコードの読解を誤るリスクが高まります。
また、将来、「うどんには揚げ玉を入れたい(ラーメンには入れたくない)」という要望が入り、「分類がうどんの場合は揚げ玉を入れる」という処理を追加してしまうと、みそラーメンにも揚げ玉が入るというバグを埋め込んでしまいます。

ここは、処理を以下のように修正し、ビジネスルールを崩さないようにすることが望ましいです。


いかがでしたでしょうか。

プログラムの改修は直せば良いというものではなく、その後の保守のしやすさも考えて直す必要があります。
今回は、実務で目にしたケースを元に、記事を書いてみました。

ソースコード中に顧客情報を記述してはいけない

特定の顧客用に特別なビジネスロジックを用意している場合、以下のようにソースコード中に顧客情報を記述することで実装することはできます。
(顧客番号が’1111111’である場合に特別なビジネスロジックを実行するとします)


しかし、この実装方法には以下の問題があり、望ましくありません。
特別なビジネスロジックを適用する対象の顧客の情報は、(データの管理にDBを使用しているシステムであれば)DBに持たせるべきです。

・頻繁なソースコード修正が必要になる

対象となる顧客が追加されたり削除されたりする度にソースコード修正が必要になる。
コンパイルやデプロイも必要となり、対応工数や対応のリスクが増加する。
→DBで管理すればDB内の情報の補正だけで済む。

・データを一元管理できなくなる

対象となる顧客を調べるためにはソースコードを見なくてはならなくなる。
→DBで一元管理すれば情報の検索が容易になる。

・セキュリティを確保するのが難しくなる

ソースコードが流出した場合に顧客情報も同時に流出してしまう。
→DBで一元管理すれば、顧客情報の流出のリスクを減らすことができる。


対象となる顧客の情報をDBに持たせる場合、以下のような実装になります。

1.特定処理対象顧客テーブルを別途作る場合

※特定処理対象顧客テーブルには、特定処理の対象となる顧客番号を格納する。

2.既存のテーブルにフラグを持たせる場合

※「特定処理対象フラグ」のカラムを追加し、処理対象なら’1’、処理対象外なら’0’をセットする。

いかがでしたでしょうか。

ソースコード中に顧客情報を記述してしまい保守性が低下するという話は良く聞きますし、私自身もそのようなソースを保守開発して苦労したことがあります。
また、最近ではソースコードと共に顧客情報が流出したという事故もニュースになっています。
そのような背景があり、今回の記事を書いてみました。

顧客情報が記述されたソースコードを目にする機会は珍しくないと思いますが、それはアンチパターンであるというのは認識しておくべきでしょう。

システム改修で発生するデータ移行とは

通常のシステムでは、データベースやファイル等にデータを蓄積します。
そして、システムの改修の際に、そのデータのフォーマットを変更することがあります。

データのフォーマットの変更には、「これまでに蓄積されたデータをどのように扱うのか」という問題が付きまといます。
開発者は、データを移行するのかしないのか、移行するのであればどのように移行するのか、ということを判断する必要があります。
ここでは、データ移行について、具体的な例を用いて見て行こうと思います。

【例】

商品購入履歴テーブルにて、清算番号をキーとし、いつ誰が何をどれだけ購入したのかを管理しているとします。
システム稼働開始時は、商品コードから単価は一意に求まるとします。
また、当該テーブルを見て、各々の顧客が過去に商品を購入したことがあるかどうかを見る必要があるとします。

ここで、各々の清算において値引きを可能にするシステム改修を行うとします。
また、売り上げを正しく求めるため、清算毎の単価を新たに管理する必要が出てきたとします。

この場合、商品購入履歴テーブルのフォーマットを以下のように変更する必要が出てきます。

ここで、過去に蓄積されたデータの扱いについて、以下のような方針を考える必要が出てきます。

【対応方針】

1.データ移行せず、旧商品購入履歴テーブルをそのまま残す

データ移行が困難であり、プログラムの改修箇所が少ないのであれば、データ移行しないという選択肢があります。
データ移行が困難な状況としては、データ量が多く時間がかかる、システムを停止できる時間が少ない、といった要因があります。

この方針の場合、過去の購入履歴を参照するプログラムに対して改修が必要になります。
新商品購入履歴テーブルと旧商品購入履歴テーブルの両方を見る、という改修を行う必要があります。
例えば、顧客コード”0000001″の顧客が過去に商品を購入したかどうかを見たい場合、新商品購入履歴テーブルになければ旧商品購入履歴テーブルを見る、とする必要があります。

この方針のデメリットとしては、プログラムロジックが複雑になり、保守性が低下することが挙げられます。
もし、自動化されていない運用作業で商品購入履歴テーブルを見る必要がある場合、その作業が煩雑になることにも注意する必要があります。運用ミスの原因にもなり得ます。

2.データ移行する

データ移行する場合、旧商品購入履歴テーブルのデータを、新商品購入履歴テーブルに移し替える必要があります。
システムを一時的に停止し、移行用のプログラムを実行させ、旧商品購入履歴テーブルのデータを新商品購入履歴テーブルのフォーマットに合わせた形で移行します。
(失敗に備えてバックアップを取得することも重要です)

データ移行する際には、フォーマットをどのように合わせるのかを考える必要があります。
今回の例で言うと、移行したデータの単価に何をセットするのか、ということを考える必要があります。
移行日以前の単価を見る必要が無いのであれば、nullで問題ありません。
見る可能性があるのであれば、現在の単価(過去に単価が変動しているのであれば当時の単価)をセットする必要があります。


いかがでしたでしょうか。

今回の記事の通り、データフォーマットを変更する場合は、変更後に追加されるデータのことだけでなく、それまでに蓄積されたデータをどうするのかということも考える必要があり、その分の工数やリスクを考慮する必要があります。

今回書いたようなことは記事として書かれることが少なく、経験則に近いものがあります。
経験則を記事に起こして共有可能とすることは重要だと思うので、今後も経験則を記事にしていきたいと思います。