「現行機能の踏襲」という要件定義のまずさと、苦し紛れの対応策

私自身は幸運なことに、要件が「現行機能の踏襲」であるプロジェクトに携わったことはないのですが、要件がこれに近いプログラムをプライベートで作成しているので、参考までにその経験を記事として残します。


私が作成しているプログラムはとあるゲーム(RPG)の戦闘部分の計算を行うシミュレータであり、ゲーム攻略のために作成しています。
要件としては下記であり、同じ計算が行われるということ自体に価値があるプログラムです。

・ゲームで行われる計算と同じ計算を実装する

・諸事情によりゲームを解析することはできない

・ゲームには場当たり的な対応やバグが多数含まれており、それも仕様として実装する

 

これを実務に置き換えると以下のような要件であり、限りなく「現行機能の踏襲」に近い状況です。

・現行システムと同じ機能を実装する

・諸事情により現行のソースコードを入手できない

(他ベンダーが開発、ソースコード紛失、等の理由で)

・場当たり的な保守・運用が多数行われており、それも仕様として実装する


私が作成しているプログラムで最も複雑な部分は、攻撃手段毎のダメージ計算式です。
攻撃手段は数百種類存在し、それぞれ異なった計算を行う必要があります。

本来であれば、各々の攻撃手段毎で共通するルールが存在するため、そのルールを抽象化して実装することで保守性を確保することができます(図「本来あるべき姿」)。
(「本来」というのは、「シリーズ初期は」や「運用当初は」といった言葉で置き換えてください)

しかし、実態としては、場当たり的な対応が行われる過程でそのルールを逸脱する計算が行われる攻撃手段が存在します(図「実態1」)。
また、実装の誤りにより、攻撃手段によっては一部計算式が異なる物もあります(図「実態2」)。

ゲームを解析することができれば、既存のソースコードをそのままコンバートしたり、ロジックを真似ることで現行のソースコードと同程度のレベルで抽象化したりすることができるのですが、今回はその手は使えません。
更に、実装を始めた段階では現行のゲームの全ての挙動が判明しているわけではなく、プログラムを作ってみて初めて判明する挙動というのも存在するため、このことも開発の難易度を高める要因となっています。
つまり、十分な抽象化が行えない状況で、将来の変更(新たに見つかった挙動への対応)を見据えたプログラミングを行う必要があります。

私が作成しているプログラムでは、下記のような対応をとっています(図「対応策」)。

1.攻撃手段毎にクラス分けする

一番避けなければならないのが、攻撃手段の分岐を計算式中で多様することでソースコードが複雑化し、保守困難になることである。
そこで、ルールに従った抽象化を行わず、攻撃手段毎にクラス分けした。
重複した記述は多くなるが、ある攻撃手段に対する変更が他の攻撃手段に影響を及ぼすのを避けることはでき、攻撃手段毎の処理内容を追うことも容易になる。
(なお、小規模な開発である場合、「クラス」は「関数」に置き換えても良い)

2.各々のクラスで使用する計算式は部品化する

ダメージ計算式は分解可能であるため、分解した計算式を部品(共通関数)として用意し、その部品を組み合わせることで各々のクラスの実装を可能とした。
攻撃手段毎の微妙な計算式の違いを、部品の組み換えや追加で吸収することが可能になり、保守性が向上する。

このプログラムは公開してから(使い物になる状態になってから)数年経っていますが、今の所は保守困難な状態にはなっていません。
しかし、十分に抽象化を行えなかった影響で、保守性は正直良くありません。
攻撃手段の数だけコピペが必要になるような対応も何回か発生しています。


「現行機能の踏襲」という要件はビジネス上の観点から見ても問題がある(業務改善ができない)と思いますが、実装上も

・共通ルールを見つけるのが困難で、十分な抽象化を行えない

・作ってから初めて明らかになる仕様があり、変更を見こさなければならない

 

という困難さがあり、実装を工夫したとしても十分な保守性を確保するのは難しいです。
保守性が悪いということは、将来の開発でQCDを確保することも困難になります。
「現行機能の踏襲」という要件は、開発コストの低減やリスクの低減に繋がらないと考えた方が無難だと思います。
今回の例のように、現行機能通りにすること自体に価値があるのであれば別ですが、そうでなければ、要件を「現行機能の踏襲」とすることには慎重になる必要があります。


プライベートでもプログラムを作っているので、その中で仕事に活かせそうなスキルや経験が得られることがあります。
また何かあれば、記事にしていきたいと思います。

クラス図とjavaソースの対応一覧

クラス図とはUMLの一種で、以下のような形でクラスの定義と各クラスの関係を表す設計図のことです。

今回は、クラス図で使われる記法とjavaソースの対応について、一覧にしてみました。
参考になれば幸いです。

【クラス定義】

【各クラスの関係】


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

クラス図を書かなくともjavaのプログラムは作れてしまうので、情報を伝達したり整理したりするためにクラス図を書かなければならないとなった時に書き方を忘れてしまうことは意外とあると思います。
javaのプログラムに慣れた方であれば、日本語で説明されるよりもソースで説明された方がわかりやすいと思ったので、このような記事を書いてみました。

フローチャートを書く意義と書き方

フローチャートは、処理の流れを図に起こして整理するために使う技法です。
フローチャートを起こすことで、他の人に処理の流れを伝えやすくなるので、設計書では良く用いられます。
また、自分自身も処理の流れを理解しやすくなるので、複雑なプログラムを組む前に作ることも多いです。

特にプログラミングに慣れていない内は処理の流れを考えるのにも一苦労だと思いますので、プログラミングする前にフローチャートを書く癖を身に付けることをお勧めします。

以下、フローチャートの書き方や例を提示していきます。

1.フローチャートの例

フローチャートは、記号を線で結ぶことで作成します。
記号の中には具体的な内容を記述します。

フローチャートには詳細なものと概要を把握するものに大別できます。
詳細なフローチャートは、プログラムの1行1行を記号に落としていくようなイメージであり、すぐにプログラムに落とし込むことが難しい初学者にお勧めできるフローチャートです。
一方、概要のフローチャートは、処理の流れを俯瞰するためのものであり、実際のプログラミングでは1つ1つの記号の中で複数の処理(順次処理、分岐処理、繰り返し処理等)を記述します。設計書に用いられることが多いのはこちらのフローチャートです。

2.フローチャートの記号の意味

フローチャートで使われる記号の意味は以下の通りです。
これ以外の記号が使われることもありますが、これ以外の記号は現場や人によって微妙に書き方が異なることも少なくないので、実際のフローチャートを読んで都度記号の意味を考えた方が良いでしょう。

3.繰り返しや定義済み処理を含むフローチャートの例

前述の例では使用していない記号があったので、以下で補足します。


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

慣れない内はプログラムのロジックを考えるのに一苦労します。
いきなりプログラムを書き始めるのが難しい場合は、フローチャートを書く所から始めると良いと思います。

ちなみに、私も、難しい処理を実装する場合は自分のためにフローチャートを書きます。
つい最近も、再帰処理を含む複雑なロジックを4ページ程のフローチャートで整理していました。
慣れていてもフローチャートを書く時は書くので、慣れない内は尚更書いた方が良いのではないか、と思っています。

磁気ディスクの構成とアクセス方法

今回は、磁気ディスクの構成とアクセス方法について、わかりやすく図解してみました。

ITパスポートや基本情報処理技術者では頻出ですので、これらの試験を受けるのであれば是非理解しておいた方が良いです。
実務ではあまり使うことはないと思うのですが、全く使う機会がないわけではありません。
例えば、メインフレームで磁気ディスクの容量計算を行う時にこの知識が必要になりますし、フラグメンテーションとデフラグは普通にPCを使っていても知っておいた方が良いことです。

・磁気ディスクの構成

・磁気ディスクのアクセス方法


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

ITパスポートを受験する知人の様子を見ていて、磁気ディスクは馴染みが無くて少し難しいと感じたので、今回の記事を執筆しました。
もしかしたら試験では理解できずに丸暗記で済ませてしまった知識かもしれませんが、知っておくに越したことは無い知識です。
今回の記事を通して、磁気ディスクについての知識が少しでも広まれば幸いです。

これからも、試験や実務で役に立つ知識を発信していきたいと思います!

O(オーダ)の概念と実務での使い道

今回の記事は、検索アルゴリズムやソートアルゴリズムの性能を評価する際に用いられる「O(オーダ)」の概念についてです。

「O」は情報処理技術者試験ではよく主題されるものの、業務系SE(特定の業務のシステム設計を得意とするSE)には無縁のものに思われがちです。
しかし、業務系SEでも「システム改修によりデータ量が○倍になるが、このバッチ処理は○分以内に終わらせないといけない」という形で性能面を考えた設計が必要になることがあります。
実際に本番と同じ環境・改修後のデータ量を用意してテストができれば良いのですが、それができない場合は本番環境の現状のデータ量・処理時間から改修後の処理時間を見積もる必要があります。
(この見積もりで求められる性能が出ない場合は、性能を向上させるための設計を考える必要が出てきます)
このような見積もりで、「O」という概念が必要になります。


下記に、代表的なアルゴリズムとそのオーダを記載します。

・線形検索

O(N)

・2分検索

O(logN)

・バブルソート、基本選択法、基本挿入法

O(N^2)

・クイックソート、マージソート、ヒープソート

O(NlogN)

ここで、Nはデータ量のことを指し、Oは処理時間のことを指します。
例えば、「O(N^3)」なら、処理時間はデータ量の増加の割合の3乗となります。
データ量が2倍になれば、処理時間は8倍(2^3)となります。

また、情報処理の分野では、logの底は2とされています。
そのため、「logN」とは、「2を何乗したらNになるのか」を指す値となります。
log2は1(2^1=2)、log4なら2(2^2=4)、log1024なら10(2^10=1024)となります。
例えば、2分検索の場合、階層が1深くなれば(処理回数が1回増えれば)2倍のデータを検索できるようになるので、「2^処理回数=データ量」の関係が成り立ち、O(logN)と表記できます。

例として、データ量が4倍になった時の処理時間の増加量を以下に示します。

・線形検索

4倍(N)

・2分検索

2倍(logN)

・バブルソート、基本選択法、基本挿入法

16倍(N^2)

・クイックソート、マージソート、ヒープソート

8倍(NlogN)

なお、実務で処理時間を見積もる際、OSや製品で用意されている検索・ソートアルゴリズムは原則として高速なものが用意されているので、2分検索やクイックソート・マージソート・ヒープソートと同じオーダで計算して良いでしょう。


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

情報処理技術者試験で学んだ知識が本当に実務で役に立ったのが個人的に印象的だったので、記事として書きました。
これ以外にも実務で役に立つ知識は少なくないので、単に合格するために試験勉強するのではなく、実務で使える引き出しを増やすために勉強した方が良いと個人的には思っています。

これからも実務で使える/使えたことがあれば、記事として発信していきたいと思います!