.NET開発者中心 厳選ブログ記事

.NET開発を始めるVB6プログラマーが知るべき9のこと
―― 「まさるDiary」より ――

masaru_b_cl
2011/05/11

「.NET開発者中心 厳選ブログ記事」シリーズでは、世界中にある膨大なブログ・コンテンツの中から、特にInsider.NET/.NET開発者中心の読者に有用だと考えられるブログ記事を編集部が発掘・厳選し、そのブログ記事を執筆したブロガーの許可の下、その全文を転載・翻訳しています。この活動により、.NET開発者のブログ文化の価値と質を高め、より一層の盛り上げに貢献することを目指しています。

本稿は、ブログ記事「まさるDiary: VBプログラマが知るべき9のこと by @masaru_b_cl」に簡単な校正・加筆を行ったうえで転載したものです。

 この記事に書いてあることは目新しいことでも何でもなく、これまで各所でいろいろといわれていたこと(下記の9点)をまとめたものである。

  1. Option Strictを「On」に
  2. On Errorは使わない
  3. Form.Show()はしない
  4. ByVal/ByRefを明示する
  5. 文字列連結は&で行う
  6. プロシージャ呼び出しの()は略さない
  7. Callを使用しない
  8. 長大なWithは避ける
  9. 無意味なNothingの代入

 なお、単に「VB」と表記しているものは、.NET以降の「Visual Basic .NET 2002/.NET 2003/2005/2008/2010」などを意味している。

1. Option Strictを「On」に

 まず真っ先にしてほしいのが、「Option Strict」を「On」にすることである。

暗黙的なデータ型変換を拡大変換だけに制限します。

 Option Strictは明示的にOnにしないと、既定ではOffと見なされるため、次のようなコードが書けてしまう。

Dim str As String
str = 100

 上記のコードでは変数の宣言と値の設定が近い位置にあるため、文字列を扱っていることがすぐに分かるが、実際現場で目にするような長大なコード*1では、その変数が文字列を扱うのか数値を扱うのか即座に判別できないため、保守が苦痛になる。

*1 場合によっては数百〜千行を超える。

 また、次のようなコードも書けてしまう。

Dim str As String
str = "12" + 34

Console.WriteLine(str) ' ここの結果は?

 Console.WriteLineメソッドでコンソールにどんな値が出力されるか、すぐに分かるであろうか?*2

*2 上記のコードの「"12"」が数値として扱われるため、正解は「36」になる。

 Option StrictをOnにすることで、上記のようなコードはすべてコンパイル・エラーとなる。

 では、どうやってOption Strictを設定するかというと、わたしはVisual Studioの[オプション]ダイアログで、[Visual Basicの既定値]の設定を変更してしまうことを勧める(下の画面を参照)*a


*a 【注記】元記事に対するコメントの引用:
Visual Studioの設定だけだと、ほかの人がコードを編集した際にバグが混入する恐れがあります。コードの先頭に以下の2行を書くのは必須ですね。これならVisual Studioの設定に左右されません。
Option Explicit On
Option Strict On

2. On Errorは使わない

 VB6時代のエラー・ハンドリング用構文である「On Error 〜」は現在のVBでも使用できる。

エラー処理ルーチンを有効にして、プロシージャ内でルーチンの場所を指定します。また、エラー処理ルーチンを無効にする場合にも使用できます。
On Error ステートメントを使用しないと、発生するすべてのランタイム エラーが致命的なエラーになります。つまり、エラー メッセージが表示され、実行が停止します。

Sub Main()
  On Error GoTo ErrorHandler

  ' 何かエラーが発生する処理
  DoRaiseErrorProcedure()

  Exit Sub

ErrorHandler:
  ' エラー処理

End Sub

 だが、On Errorはあくまで「プロシージャ単位」でしかエラーのハンドリングができないうえ、特定のエラーだけハンドリングするといったこともできない。VB6時代のソース・コードをほとんど流用するといった特殊な場合でもない限り、構造化例外処理を使用すべきである。

 以下に構造化例外処理を使った例を示す。

Public Sub Main()
  Try
    ' 何かエラーが発生する処理
    DoRaiseErrorProcedure()
  Catch e As Exception
    ' エラー処理
  Finally
    ' 共通後処理
  End Try
End Sub

 構造化例外処理では、Finallyブロックを使用して共通の後処理が行えることも大きな特徴である。

 ただし、構造化例外処理もプロシージャ単位で行っていてはOn Errorとほとんど変わらない。プロシージャでは原則として例外のCatchは行わず、「集約例外ハンドラ」(後述)で行うようにし、後処理が必要なものは、Finallyブロックで行うことを勧める。

 以下に、コンソール・アプリケーションで集約例外ハンドラを用いた例外処理の例を示す。

Sub Main()
   Public Sub Main()
    AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf CurrentDomain_UnhandledException
    Try
      ' 何かエラーが発生する処理
      DoRaiseErrorProcedure()
    Finally
      ' 共通後処理
    End Try
  End Sub

  ' 集約例外ハンドラ
  Private Sub CurrentDomain_UnhandledException(ByVal sender As Object, ByVal e As UnhandledExceptionEventArgs)
    Console.WriteLine("致命的なエラーが発生しました。")
    Environment.Exit(-1)
  End Sub

 なお、.NET言語の例外処理については、以下のブログ・エントリが詳しいのでぜひ一読してほしい(コードはC#だが意味は通じるはず)。*3

*3 上記のコードも、これらのブログ・エントリを参考にさせていただいた。

3. Form.Show()はしない

 Windowsフォーム・アプリケーションで、ほかのウィンドウを開きたい場合、通常は以下のように記述する。

Dim subForm As New SubForm()
subForm.Show()

 しかし、VBは次のような記述もできてしまう。

SubForm.Show()

 もちろん、C#で同じように書いたらコンパイル・エラーとなる。

 では、なぜVBではコンパイル・エラーとならず、しかも動作してしまうのか? それは「(Formクラスの)既定のインスタンス」と呼ばれる機能が関係している。この機能は、Visual StudioでWindowsフォーム・アプリケーションを作成する際、「プロジェクトに含まれるFormクラス」を自動的にインスタンス化し、My.Formsオブジェクトのプロパティとして公開するものだ。

現在のプロジェクト内で宣言されている各 Windowsフォームのインスタンスにアクセスするためのプロパティを提供します。

 つまり、上記のコードは、SubFormクラスの静的なShowメソッドではなく、My.FormsオブジェクトのSubFormプロパティを通じて、SubFormクラスのインスタンスを取得して、そのShowメソッドを呼び出していることになる。

 この機能はVB6時代と同じ書き方ができるように導入されたものと推測するが、Formクラスのインスタンスが「どこ」に属するのかを意識しないため、予期せぬバグを生みやすい。よって、Formクラスは明示的にインスタンス化してShowメソッドを呼ぶようにした方がよい。

 なお、「(Formクラスの)既定のインスタンス」を抑止する方法もあるようである。詳しくは以下のブログ・エントリをコメントまで含めて参照していただきたい。

4. ByVal/ByRefを明示する

 VBでは、プロシージャの引数に対してByVal(値渡し)やByRef(参照渡し)などの修飾子を指定できる。

 例を挙げると次のようになる。

Private Sub Hoge(ByVal a As Integer, ByRef b As Integer)
  a = 2
  b = 2
End Sub

Sub Main()
  Dim a As Integer = 1
  Dim b As Integer = 1

  Hoge(a, b)

  Console.WriteLine("{0},{1}", a, b) ' 結果:1,2
End Sub

 ByVal修飾子を指定した引数に値を渡しても、呼び出し元の変数の値は変わらないが、ByRef修飾子を指定した場合、呼び出し元の変数の値が書き換わる。

 この、「ByVal」「ByRef」を省略するとどうなるかというと、既定で「ByVal」として扱われる。*4

*4 VB6時代は「ByRef」として扱われていた。

 しかし、一見してどちらの動きとなるか分かりづらいため、ByVal修飾子やByRef修飾子は省略せずに書くべきである*b

*b 【注記】元記事に対するコメントの引用:
(いつのバージョンからかはいま正確に分かりませんが)VB 2008以降のバージョンなら「コードの再フォーマット」を無効にしない限り、「ByVal」を省略して書こうとしても強制的に付く仕様になっていますね。
※編集部注:逆に、VB 2010 SP1では、再び「ByVal」が自動的に付かない仕様に変更になっています(参考:「さようなら、ByVal: 放課後のVB中学校」)。

 なお、ByVal修飾子とByRef修飾子について、より詳しく知りたい場合は、以下の記事が参考になる。

5. 文字列連結は&で行う

 VBで文字列を連結するには、「+演算子」か「&演算子」を使う。

連結演算子は、複数の文字列を結合して 1 つの文字列にします。 連結演算子には、+ と & の 2 つがあります。

Dim a As String = "1" + "2"
Dim b As String = "1" & "2"

 ただ、「+演算子」は数値演算でも使用するため紛らわしい。特に「Option Strict Off」と組み合わさると凶悪さを増すことは前述のとおり。

 従って、文字列を連結する場合は、常に「&演算子」を使ってもらいたい。

 なお、相当数の文字列をループで連結するような場合*5、演算子による連結ではなく、StringBuilderクラスを使用すべきであるので、併せて覚えておいてもらいたい。

連結する String オブジェクトの数が決まっている場合は、String クラスを使用した方が効率的です。 この場合、個々の連結演算は、コンパイラによって 1 つの演算に結合されます。 これに対し、ランダムな数の文字列をユーザーから入力として受け取り、ループ処理で連結する場合など、連結する文字列の数が不定である場合は、StringBuilder オブジェクトが適しています。

*5 CSVフォーマットのテキストを生成する、などが考えられる。

6. プロシージャ呼び出しの()は略さない

 VBでプロシージャ(=メソッド)を呼び出す際、引数がない場合は(メソッド名の後に続く)「()」を省略できる。しかし、メソッドをこのように記述するとプロパティと紛らわしいため、「()」を省略してはいけない。

Private Function Hoge() As String
  ' 〜
End Function

Public Sub Main()
  Dim hoge As String = Hoge    ' プロパティ? メソッド?
  Dim fuga As String = Hoge()  ' 「メソッド呼び出し」と、すぐに分かる
End Sub

 なお、Visual Studioでコードを書く場合は、自動的に「()」が補完される。

7. Callを使用しない

 VBでプロシージャを呼び出す方法として、Callステートメントがある。

Function 、Sub、またはダイナミック リンク ライブラリ (DLL) プロシージャに制御を渡すフロー制御ステートメントです。

Private Function Hoge(ByVal a As String, ByVal b As String) As String
  Return a & b
End Function

Private Sub Fuga(ByVal a As String, ByVal b As String)
  Console.WriteLine(a & b)
End Sub

Sub Main()
  Call Hoge("a", "b")
  Call Fuga("a", "b")

  Hoge("a", "b")
  Fuga("a", "b")
End Sub

 だが、「Call」は付けても付けなくても、結果は同じである。わざわざ付ける必要はない。*6

*6 わたしの考え。人によっては「Call」を付けた方が見やすいと言われることもある。

8. 長大なWithは避ける

 VBの特徴的な記法として、Withステートメントがある。

オブジェクトや構造体への参照を繰り返し指定する、一連のステートメントを実行します。

Dim product As New Product()
With product
  .Code = "001"
  .Name = "商品"
End With

 「With」を使うことで、特定のオブジェクトのメンバへのアクセスをまとめて記述できる。

 だが、入れ子にしたり、Withステートメントの中で関係のない処理をしたり、ほかのステートメントと合わさったりすると、急に凶暴性を発揮する。

Dim org As New Organization()
With org
  .Code = "001"
  .Name = "○○株式会社"

  For Each dept As Department In .Departments
    With dept
      .Code = "001001"  ' org変数とdept変数のどっちのメンバ?
      .Name = "ソフトウェア開発事業部"

    End With

  Next

  '
  '
  '
  '
  '
  '
  '
  '
  ' ……かなりの長さの関係ない処理
  '
  '
  '
  '
  '
  '
  '
  '
  '

  .Level = Level.Top  ' どのインスタンスのメンバ?
End With

 コード例に示したように、「.〜」で指定したメンバが、どのインスタンスのものか非常に分かりにくくなってしまう。*7

*7 しかし、入れ子となっている親「With」のメンバに、子「With」の中から「.〜」という記述でアクセスすることはできない。

 Withステートメントを使う場合、そのコードが1画面の表示範囲に収まる程度の長さにし、極力、入れ子にしないよう心がけていただきたい。

9. 無意味なNothingの代入

 VB6以前では、以下のように変数に「Nothing」を代入することで、オブジェクトを破棄していた。

Set hoge = Nothing

 その延長なのか、メソッドの終わりで変数に「Nothing」を代入しているソース・コードをよく見かける。

Sub Main()
  Dim product As New Product()

  ' 〜 処理
  product = Nothing
End Sub

 このような記述はVB.NET以降では「意味がない」。なぜならNothingを代入してもオブジェクトは破棄されないからである。*8

*8 オブジェクトの破棄はGC(ガベージ・コレクタ)によって自動的に行われる。ごくまれに、GCに回収されやすくするために、「Nothing」を代入することもあるが、そんなのはレアケース。

 また、参照型でなく値型の変数に「Nothing」を代入した場合、暗黙的にその型の初期値が設定される。

変数に Nothingを代入すると、変数の宣言された型に対する既定値が変数に設定されます。型に変数のメンバーが含まれている場合は、すべてに既定値が設定されます。

Dim i As Integer = Nothing ' 0
Dim b As Boolean = Nothing ' False

 そんな「暗黙の既定値」に頼ったコードは可読性も悪く、思わぬバグも生みかねない。従って、まったく意味がないうえに余計な混乱を招く可能性がある「Nothing」の代入は行わないようにしてもらいたい。

 なお、IDisposableインターフェイスを実装した型は、Disposeメソッドを呼び出すことで、明示的に「アンマネージ・リソース」を解放する。

 Usingステートメントと併用して、原則的にDisposeメソッドを必ず呼ぶようにしてもらいたい。

Using conn As new SqlConnection()

  ' 〜 conn変数を使った処理

End Using ' ブロックを抜けるときに必ずconn.Disposeメソッドが呼ばれる

まとめ

 VB6以前とVB.NET以降は「別の言語」。各言語に沿った正しい記述を心がけよう。End of Article

【筆者プロフィール】
まさる/高野 将(たかの しょう)

 新潟県長岡市在住の子育て奮闘中プログラマー。家事や仕事の合間を縫って、Blogやコミュニティ活動を通して情報発信を行う。著書に『かんたんASP.NET』(技術評論社、2010年)。



インデックス・ページヘ  「.NET開発者中心 厳選ブログ記事」

@IT Special

- PR -

TechTargetジャパン

Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

イベントカレンダー

PickUpイベント

- PR -

アクセスランキング

もっと見る

ホワイトペーパーTechTargetジャパン

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH