А ти готовий до 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%)

але краще визначати в явному вигляді повертаються параметри вже зараз.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*