見出し画像

個サ作 #7 カレンダーLv.3 後編

こんにちは。

前回はカレンダーLv.3の実装途中でした。

If文の書き方、関数の使い方、Not演算子の使い方を学習しました。これでカレンダーLv.3を実装するための材料はそろいました。

では参りましょう。


今回のゴール

今回はカレンダーLv.3を完成させます。下図の動作が今回のゴールです。

カレンダーLv.3完成形の様子

2, 4, 6, 9, 11月は30日まで、それ以外は31日まで出力し、最後にメッセージボックスを表示します。


カレンダーLv.3 後編

前回、下記のソースコードまで進みました。

'分岐処理と関数、演算子の学習
Sub E_カレンダー3()

    If Not IsNumeric(Cells(1, 3).Value) Then
        MsgBox "月の値が不正です。" & vbCrLf & "C1セルに月を示す数字のみを入力ください。"
        Cells(1, 3).Select
        Exit Sub
    End If

End Sub
現時点の実装状態

If文にNot演算子を付与したことで、IsNumeric関数が返してくる結果が反転する、という話をして終わったのでしたね。続き(Ifの中の処理)を1行ずつ見ていきます。


vbCrLfは改行文字

その次はメッセージボックスで、指定したテキストを例のポップアップに載せて出す、という点は#3のHello, world!の頃からやってきた通りです。ですが、今回は見慣れない文字を連結していますね。vbCrLfです。

わたしは「ぶいびーしーあーるえるえふ」と読んでます。

このMsgBox関数の実行結果は以下の通りです。もうお気づきでしょうか。

MsgBox関数の実行結果

vbCrLfは|改行文字《かいぎょうもじ》といいます。この文字が改行を示しているんですね。試しに改行文字を取っ払って出してみると

vbCrLfを消して実行した様子

このようになります。改行がなくなりました。

初めてメッセージボックスのテキスト中に改行を入れるぞ、と思ったらこんな風に実装するのかと思うかもしれません。

        MsgBox "月の値が不正です。
        C1セルに月を示す数字のみを入力ください。"

でもこれだとうまくいかないので改行文字を使います。改行文字は他のプログラミング言語でもちょこちょこ使う機会があるのでこんなものがあるんだと、頭の片隅に置いておいてください。

他のプログラミング言語では「vbCrLf」とは書かずに別の専用文字があります。


ユーザエクスペリエンスの配慮

続きましては

If Not IsNumeric(Cells(1, 3).Value) Then
    MsgBox "月の値が不正です。" & vbCrLf & "C1セルに月を示す数字のみを入力ください。"
    Cells(1, 3).Select
    Exit Sub
End If

これ↓です。

    Cells(1, 3).Select

これ自体はカレンダーLv.2でもやりましたね。セルを選択状態にすることで、ループ中に値の書き出しが進むさまをリアルタイムで見る、という目的でした。

では今回はどうでしょうか。エラーを検知して処理を終了するぞ、というタイミングでやっています。これには何の意味があるのか。

エラーが発生したことをメッセージで伝えるとき、同時に我々が考えなければならないのが、次にユーザがすべき行動を促すことなんですね。これはユーザにより快適なシステム体験を提供するために重要な考え方です。

メッセージでは

次の行動がメッセージで示されています。

と伝えています。ここまで言えばユーザは迷うことなく次のアクションがわかるでしょう。しかし、限界までこちらから配慮するのがユーザに対して親切なシステムというものです。

ですので、気付いてましたでしょうか?前回の記事で最初にエラーチェックの様子をGif画像でお見せしたとき

メッセージボックスを閉じてセルの選択位置が自動でC1セルになるところまで記録しているんですね。

次がIf内の最後の処理です。

    If Not IsNumeric(Cells(1, 3).Value) Then
        MsgBox "月の値が不正です。" & vbCrLf & "C1セルに月を示す数字のみを入力ください。"
        Cells(1, 3).Select
        Exit Sub
    End If

Cells(1, 3).Selectの次の1行にご注目。


処理を途中で終了する方法

        Exit Sub

↑こんな処理をしていますね。英単語「Exitイグジット」には出口や逃げ道という意味があります。そうです、文字通りここは処理の途中で現れた出口になっています。

つまり、この処理を使うと、後続処理に進むことなく処理を終了するんですね。エラーチェックに抵触したということはこれ以上うしろの処理に進む意味がないことを意味します。

ですので、ここで処理を終了します。これはよく使う手法です。

これでひとつめのエラーチェックの解説が完了しました。次に進みましょう。


数値の範囲チェック

数値かどうかのチェックをしたことで、その後の処理においてはC1セルにはなにかしらの数値が入っていることが確約されている・・・と思いますか?

実はひとつ罠があって、先ほどのIsNumeric関数。

月を空白にして実行する様子

月の値を空白にして実行しても数値かどうか?のエラーチェックを通過してしまうんです。ですので、次にチェックしたい内容は月が空白でないこと、月として有効な数値であること、になります。


続きの実装にいきましょう。ここまでを整理すると今C1セルに入っている値は何かしらの数値か空白です。一旦変数に入れましょう。

下記を下図のように実装ください。

    Dim month As Integer: month = Cells(1, 3).Value
赤枠のソースコードを反映してね

この変数に入れるという行為、どうして一つ目のエラーチェックを終えてからやるか、おわかりでしょうか。

まず変数monthはその名前からしても月を示す数値を保持する変数です。ということはIntegerインテジャーで宣言したいんですね。ですが、一つ目のエラーチェックを行う前は必ず数値が入るとは限りません。

Integer型の変数に文字列を入れるとどうなるか、それは#4でやりましたね。型違いのエラーが発生します。これを回避するためにまずC1セルの値が数値であることがプログラム上で確定してから変数に代入しています。


さて、それでは次のIf文です。ここでは1より小さいもしくは12より大きい場合、エラーとします。

ひとつめはIsNumeric関数だけでやりたいことが網羅できましたが、今度は2つの条件を検証しないといけません。

ここで新しい演算子の登場です。Orオア演算子です。書いてみましょう。下記を下図のようにお願いします。

    If month < 1 Or month > 12 Then
        MsgBox "月の値が不正です。" & vbCrLf & "C1セルに1~12の数字を入力ください。"
        Cells(1, 3).Select
        Exit Sub
    End If
赤枠のソースコードを反映してね

いかがでしょうか。読んでそのまま、変数monthの値が1より小さい、もしくは12より大きかったらこの数式はTrueを返します。つまりエラーですね。

逆に変数monthが1から12の間の値を保持していたら month < 1 にもmonth >12 にも該当しません。つまりFalseを返します。

では、試しに実行してみましょう。

動かしてみてる様子

はい、C1セルに「23」と入力してことで数値の範囲チェックに引っ掛かりました。期待通りの挙動です。

本項の最初に

次にチェックしたい内容は月が空白でないこと、月として有効な数値であること、になります。

と、言いました。ですが、結局「空白かどうか?」のチェックはしていませんよね。これはですね、結果的に・・・

    If month < 1 Or month > 12 Then

この条件式が空白の場合も弾いてくれる(エラーとしてくれる)ので明示的に条件式には含めない、という選択をしました。


日付出力部の実装

さて、ここからやっと日付の出力部分を実装していきます。

カレンダーLv.2でForループによる出力を実装しましたよね。今回はそこに対して、If文のロジックを追加する形になります。

ですので、前回の実装部分をまるっとコピーして続きに書いてください。1点注意があって、コピーしただけだと日付の出力列が2列目のままです。

ですので、コピー+要所の変更をしましょう、下図のように。

あなたにしてほしい操作。ショートカット使ってます。

つまり、下記のソースコードを下図のように実装できていればOKです。

    Dim day As Integer: day = 1
    
    Dim i As Integer
    For i = 2 To 32
        Cells(i, 3).Value = day & "日"
        day = day + 1
        Application.Wait [Now() + "0:00:00.05"]
        Cells(i, 3).Select
    Next
現在のソースコードの期待値

はい、ありがとうございます。これでC1セルに適切な数値を入れさえすればカレンダーLv.2と同じ動きをします。

いよいよ大詰めです。2, 4, 6, 9, 11月だったら30日の出力で処理を終了するようにします。


If文のネスト

月によって、30日を出力した時点でループを抜けるのか、それとももう1周処理を繰り返すのか、という分岐を実装します。

ここでは日の値と月の値、どちらも見なければなりません。まず30日の時だけ作動するロジックを組みたい。とすると以下のIf文を書くことになります。

        If day = 30 Then
        
        End If

追加位置はここ↓。

クリックで拡大表示できます。

上記で追加したIf文「変数dayが30だった場合」の中にさらに月が2, 4 ,6 ,9 11のいずれかだった場合、という処理を組みます。ここでIf文の応用編!

If文は入れ子にすることができます。入れ子のことをネストともいいます。If文の中にIf文を書く、ということですね。

下記のソースコードを追記することで

    If month = 2 Or month = 4 Or month = 6 Or month = 9 Or month = 11 Then
        Exit For
    End If

以下のようになります。

赤枠のソースコードを反映してね

これで変数dayが30かつ、変数monthの値が2, 4, 6, 9, 11のいずれかだった場合は、ループを抜けることができます。


Eixt Forでループを抜ける

Exit Forイグジットフォーも新要素です。エラーチェックの中でExit Subを使いました。Subで始まる処理を終了する、というものでしたね。

同じ理屈でExit ForはForで始まる処理を終了します。

Subのときはマクロ丸ごと終了したのですが、今回のExit Forは処理の流れで言うと、処理自体はまだ終わりませんね。残りの周回を処理しないでループを抜けるだけです。

それを証明するために1行追加しましょう。次の1行を

MsgBox "処理が終了しました。" & vbCrLf & month & "月を" & day & "日まで出力しました。"

次のように追加ください。

赤枠のソースコードを反映してね

もうひとつだけ、仕上げが残っているのですが、この状態で処理を動かしてみましょう。

はい、いかがでしょう。首尾よく動いていますね。31日まである月を指定すると当然31日まで出力します(微修正が要るので補足の章で触れます汗)。

上図の挙動をみていただくと30日まで出力してますけど、セルの選択位置はC29セルで止まっています。そこだけ仕上げて終わりにしましょう。


最後の微調整

セルの選択といえば

        Cells(i, 3).Select

この1行でした。これが今回追加したロジックの後ろにあるから最後のセル選択がされずにループを抜けてしまうんですよね。なので、日付出力処理の直後にこれをもってきます。

ソースコードを修正する様子

はい、ではこれで再度処理を動かしてみると・・・

動作確認する様子

ちゃんとセルの選択位置が最後の出力セルまでいきました。

なにか処理を実装するときに似たような処理を別の場所からコピーしてもってくる、ということはよくあります。

そういうときに今回のような一部だけ処理の位置を変えないといけない、ということは起こりがちです。

ですが、これを結構忘れがちなんですね。すでに動いているソースからコピーしてきたのだから大丈夫だろう、と。ですので、ソースコードの実装過程が簡単でも動作確認は必要なんだ、ということは覚えておいてください。


はい!カレンダーLv.3完了です!お疲れさまでした!


今回のふりかえり

今回学んだことを以下に列挙しますね。

  • vbCrLfはVBAの改行文字

  • エラーを通知したときはユーザに次の行動を示してあげる

  • Exit Subでマクロ処理を(途中であっても)終了できる

  • Exit Forでループ処理を抜けることができる

  • OR演算子で複数条件を「もしくは」と連結できる

  • If文はネスト(入れ子)構造にできる

  • AND演算子で複数条件を「かつ」と連結できる(補足で触れます)

実装のこと、使い勝手のこと、今回はどちらの観点もありましたね。

プログラミングの学びがあるのは当然なのですが、システム体験の話(上記の上から2つ目)が出てきたのは初めてではないかと思います。

システムを作る時に、実装できることはもちろん大事ですが、それ以上に重視したいのが、そのシステムが運用に耐えうるかどうかです。平たく言うと使えるかどうか

運用できる、ということは最低限の使いやすさが保証されている、ということです。せっかく作っても使えないと意味がないですからね。

この辺りは日々の生活の中でも養うことのできる感覚です。会員制サイトやSNS、街中でシステムに触れる機会(ATMなど)にストレスがないかどうか?に注意を払ってみてください。

もっとこうしたら良くなるのに、この手順がなければより便利なのに、など気付きを重ねることがアンテナを磨くことになります


ふりかえりとしては実装の話がメインでしたね。どれも今後も使っていくものなので、意識して覚えようとしないでOK。勝手に身に付きます。


今回の補足

試してもらってもいいところもありますが、読むだけでもOKです。

入力値の受取にはこんな方法もある

カレンダーLv.3において、今回はC1セルに入力された値を月の数値として採用する手法をとりました。

でも本当に学習用って感じでなんの汎用性もないですよね。ユーザの入力値を処理に使いたい場合、こんなやり方もあります。

Sub E_値受け取りお試し()
    Dim month As String
    month = InputBox("月の値を入力してください")
    MsgBox "今ユーザに入力された値は「" & month & "」です。"
End Sub

実行してみると・・・

InputBoxで入力を受け付けている様子

はい、唐突ですが「InputBoxインプットボックス」という関数が登場しました。ユーザに入力を促すダイアログを出せるんですね。

こんな方法もあるということで頭の片隅に置いておいていただけるとよいです。余裕のある方はカレンダーLv.3にて、C1セルの入力値を採用している処理をこのInputBox関数で受け取った値に差し替えてみてもいいでしょう。


「Is」で始まる名称について

カレンダーLv.3ではIsNumericイズニューメリック関数を使いました。これは引数が数値なのかどうかによってTrueかFalseを返してくれるんでしたね。

この手のBoolean型を返す関数やBoolean型の変数・定数はその名称を「Isイズ」から始まるものにするのが良いとされています。

これはリーダブルコードのp.33の記載を参考にしています。

この先もこのルールは守っていきますので、「Is」で始まるものは真偽値を持つ/返すと覚えておいてください。


ヨーダ記法の話

あのー、月を示す数値の妥当性を検証するエラーチェックのところでね、

    If month < 1 Or month > 12 Then

こういう式が出てきたじゃないですか。これね、

    If month < 1 Or 12 < month Then

こう書くかめちゃくちゃ悩みました。。悩みましたが、結果として前者を採用しました。

If文の条件式で変数となにか(今回なら1と12)を比較するとき、変数はどちらに置くのがいいか?は結構エンジニアの悩みの種です。これが可読性に直結しているからです。

これはリーダブルコードのp.84,85で解説されている内容でして、下記の指針が示されています。

左側:「調査対象」の式。変化する
右側:「比較対象」の式。あまり変化しない。

リーダブルコード p.84

今回はこれに倣い、左辺に変数を置きました。が!

    If month < 1 Or 12 < month Then

こちらを採用してもさして問題はないです。monthの値が1より小さいもしくは12より大きい場合は検知する、ということがわかりやすいですからね。

あなたが初学者ならまだ早いけど、もう一度リンクを貼っておきますね。


「ロジック」という言葉

今回、説明中に「ロジック」という言葉を何度か使っています

  • 今回はそこに対して、If文のロジックを追加する形になります。

  • まず30日の時だけ作動するロジックを組みたい。

  • これが今回追加したロジックの後ろにあるから(略)

「ロジック」とはプログラム中の処理内容やその流れを指します。この先もちょくちょく使う言葉ですので覚えておいてください(まあ文脈でわかると思いますけども)。

実務でも口語でたまに使います。ロジック(ドヤァ


And演算子の紹介

先にふりかえりの章で出してしまったのですが、And演算子の紹介です。

カレンダーLv.3のソースコードで

        If day = 30 Then
            If month = 2 Or month = 4 Or month = 6 Or month = 9 Or month = 11 Then
                Exit For
            End If
        End If

このようなIf文を組みました。If文はネスト(入れ子に)することができるんですよ、という話でしたね。

実はこのIf文、ネスト構造を使わなくても実現することができます。ここの条件式を言葉で表現すると

「変数dayが30かつ、変数monthが2か4か6か9か11だったら」

ということですよね。察しのいい方ならOr演算子が出た時点でなんとなく予感はしていたかもしれません。

この「かつ」に相当する演算子がAndアンド演算子です。先ほどの条件式をネストをやめてひとつのIf文で実装すると・・・

If day = 30 And (month = 2 Or month = 4 Or month = 6 Or month = 9 Or month = 11) Then
    Exit For
End If

このようになります。

1点ポイントがありまして、Or演算子の方は括弧がついていますよね。これね、例えば

If day = 30 And month = 2 Or month = 4 Or month = 6 Or month = 9 Or month = 11 Then

こういう条件式だったとしたら解釈に幅ができてしまうんです。書いてある通りに読むと「変数dayが30かつ変数monthが2もしくは変数monthが4もしくは・・・」ということになります。

「変数dayが30かつ変数monthが2」というのがひとつの塊になって、それに加えてOr演算子で変数monthが 4 か 6 か 9 か 11 かを検証するように見えますよね。

でも、変数monthに入る値はひとつだけですから、2であり4でもある、ということはありえません

そこで括弧を使ってAnd演算子がどの条件式に対して掛かっているかを明示するんです。And演算子とOr演算子を同時に使うときは括弧をつけることを忘れないでください。

ちなみに、上記で示した括弧を外した条件式で「4月」で処理を実行すると1日を出力した時点でExit Forに入って処理が終了します。立派なバグです。

「もしくは4」月のところでTrueになってしまうんですね。Or演算子は複数併記した条件式の中でひとつでもTrueであればTrueとして評価するので、その影響をもろに受けるわけです。


ごめんなさい!!微修正!

すみません!!。カレンダーLv.3において、最後にメッセージボックスを出すのですが、31日まである月を指定した場合・・・

こんな風になっちゃってます・・。32日・・・。

これはですね、もともと最後にメッセージボックスを出す予定がなくて、記事の執筆中にアドリブでこれをつけたら動作確認を一部怠ってしまい、その結果起きてしまった悲劇といえます(私の怠慢です・・)。

次の処理を追加してください。

ソースコードを修正する様子

変数dayが31だったらループを抜ける処理を追加しました。追加したのは

        ElseIf day = 31 Then
            Exit For

この2行ですね。これできちんと・・・

ちゃんと出たときのポップアップ(メッセージボックス)

「~31日まで出力しました」。と出るようになりました。

31日を出力したあとももう一度変数dayの値をインクリメントしてしまう点が問題だったので、そこを回避したということになります。


おわりに

おわりです!

今回も新しいこと盛りだくさんでしたね。そろそろカレンダーは飽きてきたでしょうか。大丈夫。次回は演習問題として世界のなべあつをやります!

今回の内容を以てプログラミングの基本3原則はクリアしたことになります(順次処理、反復処理、分岐処理ね)。一応のひと段落というか、STEP1クリア!みたいに捉えてもらっても良いです。

今回も使った脳ミソの分だけゆっくりお休みになってください。このまま次に進むもヨシ、数時間あけるもヨシ、数日あけるもヨシ、ここまでを今一度振り返るもヨシです。

次の記事でお会いしましょう。ありがとうございました。


この記事が気に入ったらサポートをしてみませんか?