🎯 最終演習

VB.NETで売上伝票を店舗別に集計してみよう(総合演習)

ここまでの VB.NET レッスンで学んだことを全部つないで、動くものを1つ完成させます。新しい構文は出てきません。クラス・Enum・List(Of T)・Dictionary(Of K, V)・For Each・Select Case——すでに練習したものだけで組み立てます。

作るのは売上伝票の集計です。伝票を一覧にし、店舗ごとに合計し、いちばん売れた店を見つけて、表の形にまとめます。

VB.NETがいまも現役なのは、こういう仕事があるからです。業務システムの集計や帳票の出力——「データを読んで、まとめて、表にする」という仕事で、VB.NETは長く使われてきました。この演習は、その骨格そのものです。

このページではコードを実行していません。VB.NETはコンパイルが必要な言語で、ブラウザの中では走らせられないためです。かわりにお手本と同じコードを書けたかを、その場で判定します(判定はあなたのブラウザの中だけで行われ、どこにも送信されません)。

表示している実行結果は本物です。このページを作るときに、実際にコンパイルして走らせた出力をそのまま載せています。

完成イメージ

最後まで進むと、このコードが完成します。下の実行結果は、このページを作るときに開発機で実際にコードを走らせて得たものです(このページ上では実行していません)。

===== 今週の売上 =====
 1日 本店    ドリンク      480円 x12 =   5,760円
 1日 駅前店   フード     1,280円 x 4 =   5,120円
 2日 本店    雑貨      3,200円 x 2 =   6,400円
 2日 本店    ドリンク      480円 x 9 =   4,320円
 3日 駅前店   ドリンク      520円 x15 =   7,800円
 3日 郊外店   フード       980円 x 7 =   6,860円
 4日 駅前店   雑貨      3,200円 x 1 =   3,200円
 4日 郊外店   ドリンク      520円 x 6 =   3,120円
 5日 本店    フード     1,280円 x 5 =   6,400円
----------------------------------------------
--- 店舗別 ---
本店      22,880円  好調
駅前店     16,120円
郊外店      9,980円
----------------------------------------------
伝票枚数: 9枚
売上合計: 48,980円
平均単価: 5,442円
最も売れた店舗: 本店(22,880円)

STEP 1伝票をクラスで表す

🎯 このステップのゴール: Sub Newで値を受け取り、金額を返すFunctionを持たせる

まずデータの形を決めます。伝票1枚が持つのは「日付・店舗・区分・単価・数量」の5つです。

Class Slipの中にPublicで並べ、Sub Newで受け取ります。Sub NewがVB.NETのコンストラクタ——オブジェクトを作るときに一度だけ呼ばれる特別な手続きです。

Me.Day = dayMeは「このオブジェクト自身」を指します。引数のdayとフィールドのDayを区別するために付けています。VBは大文字小文字を区別しないので、Meが無いと自分自身への代入になってしまいます

ここはVB.NET特有の注意点です。C#ならdayDayは別物ですが、VBでは同じ名前とみなされます。Me.を付ける習慣をつけてください。

そしてPublic Function Amount()——単価×数量を返すメソッドです。

この1つを持たせるかどうかで、後の書きやすさが変わります。金額が必要になるたびにs.Price * s.Countと書いていたら、その計算が何か所にも散らばります。消費税を足すことになったとき、全部探すはめになります。

「そのデータについての計算は、そのデータの近くに置く」——クラスを使ういちばんの理由がこれです。

なお実務ではPublic ReadOnly Propertyを使うことが多いのですが、ここではレッスンで扱ったPublicフィールドの形に揃えています

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
伝票枚数: 9枚
1日 本店 単価480円 x12
金額: 5760円

STEP 2For Eachで売上を合計する

🎯 このステップのゴール: リストを回して積み上げ、整数の割り算に気をつける

売上を全部足します。For Each s As Slip In slips——1枚ずつ取り出して、金額を足していきます

As Slipと型を書いているのに注目してください。省略もできますが、書いておくとその場で型が確定しますs.と打ったときに候補が出ますし、打ち間違いもすぐ分かります。

足し始める前にDim grandTotal As Integer = 0入れ物を用意するのを忘れないでください。

そして平均単価を出すところに、VB.NET特有の落とし穴があります。

grandTotal / slips.Countと書くと——結果が小数になります。VBの/常に小数の割り算だからです。

ここが多くの言語と違います。CやC#やJavaでは、整数どうしを/で割ると整数が返ります。VBは逆で、整数どうしでも小数になります

整数のまま割りたいときは\(バックスラッシュ)を使います。grandTotal \ slips.Countなら小数点以下が切り捨てられた整数が返ります。

この2つを取り違えると、金額に小数点が出たり、逆に端数が消えたりします。/は小数、\は整数」——VBを書くなら覚えておく価値のある区別です。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
売上合計: 48980円
平均単価(整数の割り算 \): 5442円
平均単価(小数の割り算 /): 5442.222222222223円

STEP 3EnumとSelect Caseで区分を振り分ける

🎯 このステップのゴール: 取りうる区分を限定し、ラベルを1か所で決める

商品の区分をStringで持つと、"ドリンク""drink"が混ざります。集計したときに別物として数えられて、合いません。

Enum CategoryDrink・Food・Goodsの3つに限定します。それ以外は書けません——打ち間違えればコンパイルが通らないので、実行する前に気づけます。

日本語のラベルを付けるのがKindLabelです。Select Caseで振り分けます。

VBのSelect CaseにはBreakが要りません。C言語やJavaのように、書き忘れて次の分岐に流れ込む事故が起きない設計です。

Case Elseを必ず書いてください。「そのどれでもなかったとき」を受け止める場所です。いまは3つしかないので通りませんが、あとで区分を1つ足したとき、ここが最後の砦になります

ラベルを関数に切り出しておく意味を、はっきりさせておきます。「ドリンク」を「飲料」に変えたい——直すのはこの関数の1行だけです。もし表示するたびにIfで書き分けていたら、書いた場所を全部探すことになります。

「同じ判断を2回書かない」——プログラムを保ちやすくする、いちばん基本の考え方です。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
1日 本店 ドリンク
1日 駅前店 フード
2日 本店 雑貨
2日 本店 ドリンク
3日 駅前店 ドリンク
3日 郊外店 フード
4日 駅前店 雑貨
4日 郊外店 ドリンク
5日 本店 フード

STEP 4Dictionaryで店舗別に集計する

🎯 このステップのゴール: キーの有無を確かめてから足し、決まった順で取り出す

店舗ごとに売上をまとめます。使うのはDictionary(Of String, Integer)——店舗名をキーに、売上を値に持つ対応表です。

集計の中身に必ず要る一手間があります。

If Not totals.ContainsKey(s.Shop) Then totals(s.Shop) = 0

まだ無いキーにいきなり+=すると、その店舗の初回で落ちます。無いものを読もうとした、というエラーです。だから「無ければ0を入れてから足す」という順にします。

この形はVB.NETの定番なので、そのまま覚えてしまってください。Dictionaryで数え上げるときは、ほぼ必ずこの3行が出てきます。

取り出すところにも同じ注意が要ります。If totals.ContainsKey(shop) Then amount = totals(shop)——1枚も伝票の無い店舗を引くと落ちるからです。

そして表示順です。Shopsという配列を別に用意して、その順で回しています。

Dictionaryは順番を保証しません。入れた順でも五十音順でもなく、実行するたびに変わることがあります。「本店・駅前店・郊外店」という決まった順で見せたいなら、その順番を自分で持つしかありません

これは言語を問わず踏む落とし穴です。手元では正しく見えたのに、本番で並びが変わって「表がおかしい」と言われる——覚えておいてください。

いちばん売れた店を探すのは「暫定の1位を置いて、勝ったら入れ替える」という型です。bestAmount-1から始めているので、売上0円の店でも1位になれます

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
本店: 22880円
駅前店: 16120円
郊外店: 9980円
空港店: 0円

STEP 5String.Formatで表に整える

🎯 このステップのゴール: 位置と幅と書式を指定して、桁を揃える

数字が並んでいるだけでは読めません。表の形に整えます

使うのはString.Formatです。{0,2}のような書き方をします。

読み方は3つに分かれます0が「何番目の引数か」、,2が「幅」、:#,0が「書式」です。

幅はプラスが右揃え、マイナスが左揃えです。{1,-5}なら5文字ぶんで左揃え——文字は左、数字は右に揃えると読みやすくなります。

#,0が金額の書式です。2144021,440になります。3桁ごとの区切りを自分で書く必要はありません

#0の違いも覚えておくと役に立ちます。0は「値が無くても0を出す」、#は「無ければ出さない」。だから#,0は「3桁区切りで、最低1桁は出す」という意味になります。

区切り線はNew String("-"c, 46)です。末尾のcが「これは文字1つであって、文字列ではない」という印——VB.NET特有の書き方で、これが無いと型が合わずに怒られます。

日本語は文字の幅が英数字の2倍で表示されるので、{1,-5}でも見た目は完全には揃いません。大きくは整うので実用になりますが、厳密に揃えたいなら帳票の仕組みを使うのが本筋です。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
----------------------------------------------
 1日 本店    ドリンク      480円 x12 =   5,760円
 1日 駅前店   フード     1,280円 x 4 =   5,120円
 2日 本店    雑貨      3,200円 x 2 =   6,400円
 2日 本店    ドリンク      480円 x 9 =   4,320円
 3日 駅前店   ドリンク      520円 x15 =   7,800円
 3日 郊外店   フード       980円 x 7 =   6,860円
 4日 駅前店   雑貨      3,200円 x 1 =   3,200円
 4日 郊外店   ドリンク      520円 x 6 =   3,120円
 5日 本店    フード     1,280円 x 5 =   6,400円
----------------------------------------------

STEP 6ひとつのSubにまとめて仕上げる

🎯 このステップのゴール: Reportにまとめ、好調な店舗に印を付ける

最後の仕上げです。ここまでの部品をReportという1つのSubにまとめます

SubFunctionの違いを、ここで確かめておきます。Function値を返すもの、Sub返さないもの。Reportは表示するのが仕事なのでSubです。

この区別は、読む人への合図になります。Subと書いてあれば「戻り値を期待しなくていい」と分かります。C系の言語でvoidと書くのと同じ役割です。

好調な店舗に印を付けます。If amount >= GoodSales Then mark = " 好調"——基準を定数GoodSalesにしてあるのが要点です。

20000を直接書いてはいけません。読んだ人に「この2万円は何の2万円か」が分かりませんし、基準を変えたいときに探し回ることになります

並べる順番にも意味を持たせます。まず伝票1枚ずつ、次に店舗別、最後に全体のまとめ——細かいものから大きいものへという流れです。帳票はたいていこの順に並んでいます。

完成です。GoodSales15000に変えてみてください。「好調」の付く店舗が増えます。10000にすれば3店舗とも付きます。

1つの数字を変えるだけで、判定が全部連動する——定数に切り出しておいた効果が、ここで確かめられます。

次に進むなら、この伝票をCSVから読むところです。LoadSlipsの中身を差し替えるだけで、Reportはそのまま使えます。データの作り方と使い方を分けておいた意味が、そこで効いてきます。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
===== 今週の売上 =====
 1日 本店    ドリンク      480円 x12 =   5,760円
 1日 駅前店   フード     1,280円 x 4 =   5,120円
 2日 本店    雑貨      3,200円 x 2 =   6,400円
 2日 本店    ドリンク      480円 x 9 =   4,320円
 3日 駅前店   ドリンク      520円 x15 =   7,800円
 3日 郊外店   フード       980円 x 7 =   6,860円
 4日 駅前店   雑貨      3,200円 x 1 =   3,200円
 4日 郊外店   ドリンク      520円 x 6 =   3,120円
 5日 本店    フード     1,280円 x 5 =   6,400円
----------------------------------------------
--- 店舗別 ---
本店      22,880円  好調
駅前店     16,120円
郊外店      9,980円
----------------------------------------------
伝票枚数: 9枚
売上合計: 48,980円
平均単価: 5,442円
最も売れた店舗: 本店(22,880円)

よくある質問

このページではコードを実行しないのですか?

していません。VB.NETはコンパイルが必要な言語で、ブラウザの中では走らせられないためです。このページではお手本と同じコードを書けたかどうかを判定する形にしています(判定はあなたのブラウザの中だけで行われ、コードはどこにも送信されません)。

ただし表示している実行結果は本物です。このページを作るときに実際にコンパイルして走らせた出力を、そのまま載せています。

/ と \ は何が違いますか

答えが小数になるか、整数になるかです。

VBの/整数どうしを割っても小数になります7 / 23.5です。

\整数の割り算で、小数点以下を切り捨てます。7 \ 23です。

ここは多くの言語と逆なので注意してください。CやC#やJavaでは/が整数の割り算になります。他の言語から来た人が、金額の計算で端数の扱いを間違えるのは、たいていこの違いが原因です。

Me. は必ず書かないといけませんか

引数とフィールドの名前が同じときは必須です。

VBは大文字小文字を区別しません。だから引数のdayとフィールドのDay同じ名前とみなされますDay = dayと書くと、自分自身に代入するだけで何も起きません。

Me.Day = dayと書けば、左が確実にフィールドを指します

名前が違えば省略できますが、Sub Newの中では常に付ける習慣にしておくほうが安全です。

Dictionary の表示順が実行するたびに変わります

Dictionaryは順番を持たない入れ物だからです。キーから値を素早く引くことに特化していて、並び順は保証されません。

決まった順で表示したいなら、ステップ4のように並び順を配列として自分で持ちますShopsを回せば、何度実行しても同じ順です。

「本店・駅前店・郊外店」のように意味のある順に並べたいときは、並べ替えより自分で持つほうが素直です。

🚩 次はこのカテゴリへ

VB.NETとC#は同じ.NETの上で動く、双子のような言語です。List(Of T)Dictionary(Of K, V)も、C#ではList<T>Dictionary<K, V>——書き方が違うだけで、中身は同じものです。C#カテゴリの最終演習では勤怠を集計します。同じ集計をLINQで短く書く方法も見られます。

C#のレッスンへ進む
広告スペース(バナー)
広告スペース(記事内)