[[20160826111637]] 『エラー処理について』(T1608) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『エラー処理について』(T1608)

下記コードを実行すると、「オーバーフロー」エラーになります。
おそらく「0除算」が原因かと考えているのですが、何故でしょうか?
A/BでA=10,B=0などの簡単なコードでは試しましたが問題ありません。
返信遅れがちになるかと思いますが、よろしくお願いします。

 Set r3 = Range("AA2:AA" & maxR2)
    v3 = r3.Value
    i = 0
    With WorksheetFunction
        For Each t In r3
            i = i + 1
            z = t.EntireRow.Range("B1")
            z2 = t.EntireRow.Range("A1")
            On Error GoTo Err   'エラー処理
            v3(i, 1) = .CountIfs(Range("AP2:AP" & maxR), z, _
                                          Range("AQ2:AQ" & maxR), z2, _
                                          Range("AS2:AS" & maxR), "<=3") / _
                                          .CountIfs(Range("AP2:AP" & maxR), z, _
                                          Range("AQ2:AQ" & maxR), z2)
            GoTo MC     'Main Course
 Err:
            v3(i, 1) = 0
 MC:
        Next
    End With

maxR,maxR2は入力データの最終行です。

< 使用 Excel:Excel2010、使用 OS:Windows7 >


On Error GoTo文の使い方が間違っています。今後、一切この命令は使わないことをお薦めします。

まずは、この1行を消してから実行してみてください。そして、エラー停止した際に、各変数の中身がどうなっているか、全て調べてみてください。

怪しそうなのは、変数宣言部分です。そこを省略せずに、貼ってください。
(???) 2016/08/26(金) 11:44


 分子も分母も共に0の場合、オーバーフローになるようです。

 こんな風にして、0かどうか判断して進めれば、On Error 文に頼ることもないと思います。

     With WorksheetFunction
         For Each t In r3
             i = i + 1
             Z = t.EntireRow.Range("B1")
             z2 = t.EntireRow.Range("A1")

             N = .CountIfs(Range("AP2:AP" & maxR), Z, Range("AQ2:AQ" & maxR), z2, Range("AS2:AS" & maxR), "<=3")
             S = .CountIfs(Range("AP2:AP" & maxR), Z, Range("AQ2:AQ" & maxR), z2)

             If N = 0 Or S = 0 Then
                 v3(i, 1) = 0
             Else
                 v3(i, 1) = N / S
             End If
         Next
     End With

(半平太) 2016/08/26(金) 12:09


???様、半平太様、 ありがとうございます。

宣言部分を追記します。
Dim r3 As Range
Dim t As Range
Dim v3 As Variant
Dim i As Long
Dim z As Variant, z2 As Variant
Dim maxR As Long, maxR2 As Long
ご指摘の「On Error GoTo Err」を無効化した結果は「オーバーフロー」でした。
デバッグしたところ、v3(7,1)が「Empty値」となっています。

できれば、不測の事態(どのようなエラーでも)に備えつつできるかぎり簡単なコードを..と「On Error..」処理を使えないかと思ってますが、どうもお二人ともあまり推奨されていないように感じますが、何故でしょうか?
(邪推?であればご容赦願います..^^;)

関数式をマクロ化する作業を進めていますが、同様な課題がありそうな式が多くあります。
これを機会にしっかり学習いたしたく、ご教授願います。

(T1608) 2016/08/26(金) 14:20


 >どうもお二人ともあまり推奨されていないように感じますが、何故でしょうか?

 正しい使い方を理解していないように見えるから、じゃないですか。www

(とおりすがり) 2016/08/26(金) 14:35


Empty値となるのは、手抜きして変数宣言を行わなかったとか、Variant型変数とした、とか、何でもアリの型にしたためかと思います。 現状のまま、計算前に判定するには、IsEmpty関数とかIsError関数で可能なので、On Error Goto は必要ありません。

で、何故 On Error Goto を使うな、と言うのか。 これは、Goto文と似た文字列ですが、Goto文のようなジャンプ命令ではないからです。ジャンプではなく、エラートラップ命令なのです。 つまり、割り込み分岐であり、どこからでもここに飛ぶ可能性のある、特殊処理です。 近年の高級言語では、Catch〜Try 等の命令に相当します。 C言語等の、Goto文は使うな、というのとは全く違います。

本当はどうやって使うのかというと、エラートラップにかかって飛び先に移った後、エラー時の処理を行い、終わったらResume文(ヘルプの例だとReturn文)で元のソースに戻します。これをせずに飛びっぱなしにすると、次々とエラー発生するとどんどん資源を使い続け、資源不足でシステム停止してしまう事があるのです。まぁ、そこまで行くことはまずないですが、1回エラー発生し飛んでも、2回目のエラーには無反応、とかもあります。

割り込み分岐の飛び先は、なるべく迅速に処理を終えて、元のコードに戻す必要があります。これは、分岐先でまたエラーになったらどうするの?、という事になるから。

Goto文と似ているので、多くの方が間違ったまま使い、思ってもいない時に問題発生する原因となっていたりします。 間違えているのに、当人には間違っているという意識がないので、原因不明で終わったり、ネットに質問を投げても迷宮入りしたりしがち。 だから、間違った使い方をした人は、二度とOn Error Goto文は使うな、というアドバイスをするのです。
(どうしてもOn Errorが必要な場合は、On Error Resume Nextを推奨します)
(???) 2016/08/26(金) 14:46


ちなみに、今回のような処理では、半平太さんのように、計算前に分母が0かどうか判定するのが良いですね。

On Error は以降どこで発生するのか判らない割り込み処理なので、本来のロジック以外の遅さ、ロジック解析の判りにくさに繋がります。 今回は計算は1箇所ですが、実際にエラー発生した行はどこか?、も、判らなくなりますよね。 On Error Resume Nextも含め、必須な時以外は使うべきではないのですよ。
(???) 2016/08/26(金) 15:00


 ???さんから専門的なポイントでのお話がありましたので、 ちょっと別の観点で。

 どんな時にエラーが発生するか?

 1.間違えたプログラムを書いた場合
 2.特定のデータを取得するためにVBAのメソッドを使ったとして、メソッドの中には、特定のデータがない場合にエラーになるものがある。
 3.プログラムは正しいけれど、扱うデータに仕様外のものがある場合。(数値であるべきなのに英字になっているとか)

 で、このエラーに対して、エラートラップで、エラーをバイパスするわけですね。

 でも、1.は論外でしょう。
 間違いは間違いで、バグですから、早く直さなきゃいけない。エラーバイパスすると、間違いに気が付くのが遅れる。
 もしかしたら永久に間違ったまま。それでは具合悪いですね。こういった目的でエラーバイパスをやってはいけません。

 2.については、事前に特定のデータの有無を調べるという手もありますけど、面倒なので、エラーバイパスが多いですね。
 ただし、On Error Resume Next で処理して、当該メソッドの後に On Error Goto 0 でリセットしますね。

 3.が悩ましいですね。相手にするデータが、他人(取引先であるとか、社内でも別部署であるとか)が作ったものであり
 その整合性や信憑性に、今いち、信頼がおけない といったケース、少なくないですね。
 もちろん、それが少ない、あるいは、こことこと と限定できれば、処理前にチェックをかけて、エラーにならないような処理もできますけど
 あれもこれも・・・チェックのためのコードのほうが、正規処理のコードより多くなってしまう。
 (実際の業務プログラムでは、例外チェックコードが正規処理コードより多いというのは、めずらしいことではないですが)

 こういう場合に、エラートラップをかける。これはやむをえないかもしれません。
 ただし、間違ったデータだったので、処理をしなかった、あるいは 値を 0 にした といったノーティスをあげることが必須。
 メッセージも何もなしで、正常終了すると、

 ・本来は、仕様通りのデータを作ってもらうように依頼しなければいけないと思いますが、いつまでたっても、データが不備のまま。
 ・処理結果が、たとえば 0 になっていて、これが本当の 0 なのか、本来 0 ではないのに、データエラーで 0 になったのかの
  判断ができない。(そういったエラーが起こったということを誰も気づかない)
  その結果、その数値をもとに、販売活動等を行う場合、とんでもない事業損失が発生する可能性あり。

(β) 2016/08/26(金) 15:38


とおりすがり様、???様、β様

返信遅れてすみません、ありがとうございます。

原則的には半平太様ご提示案やIsEmpty関数、IsError関数で事前判定して
エラー回避をしたいと考えます。

その上で以下教えていただけないでしょうか?(今後の方針は上述なので、学習目的です)

 >Variant型変数とした、とか、何でもアリの型にしたためかと思います
 >正しい使い方を理解していないように見える
 >間違えたプログラムを書いた場合
↓
今回うまくいかない具体的な原因は何でしょうか?
追記)データを提示していないので、可能性が高い原因という意味です。

 >エラー発生するとどんどん資源を使い続け、資源不足でシステム停止してしまう事がある
↓
「オーバーフロー」と表示さるのは、このことでしょうか?

 >1回エラー発生し飛んでも、2回目のエラーには無反応
↓
これはある意味怖いですが、エクセルの「バグ」とでもいうべきものでしょうか?

 >On Error Resume Next で処理して、当該メソッドの後に On Error Goto 0 でリセット
↓
当初はこれを使いましたが、これだとエラー発生時は「0」にならず「空白」となるため、
提示コードにしました。
「On Error Resume Next」を使用して「0」処理するコードを考えてたのですが、
うまくいきません..できるものなんでしょうか?

よろしくお願いします。
(T1608) 2016/08/27(土) 09:06


 >>1回エラー発生し飛んでも、2回目のエラーには無反応
 >↓
 >これはある意味怖いですが、エクセルの「バグ」とでもいうべきものでしょうか?

 いいえ、On Error ステートメントの使い方が間違っているからです。解説サイトを探して
 勉強しましょう。www

(とおりすがり) 2016/08/27(土) 10:22


 >>当初はこれを使いましたが、これだとエラー発生時は「0」にならず「空白」となるため、提示コードにしました。
 >>「On Error Resume Next」を使用して「0」処理するコードを考えてたのですが、うまくいきません..できるものなんでしょうか?

 今回は、半平太さんから提示された事前チェックで回避できますけど、一般論として。
 エラーリセット前にエラー番号を保存しておいて、それを判定すれば、エラーが発生したのかどうかがわかります。

 Sub Test()
    Dim w
    Dim er As Long

    On Error Resume Next
    w = 100 / 0     '★エラーになる
    er = Err.Number
    On Error GoTo 0

    MsgBox er

    On Error Resume Next
    w = 100 / 10    '★エラ−にならない
    er = Err.Number
    On Error GoTo 0

    MsgBox er

 End Sub

(β) 2016/08/27(土) 17:33


こんにちは。

>当初はこれを使いましたが、これだとエラー発生時は「0」にならず「空白」となるため、
>提示コードにしました。
>「On Error Resume Next」を使用して「0」処理するコードを考えてたのですが、
>うまくいきません..できるものなんでしょうか?

理解の単位がとても大粒です。
○○のときは××を使うというような、ユニット単位で考えているように見えます。
思い通りにコーディングするには、せめて行単位(できれば単語単位)の理解が必要です。

「On Error Resume Next」の意味ですが、
「エラーが出ても無視して次の行からコードの実行を続けろ」です。
「無視して」だから、「エラーが出ました」のダイアログボックスは出ません。
ただし、エラー自体は発生しています。無視されるだけです。消えてなくなるのではありません。

そして「次の行から実行」です。エラーが出た行は実行されません。
つまり v3(i, 1) = .CountIfs(Range("AP2:AP" & maxR), z, 〜 は、実行されません。

実行されなければとうぜん v3(i, 1)になにも代入されませんので、v3(i, 1)の値は代入前のままです。
代入前の値が「空白」なのか「0」なのかは、「On Error Resume Next」と関係ありません。
関係があるのは、代入前にv3(i, 1)に何が格納されていたかです。
特に何も代入していないなら、その変数型の初期値はなにか、です。

( 佳 ) 2016/08/28(日) 08:26


とおりすがり様、β様、佳様

みなさん、ご丁寧な説明ありがとうございます。

ご指摘のとおり、中途半端な理解で安易に使えないなぁ〜と
つくづく思いました...

十分に咀嚼できているかはなはだ疑問ありですが、
作業を進めていきたいと思います。

これからも、よろしくお願いいたします。
(T1608) 2016/08/28(日) 12:58


On Error Goto は使うな、とアドバイスしましたが、使った場合のコーディング例を書いておきます。
x の値を変えてみたり、F8キーでステップ実行してみてください。
事前判定するのが一番簡単であり、安全だというのが判るかと思います。

 '分母を事前判定する例
 Sub test1()
    Dim w As Long
    Dim x As Long

    x = 0
    If x = 0 Then
        w = 0
        MsgBox "分母0は0割エラーになります。", vbCritical, "エラー"
    Else
        w = 100 / x
        MsgBox w
    End If
 End Sub

 'On Error Resume Nextで処理する例
 Sub test2()
    Dim w As Long
    Dim x As Long
    Dim iEr As Long

    x = 0
    Err.Clear
    On Error Resume Next
    w = 100 / x
    iEr = Err.Number
    On Error GoTo 0
    If iEr <> 0 Then
        w = 0
        MsgBox "分母0は0割エラーになります。", vbCritical, "エラー"
    Else
        MsgBox w
    End If
 End Sub

 'On Error Gotoで処理する例
 Sub test3()
    Dim w As Long
    Dim x As Long
    Dim iEr As Long

    x = 0
    On Error GoTo sEr
    w = 100 / x
    MsgBox w

 L1:
    On Error GoTo 0
    Exit Sub

 sEr:
    w = 0
    MsgBox "分母0は0割エラーになります。", vbCritical, "エラー"
    Resume L1
 End Sub
(???) 2016/08/29(月) 10:40

???様

再々ありがとうございます。(返信遅れましてすみません)

「On Error...」は、よほど注意深く使用しないと“ケガをする”ことが
よくわかりました。
また、コード自体も(使わない場合より)簡単とはいえないですねぇ..
“錯覚”しておりました。

ご教授陣レベルの足元に近づいたかな..と思えるまでは、「使用厳禁」と
します(苦笑)。  これからもよろしくお願いいたします。
(T1608) 2016/08/30(火) 09:37


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.