JavaのWebアプリは何から作る?要件から画面・処理・クラスを考える手順

ServletやJSPの書き方は分かってきた。でも「Webアプリを一つ作って」と言われると、何から手を付ければよいか分からない。その場合は、お客様の要求を簡単な画面案に起こし、入力値と表示結果の例を入れてみましょう。「この画面でやりたいことができるか」を話し合うと、必要な機能やルールが具体的になります。
この記事では、一つの見積もりアプリを題材に、要求 → 画面案と具体的なデータ → 話し合い・修正 → 要件の整理 → 処理とクラスの設計 → 実装・確認までをつなげます。初めから仕様をすべて決めるのではなく、画面をたたき台に認識を合わせる進め方です。
Schoo「Java入門 中級」第6回「Webアプリケーション構築演習(基礎編)」に対応する補足記事です。Java 17・Tomcat 10.1を使う、Servlet・Model・JSPの構成を想定しています。環境構築ではなく、作る内容の決め方を扱います。
画面案で認識を合わせるのは、実務でも使われる方法
画面から考えるのは、単に「見たほうが分かりやすそう」という感覚だけに基づくものではありません。画面の見本や試作品を使って要求を確認し、修正しながら具体化する「プロトタイピング」は、実務の要件定義でも使われる方法です。画面の見本は「モックアップ」と呼ばれ、まだ動かない紙の画面案でも、表示項目や操作の流れを話し合う材料になります。
たとえば「見積もりを表示する」という文章だけでは、お客様は送料の内訳まで想定し、作り手は合計金額だけを想定しているかもしれません。具体的な値を入れた画面案を一緒に見ることで、この違いをコードを書く前に見つけられます。
- 必要な項目の抜けを見つける:「送料も表示したい」「入力内容を残したい」といった要求を確認できる。
- 業務ルールを具体化する:「この数量ならいくらになるか」を話し合い、計算条件と期待結果をそろえられる。
- 実装後の手戻りを減らす:認識の違いを早く見つけ、作り込む前に画面案や仕様を直せる。
大切なのは、画面をきれいに作ることではなく、仮の案を見せて確認し、合意した内容を要件として残すことです。画面だけでは分からない権限・性能・データの保存ルールなどは別途確認します。この記事では、数量の入力と見積もりの表示を題材に、この進め方を具体的にたどります。
まず、作るまでの考え方をフローチャートでつかむ
画面案は、決まった仕様を表示するだけのものではありません。お客様と作り手の認識の違いや、要求の抜けを見つけるためのたたき台にもなります。次の図は、プログラムの実行順ではなく、画面案を使って考えを具体化する道筋です。

仮の画面で「数量5なら合計2800円と表示する案です」と見せると、「送料の内訳も見たい」「その数量なら送料無料では?」など、具体的な確認ができます。仮の値は仮と明記し、合意したルールに合わせて画面案とデータを直します。
ここでそろえるのは、まず今回作る範囲の認識です。実装中に新しい不明点が出た場合も、画面案や要件へ戻って確認します。学習用の自主制作なら、利用者の立場でこの確認を行います。
1.要求を聞き、画面案に具体的な値を入れてみる
商品の数量を入れたら、送料を含めていくらになるか分かる画面がほしい。入力を間違えたら、直してやり直せるようにしたい。
この段階で、すべてのルールが分からなくても、話し合うための画面案は描けます。まずは紙や作図ツールで「数量欄」「見積もりを表示するボタン」「結果の金額」を置きます。まだServletやJSPを実装する必要はありません。
| 最初の画面案 | 入れてみるデータ | お客様に確認したいこと |
|---|---|---|
| 入力画面:数量欄と表示ボタン | 数量5(入力例) | 商品は1種類でよいか。数量は何個までか。 |
| 結果画面:合計金額 | 2800円(仮の表示例) | 合計だけでよいか。小計と送料も必要か。 |
| 入力を直す場面 | 数量21(確認用の例) | この数量は受け付けるか。受け付けないなら何を表示するか。 |
この表の2800円は、まだ合意した正解ではなく、表示を具体的にするための仮の値です。「数量5」「合計2800円」のような例があると、空の入力欄や「結果を表示」という文章だけより、何を確認すべきか話し合いやすくなります。
2.画面を見ながら話し合い、案を修正する
| 画面を見て出た話 | 画面案・仕様への反映 |
|---|---|
| 「合計だけでは送料が分からない」 | 結果に小計・送料・合計を分けて表示する。 |
| 「小計3000円から送料は無料にしたい」 | 数量5と6の例を並べ、送料が300円から0円になることを確認する。 |
| 「入力を間違えたら、最初から打ち直したくない」 | 入力内容を残し、理由を表示して直せる画面案を用意する。 |
| 「今回は金額を確認できればよく、注文はしない」 | 注文ボタンや支払い、DB保存は今回の対象に含めない。 |
こうして見つかった疑問を確認し、次の内容で認識を合わせたとします。画面の見た目だけでなく、計算ルール・入力条件・今回作らない範囲も記録します。
| 確認したいこと | この例で決めた内容 |
|---|---|
| 商品と価格はどこから来るか | 今回は1種類だけ。単価500円を固定で使う。 |
| 数量の範囲と形式 | 半角数字で1〜20。小数・符号・前後の空白は受け付けない。 |
| 送料のルール | 小計3000円未満は300円、3000円以上は無料。 |
| 結果に何を表示するか | 小計・送料・合計の3項目。 |
| 入力を間違えたらどうするか | 理由を表示し、入力値を残して同じ入力画面で直せるようにする。 |
| 注文や支払いまで行うか | 行わない。見積もり表示だけで、DB保存もログインも今回は対象外。 |
画面案を直したら、入力から結果表示までをお客様とたどり、意図どおりか確認します。未確認の箇所は確定したように描かず、「仮」「要確認」を付けて残します。
話し合いを反映した画面案が次の図です。入力画面Aと結果画面Bの2種類を用意し、入力エラーは画面Aにメッセージと入力済みの値がある状態として表します。図中のR1・R4・R5は、次の節で整理する要件番号です。

| 画面・状態 | 表示するもの | 操作後 |
|---|---|---|
| 画面A:初回 | 空の数量欄、1〜20の案内、表示ボタン | 入力値を送信する |
| 画面A:入力エラー | 入力済みの値、エラーメッセージ、表示ボタン | 値を直して送信し直す |
| 画面B:正常結果 | 小計・送料・合計、入力画面へ戻るリンク | 新しい見積もりを入力できる |
最初の画面案に「単価」の入力欄があっても、話し合いで単価500円の固定と決まったなら外します。画面案は完成品ではないため、確認した内容に合わせて項目や表示例を修正することが大切です。
今回は結果画面から戻ったときは、新しい見積もりとして空欄に戻る設計にします。「正常結果から戻るときも前の数量を残す」なら、それは追加で決める仕様です。入力エラー時に残すR5とは区別します。
3.合意した内容を要件と期待結果に整理する
要件とは、アプリに満たしてほしい条件です。画面を使って話し合った内容を、確認できる形でまとめます。ここではR1〜R6という番号を付けます。これは特別な規格ではなく、後で画面・コード・テストのどこに対応するか追いやすくするための目印です。
| 番号 | 要件 | 確認できる形にした条件 |
|---|---|---|
| R1 | 数量を入力できる | 数量欄を一つ設ける。必須・半角数字・1〜20を受け付ける。 |
| R2 | 小計を求める | 小計 = 500 × 数量。数量5なら2500円。 |
| R3 | 送料を求める | 小計3000円未満なら300円、3000円以上なら0円。 |
| R4 | 内訳を表示する | 小計・送料・合計を表示する。合計 = 小計 + 送料。 |
| R5 | 入力を直して再送信できる | 不正入力では計算を進めず、理由と元の入力値を入力画面へ表示する。未送信なら空欄。 |
| R6 | 見積もりだけを提供する | 注文登録・支払い・DB保存は行わない。 |
「使いやすくする」だけでは、完成したか判断しにくいままです。R5のように「理由を表示する」「入力値を残す」「再送信できる」へ分けると、実装するものと確認するものが具体的になります。R6は、今回作らない範囲を明確にするための条件です。
画面案に入れた仮の値も、合意した計算ルールに照らして確かめます。数量5なら2500+300=2800円、数量6なら3000+0=3000円です。この段階で、単なる表示例から、テストでも使う期待結果になります。コードを書く前に手計算しておくことで、プログラムの出力をそのまま正解だと思い込むことを防げます。
4.画面で確認したデータを、受け渡す名前と型へ落とし込む
見えているものをすべてフォームで送る必要はありません。送るのは数量だけで、小計・送料・合計はサーバー側で計算します。
| データ | 名前・型の例 | どこで用意するか |
|---|---|---|
| 入力された数量 | quantityパラメータ:String | ブラウザから届く。Servletで検証してintへ変換する。 |
| 計算に使う数量 | quantity変数:int | Servletが変換し、Modelへ渡す。 |
| 小計・送料・合計 | QuoteResult:3つのintを持つDTO | Modelが計算して作る。 |
| 結果画面へ渡すDTO | quote属性:QuoteResult | Servletがrequestへ入れ、JSPで取り出す。 |
| エラー時の元の入力 | quantityText属性:String | 変換前の文字列をServletで残す。 |
| 画面へ表示する理由 | error属性:String | Servletが利用者向けの案内を用意する。 |
Stringの数量とintの数量を分けるのは、abcや空欄など、数値にできない入力も届くためです。数値へ変換した結果だけを持とうとすると、変換に失敗した入力を再表示できません。
単価や合計をhiddenの入力欄に置いて、その値を正しいものとして計算に使う構成にはしません。ブラウザから届いた値は変更されている可能性があります。今回は単価500円と送料のルールをサーバー側に置き、合計もそこで決めます。
5.「受け取る・計算する・持つ・表示する」をクラスへ割り当てる
| 役割 | 作るファイル | 担当すること |
|---|---|---|
| 受け取りと画面への振り分け | controller/QuoteServlet.java | 数量を受けて検証・変換し、Modelを呼び、正常時とエラー時のJSPへ進める。 |
| 業務処理(Model) | model/QuoteModel.java | 数量範囲を検証し、小計・送料・合計を計算する。 |
| 計算結果のデータ(DTO) | model/QuoteResult.java | 3つの金額を一つの結果として保持する。 |
| 入力とエラーの表示 | quote-form.jsp | 入力欄と、必要に応じて入力値・エラーメッセージを表示する。 |
| 正常結果の表示 | quote-result.jsp | DTOから3つの金額を取り出して表示する。 |
| 安全なHTML出力の補助 | view/Html.java | 入力値などの文字列を、今回のHTMLの出力先に合わせてエスケープする。 |
最初からクラスをたくさん作るために分けるのではありません。「送料の条件を変える」「見積もり結果の見せ方を変える」ときに、どのファイルを修正すればよいか説明できる分け方にします。
この例のMVCのModel側には、業務処理のQuoteModelとデータを表すQuoteResultがあります。DTOをMVCのModelと別の対立する概念にするのではなく、処理とデータをそれぞれのクラスで表現しています。
R6でDB保存は不要と決めたので、今回はDAOやデータベースのテーブルは作りません。「Webアプリだから、必ずDBが必要」というわけではありません。
6.クラス同士で使う名前と型を先に合わせる
役割が決まっても、Servletが呼ぶメソッド名とModelが用意する名前が違えばつながりません。接続する部分だけでも、次のように決めておきます。
| つなぐ場所 | 決める内容 |
|---|---|
| フォーム → Servlet | POST /quote、パラメータ名はquantity。 |
| Servlet → Model | QuoteModel.calculate(int quantity)を呼ぶ。 |
| Model → Servlet | 戻り値はQuoteResult。範囲外ならIllegalArgumentExceptionで通知する。 |
| Servlet → 結果JSP | requestのquote属性にDTOを入れ、/quote-result.jspへforwardする。 |
| Servlet → 入力JSP | quantityTextとerror属性を入れ、/quote-form.jspへforwardする。 |
| 結果JSP → DTO | getSubtotal()・getShippingFee()・getTotal()で値を取り出す。 |
quantity・quantityText・quoteは別の用途の名前です。特に setAttribute("quote", result) と getAttribute("quote") の属性名は一致させます。Javaの変数名resultまでquoteにそろえる必要はありません。
// Servletの正常時の接続部分だけを抜粋
QuoteModel model = new QuoteModel();
QuoteResult result = model.calculate(quantity);
request.setAttribute("quote", result);
request.getRequestDispatcher("/quote-result.jsp")
.forward(request, response);
これは接続を確認するための抜粋です。quantityの取得・検証・変換、例外処理、importなどを省略しているため、この部分だけでServlet全体にはなりません。ファイル単位のコードは 入力チェックと再表示の記事に掲載しています。
7.正常時とエラー時の手順を、日本語で書く
正常時:数量5を送った場合
- ServletがquantityというパラメータをStringの”5″として受け取る。
- 必須と形式を確認し、intの5に変換する。
- Modelが1〜20の範囲を確認し、小計2500・送料300・合計2800を計算する。
- ModelがQuoteResultに結果をまとめて返す。
- Servletがquote属性に結果を入れ、結果JSPへforwardする。
- JSPが3つの金額をHTMLへ出力し、ブラウザに結果が表示される。
エラー時:数量21を送った場合
- Servletが元の文字列”21″をquantityText属性に残す。
- 文字列は整数にできるので、intの21をModelへ渡す。
- Modelが範囲外を例外で知らせる。計算結果は作らない。
- Servletが例外を受け、error属性に案内を入れて入力JSPへforwardする。
- 入力JSPが21と案内を表示する。Servletではreturnし、後続の正常処理は実行しない。
空欄やabcの場合は、Modelを呼ぶ前の検証で止めます。正常時の道だけでなく、途中で止めた後にどこへ戻るかまで書くと、必要な属性と画面が抜けにくくなります。
今回のエラー時は400、正常時は200を返す方針です。画面の見た目だけでなく、HTTPの応答と、エラー後に計算しないことも確認対象にします。forwardはサーバー内で行い、JSPが作ったHTMLがブラウザへ返ります。
8.まずModelを動かし、その後で画面とつなぐ
ここからは、合意した画面案を動くプログラムにする段階です。先に画面案を描いたことと、Javaコードをどこから実装するかは別の話です。今回はDBがなく計算も小さいため、計算結果が正しいかをJavaだけで確かめてから、Webの入力と表示をつなぐ順番にします。
| 順番 | 作業 | その段階の完了条件 |
|---|---|---|
| 1 | QuoteResultの項目とgetterを作る | 小計・送料・合計を名前で取得できる。 |
| 2 | QuoteModelの検証と計算を書く | 数量5・6の結果と、0・21の拒否をJavaだけで確認できる。 |
| 3 | 入力JSP・結果JSPの項目を用意する | 画面案と同じ項目がある。表示用の仮の値があれば、接続前に置き換える。 |
| 4 | Servletで正常な入力をModelへ渡す | フォームの数量5から、2800円の結果画面まで通る。 |
| 5 | 入力不正の分岐と再表示をつなぐ | 21で入力画面へ戻り、21と理由が残る。直して再送信できる。 |
| 6 | 要件に対応した試験を行う | 未入力・境界値・再送信など、決めたケースの期待結果を満たす。 |
2の時点で送料が間違っていれば、JSPやURLを調べる必要はありません。Javaだけの計算が正しく、画面の表示だけ違うなら、属性名やgetterの使い方を調べます。少しずつ接続して確認すると、問題の場所を絞りやすくなります。
次の2ファイルは、要件R2〜R4から作った計算部分です。ServletやJSPを使わずに実行確認できます。
model/QuoteResult.java
package model;
public class QuoteResult {
private final int subtotal;
private final int shippingFee;
private final int total;
public QuoteResult(int subtotal, int shippingFee, int total) {
this.subtotal = subtotal;
this.shippingFee = shippingFee;
this.total = total;
}
public int getSubtotal() {
return subtotal;
}
public int getShippingFee() {
return shippingFee;
}
public int getTotal() {
return total;
}
}
model/QuoteModel.java
package model;
public class QuoteModel {
public QuoteResult calculate(int quantity) {
if (quantity < 1 || quantity > 20) {
throw new IllegalArgumentException("数量は1〜20で指定してください。");
}
int subtotal = 500 * quantity;
int shippingFee = 300;
if (subtotal >= 3000) {
shippingFee = 0;
}
int total = subtotal + shippingFee;
return new QuoteResult(subtotal, shippingFee, total);
}
}
DTOの項目は「結果画面へ何を渡すか」から、Modelのif文と式は「数量と送料のルール」から決まっています。コードの都合で要件を作っているのではなく、決めた要件をコードへ置き換えています。
9.要件とテストケースを対応させる
「動いたか」だけでなく、どの要件を確かめたかを残します。次の表は、そのまま確認表の出発点にできる例です。小計・送料・合計の順で金額を書いています。
| ケース/要件 | 操作・入力 | 期待する結果 |
|---|---|---|
| T1/R1・R2・R3・R4 | 数量1で計算する | 500円・300円・800円。下限を受け付ける。 |
| T2/R2・R3・R4 | 数量5で計算する | 2500円・300円・2800円。送料無料の直前。 |
| T3/R2・R3・R4 | 数量6で計算する | 3000円・0円・3000円。ちょうど3000円で送料無料。 |
| T4/R1・R2・R3・R4 | 数量20で計算する | 10000円・0円・10000円。上限を受け付ける。 |
| T5/R1・R5 | 数量0を送信する | 400。範囲の案内と0を入力画面へ表示し、計算しない。 |
| T6/R1・R5 | 数量21を送信する | 400。範囲の案内と21を入力画面へ表示し、計算しない。 |
| T7/R1・R5 | 数量を送らない/空欄を直接POST | 400。未入力を案内し、入力欄は空。 |
| T8/R1・R5 | abcを直接POST | 400。形式の案内とabcを再表示する。 |
| T9/R1・R5 | 2147483648を直接POST | 400。intの範囲を超える案内と元の文字列を再表示する。 |
| T10/R4・R5 | 21のエラー表示後、5へ直して送信 | 200。古いエラーではなく、2500円・300円・2800円を表示する。 |
送料の境界は、小計3000円です。このアプリは単価500円・数量は整数なので、2999円の小計を作る入力はありません。仕様から実際に入力できる値を選ぶと、直前の数量5と境界の数量6が試験になります。単価や数量の仕様が変われば、選ぶ境界値も見直します。
T7〜T9は、ブラウザのrequiredやpatternに止められる場合があるため、HTTPクライアントなどで直接送信してサーバー側を確認します。別に、通常のブラウザで空欄やabcの送信を止められることも確認します。画面からの試験とModelの試験は、どちらか一つで代用できるものではありません。
この10ケースだけで品質をすべて保証するわけではありません。R1の小数・符号・空白・全角数字、属性名の不一致、JSPの直接表示、入力値のHTMLエスケープなども確認対象です。R6の「DBへ保存しない」は、DB処理を呼ぶコードがないことも確認します。画面の表示だけで保存の有無を判断しません。
Javaだけの確認と、画面を通した確認を分ける
次のQuoteModelCheck.javaは、Modelの計算と数量範囲を先に確かめるための実行用クラスです。上のmodelパッケージの2クラスと同じJavaプロジェクトに追加し、mainを実行します。追加のテストライブラリは使いません。
import model.QuoteModel;
import model.QuoteResult;
public class QuoteModelCheck {
public static void main(String[] args) {
QuoteModel model = new QuoteModel();
checkResult(model.calculate(1), 500, 300, 800);
checkResult(model.calculate(5), 2500, 300, 2800);
checkResult(model.calculate(6), 3000, 0, 3000);
checkResult(model.calculate(20), 10000, 0, 10000);
checkRejected(model, 0);
checkRejected(model, 21);
System.out.println("6ケースすべてOK");
}
private static void checkResult(QuoteResult result,
int subtotal, int shippingFee, int total) {
if (result.getSubtotal() != subtotal
|| result.getShippingFee() != shippingFee
|| result.getTotal() != total) {
throw new AssertionError("期待した見積もり結果と違います。");
}
}
private static void checkRejected(QuoteModel model, int quantity) {
try {
model.calculate(quantity);
} catch (IllegalArgumentException e) {
return;
}
throw new AssertionError("範囲外の数量を受け付けています。");
}
}
成功すると「6ケースすべてOK」と表示されます。Modelの4つの正常結果と、0・21で例外が通知されることを確認しています。AssertionErrorは、この確認用コードが期待と違う結果を見つけたときに知らせるために使っています。JUnitで継続的に管理するテストへ移すこともできます。
この6ケースは、画面の入力値が残ることや、HTTP 400が返ることまでは確認していません。T5・T6のWeb画面としての結果や、T7〜T10は、ServletとJSPをつないで別途確認します。「Modelのテストが通った」と「アプリ全体を確認した」は分けて記録します。
要件・実装・テストの対応表を一枚にする
| 要件 | 主な実装箇所 | 確認するもの |
|---|---|---|
| R1 数量の入力 | 入力JSP、Servlet、QuoteModel | 必須・形式の確認、T1・T4の範囲内、T5〜T9の不正入力 |
| R2 小計 | QuoteModel | T1〜T4の小計 |
| R3 送料 | QuoteModel | T2とT3の境界、T1・T4の送料 |
| R4 内訳表示 | QuoteResult、Servletのquote属性、結果JSP | T1〜T4の画面表示と、T10の正常な再送信 |
| R5 入力のやり直し | Servletのエラー処理、quantityText・error属性、入力JSP、Html | T5〜T10、元の文字列の安全な表示 |
| R6 見積もりのみ | アプリの構成と呼び出し箇所 | 注文・支払い・DB更新が含まれないこと |
表を作る目的は、書類を増やすことではありません。「この要件はどこに実装した?」「何を試せば確認できる?」に答えられる状態を作ることです。空欄があれば、実装やテストの抜けを探す手がかりになります。
仕様変更でも、対応表から影響をたどれる
「送料無料を小計5000円以上に変えたい」と言われた場合、R3を起点に次を見直します。
- 要件表の3000円を5000円に更新する。
- QuoteModelの送料条件を変更する。
- 入力画面に送料の説明を書いているなら、その案内も変更する。
- 旧境界の数量6は、送料300円・合計3300円になることを確認する。
- 新しい直前の数量9は小計4500円・送料300円・合計4800円、数量10は小計5000円・送料0円・合計5000円になることを確認する。
結果に表示する項目自体は同じなので、DTOのフィールドや結果JSPのgetterは、その変更だけなら増やす必要はありません。一方、手計算した期待値やテストコードの送料の期待値は更新します。コードだけ直して、仕様と案内とテストが古いままにならないようにします。
確認問題
- 依頼に「数量を入力すると見積もりが分かる」としか書かれていない場合、実装前に確認したいことを三つ挙げてください。
- 小計3000円以上で送料無料というルールで、数量5と6を確認する理由を答えてください。なぜこの例では小計2999円の入力を用意しないのでしょうか。
- Modelの計算は正しいのに、画面に送料を表示できません。要件R4をたどると、Model以外にどのデータ・受け渡し・表示を確認しますか。
- 「結果画面に数量も表示する」という要件が追加されました。DTO・Model・Servlet・JSP・テストのどこを見直しますか。数量が表示できれば、入力エラー時の確認も不要になるでしょうか。
演習後の確認:こちらのページを使って、自分で考えた内容を見直せます。
まとめ
- お客様の要求を画面案に起こし、仮の入力値・表示結果を入れて話し合う。
- 画面案を修正しながら認識を合わせ、ルール・エラー時の動き・対象範囲を要件に整理する。
- 画面で確認したデータと処理をServlet・Model・DTO・JSPへ割り当てる。
- 合意した期待結果を使って小さく実装・確認し、要件と実装とテストの対応を残す。