Как избежать по умолчанию свойства getchas в VBA?

Я только время от времени использую VBA, и каждый раз, когда я возвращаюсь к нему, меня улавливают некоторые вариации следующего:

У меня есть объект Range, currentCell, который я использую, чтобы отслеживать, с какой ячейкой я работаю в электронной таблице. Когда я обновляю это, чтобы указать на другую ячейку, я пишу:

currentCell = currentCell.Offset(ColumnOffset:=1)

Проблема заключается в том, что я забыл ключевое слово Set, поэтому то, что на самом деле делает эта строка, это использовать свойство по умолчанию для объектов Range:

currentCell.Value = currentCell.Offset(ColumnOffset:=1).Value

Таким образом, содержимое текущей ячейки перезаписывается тем, что в новой ячейке, и моя переменная currentCell не изменилась, чтобы указать на новую ячейку, и я наполнился яростью, поскольку я понимаю, что сделал ту же ошибку в сотый раз.

Вероятно, нет лучшего ответа, чем поставить сообщение на моем мониторе, говоря: "Вы помнили, что сегодня используете Set?", но если у кого-то есть предложения, которые помогут мне, я был бы признателен за их слушание. В частности:

  • Есть ли способ включить предупреждения, когда вы неявно используете свойства по умолчанию? Я никогда не использовал их специально для этого, я всегда называл Range.Value, если бы это имел в виду.
  • Есть ли хорошая практика для обозначения переменных как "это следует использовать только для чтения из электронной таблицы"? В большинстве кодов, которые я пишу, почти все мои переменные предназначены для сбора данных, и было бы полезно получить предупреждение, если что-то начнет непреднамеренно редактировать ячейки, подобные этому.

Ответ 1

Есть ли способ включить предупреждения, когда вы неявно используете свойства по умолчанию?

Нет.

Есть ли хорошая практика для обозначения переменных как "это следует использовать только для чтения из электронной таблицы"?

Хорошо, вы могли бы составить свое собственное соглашение об именах переменных, à la Сделать неправильный код Look Wrong, но вам все равно придется проверять ваш собственный код визуально, и компилятор не поможет вам это сделать. Поэтому я не стал бы слишком полагаться на это.

Лучшим вариантом является обход необходимости многократного переделать currentCell с помощью .Offset в целом.

Вместо этого прочитайте весь диапазон интереса к массиву Variant, выполните свою работу над этим массивом, а затем удалите его обратно на лист, когда вы его измените.

Dim i As Long
Dim j As Long
Dim v As Variant
Dim r As Range

Set r = Range("A1:D5") 'or whatever

v = r.Value 'pull from sheet

For i = 1 To UBound(v, 1)
    For j = 1 To UBound(v, 2)
        'code to modify or utilise element v(i,j) goes here
    Next j
Next i

r.Value = v 'slap v back onto sheet (if you modified it)

Вуаля. Не использовать свойства по умолчанию или что-либо, что можно было бы смутить как таковое. В качестве бонуса это ускорит выполнение вашего кода.

Ответ 2

Мне потребовалось некоторое время, чтобы понять, как вы столкнулись с проблемой, поскольку я не думаю, что у меня когда-либо была эта проблема, несмотря на использование объектов диапазона в течение многих лет. Подумав о вещах, я понял, что выполняю следующие 100% времени при работе с ячейками - я постоянно использую функции offset или cells - редко использую Set для переопределения текущей переменной.

Если у меня есть цикл, который я повторяю, чтобы пройти через электронную таблицу, я могу сделать что-то вроде

Dim startRng as Range
Set startRng = range("A1")

for i = 1 to 100
   startRng.offset(i,0).value = i
   startRng.offset(i,1).value = startRng.offset(i,0).value
next i

или

for i = 1 to 100
    cells(i,0).value = i
    cells(i,1).value = cells(i,0).value
next i

Любое из этих обозначений означает, что я почти редко должен использовать Set с объектом диапазона - почти всегда это происходит один раз (если вообще) и указывает первую ячейку диапазона. Я буду перебирать или ссылаться.

Также ясно, что означает offset, поскольку вы указываете row/column, что делает его очень простым, что происходит в коде и легче отслеживать, поскольку оно ссылается на одну ячейку. Вам не нужно отслеживать и трассировать назад до последних 3-х мест, где вы обновляете объект currentCell Range.

Принятие стиля кодирования с использованием этих стилей должно устранить почти все эти ошибки, которые вы делаете. Я довольно серьезен, когда говорю, что не могу вспомнить, что когда-либо делал подобную ошибку за все мои годы кодирования VBA - я постоянно использую функции offset и cells в своем коде (циклы в этих примерах, но я использую аналогичные методы с все остальные примеры в коде) вместо того, чтобы устанавливать диапазоны в новые диапазоны. Побочным эффектом является то, что когда вы устанавливаете диапазон в своем коде, он почти ВСЕГДА сразу после оператора Dim и гораздо более понятным.

Ответ 3

Что бы вы ни выбрали, вам, вероятно, понадобятся заметки, сделанные после этого. В конце концов, установка значения объекта диапазона в значение в другой ячейке является абсолютно правильной и общей задачей. Код не знает, что вы хотите, чтобы он делал что-то другое, кроме того, что вы просите.

Вы можете попробовать проверить адрес объекта диапазона до и после его обновления, чтобы убедиться, что он отличается, но если вы запомните это, вам лучше просто использовать ключевое слово set, чтобы обновить объект так, как вы предназначен.

Теперь, когда этот вопрос взбудоражил вас до публикации вашего вопроса, я предполагаю, что вы никогда не забудете его снова, независимо от того, сколько времени прошло до вашего следующего визита в VBA. Так что, возможно, вам не понадобится пост-это записка в конце концов.

Ответ 4

Вероятно, нет лучшего ответа, чем поставить сообщение на моем мониторе, говоря: "Вы помнили, что сегодня используете Set?", но если у кого-то есть предложения, которые помогут мне, я был бы признателен за их слушание. В частности:

Я бы слегка изменил формулировку на Post It

"Вы уверены, что не забыли использовать Option Explicit и Error Handling?"

В противном случае поверьте мне, нет лучшего способа! Сказав это, я хотел бы подтвердить, что "использование набора" - это наименьшее из ваших забот. Что должно быть на вершине ваших основных забот - "Написание хорошего кода", и это не наступает в одночасье. Все это происходит на практике.

Мой совет всем новичкам. Никогда не предполагайте! Например, .Value является свойством по умолчанию для диапазона, поэтому

Range("A1") = "Blah" 

является правильным. Но все равно не используйте это.

  • Всегда Полностью квалифицируйте свои переменные
  • Всегда использовать параметр Явно
  • Всегда использовать обработку ошибок

Например, это работает.

Option Explicit

Sub Sample()
    Dim ws As Worksheet
    Dim rng As Range

    On Error GoTo Whoa

    Set ws = ThisWorkbook.Sheets("Sheet1")
    Set rng = ws.Range("A1")
    rng.Value = "Blah"

    Exit Sub
Whoa:
    MsgBox Err.Description
End Sub

Теперь попробуйте приведенный выше код, не используя команду Set. Попробуйте приведенный ниже код. Что происходит?

Option Explicit

Sub Sample()
    Dim ws As Worksheet
    Dim rng As Range

    On Error GoTo Whoa

    ws = ThisWorkbook.Sheets("Sheet1")
    rng = ws.Range("A1")
    rng.Value = "Blah"

    Exit Sub
Whoa:
    MsgBox Err.Description
End Sub

Рекомендуемое чтение.

Тема: To 'Err is Human

Ссылка: http://siddharthrout.wordpress.com/2011/08/01/to-err-is-human/

Ответ 5

Я думаю, что enderland выделила основное решение, которое должно обрабатывать обработку нескольких ячеек в цикле. Чтобы сделать это дальше, я бы предложил использовать цикл For Next для циклического перемещения по ячейкам. Вероятно, из наиболее распространенных битов кода, которые я пишу, есть что-то вроде:

Dim cell as Excel.Range
Dim rngCellsToProcess as Excel.Range

Set rngCellsToProcess = 'whatever you set it to
For each cell in rngCellsToProcess 
   'do something
Next cell

Это не устранит необходимость в Set, но может помочь вам напомнить вам об этом, сделав более понятным, что происходит.

Ответ 6

Может быть, написать свою собственную функцию и использовать ее вместо этого?

Sub offset_rng(ByRef my_rng As Range, _
    Optional row As Integer, Optional col As Integer)
    Set my_rng = my_rng.Offset(row, col)
End Sub

Может использоваться следующим образом:

Sub test()
    Dim rng As Range
    Set rng = Range("A1")
    offset_rng my_rng:=rng, col:=1
    rng.Value = "test1"
    offset_rng my_rng:=rng, col:=1
    rng.Value = "test2"
    offset_rng my_rng:=rng, col:=1
    rng.Value = "test3"
    offset_rng my_rng:=rng, col:=1
    rng.Value = "test4"
End Sub