[[20200825081004]] 『VBAでActiveSheetを使い続けるリスク』(k) ページの最後に飛ぶ

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

 

『VBAでActiveSheetを使い続けるリスク』(k)

Sub 取引先ごとのシートをコピー()
Dim rowsClient As Long
rowsClient = wsClient.Cells(Rows.Count, 1).End(xlUp).Row '取引先マスタの最終行数
Dim n As Long
For n = 2 To rowsClient

    Dim client As String
    client = wsClient.Cells(n, 1).Value
    wsTemplate.Copy After:=wsTemplate
★  Dim ws As Worksheet
★  Set ws = ActiveSheet
★   ws.Name = client    
Next n
End Sub

VBAでActiveSheetを使い続けるリスクの説明があり、

マクロを実行中に、ユーザー操作によってアクティブなシートが変更されてしまい、プログラム内で想定していないワークシートが処理の対象となってしまう可能性があるからです。
なので、コピーして生成されたActiveSheetを、すぐさま「オブジェクト変数」にセットして、その「オブジェクト変数」を対象にマクロの処理を進めていくのが理想です。

と、あるのですが、★の部分のところがプログラムのやっていることはわかるのですが、やる意味が、いまいちよくわかりません。宜しくお願い致します。

< 使用 Excel:Excel2011(Mac)、使用 OS:MacOSX >


 >やる意味が
 説明文そのものです。
(H) 2020/08/25(火) 09:43

今、意味が判らないなら、読み捨てておいて良いですよ。 そのうち、アクティブを対象としたせいで誤動作する、という場面に遭遇して、初めて理解できることでしょう。 本を読むだけでは身に付かないものです。

マクロの記録を使うと、いろいろとアクティブなオブジェクトに対して操作するコードが得られます。 例えば、ブックを2つ開いておいて、1つはマクロ入りでこれを実行したとします。 処理完了まで時間がかかるので、その間にもうひとつのブックを手作業で編集しよう、とした場合、マクロがアクティブなブックにいろいろ書き込むコードであれば、手作業しようとしたブックをアクティブにした途端、マクロの出力対象が変わってしまいます。
(???) 2020/08/25(火) 09:44


  >やる意味が、いまいちよくわかりません。

  そのコードに関しては、やる意味ないです。
 「プログラム内で想定していないワークシートが処理の対象となってしまう可能性」が絶無ですから。

 別件ですが、Dim ws As Worksheet の定義はループに入る前に済ませて置けませんか。
 私は気持ちが悪いんですが。

(半平太) 2020/08/25(火) 09:46


こんなかんじに置き換えてみましたが。。
ユーザーに操作させたくないのならApplication.ScreenUpdating = Falseとか
使えばいいと思います。
☆は常に皆様が言っています通りです。

Sub 取引先ごとのシートをコピー()

'コントロールの操作不可
Application.ScreenUpdating = False

Dim rowsClient As Long
Dim wsClient As Worksheet
Set wsClient = Worksheets("wsClient")
Dim wsTemplate As Worksheet
Set wsTemplate = Worksheets("wsTemplate")

rowsClient = wsClient.Cells(Rows.Count, 1).End(xlUp).Row '取引先マスタの最終行数

Dim n As Long
For n = 2 To rowsClient

    Dim client As String
    client = wsClient.Cells(n, 1).Value
    wsTemplate.Copy After:=wsTemplate
    ActiveSheet.Name = client
n = n + 1
Next n

   Application.ScreenUpdating = True

End Sub

(うさぎたん) 2020/08/25(火) 09:52


 こちらのサイトからですか?
https://tonari-it.com/excel-vba-object-variable-set/

 なんか、全体的に気持ち悪いコードですね・・・コンパイル通らないですよね?
 変数の宣言してないし、ワークシートセットしてないし、なんだこれ?
 変数宣言もループ内・・・?

 ↑のサイト参考にしないほうがいいと思いますよ。

(稲葉) 2020/08/25(火) 11:37


 >VBAでActiveSheetを使い続けるリスクの説明

 今まで1度も考えた事がないです。
 マクロの起動方法や内容で変わるし。

 大体、マクロ実行中にセルとかに触れる様にしてないし。
 普通に書けば触れると思えない。
 あきらめないでひたすら連続クリックすれば触れる可能性はあるようにはしてあるかな。
 先走りの知識だけ詰め込むのもどうだろう?
(Why) 2020/08/25(火) 12:10

 いくつか、気づいた点をメモします。

 (1)
 引用サイトを拝見しました。
 質問者さんの引用されたコードだけでは
 Set ws = ActiveSheet
 とする必然性はまったく感じられません。皆さんご指摘のとおりです。

 しかし、サイトを最後まで読むと、最後にまとめてあるコードでは、
 その後で、wsに対する処理をしているのですよ。
 それなら、可読性向上の観点からも、Activesheetのままでそれに処理を加えるより、
 オブジェクト変数に代入することは妥当なことだと思います。
 別のところで、シートをアクティブにするリスクもあるし、
 特に長いコードになると、現在アクティブなシートが何か、
 に常に気を使わないといけないという負荷もあります。
 明示的に、シート内容がわかる変数名に代入すべき、というのは妥当でしょう。
 他人(将来の自分も含む)にとっても理解し易くなるという道理でしょう。

 (2)
 また、変数宣言の位置に関しては、
 プロシージャの最初にまとめて宣言する方式の他、
 使う時点で宣言する方式もあります。
 これは、VB.Netなどではこうした使い方が普通のようです。

 ちなみに、私は前者の方式を使います。
 後者の方式ですと、途中に非実行文が入り、
 論理が中断されてしまう気がして、慣れません。
 型宣言を使用しない言語を長く使っていたせいもあるかもしれません。

 後者の方式のほうが分かり易い、という方もいらっしゃいます。

 (3)
 | ユーザーに操作させたくないのならApplication.ScreenUpdating = Falseとか
 | 使えばいいと思います。
 こういう説明の仕方は初めて拝見しました。
 「画面更新の抑止」などと説明されることが多いのではないですか?

 ユーザー操作を不可とさせるという目的のためには、
     Application.Interactive = False
 という書き方が使われますね。めったに使いませんが。
 これならキー操作マウス操作すべて排除できます。

 なお、コードを書き直して提示されるなら、インデントはしっかりつけて欲しいものです。
(γ) 2020/08/25(火) 12:47

稲葉さんが見つけてくれたサイトを拝見しました。

やる意味については、ws.Nameを変更するだけであれば必要ないです。
(半平太さんのおっしゃる通り)
ただ、後半でws.Rangeを複数操作しており、そこそこ処理に時間がかかるなら
なんらかの事情でActivesheetが変わってしまうリスクはあるでしょう。
また、普通はコードを記載するとき「どのワークブックのどのワークシートのどのセルがActiveか」
のようなことを気にするのは大変なので、オブジェクト名等でシートは特定し、
Activesheetを使う機会はあまりないものです。

お示しになったコードについてコメントすると、
ホンネを言うと、
Set ws = wsTemplate.Copy(After:=wsTemplate)
(※このコードは動きません)
ってやりたいんです。これならコピーしたシートがactiveかどうかかにすることなく、
wsに対する操作ができますから。
だけど、Copyメソッドは、シートオブジェクトを返さない(Subで定義されている)ので、
仕方ないから、コピー直後にオブジェクト変数にActivesheetを使って代入してるのです。
(DS) 2020/08/25(火) 12:58


 本筋と関係無い話ですが
 γさんがおっしゃてる
 >また、変数宣言の位置に関しては、
 >プロシージャの最初にまとめて宣言する方式の他、
 >使う時点で宣言する方式もあります。
 >これは、VB.Netなどではこうした使い方が普通のようです。
 について、VB.Netでは、ブロックスコープの変数が宣言できるんです。

 https://docs.microsoft.com/ja-jp/dotnet/visual-basic/programming-guide/language-features/declared-elements/scope

 For〜Nextや、Do〜Loop、With〜End With 等のブロックの中で宣言した変数は、
 そのブロックの中だけのスコープになります。
 わかりやすい/わかりにくい というより、変数のスコープを局所化するためです。

 VBAではこれができませんので、利用直前に宣言するのは、価値がありません。
 変数はスコープの先頭で宣言するのがわかりやすいと思います
(てくてく) 2020/08/25(火) 13:30

ブロックスコープの件、承知しています。

下記を拝見すると、
VBAでも「使用する直前で宣言派」も多いようですよ。
https://teratail.com/questions/137797

はじめに纏めて宣言派は、時代遅れ?らしい論調が個人的には気になっています。

(γ) 2020/08/25(火) 13:56


質問?の答えについては皆さんが仰っている通りなので割愛するとして、私もループ内で変数の宣言をしていたり、インデントが不自然だったりするのが気持ち悪いと思いましたが、参考にされたサイトでそうなっていたんですね・・・・

ActiveSheetに依存するコードはお勧めしないのは、私も同意見ですが、今回のようなケースであれば↓のようにすれば、オブジェクト変数にセットする必要もないとおもいます。

    Sub 取引先ごとのシートをコピー改()
        Dim n As Long

        Stop 'ブレークポイントの代わり

        With Worksheets("取引先マスタ")
            For n = 2 To .Cells(.Rows.Count, 1).End(xlUp).Row

                '▼シート末尾(の後ろ)に「ひな型」シートをコピー挿入
                Worksheets("ひな型").Copy After:=Worksheets(Worksheets.Count)

                '▼(コピー挿入されてシート末尾になった)シートの名前を変更
                Worksheets(Worksheets.Count).Name = .Cells(n, 1).Value
            Next n
        End With

    End Sub

 ※解ると思いますが、シート名は適宜読み替えて下さい。

(もこな2 ) 2020/08/25(火) 14:39


脱線ですが、変数宣言は少しだけ大事(大きいのか小さいのかどっちよ!?)なので、私の見解なぞ。

元々、BASIC言語はFORTRANに近い考え方ですが、FORTRANのような初期の高級言語は、コンパイル→リンクして実行モジュールを作ってました。 このとき、変数宣言をまとめておくと、リンクしたときにそのまま並ぶので、リンクマップ情報からメモリダンプしたり、アドレス指定でメモリ直接書き換えたりするのが容易だったのです。(1回のビルドで何十分とか何時間とかかかっていたので、5分くらいでメモリ書き換える方が修正時間がかからなかった)

その後、PC性能がどんどん上がり、ビルド時間が大幅に短縮されたため、誰もリンクマップなんて気にしなくなったし、コンパイラはどこに変数宣言しても、ビルドすると勝手に1か所にまとめるようになりました。 更には現在の高級言語各種では、コンパイラが自動的に不要になった変数解放をするようになったので、スコープ内で確保/解放することで、使いっ放しの無駄をなくそう、という考えが主流になってます。 高級言語については、私もこれに全面賛成です。

しかしながら、VBAはBASIC言語の基本を継承しているので、スコープを外れると自動開放ではなく、プロシジャを抜けると自動開放するという動きです。 インタプリンタなので、スコープの意識が無いのでしょうね。 だから、使用する直前に宣言しても、使い終わった直後ではなく、プロシジャを抜けるときに開放なので、今どきの高級言語ほど効率的ではないんですよね。

確保はぎりぎりでも開放は最後、と片手落ちなコーディングをするくらいなら、変数宣言を先頭に並べることで宣言ミスを目視確認できる場合もあるので、先頭にまとめる方が、VBAには適していると思っています。 そもそも遅いインタプリンタ言語だし、速度やリソースの小さい差を気にしてどうするの?、って感じです。

まとめて宣言が時代遅れなのは確かだと思いますが、VBA自身が時代遅れなので、今風な書き方をしても見た目だけで効果無し、と思います。
(???) 2020/08/25(火) 14:46


脱線ついでで、済みません。教えて下さい。

>確保はぎりぎりでも開放は最後
本当にぎりぎりに確保されるのでしょうか?

以下のコードでは、宣言前なのにローカルウィンドウにsがString型変数として表示されます。

 Sub test()
    Dim i As Long:      i = 10
    Stop
    Do Until i = 12
        Dim s As String:    s = i
        i = i + 1
    Loop
 End Sub

以下のコードではコンパイルエラーになります。
つまり、どこで宣言してもコンパイル時点で領域が確保されているのでは無いのでしょうか?

 Sub test2()
    Dim s
    Dim s As String
 End Sub

(kazuo) 2020/08/25(火) 20:17


何だか回答者同士の論議になっていませか?
質問の趣旨からはずれていませか?

(MOKA) 2020/08/25(火) 20:34


質問者さんの疑問には既にお答えしています。
派生した話に及んでもいいんじゃないですか?
質問者にも閲覧者にも参考になるものと思いますよ。
(γ) 2020/08/25(火) 21:01

 VBAしかしらんけど、局所使用の使い勝手がよければ
 もっと浸透してると思うんですけどねぇ

 私も紆余曲折こちらで勉強させてもらって、細かすぎるサブプロシジャもやってみては可読性が落ちるし
 都度宣言方式も、やってみたけど、一画面で見渡せるぶんには、都度宣言になんのメリットもなかった

 そもそもプロってVBA本気で組むことあるんですか?
(稲葉) 2020/08/25(火) 21:41

 (1)
 私の記憶では、VBAはP-CODEという中間言語を作成します。
 ですからステップ毎の実行に先立って変数宣言は終了しています。
 私が、
 >途中に非実行文が入り、
 と、申し上げたのはその意味です。

 (2)
 これまた別件で叱られそうですが、変数宣言でよく勘違いされる議論は、こんな話です。
 Sub test()
     Dim i As Long, k As Long
     For k = 1 To 2
         For i = 1 To 3
             Dim j As Long
             j = j + 1
             Debug.Print i; j
         Next
     Next
 End Sub
 を実行したとき、
  1  1 
  2  2 
  3  3 
  1  1 
  2  2 
  3  3  
 が出力されると思いますか?
 実は、そうはならないんですよ。
 別に   Dim j As Long で初期化が起きるわけではないんですね。

 (3)
 変数のスコープは、VBA(VB6)はプロシージャ単位より細かくはならないので、
 その点からのメリットはないことは、既にご指摘のとおりです。

 ただし、変数宣言分散派の主張のなかには、
 「意味的にまとまっているところは、それぞれで宣言した方が
 理解もし易いだろう」という発想も含んでいると思います。

 ただし、これは、先頭宣言派に取ってみれば、
 それはプロシージャを余り長くせずに、適切な長さに保持することで十分対応できる、
 と考えるわけです。

(γ) 2020/08/25(火) 22:36


 自分は先頭宣言派だと信じて疑わなかったのですが、
 このトピを拝見して、よく思い返してみましたところ、
 どうやらそうでもなかったみたいです^^;

 ・プロシージャの全体に渡って登場する主役級の変数
 ・ループカウンタの様にそれ自体あまり意味を持たない変数(例: i As long)

 は先頭で宣言してますが、

 ・そのブロックでしか登場しない局所的な変数
 ・For Each文のイテレータ変数
 ・一旦完成した後から追加した処理に使う変数(多分プロシージャを分けるのが面倒だっただけ)

 なんかは使い始める直前でDimってたりしてました。
 でも厳密に使い始める直前とも限りませんね。
 どちらかというと「各ブロックの先頭でまとめて宣言」の方が近いかも。

 あと、
 イベントプロシージャの先頭でエラー回避のIf 〜 Then Exit Subを入れたりしますが、
 これは先頭のDimよりも上に書く様にしてます。
 それとか、
 ソートアルゴリズムの様に再帰処理を行うプロシージャなんかは、
 再帰呼出し以降に発生する変数の宣言も、再帰呼出しの後に書いてます。
 多分、その方が「しっくり」きたんでしょう。単純に。

 結局は文脈次第で自分が読み易い様にいろいろやってたんですねー。

(白茶) 2020/08/26(水) 00:24


コメント返信:

[ 一覧(最新更新順) ]


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