Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Все статьи »

Обсудить на форуме Обсудить на форуме

А ты готов к Visual Basic.NET? - II

 
Советы тем, кто собирается работать с новой версией Visual Basic (продолжение; начало см. в Byte/Россия №3/2001).

В мировом (в первую очередь американском) сообществе VB-программистов продолжаются весьма эмоциональные споры вокруг будущей версии Visual Basic. По оценкам экспертов журнала Visual Basic Programmer's Journal, большинство разработчиков одобряют технологию .NET, но их благодарность корпорации Microsoft никак нельзя назвать безграничной.

Тем не менее можно констатировать, что многомиллионная (по некоторым оценкам, от 2 до 4 млн. человек) армия пользователей VB-инструмента разделилась на две части. Сторонники новшеств технологии .NET (во главе с Microsoft) упирают на то, что с помощью нового Visual Basic они получат возможность создавать приложения масштаба предприятия, в том числе Web- и серверные приложения. Противники (точнее, критики) говорят о серьезной угрозе стабильности огромной базы существующего VB-кода. Собственно, почти все согласны с необходимостью реформирования Visual Basic, но высказывают настойчивые пожелания обеспечить более высокую совместимость с существующими версиями и растянуть реализацию всех новшеств на несколько последующих версий пакета.

При этом понятно, что накал дискуссий определяется не эмоциями, а чисто практическими интересами — все разработчики понимают, что реализация новшеств Visual Basic.NET может серьезно повлиять на их личную судьбу. Есть опасения, что переход от нынешней архитектуры Windows к будущей .NET может оказаться столь же болезненным, как переход в начале 90-х от DOS к Windows: значительная часть DOS-программистов просто не смогла (в том числе и чисто психологически) адаптироваться к новым методам разработки.

Сейчас никто не отрицает вероятность того, что Microsoft пойдет на некоторые уступки критикам, но все уверены, что корпорация будет продвигаться по выбранному ее руководством курсу, нацеленному, в частности, на завоевание сегмента крупных корпоративных клиентов. По крайней мере, вся десятилетняя история Visual Basic показывает, что за внешней внимательностью к запросам разработчиков лежит довольно жесткая линия стратегии Microsoft, которая ведет сообщество программистов в нужном корпорации направлении.

Все мы, конечно, понимаем, что за риторикой о "наиболее полном удовлетворении нужд трудящихся" скрывается изначальная цель любого коммерческого предприятия — увеличение доходов его акционеров, причем в долгосрочной перспективе. Другое дело, что бизнес-успех вряд ли возможен без учета потребностей рынка, но всем понятно, что взаимоотношения поставщиков и покупателей строятся на основе диалектического закона единства и борьбы противоположностей и выливаются в поиск компромисса между интересами двух сторон.

А теперь перейдем к советам...

Совет 18. Будьте внимательны при определении массивов

При работе с массивами VB-программистов ожидает целый ряд сюрпризов, причем не очень приятных. Общие рекомендации таковы: исключите использование оператора Option Base 1 (его нет в Visual Basic.NET), применяйте массивы с нулевой нижней границей индекса и пересмотрите варианты динамического определения массивов.

Приверженцы истинного Basic* могут радоваться — мы возвращаемся в далекое прошлое: теперь в "родном" варианте Visual Basic.NET поддерживает только массивы с нулевой нижней границей.


*Кстати, в середине 80-х годов Томас Курц, один из создателей первого Basic (это было в 1964 г.), разработал систему, которую назвал True Basic. Тем самым он хотел подчеркнуть, что многие другие Basic-инструменты отошли от начальных канонов этого языка.

Но как раз здесь программистов ожидает «подводный камень» — одна и та же конструкция в Visual Basic.NET и во всех предыдущих версиях будет работать по-разному. Иначе говоря, фрагмент кода

 

Dim Arr(4)
For i = LBound(Arr) To 4
Arr(i) = 0
Next

 

работавший всегда безукоризненно, в Visual Basic.NET выдаст ошибку при i = 4. Дело в том, что определение массива:

Dim Arr(N)

во всех версиях Basic интерпретировалось как

Dim Arr (0|1 To N)

(0|1 — в зависимости от оператора Option Base), т.е. N обозначало верхнюю границу индекса. Но в Visual Basic.NET это будет указывать число элементов:

Dim Arr (0 To N-1)

Тут полезно напомнить, что в Visual Basic и ранее допускалось два формата описания размерности массива (на примере одномерного):

Dim Arr(N)

и

Dim Arr(lowIndex To hightIndex)

Общая рекомендация всегда была одинакова — нужно использовать вторую форму во избежание двусмысленности. Дело в том, что в классическом, древнем Basic имелась только первая форма определения индекса массива, которая соответствовала описанию

Dim Arr(0 To N)

Затем, чтобы обеспечить совместимость с FORTRAN (там нумерация начиналась с 1), был введен управляющий оператор Option Base 0|1, который позволял устанавливать нижнюю границу с нуля или единицы. Но это породило проблему неопределенности. Например, при копировании фрагмента кода Dim Arr(N) в некий модуль конкретное значение нижней границы массива определялось оператором Option Base именно этого модуля. В одном случае это мог быть 0, в другом — 1.

Таким образом, только явное задание нижней границы может устранить эту неопределенность и минимизировать проблемы при переходе в Visual Basic.NET. А еще лучше, если при обработке массива вы будете постоянно контролировать текущие значения границ индекса. Такой код гарантирует, что вы не выйдете за пределы индекса:

 

For i = LBound(Arr) To UBound(Arr)
Arr(i) = 0
Next

 

Но сложности при использовании ненулевой нижней границы останутся — все такие массивы будут преобразованы в массивы типа Wrapper Class, т.е. строка

Dim a(1 To 10) As Integer

поменяется на

Dim a As Object = New VB6.Array(GetType(Short), 1, 10)

Проблема заключается в том, что такие массивы работают заметно медленнее по сравнению с "родными" и существуют некоторые ограничения на их применение. Например, wrapper-массивы нельзя передавать в C-классы и в процедуры, использующие параметры типа Array.

Что касается динамического определения массива, то теперь конструкция

Dim v
ReDim v(10) ' работает в Visual Basic 6.0

работать не будет — переменная сразу должна быть определена как массив:

Dim v(2)
ReDim v(10) ' работает в Visual Basic.NET

Совет 19. Откажитесь от неявного преобразования типов данных

Этому совету стоит следовать независимо от того, собираетесь ли вы переходить на Visual Basic.NET. В обоснование этого тезиса можно привести много примеров; мы ограничимся двумя. Так, некоторые программисты для управления состоянием флажка вместо конструкции:

 

Dim newDelete As Boolean
If newDelete Then
chkDeleteMe.Value = 1
Else
chkDeleteMe.Value = 0
End If

 

используют более короткий код:

chkDeleteMe.Value = -1 * newDelete

Тут надо иметь в виду несколько явных минусов второго варианта.

  1. Он кажется более коротким, но с точки зрения создаваемого машинного кода менее эффективен, по крайней мере, по скорости выполнения.
  2. Результат работы этого кода неочевиден. Не уверен, что любой VB-программист с ходу ответит, каков будет результат при DeleteMe = True.
  3. В Visual Basic.NET он не будет работать (об этом ниже).

Второй пример связан с тем, что при использовании неявного преобразования типов данных возрастает опасность, что программа будет работать совсем не так, как виделось ее автору. Следует также учитывать особенности национальных форматов представления данных (для вещественных чисел и дат). Вот еще один пример на эту тему, реализованный в Windows с русскими региональными установками:

 

strR1$ = Str$(2.34)
strR2$ = 2.34
Print strR1$, strR2$ ' будет напечатано 2.34 2,34

 

Как видите, мы получили два разных результата, хотя, казалось бы, они должны быть одинаковы. Здесь следует обратить внимание на то, что результат выполнения первой строки кода (с использованием функции Str$) не зависит от национальных установок, а второй — зависит.

Совет 20. Забудьте о целочисленных значениях булевых переменных

Программный код

 

Dim i As Integer
i = True
If i = -1 Then
MsgBox "Истина"
Else
MsgBox "Ложь"
End If

 

будет выдавать в Visual Basic 6.0 — "Истина", а в Visual Basic.NET — "Ложь". Причина в том, что целочисленное значение True поменялось с -1 на 1. Рекомендации очевидны: используйте только тип данных Boolean для логических переменных и при работе с ними применяйте только имена констант True и False. Иными словами, приведенный выше пример следует записать так:

 

Dim i As Boolean
i = True
If i = True Then
MsgBox "Истина"
Else
MsgBox "Ложь"
End If

 

С одной стороны, изменение числового значения для True выглядит достаточно логично, так как булева алгебра всегда строилась на логике 0/1 (Ложь/Истина). К тому же это устраняет важное расхождение с языком C. Но, с другой стороны, это связано с некоторыми внутренними проблемами Visual Basic.

Появление в свое время значения -1 для True объясняется тем, что фактически в качестве булевой переменной используется целый тип данных и все операции с ними на уровне машинных команд выполняются путем поразрядных операций. Соответственно код

Dim a As Integer a = 0 ' ложь a = Not a

приводил к результату -1.

Тут отметим одно противоречие Basic: на самом деле целочисленные переменные выступают в виде то чисел со знаковым разрядом (в арифметических операциях и при использовании десятичных литералов), то без него (в логических операциях и в шестнадцатеричных и восьмеричных литералах).

Но проблемой Visual Basic была (и остается проблемой Visual Basic.NET) возможность неявного преобразования целых чисел в булевы значения и наоборот. В результате выполнения следующего кода:

 

Dim intVar As Integer
Dim blnVar As Boolean
...
blnVar = intVar

 

при любом ненулевом значении intVar результатом всегда будет True. А это приводит к достаточно нелепой ситуации, когда в результате последовательности присвоений изменяется исходное значение:

 

Dim a As Integer
Dim b As Integer
Dim c As Boolean
a = 5
c = a
b = c
Print a; b

 

В Visual Basic 6.0 будет напечатано "5 -1", в Visual Basic.NET — "5 1".

Заметим еще, что результат выполнения следующего кода совершенно неочевиден:

 

Dim a As Integer
Dim c As Boolean
a = 5
c = 0
MsgBox c Or a

 

Кто из читателей сможет, глядя на этот код, уверенно предсказать результат операции?

Совет 21. Нужно готовиться к разделению логических операций

Описанные выше проблемы совместной обработки логических и целых переменных связаны еще и с тем, что, например, ключевое слово And выступает в зависимости от контекста в роли или логического, или побитового And. В Visual Basic.NET эта неопределенность устранена — And соответствует только логической операции с переменными типа Boolean, а для побитовых преобразований целых чисел будут использоваться новые операторы — BitAnd, BitOr, BitNot, BitXor. Как следствие, приведенный выше код (последний фрагмент в предыдущем разделе) в Visual Basic 6.0 будет выдавать 5, а в Visual Basic.NET — True (1).

Покажем то же самое на примере следующего кода:

 

Dim a As Integer
Dim b As Integer
Dim c As Boolean
a = 1
b = 2
c = a And b
MsgBox "Результат = " & c

 

В Visual Basic 6.0 будет получен ответ False (выполнялось поразрядное логическое умножение двух целочисленных переменных с последующим преобразованием в логическое значение), а в Visual Basic.NET — True (целые числа сначала были преобразованы в логические, а потом с ними выполнили операцию And).

Для обеспечения совместимости кода Visual Basic.NET включает функции VB6.And, VB6.Or и VB6.Not, которые эквивалентны существующим сегодня And/Or/Not (почему-то для Xor такой функции нет). Соответственно средства обновления кода преобразуют приведенный выше фрагмент следующим образом:

 

Dim a As Integer
Dim b As Integer
Dim c As Boolean
a = 1
b = 2
c = VB.And(a, b)
MsgBox "Результат = " & c

 

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

 

Dim a As Integer
Dim b As Integer
Dim c As Boolean
a = 1
b = 2
c = (a <> 0) And (b <> 0)
MsgBox "Результат = " & c

 

В этом случае никаких проблем с переносом кода не будет и средства автоматического обновления просто не понадобятся.

Совет 22. Уделяйте еще больше внимания логическим операциям

В Visual Basic.NET будет повышена "интеллектуальность" логических операций для увеличения их быстродействия. Это означает, что вычисление:

 

Dim a As Boolean
Dim b As Boolean
Dim c As Boolean
a = b And c

 

будет завершено досрочно (с результатом False), если окажется, что b = False. Это очень хорошо, но тут есть «подводный камень», который виден из такого примера:

Dim b As Boolean
b = Function1() And Function2()

Дело в том, что если значением функции Function1 окажется False, то обращения к Function2 просто не будет (а это может потребоваться). Средства миграции выполнят замену кода:

Dim b As Boolean
b = VB6.And (Function1(), Function2())

но надежнее сразу записать программу в таком виде:

 

Dim b As Boolean
Dim c As Boolean
Dim d As Boolean
c = Function1()
d = Function2()
b = c And d

 

Совет 23. Используйте внутренние константы

Хороший стиль программирования подразумевает, в частности, использование внутренних глобальных констант Visual Basic вместо литералов. Речь, конечно же, идет о работе с элементами управления, методами и процедурами, использующими в качестве параметров фиксированные наборы значений:

 

Me.WindowState = vbNormal
MsgBox "Error!", vbCritical
txtTextbox.Visible = True

 

Приведенный вариант гораздо лучше такого кода, использующего числовые литералы или переменные:

 

x = 16
Me.WindowState = 0
MsgBox "Error!", x
txtTextbox.Visible = -1

 

Использование имен констант существенно повышает наглядность кода (имя говорит о сути выполняемой операции) и снижает вероятность ошибок из-за тривиальных опечаток. Впрочем, переменные, конечно, порой даже нужно использовать, но формировать их лучше с помощью опять-таки констант:

 

x = vbCritical
...
MsgBox "Error!", x

 

Еще одно важное преимущество — минимизация возможных осложнений при обновлении программ и переносе кода, например, при смене версий. В случае с переходом к Visual Basic.NET подобные проблемы как раз могут возникнуть, так как в нем изменены значения (и даже в некоторых случаях имена), в частности для той же True.

Рекомендация использовать константы относится не только к встроенным функциям Visual Basic, но и к создаваемому вами коду. Например, если у вас есть такой код, который увеличивает несколько переменных на одинаковую величину:

a = a + 2
b = b + 2

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

 

Const myStep = 2
a = a + myStep
b = b + myStep

 

Это повышает надежность и упрощает обновление кода. Ведь переменных может быть гораздо больше, а приведенные строки кода могут быть в реальности разбросаны по программе (можно легко не заметить, где нужно выполнять обновления).

Совет 24. Для работы с датами используйте только тип Date

Во всех существовавших до сих пор версиях Visual Basic для хранения даты фактически использовались переменные типа Double (отдельный тип Date появился только в версии 4.0). Дата записана в числовом формате IEEE, при этом целая часть соответствует номеру суток, начиная от 30 декабря 1899 года, а дробная — времени суток. Возможно, этот формат сохранен и в Visual Basic.NET, но вольность в преобразовании дат с обработкой их как обычных чисел будет прекращена. Подобные нелепые конструкции, вполне допустимые в Visual Basic 6.0:

 

Dim a As Date
Dim d As Double
a = 1101.27
MsgBox a ' выдается 05.01.1903 6:28:48
d = Now * 1.4 + 0.347
a = d
MsgBox a ' выдается 29.09.2041 4:48:35

 

в Visual Basic.NET работать не будут. Для обработки дат нужно использовать только набор соответствующих функций, которых более чем достаточно.

Совет 25. Не используйте недокументированные функции

Многие VB-программисты даже не подозревают о существовании недокументированных функций VarPrt, VarPrtArray, VarPrtStringArray, ObjPrt и StrPrt, позволяющих получать значения адреса памяти, где хранятся соответствующие переменные. Но они порой очень полезны при работе с Win API, в частности когда нужно выполнять изощренные операции с буферами.

Microsoft заявляет, что в Visual Basic.NET таких функций не будет. Впрочем, корпорация умалчивала об их существовании на протяжении десяти лет. Может быть, подобные функции останутся, но только под другими, секретными именами?

Совет 26. Не используйте LSet для структур

В Visual Basic 6.0 оператор LSet можно было использовать для присвоения переменной одного пользовательского типа значения переменной другого пользовательского типа. Теперь такая возможность исключена.

Совет 27. Лучше не использовать строки фиксированной длины

В Visual Basic.NET строки фиксированной длины не будут "родными" (не будут входить в состав базового набора типов данных, непосредственно поддерживаемых транслятором). Для обеспечения совместимости будет использоваться специальный объект, поэтому код Visual Basic 6.0:

Dim MyFixedLengthString As String * 12
будет преобразован в
Dim MyFixedLengthString As New VB6.FixedLengthString(12)

Но следует иметь в виду, что применение такого объекта будет ограничено. Например, его нельзя будет использовать при работе с функциями Win API. Таким образом, например, вместо варианта резервирования буфера:

Dim Buffer As String * 25

нужно писать:

Dim Buffer As String
Buffer = String$(25, " ")

Совет 28. Будьте внимательны при использовании строк и массивов в структурах

Еще одна ожидаемая проблема заключается в том, что при определении структур (которые также называются пользовательскими типами данных) не будет автоматически выполняться создание класса строки фиксированной длины и массива фиксированного размера. При обновлении такой структуры в Visual Basic.NET она будет помечаться комментарием с соответствующим предупреждением. Чтобы избежать проблем, лучше сразу заменить следующий код:

 

Private Type UserType
UserArray(10) As Integer
UserFixedString As String * 30
End Type
Sub UserProc()
Dim UserVariable As UserType
End Sub

 

на такой:

 

Private Type UserType
UserArray() As Integer
UserFixedString As String
End Type
Sub UserProc()
Dim UserVariable As UserType
ReDim UserVariable.UserArray(10) As Integer
UserVariable.UserFixedString = String$(30, " ")
End Sub

 

Парадокс данного изменения в формировании структур заключается в том, что в первых реализациях пользовательских типов (это было в QuickBasic в середине 80-х) в качестве полей допускалось использование только переменных фиксированной длины. Возможность применения массивов и строк переменной длины появилась гораздо позднее.

Совет 29. Не используйте As Any при обращении к Win API

Множество функций Windows API имеют возможность принимать параметры различных типов — интерпретация передаваемых данных выполняется в зависимости от значения других параметров. Пример такой функции — SendMessage, в которой в качестве последнего параметра может выступать как целое число, так и строка байтов произвольной длины. Для таких случаев Visual Basic использует описание параметров As Any, которое говорит о том, что в стек будет помещен некоторый адрес буфера памяти. Фактически это означает, что компилятор снимает с себя ответственность за контроль передаваемых данных.

Практически во всех рекомендациях по работе с Win API говорится о том, что необходимо особое внимание при использовании описания As Any, и дается совет использовать несколько псевдонимов (Alias) с созданием двух и более объявлений для одной и той же функции, причем в каждом из описаний указываются параметры определенного типа. В Visual Basic.NET это будет уже не пожелание, но требование — в нем исключена возможность использования типа As Any.

Мы рассмотрим возможности применения As Any, подстерегающие здесь программиста опасности и варианты использования Alias на примере функции lread:

 

Declare Function lread Lib _
"kernel32" Alias "_lread" _
(ByVal hFile As Long, lpBuffer As Any, _
ByVal wBytes As Long) As Long

 

В Visual Basic аналог этого — оператор Get при чтении файлов типа Binary. Обратим сразу внимание на необходимость использования ключевого слова Alias в объявлении функции. Настоящие названия функции в библиотеке начинаются с символа «подчеркивание» (стиль, типичный для языка C), что не разрешается в Visual Basic.

В данном случае параметр lpBuffer — это именно адрес буфера, куда будут считываться данные (wBytes указывает число читаемых байтов). В качестве буфера может выступать простая переменная, массив, строка.

 

' чтение вещественного числа, 4 байта
Dim MyVar As Single
wBytes = lread (hFile&, MyVar, Len(MyVar)
...
' чтение 10 элементов массива
Dim MyArray(0 To 9) As Byte
wBytes = lread (hFile&, MyArray(0), Len(MyArray(0))* 10)

 

Обратите внимание: второй аргумент функции передается по ссылке, остальные — по значению.

Однако, если вы хотите прочитать символьные данные в строку переменной длины, вам нужно использовать обращение иного вида:

 

' чтение символьной строки, 10 символов
Dim MyVar As String MyVar = Space$(10)
wBytes = lread (hFile&, ByVal MyVar, Len(MyVar))

 

Здесь видно важное отличие от приведенного ранее примера — строковая переменная обязательно сопровождается ключевым словом ByVal. Если мы этого не сделаем, то будет передаваться не адрес строки, а адрес ее дескриптора, и фатальная ошибка будет неизбежна.

Чтобы обезопасить себя, можно добавить специальное описание этой же функции для работы только со строковыми переменными:

 

Declare Function lreadString _
Lib "kernel32" Alias "_lread" _
(ByVal hFile As Long, ByVal lpBuffer As String, _
ByVal wBytes As Long) As Long

 

При работе с этим описанием указывать ByVal при обращении уже не нужно:

wBytes = lreadString (hFile&, MyVarString, Len(MyVarString))

Правда, полный отказ от использования As Any имеет и свои минусы, в частности, как раз при работе с функцией lread — придется дублировать ее описания для всех возможных вариантов простых переменных.

Совет 30. Точно объявляйте способ передачи параметров

Обычно мы указываем способ передачи параметров — ByRef (по ссылке) или byVal (по значению) — только при работе с Win API (точнее, со всеми DLL-функциями). В предыдущем совете мы показали, как это важно. При работе с процедурами внутри среды Visual Basic исторически было принято, что по умолчанию параметры всегда передаются по ссылке. Но в Visual Basic.NET это изменилось — теперь по умолчанию все параметры будут передаваться по значению.

Безусловно, это положительное изменение, так как оно обеспечивает более надежное программирование — специальным образом должна описываться именно возможность возврата измененного параметра. Тем более, что двусторонний обмен данными встречается на практике гораздо реже, чем односторонний. Но при переходе от Visual Basic 6.0 к Visual Basic.NET это изменение может вызвать неприятности:

 

Dim a%, b%, c%
a = 0: b = 1: c = 2
Call MyTest(a, b, c)
MsgBox a & " " & b & " " & c
Public Sub MyTest(ByVal d%, f%, ByRef g%)
d = 10: f = 11: g = 12
End Sub

 

При работе в Visual Basic 6.0 будет получен результат

0 11 12

а в Visual Basic.NET:

0 0 12

Надеюсь, что средства миграции обеспечат замену описания подпрограммы на

Public Sub MyTest(ByVal d%, ByVal f%, ByRef g%)

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


Может пригодится:


Автор: Андрей Колесов
Прочитано: 4052
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру