見出し画像

個サ作 #9 カレンダーLv.4 前編

こんにちは。

前回は世界のなべあつ氏の芸をプログラミングしました。

これまでは組込関数というVBAに元から用意されている関数を使用して各種実装をしてきましたが、今回は関数を自分で作っちゃいます!

これができるようになると、飛躍的に作成できる処理や機能が増えます

万人に向けて提供されているものは使わずに、このシステム専用の関数を作るということです。わばオーダーメイドです。「オーダーメイド」って言葉だけでワクワクしちゃいますよね。

では参りましょう。


今回のゴール

目次からなんとなく察することができると思いますが、今回は

  • isYear関数

  • isMonth関数

  • isLeapYear関数

の作成とisDayWritable関数を仕掛かるところまでやります。まだカレンダーLv.4の完成には届きません。

動作的にはエラーチェックと初期化処理までなので、下図のようになります。

画像クリックで拡大できます。

年の値チェック、月の値チェック、クリア処理が働いている様子がわかりますね。


カレンダーLv.4 前編

さて、予告通り自作関数を用いたカレンダー作成機能を作ります。まずは完成形の動作をご覧いただきましょう。

完成形の動作

D1セルに年を表す数値、E1セルに月を表す数値を入力し、その後にマクロを実行します。

カレンダーLv.3までは2月の場合でも30日まで出力してしまい、「カレンダー」とは言ってもクオリティの低いものでした。ですが今回やっと2月の閏年も考慮したカレンダーを作成できるようになります。

それでは側を作成しましょう。「F_世界のなべあつ」の次に以下を入力ください。

'自作関数の学習
Sub G_カレンダー4()
    'エラーチェック
    
    
    '初期処理
    
    
    '本処理
    
    
End Sub
現在の実装状態。

今回は処理のおおよその流れをコメントしました。本格的なコーディングをする前にだいたいの流れがわかっているときは思考を整理する意味でもよくこのようにします。

では!参ります。


改めて、関数とは?

これまで用意されている関数を使用してきました。今一度改めて、自分で用意する、という観点から「関数」の作りをお伝えしましょう。

こちら↑の公式リファレンスに次のような記述があります。

Function プロシージャは、Function ステートメントと End Functionステートメントで囲まれた一連の Visual Basic ステートメントです。

関数プロシージャの記述

#3にて「Sub」で始まる処理群のことを「サブプロシージャ」と案内しましたが、「Functionファンクション」で始まる処理群は「ファンクションプロシージャ」です。

そして上記引用の中に「Function」ステートメントと「End Function」ステートメントで囲まれた一連の~という記述があるので、作りとしては・・・

Function

End Function

こんな感じになるであろうことは想像がつくでしょう。

さらに引用します。

Function プロシージャは、定数変数など、引数を取ることができ、これらの引数は、呼び出し元のプロシージャから関数に渡されます。 Function プロシージャに引数がない場合、その Function ステートメントに空のかっこセットを含める必要があります。

関数プロシージャの記述

呼出し元から変数、定数、式などの引数を受け取ることができる旨の記載があります。さらに、引数がない場合は空の括弧を書く必要があると、書いてありますね。つまり・・・

'引数がある場合
Function([受け取る引数の情報])

End Function
'引数がない場合
Function()

End Function

このような形になるでしょう。さらに引用します。

関数は、プロシージャの 1 つ以上のステートメント内で値をその名前に割り当てることで値を返します。

ここが初見では少々わかりづらいです。

「値をその名前に割り当てることで値を返します」とあるので、呼び出し元へ返す戻り値の書き方に関する記載ですね。この中にある「その名前」とはどの名前でしょう。はい、関数の名前です。ですので・・・

'引数がある場合
Function name([受け取る引数の情報])
    name = [戻り値となる値]
End Function

このような書き方で戻り値とできます。

戻り値の書き方の説明には「プロシージャの 1 つ以上のステートメント内で」という言葉が頭についていました。上記ソースコードでいうところの

name = [戻り値となる値]

この記載は関数中に複数回書ける、ということを意味しています

ここまでを読んで「SubとFunctionは書き方やその働きがよく似ているね」と思われた方もいらっしゃるでしょう。そうです、よく似ています。

この両者の代表的な違いとして「戻り値が返せるかどうか」があります。これを抑えておきましょう。

  • Subプロシージャ・・・戻り値を返せない

  • Functionプロシージャ・・・戻り値を返せる

です。また、Functionで記載した処理群(=関数)はマクロダイアログの処理一覧には表れません。

マクロダイアログに関数が表示されないことの説明


関数の書き方

細々こまごまとした説明が続きましたが、この項で書き方を整理します。

関数作成にあたって必要な情報として

  • これが関数であるという印(Function)

  • 関数の名前(前項でいうname)

  • 処理を行うための引数(前項でいう[受け取る引数の情報])

これらがあります。

構文は以下。

Function [関数名]([引数名] As [データ型])
    [ここに任意の処理]
End Function

このように書きます。

Functionファンクション」は日本語に訳すと「関数かんすう」という意味があります。そのままですね。

[関数名]は関数の名称です。IsNumericイズニューメリック関数とかRGBアールジービー関数とか既出のもので言うとその手の名前です。もちろん自分でつけます。

[引数名]は引数を関数内で使用するために必要です。引数ひきすうと言ってはいますが、振る舞いとしては変数と同様です。引数が不要な関数だったら後ろのデータ型とともに不要です。

[データ型]は#4の変数宣言で説明したのと同じデータ型です。StringやInteger、Booleanなどです。

今回はカレンダーLv.4を通して5つの関数を作るのですが、その1つ目を例に次項から説明します。


isYear関数 作成編

では先ほどの関数の書き方に倣って関数を書きましょう。

最初の関数はisYearイズイヤー関数です。これは指定された値が年を示す値として有効かどうかを返します。

#7の補足にて「Is」で始まる名称について説明しましたね。この関数は真偽値を返すので「is」で始めています

※「i」が小文字な点は今回の補足で触れます。

では書きましょう。以下をG_カレンダー4()の下に書いてください。

'年の妥当性チェック
Function isYear(year As String)

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

がわはこのようになります。数値かどうか?の関数はIsNumericという名前でした。今回は有効な年かどうか?だからisYearイズイヤーです。

そして引数ですね。ここは変数宣言からDimを省いた形です。yearなので、型はIntegerかと思いきやStringです。これはこれから検証するために外部から受け取るので、なんでも受け取ることができるStringにしています。

#4の暗黙的型変換の説明で検証した通り、Integer型として受け皿を用意したところに文字列を受け取るとその時点でエラーとなってしまうためです。

次にこの中身を書きましょう。

どんな処理をするか。今回は変数yearの値が数値でかつ4桁であること、でいきます。そして重要なのが、処理結果の返し方です。

このisYear関数内で答えを出しますよね。与えられた値は年として有効だったのか無効だったのか。それをTrueもしくはFalseで返す、という話でした。

関数を使うことを呼び出すとかcallコールする、と言います。つまり、関数を使った側のことを呼び出し元と言います。

前項で説明しました通り、関数内から呼び出し元に値を返す場合、返したい値を関数名に代入します

初学者向けにまず、変数returnValueを宣言しましょう(学習用に順を追いたいだけなのでこの変数は後で削除します)。

    Dim returnValue As Boolean
あとで削除するソースコード

では、条件式を書いて、今宣言した変数returnValueに代入するようにします。

    returnValue = IsNumeric(year) And Len(year) = 4

IsNumericはカレンダーLv.3でやったのでいいでしょう。またひとつ新たな関数が出てきました。Len関数です。

このLenレンというのはLengthレングスの略です。Lengthとは「長さ」を意味する英単語で、プログラミングにおいては桁数や文字数、サイズの意味で使われることが多いです。

Len関数は指定した値の文字数を返してくれます。今回はその結果を4と比較しています。

    returnValue = IsNumeric(year) And Len(year) = 4

ですので、これで変数yearが数値かつ4桁の値かどうか?その結果を変数returnValueに代入することができます。

1行の中にイコールが2つあるから紛らわしいですが、ちゃんとAnd演算子で繋がれた2つの評価を条件式として最終的にreturnValueに代入するように処理が動いてくれます。

どうしてもわかりにくいな、と思う方は

isYear = (isNumeric(year)) And (Len(year) = 4)

こんな風に書いてもOKです。ふたつの条件式をそれぞれ括弧で囲んでいます。これをやると少しわかりやすくなりますね。


そして、先ほど呼び出し元に結果を返すには関数名に代入すると説明しました。ですので最後に

    isYear = returnValue

とすればこの関数はひとまず出来上がりです。

しかし先ほど、変数returnValueは学習用に順を追いたいだけだといいました。そうです。よりすっきりした形にして完了としましょう。

変数returnValueを経由せずとも

IsNumeric(year) And Len(year) = 4

この条件式自体がTrueかFalseの結果そのものなんです。なので・・・

    isYear = IsNumeric(year) And Len(year) = 4

このように書けばすっきり爽快出来上がりです!おめでとう!

直接、関数名に代入していますね。

初めての自作関数、完成!


isYear関数 call編

では自分で作った関数はどうやって呼び出すのか?となりますが、実は組込関数とまったく同じです。

今回はD1セルに年を示す数値を入れる、という話だったので・・・

    If Not isYear(Cells(1, 4).Value) Then
    
    End If

このソースコードを「'エラーチェック」の直後に書きましょう。

年チェックのエラーチェックを呼び出すソースコード

こうですね。カレンダーLv.3でもやりましたが、今回も年として有効な数値であればIfの評価に入る必要がありません。ですので、有効ではなかったら、ということでNot演算子を付与しています。

Ifの中には前回と同じように次の処理を書きましょう。

        MsgBox "年の値が不正です。" & vbCrLf & "D1セルに年を示す数字を4桁で入力ください。"
        Cells(1, 4).Select
        Exit Sub

はい、もう解説は不要でしょう。ユーザへメッセージを通知し、入力セルを選択状態にし、処理を終了、という3ステップをしています。

これで年のエラーチェックは完了ですね。最後に動作確認です。

年の妥当性チェックが動く様子

次に月のチェックをします。年チェックと似たようなことをしますが、ほんの少し罠があります。


isMonth関数 作成&call

それではisYear関数の下に次のソースを入力ください。

'月の妥当性チェック
Function isMonth(month As String)

End Function

OK、ありがとうございます。

月の妥当性は前回やったカレンダーLv.3と同じように、数値でありかつ1から12の間の数字かどうか?でいきましょう。

前項の最後に「少し罠がある」と言いました。まずはそれを実証します。

年チェックと同じノリで実装するならば・・・

    isMonth = isNumeric(month) And 1 <= month And month <= 12

こういうソースになります。例えば、これに対して呼び出し元で

    Debug.Print isMonth("12")

このように書いてイミディエイトウィンドウに何が表示されるかを見てみましょう。これは、実際にはセルの入力値をisMonth関数に渡すので、「12」が入力されていたと仮定した場合、どうなるかを確認しています。

はい、上図Gif画像を見ていただくとイミディエイトウィンドウには「True」が出力されていますね。12は月として有効なので当然です。

ではですね、次に「abc」という数値ですらないものを確認してみましょう。期待値としては「False」を返してほしいです・・・!

はい。「型が一致しません。」というエラーが出てしまいました。何が原因かというと「abc」に対して1から12の間に該当するかどうかを評価しようとしてるんですね。

でも文字列だからそれはできないと。型が違うでしょと。そういうエラーになっています。

ですので、ここではisYear関数を踏襲しないでカレンダーLv.3でやったように数値かどうか?の評価と1から12の間の値かどうか?の評価は分けて実装する必要があります。

数値であることが確定してから1から12の範囲チェックを行います。ですので、以下のように書きましょう。

    If IsNumeric(month) Then
        isMonth = 1 <= month And month <= 12
    End If

Ifが2回登場すると思われたかもしれません。1回でいいんです。2回目は分岐しないでもうその結果を返すだけなのでisYear関数と同じ要領で変数isMonthに結果を代入します。

代入をしているイコールと条件式の小なりイコールが続いて読みにくい場合は・・・

isMonth = (1 <= month And month <= 12)

このように書いてもOKです。

'月の妥当性チェック
Function isMonth(month As String)
    If IsNumeric(month) Then
        isMonth = (1 <= month And month <= 12)
    End If
End Function

これでisMonth関数も完成しました。動作確認しましょう。

「qq」と「55」でエラーを発しています。期待通りだっ

ここでね、察しのいいあなたならこんなことを思ってくれたと思います。

Ifの条件分岐に入らなかったとき、この関数は何を戻すの?

とね。こんな↓記事があります。

結論から言うと、勝手にFalseを返してくれるんですね。そういう仕組みがあるので、今回は利用します。Booleanブーリアン型のデフォルト値はFalseです。

つまり、この場合だと「月として有効な値ではない!」という結果を返します。

さて、呼び出し元の実装も済ませましょう。

    If Not isMonth(Cells(1, 5).Value) Then
        MsgBox "月の値が不正です。" & vbCrLf & "E1セルに月を示す数字を入力ください。"
        Cells(1, 5).Select
        Exit Sub
    End If
赤枠のソースコードを反映してね

isYear関数を参考にすれば特に難しくはないでしょう。

今の段階では下図のようなソースコードです。

OK。これでエラーチェックは完了しました。


初期処理

エラーチェックの次は初期処理です。

「初期処理って何をするの?」という感想かと思います。

初期処理では後続の本処理(今回なら各日の出力)に必要な前提条件を整えます。前提条件とは、文字通り処理を開始するにあたっての必要事項です。

今回は2つ。出力対象セルの初期化と変数の準備です。


まず出力対象セルの初期化。

今までは機能を動かすたびに、出力した各値を事前に手動で削除しなきゃいけませんでした。そうしないと既に値があるところに出力していくからちゃんと動作しているのかわからないという状況がありました。

毎回手で消すって面倒ですよね。今回でそれは卒業します。次の1行を下図のように入力ください。

    Range("E2:E32").Clear
赤枠で囲ってないけど、上図のようにソースコードを反映してね

はい、話の流れから何をしているかはお分かりでしょう。E2セルからE32セルの範囲にわたってセルの値をクリアしています。

これまでセルへのアクセスはCells(n, n)を使ってきましたが、Rangeというキーワードの引数にセルを示す文字列を渡すことでも可能です。


さて、変数の準備もしてしまいます。

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

これは今までもやってきましたね。変数の宣言&定義をしています。

変数yearと変数monthはエラーチェックを通過しているので心置きなく変数に代入することができます。ここにきてデータ型が違うという心配もありません。事前に弾いているのでね。

これで初期化処理も完了です。今ソースは

現在のソースコード

このような状態です。よし、次いこう。


isDayWritable関数 概要

このカレンダーLv.4における目玉であるisDayWritableイズデイライタブル関数を実装します。

この関数は引数として受け取った年、月、日の3つの値から存在する日かどうか、すなわち出力できる日付かどうかを返します

例えば2024年2月29日ならTrueを返します。しかし、2025年2月29日ならFalseです。2025年は閏年ではないですからね。

この関数は月の値によって30日までなのか31日までなのかはもちろんのこと、年によって閏年なのかどうなのかまで判定する役割を担わなければならりません。

まず側を用意しましょう。

'年月日の妥当性チェック
Function isDayWritable(year As Integer, month As Integer, day As Integer)

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

はい、年と月と日を表す引数を受け取ります。

今回、年は4桁の数字としていますので、1000年から9999年が対象になります。ただ、そんな遠い過去や遥か未来のカレンダーの行く末まではわからないので、この関数において網羅するのは以下の3点です。

  • 閏年の算定式を用い、該当すれば2/29の出力を許容する

  • 4, 6, 9, 11月なら30日を最終日とする

  • 1, 3, 5, 7, 8, 10, 12月なら31日を最終日とする

上記3点を正確に判定し、その結果を呼び出し元に返す処理を実装します。

呼び出す側の処理ももう書いてしまいましょう。その方がイメージしやすいと思います。

    Dim i As Integer
    For i = 2 To 32
        If isDayWritable(year, month, day) Then

        Else
            Exit For
        End If
        day = day + 1
    Next
赤枠のソースコードを反映してね

はい、変数yearと変数monthは処理の初めから終わりまでずっと同じ値を持つので、変数とは言いつつも実質固定値です。変数dayだけループが進むたびに値が変わる、という構造になっていますね。

そして注目はisDayWritable関数の使い方です。ここでは戻り値としてTrueを受け取ればIfの中にある処理を実施し、Falseが返ってくればループを抜けるという流れになっています。

Falseが返ってくるのはこれ以上日付の出力をしなくていいことを意味します。ですから、これ以上の周回は不要とし、Exit Forをしています。

If文の後は変数dayをインクリメントしていますね。ループ処理で次の日付を検証するためです。


isDayWritable関数 実装 前半

では、詳細な関数の中身を実装していきましょう。おさらいですが、

  • 閏年の算定式を用い、該当すれば2/29の出力を許容する

  • 4, 6, 9, 11月なら30日を最終日とする

  • 1, 3, 5, 7, 8, 10, 12月なら31日を最終日とする

この3つのポイントを網羅する、という話でした。

ということはパッと思いつくだけでも

  • 変数dayが29の場合の条件式

  • 変数dayが30の場合の条件式

  • 変数dayが31の場合の条件式

が必要になりそうです。ここまではなんらストレスなく受け入れられるでしょう。ここから少しずつテクニカルかつプログラマ然とした話になってきます。

まず、isDayWritable関数内において最初に登場する条件式は

    If day <= 28 Then
        isDayWritable = True
        Exit Function
    End If
赤枠のソースコードを反映してね

これにしましょう。記載ください。変数dayが28以下かどうか?です。

前段の話としては変数dayが29, 30, 31の場合に条件式が必要になりそうだ、という話でした。であるにもかかわらず最初に登場した条件式では変数dayが28以下かどうか?を見ています。

これには読み手の負担を軽減する狙いがあります。変数dayの値が最大で31まで遷移するとき、29, 30, 31の値は全体の約10%でしかありません。

これは90%の場合においては長ったらしい条件式やロジックのことを気にしなくていいことを意味しています。であれば、そのことを読み手に最初に伝えた方が余計な脳のリソースを使わせなくて済みます

配慮の行き届いたソースコードを書くとき、こういうことを考えます。

そして、90%の場合においては気にしなくていい、と言ったからには条件式の中は

        isDayWritable = True
        Exit Function

ほら、見てください。最低限のことしかしていません。戻り値の設定と関数を抜ける処理の2行です。

あ、Exitイグジット Functionファンクションは初めて出てきましたね。これもここまでに出てきたExit SubExit Forと同じで、この命令文が属する処理の塊から抜ける、という命令をしています。

ですので、変数dayが28以下の場合はこれ以降のロジックは気にしなくていいことが容易にわかります。

言ってしまえば、この時点でカレンダー出力処理におけるisDayWritable関数の使い道のうち、90%を占める動作パターンについてはもうコーディングを済ませた、と言えます。

では、残りの10%を攻めていきましょう。

次は変数dayが29の場合の評価を見てみるのですが、2月以外の場合は無条件で29を出力しちゃっていいんですよね。2月以外ならいずれの月も29日はありますから。

なので、変数monthが2でありかつ変数dayが29、までは想像がつきますが、あともうひとつ大事な条件がありますね。

お分かりでしょうか。2月29日を出力するのか否か。

そうです。変数yearの値が閏年かどうか?です。

整理すると次に用意する条件式は

  • 変数yearが閏年でかつ

  • 変数monthが2でかつ

  • 変数dayが29

・・・だったら出力をする(Trueを返す)、でなければ出力しない(Falseを返す)という処理になります。

ここでネックになるのが閏年の判定ですね。

実はここも自作関数でやっちゃいます。次項へGo!


isLeapYear関数

みなさん、何がどうなれば閏年か、考えたことはあるでしょうか。実は私も今回の機会を以て初めて調べてみました。

「閏年の条件」で検索してみると・・・・

(1)西暦年号が4で割り切れる年をうるう年とする。
(2)(1)の例外として、西暦年号が100で割り切れて400で割り切れない年は平年とする。

どの年がうるう年になるの?

ということらしいです。この2つの条件をソースコードで表現するのがこの項の目標です。

うるう年は英語でLeap Yearというそうです。閏年かどうか?を返す関数なので今回はisLeapYear関数と名付けます。Boolean型を返す場合は「is」から始まる関数名にするのでしたね。

今回は先に答えをお伝えしましょう。こちらです。

'閏年かどうかを返す
Function isLeapYear(year As Integer)
    isLeapYear = year Mod 4 = 0 And Not (year Mod 100 = 0 And year Mod 400 > 0)
End Function
赤枠のソースコードを反映してね

上図のような感じで書きましょう(関数の位置はどこでも問題ありませんが、#16の補足で少し触れます)。ここからロジックの解説をします。

まず

(1)西暦年号が4で割り切れる年をうるう年とする。

こちらに関しては特に問題ないでしょう。割り切れるというのは除算を行った時に余りが出ない、ということですね。

これは#8の世界のなべあつ氏の芸でやりました。3の倍数か3がつく数字の時、という条件式で

If count Mod 3 = 0 Or count Like "*3*" Then

このようなソースコードを書きましたね。これと同じことを

year Mod 4 = 0

この↑ソースコードがしています。

今回少し頭を悩ませるのが

(2)(1)の例外として、西暦年号が100で割り切れて400で割り切れない年は平年とする。

こちらです。ソースコードとしては

And Not (year Mod 100 = 0 And year Mod 400 > 0)

この部分です。解説しましょう。

まずはNot ( )について。

2つ目の条件で「西暦年号が100で割り切れて400で割り切れない年は平年とする。」とありますね。この関数はうるう年だったらTrueを返し、平年だったらFalseを返します。

なので「西暦年号が100で割り切れて400で割り切れない」場合にTrueを返してしまうと具合が悪いんですね。これは平年の条件だから。

この場合、返したいのはFalseなので、Notで反転させています。そして括弧を付けているのはNotの範囲を指定するためです。

And Not (year Mod 100 = 0 And year Mod 400 > 0)

これによって、「西暦年号が100で割り切れて400で割り切れない」という条件に該当しない場合はうるう年だ、という判定をすることができます。

ですので、一つ目の条件である「西暦年号が4で割り切れる年」と併せて

    isLeapYear = year Mod 4 = 0 And Not (year Mod 100 = 0 And year Mod 400 > 0)

この式は

  • 西暦年号が4で割り切れる年であり、かつ

  • うるう年の例外条件を満たさないとき

うるう年と判定する結果を返すことができます。

Notの中身は2つの条件式をAnd演算子で繋いでいますが、

Not (year Mod 100 = 0 And year Mod 400 > 0)

ここまで取り組んでこられたあなたなら読み解くのは簡単でしょう。

  • 100で割り切れる

  • 400で割り切れない

をそれぞれ表しています。Mod演算子は余りを返すので、それが0なら余りがない(=割り切れた)ということ、余りが1以上あれば割り切れなかったということです。

ここまでの解説結果を・・・

    isLeapYear = year Mod 4 = 0 And Not (year Mod 100 = 0 And year Mod 400 > 0)

関数名である「isLeapYear」に代入すれば呼出し元に判定結果を返せる、というわけですね。

今回はここまでです。

[Ctrl] + [S]キーで保存してください!


今回のふりかえり

今回は新しい要素少な目でしたね。その分、基礎が積みあがってきており、新たに教えることが減っているということです。

  • 関数の書き方

  • 一連の処理における初期(化)処理という概念

・・・くらいでしょうか。。引数の書き方や戻り値の書き方が重要ですが、今後も関数を作る機会は何度もあるので、手が勝手に覚えていきます。


今回の補足

公式リファレンスを交えて説明する理由

今回の「改めて、関数とは?」の項、ちょっと回りくどくなってしまったなと思っています。

私、説明の中でちょいちょい公式リファレンスを案内することがあるのですが、その真意についてここでお伝えしておきますね。

私も書籍やネットを通して新しい学び事をすることがあるのですが、そこで受ける各種説明はその界隈で公式的なものなのか、慣例的なものなのか、その筆者独自のものなのか、初学者にはわかりませんよね。

その情報確度かくど不透明性ふとうめいせいが嫌でして、公式リファレンスを都度都度案内しています。公式が出しているのですから、間違いがないといえます。

また、公式リファレンスって実際はあまり読むことがありません。#4の補足の章でも伝えたように有志がまとめたものの方がわかりやすいことが往々にしてあるからです。

しかし、だからこそ学び始めの早い段階から公式リファレンスを読む癖をつけておいてほしいと思っています。このネットという広い海には間違った情報も混ざっています。玉石混交ぎょくせきこんこうです。

だから、ちょっとくどくはなりますが、公式リファレンスの読み方・読み解き方を交えながら説明を進めることがこの先も何度かあります。ご容赦・ご了承ください。


Rangeの紹介

今回はじめてRangeというキーワードが登場しましたね。少しこれに触れておきます。

Range("E2:E32").Clear

本編中には公式リファレンスへのリンクを置いただけでした。

セルを示す情報の書き方が違うだけで基本的にCells()プロパティと同じ使い方をできます。これまでCells()プロパティを使ってきたときは

Cells(2, 1).Value = "1日"

このように特定の単一セルを対象に処理をしてきましたが、上記のClear処理をしているソースのようにRangeオブジェクトを使うと範囲指定も柔軟にできます。

また、「オブジェクト」という単語が耳慣れないかもしれませんね。これは#5でプロパティの説明をするときに人間や自動車、セルを例にしました。

プロパティはその主体を説明するための性質や属性だったのですが、オブジェクトはその主体を指す言葉です。

よくわからないという方、それで大丈夫!!#12の補足でも触れます!でもそこでも「よくわからなくても大丈夫」っていってます。つまりそれくらいのことです。


デバッグで関数callを目で追う

#5にて、デバッグのやり方をお伝えしましたね。

赤色のブレークポイントを設置したあとで処理を実行し、ブレークポイントで処理が一時中断するのでそこからは[F8]キーで進める、というものです。

今回初めて自作関数を用意しました。これにより、処理の流れが自作関数に移るところもデバッグにて確認できるようになります。

組込関数はどこかのライブラリにあるのでそこにアクセスすることはできませんでした。

ちょっとやってみますね。ご自身でされる場合は下図Gif画像の内容を真似してみてください。

処理を追うところは[F8]キーを連打しています。

いかがでしょう。isYear関数、isMonth関数のところになると黄色いハイライトが関数の方に移動していますよね。これで処理の流れを関数呼び出し時の内部的な動きも含めて確認することができます。


関数の命名規則

最後に、関数名についてです。今回、未完成のものも含め以下の関数を作成しましたね。

  • isYear

  • isMonth

  • isLeapYear

  • isDayWritable

これに比べ、ここまで使ってきた組込関数は

  • IsNumeric

  • Len

このような名前をしています。先頭の文字が大文字か?小文字か?に違いがありますね。これはどんな規則で命名したか?が関係しています。

プログラミングの世界には変数や定数、関数の命名規則めいめいきそくというものがありまして・・・参考に以下のページをご覧ください。

今回はキャメルケースを採用しています。

キャメルケースとは複数の英単語が連なる形でひとつの名称を表現している場合に、各単語の先頭文字を大文字にしているものです。

例:ReturnValue, isLeapYear, IsNumeric

そして、キャメルケースの中でも

  • 先頭の文字が文字ならローワーキャメルケース

  • 先頭の文字が文字ならアッパーキャメルケース

と言います。lowerCamelCaseとUpperCamelCaseです。

で、VBAに用意されている関数名はどうやらアッパーキャメルケースのようですね。IsNumeric関数なんてまさにそうです。先頭が大文字。

そして今回作成いただいた各関数はローワーキャメルケースで命名しました。ここはもう好き好きです、個人で趣味の範囲でコーディングをする場合は特に。

どうして組込関数がアッパーキャメルケースであるのに対して、自作関数はローワーキャメルケースにしたか?と問われると先頭文字が小文字か大文字かにより自作かどうかをパッと判別できるから、と答えます。

呼び出す側から見たときの話ですね。

そういうわけなので、もしも気に入らないとかなにかしっくりこない、という想いがおありでしたらアッパーキャメルケースに変更いただいても大丈夫です。

アッパースネークケースというのもありまして、それは#13で使います。

この項の内容としては変数、定数、関数には命名規則と言いものがあるのだな、ふむふむ、と思っていただけましたらOKです。


ソース内での統一性(蛇足です)

今回実装したisYear関数とisMonth関数、どちらも有効な値だったらTrueを返す、としていて、callする方では無効な値だったらエラーとしたいからわざわざNot演算子をつけて結果を反転させていますよね。

If Not isYear(Cells(1, 4).Value) Then

これと

If Not isMonth(Cells(1, 5).Value) Then

これです。IsNumeric関数だったらもともと「数値だったらTrueを返す」、という作りをしているので、こちらで反転させるしかありません。

ですが、今回自作関数であっても同様に有効値ならTrueを返す、としました。これ、実装の手段としてはNot演算子を呼び出し元のIf文から関数側に移動させても問題ありません。同様の動きをしますからね。

ただしその場合、関数名をisNotYear、isNotMonthのように変更する必要があります。そうしないと関数名と戻り値に不整合が生じるからです。

実装に反映するとこんな↓感じ。

'年の妥当性チェック
Function isNotYear(year As String)
    isNotYear = Not (IsNumeric(year) And Len(year) = 4)
End Function
'月の妥当性チェック
Function isNotMonth(month As String)
    If IsNumeric(month) Then
        isNotMonth = Not (1 <= month And month <= 12)
    End If
End Function

呼び出し側が↓こうなります。

'年の方
If isNotYear(Cells(1, 4).Value) Then

'月の方
If isNotMonth(Cells(1, 5).Value) Then

書いてみたらこっちの方がわかりやすいかもしれませんね。直観的っていうか。

ではどうしてこの方法を取らなかったかっていうと、IsNumeric関数の使用例に合わせたからですね。

今回あまりお隣のマクロとの整合性は気にしていないけど、肌感覚として同じモジュール内で片方は呼び出し側でNot演算子を使用、もう片方は関数側でNot演算子を使用、となると混乱の種を残すことになるからです。

同じような処理パターンなのに書き方が異なるのは避けようという意図がありました。・・・が、組込関数はこっちからは手の出しようがないので、あまり深く考えなくても良いです。蛇足みたいな補足と受け取ってください。


おわりに

おわりです。

今回は少しキリが悪いですが、ボリュームへの配慮もあり途中での終了となりました。

次回はisDayWritable関数の続きからします。実はもうひとつ新しく関数を作ります。そこで新しい要素も登場します。

このVBAでプログラミングを学ぼう編は残すところが

  • カレンダーLv.4のつづき

  • カレンダーLv.5(正規表現)

  • 演習問題:あみだくじ

  • カレンダーLv.6(二重ループ)

  • カレンダーLv.7(三重ループ)

この5つです。カレンダーLv.1から基礎知識を埋めていく形でやっているので、進めば進むほど応用的な内容テクニカルな処理美しいソースコードへ向けた一歩踏み込んだ実装が増えてきます。

進みづらさを感じた場合はここまでの内容を振り返ったり、自分でお遊びコードをかいてみたりなど、知識をご自身に浸透できるように工夫してみてください。不明点は私へご質問くださいね(ページ下部のコメント欄から)。

1から31の数字を出力するばかりで飽きてくるかもしれませんが、基礎学習ってだいたいつまらないものなので、もう少しだけ辛抱してもらえると幸いです(つまらないものを楽しく工夫するのが私の役目なのですが)。

はい!では、次でカレンダーLv.4は片づけてしまいましょう!

今回もありがとうございました。


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