ExcelVBA入門第10回 3種類のプロシージャと命名規則
プロシージャは、プログラムで処理する「まとまり」のことです。今まで主に「Sub」から始まるプロシージャを使ってきましたが、他にも種類があります。その違いを、私が好きなドラクエ風味で解説してみます!
プロシージャの特性
プロシージャは以下の3つの種類があり、それぞれに特性があります。
- Subプロシージャ
- Functionプロシージャ
- Propertyプロシージャ
そもそもプロシージャは、その1つ1つを「できるだけ短い行数」で完結させるのが理想です。
というのも、やりたいことをただ並べていくだけでは、1つのプロシージャが際限なく長くなってしまうのです。作ったそのときは良くても、後で修正しようとしたら大変です。あの処理はどこに書いたの…? これは何をしているところなの…? と、すぐにわからなくなってしまいます。
もちろんそうならないためにコメントアウトなどでメモをしておくべきですが、それでも、1つのプロシージャが長ければ長いほど解読しにくくなるのです。
そのため、プロシージャの特性を理解して、出来るだけ短く、無駄なく、他人が見てもわかるように書く、という心がけが大切です。
(数年後の自分も、他人ですw 自分で書いたコードもさっぱりわからなくなっちゃいます!)
Subプロシージャ
Sub は「サブルーチン」の略で、一番よく使われるプロシージャです。1人で大抵のことはできちゃうので、私は勇者と呼んでいます。
※イメージです
例として、このSubプロシージャを見てみてください。
Sub Main() '処理1 Dim i As Long i = 100 * 2 '指定数を2倍 Range("A1") = i 'セルに代入 '処理2 Dim j As Long j = 50 * 2 '指定数を2倍 Range("B2") = j 'セルに代入 '処理3 Dim k As Long k = 80 * 2 '指定数を2倍 Range("C3") = k 'セルに代入 End Sub
3つの処理をしています。それぞれ、指定の数を2倍して指定のセルに入れる、という内容です。
これ、よく見ると、数値とセルの位置が微妙に違うだけで、似たようなコードを3回書いていますよね。こんな場合は、この処理を共通化したSubプロシージャを作ってしまえば、かんたんになります!
Sub runTask(ByVal user_range As Range, ByVal n As Long) Dim i As Long i = n * 2 '指定数を2倍 user_range = i 'セルに代入 End Sub
このように、runTask
という処理用のプロシージャを作ります。1行目のかっこの中身は、引数(ひきすう)と呼びます。このプロシージャを用意すると、Main
側のプロシージャでは、数値とセルを引数として持たせて、runTask
を呼び出すだけでよくなります。
Sub Main() Call runTask(Range("A1"), 100) '処理1 Call runTask(Range("B2"), 50) '処理2 Call runTask(Range("C3"), 80) '処理3 End Sub
こうなります。Main
側のプロシージャはスッキリしましたし、同じrunTask
というプロシージャを呼び出すことで、この3つの処理は似たようなことをしているんだな、という予測がつきます。
実際には、runTask
などのプロシージャ名はもっと具体的な名称(何をどうする、のような)にしておくと、更に可読性が良くなります。
ちなみに、Call
は省略することができるので、書かなくても動きます。ただ個人的な好みとして、私は書いたほうが「呼び出す」という意図が明確に見えて好きなので、書いています。
Functionプロシージャ
ファンクションとは「関数」という意味です。単体では動けないので、他のプロシージャから呼び出して使います。補助要員です。
※イメージ
Subプロシージャとの違いとして、Functionは実行後、対応する値を取得することができます。
「対応する値」とは、なんでしょうか?
まずは説明のため、このコードを見てみてください。
Sub Sample() If IsNumeric("abc") = False Then 'カッコの中身が数値じゃなかったら MsgBox "数値じゃありません" 'メッセージを出す End If End Sub
これはSubプロシージャですが、2行目で “abc” という文字列が「数値かどうか」の判定をして、数値じゃなければメッセージを出しています。この判定にはIsNumeric()
という関数を使っています。あっ、「関数」という言葉が出てきましたね?
このIsNumeric()
関数は、あらかじめExcel側で用意されている機能で、「カッコの中に何か入れると、それに対応する値を教えてくれる」仕組みを持っています。この場合は、数値なら True、数値じゃなければ False を教えてくれるので、それを If 文で利用しているわけです。
このような働きをする機能を「関数」と呼びます。Functionプロシージャは、こういった「関数」を、自作できるプロシージャです。
たとえば、「この名前のシートがあるか?」というコードを書きたいとき、現状Excel側には、それを一発で取得する関数は、用意されていません。なので、自力で書くとこうなります。
Sub Sample() Dim ws As Worksheet, exist_flg As Boolean '変数とフラグを宣言 For Each ws In Worksheets 'ブック内の全てのシートをループ If ws.Name = "あああ" Then '特定の名前のシート名だったら exist_flg = True 'フラグを立てる End If Next ws If exist_flg = False Then '全部ループさせたらフラグの中身を確認して MsgBox "存在しません" 'フラグが落ちてたらメッセージを出す End If End Sub
ひとつならまだいいですが、名前を変えて何回も確認したかったら、ちょっと面倒ですよね…。
Sub Sample() If hasSheet("あああ") = False Then '「あああ」というシートがなかったら MsgBox "存在しません" 'メッセージを出す End If End Sub
そんなとき、IsNumeric()
関数みたいに、こんな架空のhasSheet()
なんて関数で書けたら素敵じゃないですか? この関数、自作できちゃうんです!!!
Function hasSheet(sheet_name As String) As Boolean 'ThisWorkBook内シートの存在有無をBooleanで返す Dim ws As Worksheet For Each ws In Worksheets 'ブック内の全てのシートをループ If ws.Name = sheet_name Then 'シート名が引数と一致したら hasSheet = True 'フラグを立てる End If Next End Function
こんなFunctionプロシージャを書いておけば、他のプロシージャからhasSheet("シート名")
と書くことで、シートの存在有無がかんたんにチェックできるようになります。シート名を変えて何度も使うときなどに、とても便利です。
「こんな機能があったらなー」というものを自分で作れるので、メインのプロシージャはとってもスッキリしますよね。
なお、今回紹介した例では Boolean 型で True/False が返ってくる関数を作りましたが、返り値の型はなんでもOKです。前に書いた記事で、カタカナを引数にして数値として返す関数や、アルファベット←→数値をフレキシブルに変換する関数などを紹介しているので、よろしければ参考になさってください。
それと、Functionプロシージャは大抵どこに書いても動作すると思いますが、私は標準モジュールでオブジェクト名を「Functions」へ変えたものを作り、よく使う関数はそこへまとめています。メンテのときにも便利です。
Propertyプロシージャ
プロパティは「属性」という意味です。使いどころは、個人的にはクラスモジュールと組み合わせて使うことが多いかなと思うので、難易度は高めです。
※イメージ
- ActiveSheet.Name → 現在アクティブになっているシートの、名前
- ThisWorkbook.Path → 現在操作しているブックの、パス
この黄色くマークしてある部分のことを、プロパティと呼びます。
大抵はExcel側であらかじめ用意されているプロパティに対して、設定したり呼び出したりして使うものですが、Propertyプロシージャでは、好みの「属性」を自作することができます。
こちらの記事に、クラスモジュール + Propertyプロシージャの使い方を詳しく書いたので、ここではかんたんな説明にとどめておきますが、
Private Price_ As Integer '値を引き渡す変数 '「Price」というプロパティの設定プロシージャ Property Let Price(ByVal new_Price As Integer) Price_ = new_Price End Property '「Price」というプロパティの取得プロシージャ Property Get Price() As Integer Price = Price_ End Property
このようなPropertyプロシージャ(LetとGetでワンセット)を書くことで、
- obj1.Price → オブジェクト1の、値段
このようなものを自分で設定して使うことができるようになります。最初からは難しいので、「こういうものもあるんだー」くらいに思ってもらえればOKです。
命名規則
ここまで見てきたように、プロシージャは、1つ1つはできるだけ短く書き、それをいろんな場所から呼び出す、という使い方をします。
そこで、呼び出した際に何をしているのかわかりやすくするため、そのプロシージャの機能を簡潔に表した名前をつけることをおすすめします。これにより、コードがとても読みやすくなります。
プロシージャだけでなく、変数などにも名前のルールを決めておくことで、誰が読んでもわかりやすいコードを目指します。こういったルールのことを、「命名規則」と言います。
今回、こちらの記事を参考にさせていただきました。
私もいままでずっと個人プレーで、チームでプログラミングをしてきたことがなかったので、命名規則の重要さに気がついたのは最近なんです…! 今までこのブログに書いたコードはこの命名規則に従ってないものも数多くありますが、過去記事を直したり、これから書くものはちゃんと書いていこうと思っております。
なお、ガイドラインはあくまで「原則」なので、「絶対守らないとダメ!!」なものではないです。チームの方針や、ここはこういう意図があるので例外的に…ということも、もちろんOKです。ここから紹介する内容も、私の個人的な好みなども含まれますので、「参考」程度に留めてください。
自分で作ったルールを自分で守れないことも…、特にクラスモジュールなんかは、変数名とプロパティ名に関連性を持たせた書き方のほうが個人的に理解しやすかったので、ここに書くルールにそぐわない書き方をしている箇所もあります。。
プロシージャは動詞+名詞
Propertyプロシージャは例外として、Sub と Function は基本的に「何かをする」ので、その内容がぱっと見てわかる名前が推奨されます。単語の区切りを大文字にしてつなげて書きましょう。
Subプロシージャ
上で挙げた例ではrunTask
という名前を書いていますが、実務ではもっと具体的な命名が良いです。
loadNewFile
とか、clearOldData
とか、addPerson
とか、そういう感じで…!
Fonctionプロシージャ
カッコ内に引数を入れて返り値を取得するので、get○○
という風に書くことが多そうです。あと、変換するときはchange○○
とか。VBAでは標準装備のChange関数でCStr()
のように Change を C の1文字で表しているので、私もChange系はそれに倣って書くことがあります。
そのほかには、返り値が Boolean(True/False)の場合、is○○
、can○○
、has○○
みたいに書くとわかりやすいです!
変数は名詞
プロシージャが動詞で始まるものに対して、変数は名詞で表すと違いがわかりやすいです。userName
などのように書きましょう。
私は一時期、短いほうが良いものかと思ってやたらと略して命名していたことがあったのですが(シート名の変数をshtNam
とか)、一般的に略語で通じるものならともかく、よくわからん略し方をして結局後で読めないならsheetName
ってしっかり書くべきだと痛感して、今はキッチリ書くことにしています。。
また、thomさんのガイドラインを読んで、変数の中でも、引数は「小文字_小文字」で下記のように書くと区別ができていいなと思いました。
Sub runTask(ByVal user_name As String) '略 End Sub
この1行目のような部分で使う変数ですね。ローカル変数とは別に、「他から持ってきてる」というのがわかりやすくて良いと思いました。
定数は名詞&全大文字で
命名規則の話が出たので、ついでに定数についても。
Sub Sample1() Workbooks.Open "C:\aaa\bbb\test.xlsm" End Sub Sub Sample2() Kill "C:\aaa\bbb\test1.xlsm" End Sub Sub Sample3() FileCopy "C:\aaa\bbb\test1.xlsm", "C:\aaa\bbb\test.xlsm" End Sub
こういう、いろんなところで使っている同じパスがあるとします。これ、パスが変わって全部直さなきゃならなくなったらツライです。
Const USER_PATH As String = "C:\aaa\bbb\" Sub Sample1() Workbooks.Open USER_PATH & "test.xlsm" End Sub Sub Sample2() Kill USER_PATH & "test1.xlsm" End Sub Sub Sample3() FileCopy USER_PATH & "test1.xlsm", USER_PATH & "test.xlsm" End Sub
そんな場合、定数を使ってこのように書くと修正が楽です。こういう定数は、大文字を_でつなげて書くのが、他の言語でも共通だそうです。
命名に迷ったらこういうサイトを参考にすると良いと思います!
まとめ
前回は5種類のモジュールについて、そして今回は3種類のプロシージャについて解説しました。これらの特性を上手に使うにあたり、さらに「スコープ(適用範囲)」というものを知っておくと、さらに理解が深まります。
というか、モジュール、プロシージャ、スコープの3つの関係はけっこう密接だと思っていて、最初1つの記事でこの3つについて書こうと思っていたらとんでもなく長くなってしまいそうだったのでw、あわてて3記事に分けました…。なので、第11回でスコープについて書きますので、是非そこまで通して読んでいただければ嬉しいです!
追記:デザインパターンのお話
VBAからプログラミングに初めて挑戦している方に、ちょっと小耳に挟んでおいてほしいお話も書いておきます。
プログラミングには、デザインパターンというものがあります。全体像を「どのように設計するか」、VBAで具体的に言うと、どんなモジュールを用意して、どのモジュールにどんなプロシージャを書くか、という考え方の「指針」です。
パターンにも種類があってめちゃめちゃ奥が深い世界なので、私などが解説できるレベルではないのですが、「変更や修正が必要になったときの手間(コスト/リスク)が極力小さくなる構造」にしましょう、そのための設計をしましょう、ということなんじゃないかなと、個人的には解釈しています。
たとえば、1つ変更したいことが出たときに、多数のプロシージャであちこち直さなきゃならない、修正したいプロシージャがどこにあるのかわからない、個々のプロシージャが何の処理をしているのか推測できない、etc…、という状態だと、大変ですよね。しかもこれを、第三者がやらなきゃいけないなら、もはや作り直したほうが早いんじゃないかなんてことにもなりかねません。
適切な設計とルールに基づいたプロジェクトになっていて、「この変更なら、修正すべきなのはこのモジュールのこのプロシージャだな」という推測ができて、直す箇所が少なく済んだら、スピードは段違いですよね。それが第三者でも可能だったら、素晴らしいですよね。そんな設計をするために、「デザインパターン」を勉強すべきなんだな、と。
これは、ひとまず目的を達成するためのVBAが書けるようになった後、更にその次の段階のお話です。恥ずかしながら、私も最近ようやく足を突っ込み、その世界の広さを見て、井の中の蛙感に打ちひしがれる思いです…。「動けばいい」コードではダメだなって…。
でも、VBAから入ってプログラミングの面白さにふれて、「他の言語もやってみたい」とか「プログラマになりたい」とか思う気持ちが芽生えたら、最初からできなくても「こういう考え方がある」ということを知っておくだけでも、後になって違うんじゃないかなーと思います。よかったら、頭の片隅に置いておいてください。
さらにもうちょっと突っ込んだお話。
JavaやPHPを勉強していると、MVC(Model-View-Controller)モデルという言葉をよく目にします。このパターンは、Model – 「データを扱うこと」(検索、変換、処理など)と、View – 「出力、描写すること」(文書や図表など任意の形)と、Controller – 「データを受け取って他へ渡すこと」(入力の検知、受け渡し指示)は、明確に分割させてプログラミングしましょう、という考え方で、VBAでもこれを意識すれば、メンテナンス性が高い設計にできるんじゃないかなと思っています。
ただ、他言語に比べて、ExcelVBAは「シート」の存在感が強力なので、MVCの役割を完璧に分離してしまったら、初学者でも学びやすい、理解しやすいというメリットは低下する可能性もあるんじゃないかなという気持ちもあります。
バリバリの他言語プログラマさんがVBAを扱う場合ならまだしも、こういうのって、プロジェクトの目的や規模、チームや個人のポリシーとかひっくるめて一概に「コレが正しい!」というモノはないと思うので、いろんなパターンを参考に、「その現場でいちばん使いやすいカタチ」を追求していくのがいいんだろうな、と思っています。
…という、自分でもとりとめのない話を書いているなーと思っていたら、勝手に師匠と仰いでいるthom神がすでに似たようなことを書いておられました。
この記事、たぶん前読んだときは、自分がそのレベルに達してなくてピンと来なかったんだな。やっと読んで頭に入ってくるレベルになったということか…!
ほかの入門記事はこちら
- ExcelVBA入門第0回 始める前に
- ExcelVBA入門第1回 動かしてみる
- ExcelVBA入門第2回 とりあえず覚えておくべきこと
- ExcelVBA入門第3回 変数の宣言
- ExcelVBA入門第4回 RangeとCells
- ExcelVBA入門第5回 ステップ実行
- ExcelVBA入門第6回 If ~ End Ifステートメント
- ExcelVBA入門第7回 インデントとコメントアウト
- ExcelVBA入門第8回 繰り返し処理
- ExcelVBA入門第9回 5種類のモジュールの違い
- ExcelVBA入門第10回 3種類のプロシージャと命名規則
- ExcelVBA入門第11回 スコープ(適用範囲)
- これからExcelのマクロを始めたいという方に!簡単な練習問題作りました。
- 私がExcelVBAでよく使う便利なコード・スニペットまとめ
- プログラム初心者さんへ贈る、エラーが起きたら試してみて欲しいこと
- ExcelVBAのクラスモジュールって何?という人向けの使い方まとめ
書籍を執筆しています。
2件のコメント
大変勉強になります。
独学の付け焼刃で業務プログラムを開発してきましたが、こちらのサイトを参考にレベルアップを測りたいと思っています。
Functionまでは使っていて理解できるのですが、Propertyは使いどころが難しいですね。
次回のスコープも楽しみにしています。
PPPさん、コメントありがとうございます!
Propertyは難しいですよね。私も最近ようやくわかるようになってきたところで、クラスモジュールと一緒に使うと本当に世界が広がりました!
スコープの記事にも言及いただいてありがたい限りです。レベルアップのお役に立てたら光栄です(*´∀`)
コメントは承認制ですので、反映までしばらくお待ち下さい。(稀にスパムの誤判定にて届かないこともあるようですので、必要な際はお問い合わせからお願い致します。)
YouTubeでQ&Aコンテンツを企画しています
運営しているYouTubeチャンネルで、ご相談やご質問を募集しています。動画のコメントやお問い合わせページからお気軽にご相談をお寄せください。