技術とか戦略とか

SIerで証券レガシーシステムを8年いじってからSESに転職した業務系エンジニアによる技術ブログ。

試験工程管理の概論

SI業界では、開発に関する知識や経験が不十分なメンバーを試験工程の管理者として任命することが少なくありません。
本来であれば、応用情報処理技術者試験(最低でも基本情報処理技術者試験)に相当する知識、及びその知識を実務で使った経験を備えた者を管理者として任命するべきだと思うのですが、そうすることができない事情もあると思います。
 
そこで、今回の記事では試験工程の管理に必要な知識の概要を書いていきます。
当記事では概要しか書きませんので、細かい所は別途書籍やWEBで調べて下さい。
また、現場毎で試験の進め方が微妙に異なりますので、現場の進め方に適宜合わせていただければと思います。
 
なお、当記事では、ウォーターフォール(要件定義→設計→製造→試験→リリース、という順番に沿って、数ヶ月~数年の大規模な開発プロジェクトを予定通りに進める開発手法)を前提とします。
アジャイル(要件をこまめに取り入れながら、1週間~数週間毎に小規模なリリースを繰り返す開発手法)の場合は、各々の開発者が試験に関しても自主的にコントロールする形になるので少し違う話になります。
(しかし、アジャイルのプロジェクトに関わる場合も、ウォーターフォールのプロジェクトで培った試験管理に関する知見を活かすことはできます)
 
【試験工程の分類】
試験工程はいくつかの工程(段階)に分かれます。
具体的には以下のように分かれます。
 
・単体試験(単体テストユニットテスト、UT)
単独のプログラムを対象とした試験。
この工程では、プログラムの開発者自らが試験を行うことが多い。
あるプログラムから別のプログラムを呼び出すことがあるが、どちらかのプログラムが未完成の場合は「スタブ」や「ドライバ」といった仮のプログラムが使用される。
「スタブ」は呼び出し先のプログラムに相当する仮のプログラムであり、「ドライバ」は呼び出し元のプログラムに相当する仮のプログラムである。
試験で使用される仮のプログラムについては、比較的新し目の技術を使う現場では「モック」(完成品のような見た目に見せかけた仮のプログラム)と呼ばれることも多い。
 
・結合試験(結合テスト、IT)
あるプログラムから別のプログラムへの呼び出しの箇所の担保を取ることに着目した試験。
単体試験の延長線上のような体制で行われることもあれば、専任のテストチームが試験を実施することもある。
 
・総合試験(総合テスト、システムテスト、ST)
プログラム等の改修を行ったシステムについて、システム全体の挙動を確認する試験。
人数の少ないプロジェクトである場合や技術的なサポートが必要な場合は開発者が試験実施に携わる場合もあるが、原則としては専任のテストチームが試験を実施する。
他システムとの連携についても、この段階で行う。
 
・受入試験(運用テスト、UAT)
改修後のシステムについて、実運用上の問題がないかを確認する試験。
システムの実際の利用者が試験を実施するが、利用者が一般ユーザー(エンドユーザー)である場合、受入側の会社の社員が代わりに試験を行う場合が多い。
 
それぞれの試験の工程がウォーターフォールのプロセスの中でどのような立ち位置にあるのかは別の記事(ウォーターフォールモデルとV字モデル(https://cyzennt.co.jp/blog/2019/10/26/%e3%82%a6%e3%82%a9%e3%83%bc%e3%82%bf%e3%83%bc%e3%83%95%e3%82%a9%e3%83%bc%e3%83%ab%e3%83%a2%e3%83%87%e3%83%ab%e3%81%a8%ef%bd%96%e5%ad%97%e3%83%a2%e3%83%87%e3%83%ab/))に投稿していますので、よろしければそちらもご参照ください。
 
【試験工程のスケジュール】
それぞれの試験の工程では、一般的に以下のような順番で作業が行われます。
(いくつかの作業は省略される場合があります)
 
試験の計画の策定

試験項目の作成

試験の準備の実施(テストデータやテスト環境やテスト用プログラムの作成)

試験の実施とバグ対応(バグが頻出する場合はこの時点で品質強化を行う場合がある)

上長への試験結果の報告(試験の一部再実施や品質強化を指示される場合がある)
 
それぞれの作業については、プロジェクト初期の見積もり段階で
「どのような役割の人を何人投入して、いつからいつまで作業を行うのか」
という計画が原則として立っています。
この計画については、それぞれの作業についての工数を積み上げて計画が立てられている場合もあれば、全体の工数を見積もった後に「単体試験は全体の20%、結合試験は全体の15%…」といった形で概略的に立てられている場合もあります。
見積もりと計画についての詳細は、別の記事(「見積もり概論」社内勉強会用のパワポの公開(https://cyzennt.co.jp/blog/2022/04/19/%e3%80%8c%e8%a6%8b%e7%a9%8d%e3%82%82%e3%82%8a%e6%a6%82%e8%ab%96%e3%80%8d%e7%a4%be%e5%86%85%e5%8b%89%e5%bc%b7%e4%bc%9a%e7%94%a8%e3%81%ae%e3%83%91%e3%83%af%e3%83%9d%e3%81%ae%e5%85%ac%e9%96%8b/))に投稿しています。
また、
「どのプログラムについてどのようなテストが必要か。そのためにどのような準備を行うのか。」
といった細かいことについては、各々の試験工程の序盤で行うことが多いですが、これは開発に携わっているメンバーでないと実施困難であり、必要となる知識も広範かつ各々のプロジェクトに寄る所も大きいので、この記事では詳細は書けません。
試験の準備の実施、その他バグ対応や品質強化の実作用についても同様に、開発に携わっているメンバーでないと実施困難な作業になります。
 
試験項目の作成方法については、いくつかのテクニックがあります。
キーワードだけ書いておくと、「ホワイトボックステスト/ブラックボックステスト」「デシジョンテーブル」といったテクニックが使われます。
(詳しくは、情報処理技術者試験の情報を参考にするのが良いと思います)
なお、試験項目を挙げ終わったら、試験の項目数を集計し、総件数/着手件数/完了件数を把握できるようにするべきです。
これは、試験工程の進捗を確認する上で必要になる情報の一つになります。
 
予定通りの試験実施を妨げる最も典型的な問題の一つとして
「特定のプログラムでバグが頻発し、そのことが原因で関連するプログラムの試験項目を消化できない」
という問題があります。
この問題の対処のために
「バグが頻発するプログラムについて追加レビューや追加テストを集中的に実施し、バグを見つけきって、見つかった全てのバグを直して、元の作業に戻る」
という追加作業を実施する場合があります。
この作業は、「品質強化」と呼ばれることが多いです。
品質強化については、別の記事(類似バグを効率的に見つけ出すための観点(https://cyzennt.co.jp/blog/2022/01/25/%e9%a1%9e%e4%bc%bc%e3%83%90%e3%82%b0%e3%82%92%e5%8a%b9%e7%8e%87%e7%9a%84%e3%81%ab%e8%a6%8b%e3%81%a4%e3%81%91%e5%87%ba%e3%81%99%e3%81%9f%e3%82%81%e3%81%ae%e8%a6%b3%e7%82%b9/))に詳しくテクニックを紹介しています。
 
【バグ管理表の運用】
それぞれの試験工程では、試験で見つかったバグを一覧にして管理するためのバグ管理表(不具合管理表、障害管理表)を用います。
バグ管理表はExcel若しくはスプレッドシートで作成されることが多く、フォーマットのイメージについてはWEBで調べることができます。
(プロジェクトによっては、BacklogやJiraといったチケット管理ツールが用いられる場合もあります)
 
バグ管理表には、一般的に、以下のような項目が含まれます。
それぞれの項目について、管理者が着目するべきポイントも合わせて説明していきます。
 
・通番、タイトル
見つかったバグを一意に特定するための情報です。
報告書上や会議の場でも、「『No.3 ○○システムで作成したユーザーでログインできない』の対応状況に関しましては…」といった書き方/言い方をするので、通番と完結なタイトルがあると便利です。
 
・発生日
バグが発見された日を記入します。
詳しくは後述しますが、試験工程の進捗を確認する一つの方法として、バグが何件発見されたのかを日毎に確認する、というものがあります。
その際に必要となる情報になります。
 
・ステータス
それぞれのバグについて、対応が現在どの段階にあるのかを記入します。
順番に書くと、最低限「起票」「原因調査中」「バグ修正中」「修正確認中」「対応完了」といった段階に分ける必要があるでしょう。
試験工程が予定通りに進んでいない場合、ステータスからどこがボトルネックになっているのかを掴める場合があります。
例えば、「起票」「原因調査中」「バグ修正中」で止まっているバグが多ければ、バグが多すぎて開発者の手が回っていないことが予想されますし、「修正確認中」で止まっているバグが多ければ試験担当者の手が回っていないことが予想されます。
どこがボトルネックになっているかで対策も変化します。例えば、開発者の手が回っていない状況で試験担当者を増やすことだけしても意味がありません(開発者が抱えている作業の一部を試験担当者に回す、という手を併用するなら意味があります)。
なお、バグではない事象が書き込まれたり、一つのバグが複数個所に重複されて起票されたりする場合もあるので、それを示すためのステータス(「バグではない」「重複起票」等)も必要でしょう。
詳しくは後述しますが、試験工程毎で見つけるべきバグ数を計測する場合があります。
その際に、バグとしてカウントする必要がないものは計測対象外とする必要があります。
 
・バグ内容
バグの内容やバグを発生させる手順を記入します。
ここを参照するのは基本的にバグを修正する開発者ですが、品質強化策を考えるヒントを得るための情報の一つにもなり得ます(ここからヒントを得るためには、開発に関する知見が必要になる場合があります)。
 
・原因、修正方法、対象プログラム
バグの原因や修正方法、修正する必要があるプログラム名、といった、バグ修正に必要な情報を記入します。
単純なバグであれば試験担当者が記入できる場合もありますが、基本的にはバグを修正する開発者が記入する項目であり、開発者のための項目でもあります。
この項目も、品質強化策を考えるヒントを得るための情報の一つになり得ます。
特に、対象プログラムについては、開発に関する知見が無くとも機械的に数を集計できる項目であるため、品質強化が必要なプログラムを特定する上で有用な項目になります。
 
・バグ修正予定日、完了予定日
開発者によるバグの修正が完了する予定日、及び試験担当者による修正確認が完了する予定日を記入します。
この項目は進捗管理に使用することができ、上がってきたバグの対応が試験期間内に終わるかどうかを確認することができます。
試験期間終了日が近づいてくればくるほど重要な意味を持ってくる項目です。
 
・発見するべき工程
そのバグがどの工程で発見するべきだったのか、ということを記入する項目です。
例えば、現在が総合試験工程である場合、システム内の修正対象外のプログラムとの連関で発生したバグなら「総合試験工程で見つけるべきバグ」となりますが、プログラム内の単純なロジックのミス(製造ミス、修正ミス)で発生したバグであれば「単体試験工程で見つけるべきバグ」となります。
これは、品質強化策を考える上で重要な項目になります。
ウォーターフォールでは「前工程が完全に終わってから次工程に進む」というのが前提となっており、この前提が崩れている場合、現工程の作業が前工程の問題により進めることができなくなり、予定通りに作業を完了させることができなくなります。
もし、前工程で発見するべきバグが多ければ、この前提が崩れているということになりますし、この前提が崩れているプログラムについては品質強化が必要となります。
これは私見ですが、目安として、前工程で発見するべきバグが全体の20~30%程度であれば品質強化を検討、50%を超えたら品質強化が必須、と考えて良いと思います。
現場によっては、品質強化を行うべきラインが計測・策定されている場合もあるので、そのような数字が現場あればその数字に従うべきです。
 
【バグの発生数と進捗の関係】
バグの発生数を見ることでも試験の進捗を見ることができますが、これには少々コツが必要です。
一般的には、一定のペースでバグの発生数し続けるということはなく、試験実施の初期はバグが発生しにくく、中期にバグが多量発生し、後期にバグの発生が少なくなる、という経緯を辿ります。
 
試験手順が確立していないため、バグがなかなか見つからない

試験手順が確立したため、順調にバグが見つかる

品質が向上し残りの試験項目もレアケースのみとなるため、バグが見つかりにくくなる
 
という流れに一般的にはなるため、先に挙げたような経緯を辿ることになります。
(これをグラフにして表したものをゴンペルツ曲線(信頼度成長曲線)と呼びます。詳しくは別の記事(ゴンペルツ曲線(信頼度成長曲線)とは(https://cyzennt.co.jp/blog/2021/01/01/%e3%82%b4%e3%83%b3%e3%83%9a%e3%83%ab%e3%83%84%e6%9b%b2%e7%b7%9a%ef%bc%88%e4%bf%a1%e9%a0%bc%e5%ba%a6%e6%88%90%e9%95%b7%e6%9b%b2%e7%b7%9a%ef%bc%89%e3%81%a8%e3%81%af/))を参照して下さい。)
 
もし、このような経緯を辿らない場合は、何かしらの問題があることを示しています。
 
例えば、初期からバグが大量発生している場合や、いつまでもバグが大量発生し続ける場合は、プログラムの品質が悪いことが伺えますので、品質強化を考える必要があります。
逆に、試験手順が確立したのにも関わらずバグが発見されない場合は、テストケースの作り込みが甘いケースが考えられます。
 
なお、試験工程で見つけるべきバグ数については、現場によっては計測・策定されている場合があります。
そのような数字があれば、それに従ってバグ発生数が多い/少ないを判断すれば良いですが、無い場合は、肌感覚で判断せざるを得ない場合もあります。これには、開発に関する知見や開発経験が必要になります。

保守作業をスピードアップする方法のまとめ

システム保守の作業では、通常のシステム開発の他に、システム・データの調査依頼や、データの抽出・補正の作業依頼等、様々な作業が行われます。
これらの作業は不定期に、時に大量に依頼されるため、作業のスピードアップが重要になります。
 
この記事では、作業をスピードアップする方法について、簡単に網羅的に書いていこうと思います。
 
【作業をスピードアップする方法】
1.タイピング速度の向上
 文章を書くにもコマンドを発行するにも、タイピングが必要になります。
 タイピング速度の向上は、作業のスピードアップに直接的に繋がります。
 
 タイピング速度を向上させるためには、ホームポジションを覚えた上で、
 タッチタイピングができる程にタイピングに慣れる必要があります。
 
 チャットやSNS等でタイピングに慣れている方であれば、問題はないと思います。
 普段PCを使わないという方は、タイピング練習ゲームで練習するのが良い方法です。
 
2.ショートカットキーの利用
 Windows端末はマウス操作でも作業を行えますが、
 ショートカットキーを使用してキーボードで作業をすると作業スピードが速まります。
 
 最も初歩的な例として、多くのソフトでは、
 Ctrl+Sの同時押しでファイルの上書き保存ができます。
 また、Explorer(フォルダやファイルを操作するWindows標準のソフト)では、
 F2キーを押すことでフォルダ・ファイル名の変更が可能です。
 他にも、数多くのショートカットキーが存在し、
 マウス操作でしか行えない作業は基本的に無いと思って良いぐらいです。
 
 全てのショートカットキーはとても覚えきれないですし、
 ショートカットキーを調べたり思い出したりするのに時間がかかっても本末転倒です。
 しかし、よく行う作業については、ショートカットキーを覚えた方が良いです。
 
3.作業のスピードアップを図るツールの利用
 作業のスピードアップを図るツールを利用するのも有効な手段です。
 
 例えば、ディスプレイに表示されている画面の中から一部を抜き出したい場合、
 PrintScreenキーで画面をキャプチャしてからMSPaintで必要な個所だけ切り出す、
 と作業すると時間がかかります。
 そこで、WindowsOSに標準でついてくる「Snipping Tool」を使用すると、
 マウスで選択した範囲のみキャプチャを取ることができるので、
 作業をスピードアップさせることができます。
 
 また、ファイルの差分を見たい場合、
 コマンドプロンプトのfcコマンドでも差分を見ることができます。
 しかし、フリーソフトの「WinMerge」を使った方が、
 差分比較結果が視覚的にわかり作業が速くなりやすいですし、
 フォルダごと比較したり3ファイル同時比較したりする機能もあります。
 
4.マクロやプログラム等で自動化する
 複雑な作業を繰り返し行う場合は、
 マクロやプログラム等を作ることで作業のスピードアップを図れる場合があります。
 
 作業のスピードアップに使えるものとしては、例えば以下のものがあります。
 ・Windowsバッチ、WindowsPowerShell
 ・C#(プログラム言語として生産性が高く、WindowsOS標準のコンパイラも使える)
 ・Excel 関数、ピボットテーブル、VBAマクロ
 ・サクラエディタ マクロ(操作を記録できる、コマンドラインからの実行も可能)
 
 基本的には手作業で行っていることを
 そのままマクロやプログラム等で自動化すれば良いのですが、
 複雑な作業の場合は自動化のアルゴリズムが思い浮かばない場合もあります。
 その場合は、C言語で良く書くファイルをシーケンスで1バイトずつ読み込む手法や、
 COBOLで良く書くコントロールブレイク処理・マッチング処理といった、
 昔のCUIプログラムで使われていた手法を応用すると、
 アルゴリズムを思い浮かびやすくなります。

COBOLのマッチング処理をC#で実装する

COBOLで使われているテクニックは過去のもののように思われがちですが、現在でもちょっとしたツールを作る時に役立ちます。
WindowsOS環境の場合は、ちょっとしたツールはC#で作るのが便利なので、今回はC#COBOLのマッチング処理を実装してみました。
 
今回は、下記の記事を参考に実装しています。
https://cyzennt.co.jp/blog/2019/06/01/%E3%83%9E%E3%83%83%E3%83%81%E3%83%B3%E3%82%B0%E5%87%A6%E7%90%86%E3%81%AE%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF/
 
HIGH-VALUEを使う代わりにEOFを示すフラグ変数を使用しているので、その分だけ処理が複雑になっていることには注意してください。
 
【フォルダ構成】
execute.bat
matching.cs
files┬master.csv
     └transaction.csv
 
ソースコード
・execute.bat
@echo off

C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe matching.cs
matching.exe
del matching.exe

pause
 
・matching.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace Program
{
    class Program
    {
        // EOFフラグ
        static bool isSrmEof = false;
        static bool isSrtEof = false;
        
        static void Main(string args)
        {
            // ファイルオープン
            StreamReader srm = new StreamReader
                (@"files\master.csv", Encoding.UTF8);
            StreamReader srt = new StreamReader
                (@"files\transaction.csv", Encoding.UTF8);
            StreamWriter sw = new StreamWriter
                (@"files\matched.csv", false, Encoding.UTF8);
            
            // 先読みRead
            string
mRecord;
            string tRecord;
            mRecord = mRead(srm);
            tRecord = tRead(srt);
            
            // マッチング処理のループ
            while (!isSrmEof || !isSrtEof)
            {
                // masterのみの場合
                if ((!isSrmEof && isSrtEof) ||
                    (string.Compare(mRecord[0],tRecord[0]) < 0))
                {
                    // 何もしない
                    // master読み込み
                    mRecord = mRead(srm);
                }
                
                // マッチした場合
                else if ((!isSrmEof && !isSrtEof) &&
                         (string.Compare(mRecord[0],tRecord[0]) == 0))
                {
                    // transactionが次のキーに進むまでループ
                    while ((!isSrtEof) &&
                           !(string.Compare(mRecord[0],tRecord[0]) < 0))
                    {
                        // ファイル出力
                        sw.WriteLine(mRecord[1] + "," + tRecord[1]);
                        
                        // transaction読み込み
                        tRecord = tRead(srt);
                    }
                    
                    // master読み込み
                    mRecord = mRead(srm);
                }
                
                // transactionのみの場合
                else if ((isSrmEof && !isSrtEof) ||
                         (string.Compare(mRecord[0],tRecord[0]) > 0))
                {
                    // エラー出力
                    Console.WriteLine("Error:" + tRecord[0] + " is tran only.");
                    
                    // transaction読み込み
                    tRecord = tRead(srt);
                }
            }
            
            // ファイルクローズ
            srm.Close();
            srt.Close();
            sw.Close();
        }
        
        // MasterファイルRead
        static string
mRead(StreamReader srm)
        {
            if (srm.Peek() == -1)
            {
                isSrmEof = true;
                return null;
            }
            else
            {
                string str = srm.ReadLine();
                return str.Split(',');
            }
        }
        
        // TransactionファイルRead
        static string[] tRead(StreamReader srt)
        {
            if (srt.Peek() == -1)
            {
                isSrtEof = true;
                return null;
            }
            else
            {
                string str = srt.ReadLine();
                return str.Split(',');
            }
        }
    }
}
 
【実行前のファイル】
・files\master.csv
0000001,hoge                
0000002,fuga                
0000004,piyo                
 
・files\transaction.csv
0000001,20180401,00100,00010000
0000001,20180402,00200,00020000
0000003,20180401,00001,00001000
0000004,20180401,00002,00002000
 
【実行結果】
execute.batをダブルクリックして実行する。
 
・files\matched.csv
hoge                ,20180401
hoge                ,20180402
piyo                ,20180401

 
・標準出力
Microsoft (R) Visual C# Compiler version 4.7.3062.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, w
hich is no longer the latest version. For compilers that support newer versions of the C# programming language, see http
://go.microsoft.com/fwlink/?LinkID=533240

Error:0000003 is tran only.
続行するには何かキーを押してください . . .

ソースコードの重複の排除を関数で学ぶ

ソースコードから重複を排除して保守性を高める」という考え方は、実務で良いコードを書く上で重要な考え方です。
この考え方が身に付かない内はオブジェクト指向の理解も不十分になるのですが、いきなりオブジェクト指向から入るとこの考え方の重要性がわかりにくくなることがあります。
 
そこで、ソースコードから重複を排除することの意義を、関数の使い方から学ぶことが有効になることがあります。
 
今回は、関数を使うことでソースコードから重複を排除し、保守性が高まる例を挙げていきたいと思います。
(サンプルコードhJavaで記述します)
 
----
 
今回のサンプルコードでは、複数の商品の販売価格を計算します。
まずは関数を使わないサンプルコードから書いていこうと思います。
 
【サンプルコード(関数未使用・修正前)】
・FunctionTestMain.java
public class FunctionTestMain {

    public static void main(String args) {

        // 商品の定価
        int item1OrginalPrice = 100;
        int item2OrginalPrice = 200;

        // 販売額計算
        double taxRate = 1.08;
        double item1SalesPrice = Math.floor(item1OrginalPrice * taxRate);
        double item2SalesPrice = Math.floor(item2OrginalPrice * taxRate);

        // 結果表示
        System.out.println("item1の販売額:" + item1SalesPrice);
        System.out.println("item2の販売額:" + item2SalesPrice);

    }

}
 
【実行結果】
item1の販売額:108.0
item2の販売額:216.0
 
----
 
先ほどのソースコードに対して、「店舗独自の割引額を考慮する」という修正を入れていきます。
 
関数を使用しない場合、割引額(discountRate)を掛けるという修正を複数個所に入れることになります。
これが、ソースコードに重複が発生している状態です。
 
ソースコードに重複が発生していると、一部だけ修正を漏らすことによるバグに繋がりやすくなります。
このバグを潰すために、テストする範囲も広がってしまいます。
 
【サンプルコード(関数未使用・修正後)】
・FunctionTestMain.java
public class FunctionTestMain {

    public static void main(String args) {

        // 商品の定価
        int item1OrginalPrice = 100;
        int item2OrginalPrice = 200;

        // 販売額計算
        double taxRate = 1.08;
        double discountRate = 0.95;
        double item1SalesPrice =
                Math.floor(item1OrginalPrice * taxRate * discountRate);
        double item2SalesPrice =
                Math.floor(item2OrginalPrice * taxRate * discountRate);

        // 結果表示
        System.out.println("item1の販売額:" + item1SalesPrice);
        System.out.println("item2の販売額:" + item2SalesPrice);

    }

}
 
【実行結果】
item1の販売額:102.0
item2の販売額:205.0
 
----
 
次に、ソースコードを一旦修正前の状態に戻して、関数を入れていきます。
 
販売額を計算する関数(salesPriceCalc)を入れることで、ソースコードから重複を取り除くことができています。
 
【サンプルコード(関数使用・修正前)】
・FunctionTestMain.java
public class FunctionTestMain {

    public static void main(String args) {

        // 商品の定価
        int item1OrginalPrice = 100;
        int item2OrginalPrice = 200;

        // 販売額計算
        double item1SalesPrice = salesPriceCalc(item1OrginalPrice);
        double item2SalesPrice = salesPriceCalc(item2OrginalPrice);

        // 結果表示
        System.out.println("item1の販売額:" + item1SalesPrice);
        System.out.println("item2の販売額:" + item2SalesPrice);

    }

    public static double salesPriceCalc(int originalPrice) {

        double taxRate = 1.08;
        return Math.floor(originalPrice * taxRate);

    }

}
 
【実行結果】
item1の販売額:108.0
item2の販売額:216.0
 
----
 
関数を使用したソースコードに対して、先ほどと同じように割引額を考慮する修正を入れます。
 
重複が関数により排除されているので、割引額を入れる修正は1カ所で済んでいます。
修正箇所が減っているため、修正漏れを心配する必要がなくなり、ソースコードの保守が容易になっています。
言い換えると、時間をかけずにバグが出にくい修正を行うことができるようになります。
修正が繰り返される実務のソースコードでは、これは重要なことです。
 
【サンプルコード(関数使用・修正後)】
・FunctionTestMain.java
public class FunctionTestMain {

    public static void main(String args) {

        // 商品の定価
        int item1OrginalPrice = 100;
        int item2OrginalPrice = 200;

        // 販売額計算
        double item1SalesPrice = salesPriceCalc(item1OrginalPrice);
        double item2SalesPrice = salesPriceCalc(item2OrginalPrice);

        // 結果表示
        System.out.println("item1の販売額:" + item1SalesPrice);
        System.out.println("item2の販売額:" + item2SalesPrice);

    }

    public static double salesPriceCalc(int originalPrice) {

        double taxRate = 1.08;
        double discountRate = 0.95;
        return Math.floor(originalPrice * taxRate * discountRate);

    }

}
 
【実行結果】
item1の販売額:102.0
item2の販売額:205.0

オブジェクト指向を利用する本当の理由

オブジェクト指向を利用する理由として、プログラミングの入門書には
オブジェクト指向を用いると、現実世界をプログラミングでそのまま表現できる」
という意のことが書いてあることが多いです。
その例として、
・犬に「ワン」と鳴かせ、猫に「ニャーン」と鳴かせる
・乗用車に普通に走らせ、レーシングカーに速く走らせる
といった例が用いられることが多いです。
 
しかし、実務での使われ方を見ると、
「現実世界をプログラミングでそのまま表現する」
というのは、目的ではなく手段である、という感があります。
実務では、
ソースコードから重複を取り除き保守性を高めるため」
という目的で使用されます。
オブジェクト指向では、現実世界の関係性を機械の都合に置き換える必要が無くなるため、ソースコードから重複を取り除きやすくなります。
オブジェクト指向の特徴である「継承」や「ポリモーフィズム」は、ソースコードから重複を取り除く上で強力な特徴です。
(同じくオブジェクト指向の特徴として挙げられる「カプセル化」も、オブジェクト指向のルールを守らせるという意味で強力な特徴です)
 
目的が保守性の向上であるため、現実世界では意識されにくい「思考ルーチン」「出力機能」といったものも、クラス化されることがあります。
また、保守性の向上に繋がらないのであれば、現実世界にあるものをクラスとして記述しないこともあります。
 
重複を取り除くことによる保守性向上、というのはイメージしにくい所だと思いますので、以下ではJavaのサンプルコードを用いて説明していきます。
 
----
 
今回のサンプルコードでは、「乗り物に乗って、タイミングを見計らって、乗り物を走らせる」という処理を実装していきます。
「乗り物に乗る」という処理と「乗り物を走らせる」という処理は別々のタイミングで実行する必要があるので、別々に実装する必要があります。
また、乗り物は複数種類があり、それぞれの種類毎で異なる処理を行う必要があるとします。
 
オブジェクト指向を使用しない場合は、下記に示すようなサンプルコードになります。
 
「乗り物に乗る」という処理と「乗り物を走らせる」という処理は、別々のメソッドとして実装しています。
乗り物の種類毎で処理を分ける必要があり、オブジェクト指向を使用しないサンプルコードではフラグ変数により分けています。
 
注目するべきは、各々のメソッドの中でフラグ変数を参照していることです。
各々のメソッドの中でフラグ変数を見ているため、フラグ変数を参照する箇所はメソッドの数だけ存在することになり、フラグ変数の参照方法の変更(例えば「自転車」を示すフラグが2から3に変更になった場合)が発生した場合の修正箇所が増え、保守性が低下します。
具体的には、一部の箇所だけ修正し忘れるようなバグが発生しやすくなります。
 
オブジェクト指向を使用しない場合のサンプルコード】
・OOPTestMain.java
public class OOPTestMain {

    public static void main(String args) {

        // 自動車を定義
        int vehicleFlag = 1;

        // 乗る動作
        ride(vehicleFlag);

        // 準備(5秒待つ)
        System.out.println("準備中です…");
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
        }

        // 走る動作
        run(vehicleFlag);

    }

    public static void ride(int vehicleFlag) {

        // 自動車の場合
        if (vehicleFlag == 1) {
            System.out.println("ドアを開けて運転席に座る");
        // 自転車の場合
        } else if (vehicleFlag == 2) {
            System.out.println("またがる");
        }

    }

    public static void run(int vehicleFlag) {

        // 自動車の場合
        if (vehicleFlag == 1) {
            System.out.println("アクセルを踏み込む");
        // 自転車の場合
        } else if (vehicleFlag == 2) {
            System.out.println("ペダルをこぐ");
        }

    }

}
 
【サンプルコードの実行結果】
ドアを開けて運転席に座る
準備中です…
アクセルを踏み込む
 
----
 
サンプルコードにオブジェクト指向を適用すると下記のようなコードになります。
 
注目するべきは、継承やポリモーフィズムを使用することで、フラグ変数参照に相当する記述を削減できていることです。
フラグ変数参照に相当する修正としては、クラス名の変更がありますが、クラス名変更が発生したとしても修正箇所は1カ所に留まるため、保守性が向上します。
 
オブジェクト指向を使用する場合のサンプルコード】
・OOPTestMain.java
public class OOPTestMain {

    public static void main(String args) {

        // 自動車を定義
        OOPTestVehicle vehicle = new OOPTestCar();

        // 乗る動作
        vehicle.ride();

        // 準備(5秒待つ)
        System.out.println("準備中です…");
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
        }

        // 走る動作
        vehicle.run();

    }

}
 
・OOPTestVehicle.java
public interface OOPTestVehicle {

    public void ride();
    public void run();

}
 
・OOPTestCar.java
public class OOPTestCar implements OOPTestVehicle {

    @Override
    public void ride() {
        System.out.println("ドアを開けて運転席に座る");
    }

    @Override
    public void run() {
        System.out.println("アクセルを踏み込む");
    }

}
 
・OOPTestBicycle.java
public class OOPTestBicycle implements OOPTestVehicle {

    @Override
    public void ride() {
        System.out.println("またがる");
    }

    @Override
    public void run() {
        System.out.println("ペダルをこぐ");
    }

}

EmEditorとサクラエディタの性能比較(巨大ファイルを開く)

EmEditorは、サクラエディタとほぼ同時期(2000年頃)に生まれたWindows用のテキストエディタです。
サクラエディタと比較すると、巨大ファイルを開く時の速さに定評があります。
 
実際にどれほどの差があるのかを計測しましたので、計測結果を公開します。
先に結論を書くと、今回の計測では、EmEditorの方が40倍以上ファイルを早く開けるという結果になりました。
 
【動作環境】
・OS:Windows8.1 64bit
・CPU:Inter(R) Core(TM) i5-4210U CPU @ 1.70GHz 2.40GHz
・メモリ:8.00GB
・ディスク:SSD 128GB
EmEditorのバージョン:Version 21.6.1
サクラエディタのバージョン:Ver. 2.2.0.1
 
【開閉対象のファイル】
・約170MB(179,148,566バイト)
 
【計測結果】
EmEditor(Free版)
 1回目:0.52秒
 2回目:1.13秒
 3回目:0.53秒
 平均 :0.73秒
 
サクラエディタ
 1回目:30.56秒
 2回目:31.93秒
 3回目:31.72秒
 平均 :31.40秒

二重サブミットのテストにはクリック連打ツールを用いる

Webシステムでは、同一のサブミット(リクエスト)が二重送信されることによる障害が起こり得ます。
二重サブミットが発生する原因としては、以下の3つが挙げられます。
①サブミットボタンを連打する
②ブラウザの戻るボタンで遷移元の画面に戻り、再度サブミットボタンを押下する
③サブミット後に遷移する画面で、ブラウザよりリロード操作が行われる
 
これらの原因による二重サブミットは、実装により回避することができます。
回避できているかどうかは、①~③の操作を行うことでテストすることができます。
 
----
 
ここで、①の操作については、手動でのクリックでは発生しないことがあるので、注意が必要です。
手動の場合は約0.1秒おきにクリックするのが限度だと思うのですが、端末やWebシステムの性能が良すぎる場合、手動で2回目のクリックを行う前に画面遷移が行われてしまい、二重サブミット対策の実装が行われていないのにも関わらず二重サブミットの問題が発見できないことがあります。
 
そこで、①の操作を試す場合は、クリック連打ツールを試すのが有効です。
クリック連打ツールであれば、0.01秒おきや0.001秒おきのクリックが可能になり、性能が良い場合にも二重サブミットの問題をより高い精度で発見することができるようになります。
 
以下は、自作のWebシステムで、クリック連打ツール「速打くん」を試した結果です。
自作のWebシステムは、掲示板に文章を投稿するというだけの簡単なものであり、クリックでは簡単に二重サブミットが発生しないほど性能は良いです。
このような性能が良いシステムに対しても、連打くんで0.01秒おきにクリックを行うことで、二重サブミットの発生を確認することができます。
 
・実行前

f:id:akira2kun:20211229193103j:plain

・実行後

f:id:akira2kun:20211229193122j:plain