Главная страница Visual 2000 · Общий список статей
Андрей Колесов
© Андрей Колесов, 2000
Мы строим свои системы так же,
как братья Райт строили свои самолеты:
создаем всю систему целиком, запускаем ее
— пусть она развалится! — и начинаем все сначала.
Проф. Грехем, сборник Software Engineering, 1970 г.
В этой статье на простом примере проиллюстрированы ключевые тезисы о стиле разработки программ, опровергающие утверждение, приведенное в качестве эпиграфа. Более того, этот материал подготовлен в качестве практического примера реализации идей, высказанных в цикле статьей "Поговорим о программировании. Размышления бывшего программиста". Общий смысл современного подхода к разработке приложений заключается в переходе от последовательного выполнения трех основных этапов разработки (проектирование, кодирование и отладка) к параллельному.
Точнее, принцип этой технологии заключается в пошаговой реализации проекта, опирающейся на следующие основные положения:
В статье показано, что использование правильной методики позволяет при высокой скорости разработки поучать эффективные решения. Кроме того, при такой работе "с колес" можно добиться минимальных потерь в виде "ненужного" кода (кода, который был написан, но впоследствии удален за ненадобностью).
Здесь мы будем говорить о разработке локального, небольшого приложения. Разумеется, для создания даже средних систем этап "бумажной" прорисовки проекта является необходимым, и его качество в решающей степени определяет будущий результат. Однако при внимательном изучении вопроса легко увидеть, что многие приведенные выше положения вполне годятся и для создания крупных проектов. Просто там под "шагом" будут подразумеваться не десять строк кода, а другие, более объемные компоненты проекта.
Задача связана с процедурой поддержки (обновления) моего персонального Web-узла. Хотелось бы сразу предупредить, что я — довольно неопытный Web-мастер и поэтому, вполне вероятно, даже не догадываюсь о существовании готовых средств решения задач, о которых пойдет речь далее. Но, во-первых, в данном случае мы обсуждаем вопрос технологии разработки программ, а не инструменты работы с Web. А во-вторых, даже если мы "изобретем велосипед", созданное нами приложение может оказаться полезным для реализации аналогичных задач.
Работаю я со своим Web-сервером следующим образом: его разработку я веду на персональном компьютере в каталоге MySourceWebSite, а пользователи Интернета имеют дело с его копией, находящейся на каком-то удаленном сервере с именем Vusial2000. Периодически производится обновление содержимого сервера: сначала я выполняю все операции с файлами в каталоге MySourceWebSite, а потом переписываю обновления на Visual2000 с помощью FTP-клиента.
Проблема перезаписи заключается в том, что на локальном компьютере я работаю с файлами в кодировке Windows-1251, а на Web-сервере они должны существовать в кодировке KOI-8 (сохраняя при этом строку <Meta charset=windows-1251>). Это выглядит довольно странно, однако таковы были указания Web-провайдера, а проведенные мною различные эксперименты подтвердили их правильность.
Поскольку мой FTP-клиент не умеет перекодировать файлы в момент перезаписи, я вынужден держать у себя также копию Web-сайта в кодировке KOI-8 в подкаталоге MyKoi8WebSite. В результате процедура перезаписи обновлений может быть представлена в виде следующих шагов:
Поскольку за один сеанс (а это происходит в среднем два раза в месяц) обновляется 10-20 файлов, операция перезаписи занимает не более 10 минут (не считая, естественно, времени перекачки через модем, которое не всегда предсказуемо). Конечно, это несоизмеримо с затратами на создание и коррекцию HTML-файлов, которые для такого объема могут легко занять весь рабочий день.
И тем не менее процедуру перезаписи было бы неплохо автоматизировать, поскольку при ее выполнении нужно напрячь внимание (не те файлы переписали, какие-то забыли, неверно перекодировали и пр.), а делаю я это обычно в конце рабочего дня.
Думаю, гораздо лучше выглядел бы следующий вариант: вызвал утилиту, указал в окошке дату начала обновления и нажал кнопку "Переписать файлы из MySourceWebSite". И потом только смотри, как утилита соединилась с удаленным FTP-сервером, провела авторизацию, начала копировать только нужные файлы, одновременно выполняя их перекодировку, и в конце выдала отчет о проделанной работе.
Вот и попробуем создать такое приложение. Честно говоря, не уверен, что мне удастся реализовать этот план до конца, хотя бы потому, что я никогда не имел дело с программированием FTP-доступа.
Обращаю внимание читателей на следующий момент: этот текст будет точно отражать весь ход реализации проекта. В момент написания этой фразы у меня нет никаких заготовок, и поэтому мы начнем с запуска VB 6.0 и выбора опции New File.
Замечание 1. Перед началом работы убедитесь, что в среде VB (окно Options) у вас установлены режимы Require Variable Definition (Обязательное объявление переменных) и Save Changes (Сохранение изменения проекта при каждом запуске). Если вы все же хотите иметь возможность отката к неким предыдущим версиями проекта, стоит подумать об использовании системы управления версиями, например Visual SourceSafe. Советую также сбросить флажок Compile on Demand.
Сразу сохраните проект, чтобы указать местоположение его файлов. Это должен быть каталог, отличный от того, где находится Visual Basic. Используйте режим Require Variable Definition (Обязательное объявление переменных) и Save Changes (Сохранение изменения проекта при каждом запуске).
Шаг 1. Создаем новый проект Standard Exe. Сразу же меняем стандартные имена Project1 и Form1 на какие-либо осмысленные названия, в данном случае CopyKoi8 и frmCopy8. Устанавливаем свойство Caption формы как "Преобразование из Win в KOI8". Запустите созданный проект на выполнение и запишите его модули в соответствующий каталог.
Замечание 2. Первый запуск с "пустым" проектом нужен для того, чтобы определиться с местом, где он будет храниться. По умолчанию будет предложен каталог, в котором находится сам VB. Ни в коем случае не записывайте туда свой проект! Для хранения своих проектов и данных заведите отдельный каталог (а для каждого проекта — подкаталог). Отдельный каталог нужно сделать для хранения повторно используемых компонентов.
Правильнее всего разбить жесткий диск на два (или больше) логических диска. Один диск (С:) можно использовать для хранения операционной системы, другой (D:) — для записываемых приложений (Office, VB и пр.), третий — для хранения создаваемых вами файлов (проектов, документов, почтовых сообщений и пр.). За счет этого существенно повышается надежность хранения информации. Понятно, что резервная копия будет делатьcя простым копированием только диска E:, так как в случае краха системы и систему, и приложения можно восстановить с дистрибутивов. Самая ценная информация — созданная именно вами.
Замечание 3. В случае применения для форм и элементов управления неких осмысленных имен (свойство Name) вместо стандартных (что очень рекомендуется), их нужно устанавливать сразу после создания соответствующих объектов и желательно не менять в дальнейшем. Дело в том, что при замене имени объекта, например с Text1 на txtFileName, имена всех созданных ранее событийных процедур Text1_ХХХ. и ссылки на Text1 нужно менять вручную. Если вам все же придется корректировать имена уже задействованных объектов и переменных, стоит воспользоваться командой Replace для просмотра всех модулей проекта.
Шаг 2. Теперь разместим элементы управления, которые нужны нам для начала работы. Пока представляется необходимым иметь элементы, размещение которых на форме приведено на рис. 1:
Рис. 1.
Тип элемента | Свойство Name | Примечание |
Label | lblFrom | Имя главного каталога, откуда будут копироваться файлы |
Label | lblTo | Имя главного каталога, куда будут копироваться файлы |
Label | lblCopy | Число файлов, скопированных без перекодировки |
Label | lblConv | Число файлов, скопированных с перекодировкой |
Label | lblReplace | Число измененных файлов (ранее существовавших) |
Command | cmdCopy | Команда на выполнение копирования (необходимость перекодировки будет определяться автоматически по расширению файла.) |
Command | cmdExit | Команда на завершение утилиты |
DirListBox | dirList | Окно для выбора каталога |
FileListBox | filList | Список файлов выбранного каталога |
Два примечания:
Теперь сформируем программный код для данного шага отладки:
Dim PathFrom$, PathTo$ ' имена каталогов ОТКУДА и КУДА ' счетчики скопированных, перекодированных и замененных файлов Dim CopyCount&, ConvCount&, ReplaceCount& Private Sub Form_Load() ' Начальная настройка ' Имена каталогов Откуда и Куда PathFrom = "d:\my-sites\visualmy\" PathTo = "e:\dreamwear\visual\" ' Установка свойств lblFrom.Caption = "Откуда: " + PathFrom lblTo.Caption = "Куда: " + PathTo ' обнуление счетчиков CopyCount = 0: ConvCount = 0: ReplaceCount = 0 Call CountCaption ' названия меток со счетчиками ' начальная установка каталога ОТКУДА dirList.Path = PathFrom$ filList.Path = dirList.Path filList.Pattern = "*.*" ' все файлы в каталоге End Sub Public Sub CountCaption() ' вывод содержимого счетчиков lblCopy.Caption = "Скопировано файлов = " & CopyCount lblConv.Caption = "Перекодировано файлов = " & ConvCount lblReplace.Caption = "Заменено файлов = " & ReplaceCount End Sub Private Sub cmdExit_Click() Unload Me ' завершить работу End Sub Private Sub Form_Unload(Cancel As Integer) MsgBox "Конец работы" End Sub
Несколько примечаний:
Запустите проект и убедитесь, что все названия меток сфомированы верно.
Шаг 3. Займемся элементом управления dirList (DirListBox — выбор каталога). Для начала проверим, в каком виде выдается имя каталога. Напишем такой код:
Private Sub dirList_Change() MsgBox dirList.Path End Sub
Запустите проект и посмотрите, что выдается для корневого каталога и подкаталогов.
Замечание 4. При работе с именами каталогов следует учитывать возможности их двоякого обозначения: как с обратной косой чертой в конце имени, так и без нее, что создает определенную путаницу и может стать причиной ошибок. Рассмотрим это на примере элемента управления DirListBox:
Print DirListBox.Path 'для корневого каталога черта всегда 'выдается (C:\), для всех остальных — нет (C:\TMP). DirListBox.Path = "C:\TMP" DirListBox.Path = "C:\TMP\" 'правильно работают оба варианта DirListBox.Path = "C:\" ' будет установлен каталог C:\ DirListBox.Path = "C:" ' будет установлен ТЕКУЩИЙ каталог 'диска C: (например C:\Winodws)
Итак, мы увидели, что:
Внимание! Чтобы избежать проблем с формированием полных имен файлов, всегда будем задавать в переменных PathFrom и PathTo идентификаторы с косой чертой в конце.
Теперь изменим код следующим образом:
Private Sub dirList_Change() 'MsgBox dirList.Path ' проверка на допустимость имени каталога If InStr(dirList.Path, Left(PathFrom, Len(PathFrom) - 1)) <> 1 Then MsgBox "Вышли за пределы заданного каталога!" dirList.Path = PathFrom ' принудительная установка End If filList.Path = dirList.Path ' текущий каталог для списка файлов End Sub
Передвигаться от заданного главного каталога можно вниз по дереву, но вверх попадать нельзя. Для этого нужна реализованная выше проверка.
Запустите проект и убедитесь, что созданная проверка действительно правильно работает. Убедитесь, что смена списка каталогов в элементе filList выполняется верно.
Шаг 4. Теперь приступим к реализации процедуры копирования перекодировки файла. Она будет выполняться либо нажатием кнопки "Копировать+Перекодировать", либо двойным щелчком по имени файла в списке. Для начала напишем такой код:
Private Sub filList_DblClick() Call cmdCopy_Click End Sub Private Sub cmdCopy_Click() ' Копирование и перекодирование файла ' Dim FileFrom$, FileTo$ ' имена исходного и результирующего файлов MsgBox filList.FileName ' в каком виде выдается имя файла? FileFrom = filList.FileName If FileFrom = "" Then MsgBox "Не выбран файл!": Exit Sub End If ' далее пойдет код дальнейшей обработки End Sub
Несколько примечаний:
Запустите проект, посмотрите, в каком виде выдается имя файла, и убедитесь, что проверка отсутствия выделения работает правильно.
Шаг 5. Далее мы будем дописывать процедуру cmdCopy_Click, чтобы выполнить операции копирования и перекодировки файла. Допишем внизу такой код для формирования полного имени исходного и результирующего файлов:
Dim a$, i% If Right(filList.Path, 1) <> "\" Then a$ = "\" Else a$ = "" FileFrom = filList.Path + a$ + FileFrom FileTo = PathTo + Mid$(FileFrom, Len(PathFrom) + 1) MsgBox FileFrom & vbCrLf & FileTo
Здесь мы подстраховались на случай, если кто-то в качестве исходного каталога будет задавать корневой.
Внимание! В нашей утилите мы не предусматриваем случаи, когда в каталоге КУДА нужно создавать новые подкаталоги. В будущем такую возможность можно реализовать, причем это легко сделать вручную, поскольку новые разделы Web-узла создаются достаточно редко.
Запустите проект для разных значений PathFrom и PathTo. Введенные ранее значения можно закомментировать и попробовать разные сочетания имен типа C:\ и C:\TMP\. Убедитесь, что имена файлов всегда формируются правильно.
Шаг 6. Следует различать две ситуации: создание нового результирующего файла и замена уже существовавшего. Для этого допишем далее следующий код:
Dim sReplace$ If Dir(FileTo) <> "" Then ' результирующий файл уже существует If vbYes = MsgBox("Удалить существующий файл " & FileTo & " ?", _ vbYesNo, "Удалить?") Then ' удалить Kill FileTo: sReplace = " Замена" ReplaceCount = ReplaceCount + 1 ' счетчик замененных файлов Else: Exit Sub End If Else: sReplace = " Новый" End If
Несколько примечаний:
Внимание! Теперь начинаем экспериментировать с процедурами удаления и копирования файлов в каталоге КУДА.
Для этого создадим временный файл для тестирования, например E:\MyTest\, и это имя пока запишем для установки переменной PathTo. Скопируем туда несколько файлов из исходного каталога. Запустите проект и убедитесь, что программа правильно определяет наличие уже существующих файлов и при соответствующем ответе пользователя удаляет его.
Шаг 7. Теперь напишем код для копирования файлов в конец процедуры cmdCopy_Click:
Dim FileExt$, sCopy$, lnFrom%, lnTo% FileExt$ = Right$(FileFrom, 4) ' расширение файла If FileExt$ = "****" Then ' такое условие никогда не выполнится ' If FileExt$ = ".txt" Or FileExt$ = ".htm" Then 'перекодирование + копирование ConvCount = ConvCount + 1 sCopy = " Перекодирован Win -> KOI8" Else ' только копирование ' открыть файлы lnFrom = FreeFile: Open FileFrom For Binary As #lnFrom lnTo = FreeFile: Open FileTo For Binary As #lnTo 'копирование содержимого ReDim Buffer(1 To LOF(lnFrom)) As Byte Get #lnFrom, , Buffer() ' читаем полностью Put #lnTo, , Buffer() ' записываем полностью CopyCount = CopyCount + 1 'счетчик скопированных sCopy = " Скопирован" End If Close #lnFrom: Close #lnTo ' обновление счетчиков на форме Call CountCaption
Несколько примечаний:
lnFrom = FreeFile: lnTo = FreeFile
'обе переменные получили одинаковое значение!
Open FileFrom For Binary As #lnFrom
Open FileTo For Binary As #lnTo
Запустите проект и убедитесь, что программа правильно производит копирование файлов, предупреждая об уже существующих файлах и верно изменяя счетчики. Попробуйте скопировать файл из подкаталога, которого нет в каталоге КУДА, — должна появиться ошибка с сообщением "Path not found" ("Не найден путь").
Такая ситуация будет встречаться редко, но лучше ее обработать программным путем, без аварийного завершения утилиты. В начало процедуры cmdCopy_Click запишем:
On Error GoTo RathNotFound
а в самый ее конец:
Exit Sub RathNotFound: ' обработка ошибки MyError = Err If MyError = 76 Then If MsgBox("Нельзя создать файл " & FileTo, _ vbRetryCancel, "Нет такого каталога!") _ = vbRetry Then Resume ' повторить попытку Else ' какая-то другая ошибка MsgBox "Ошибка = " & MyError End If
Мы решили программно не создавать несуществующие каталоги, так как подобная необходимость появляется довольно редко. Но стоит обратить внимание на то, что в момент получения такого предупреждения пользователь может, не прерывая выполнение утилиты, создать каталог с помощью проводника, а затем повторить попытку записи файла, нажав кнопку Retry.
Запустите проект и убедитесь, что программа правильно копирует файлы, предупреждая об отсутствии существующих каталогов, повторяя копирование после создания каталога и верно изменяя счетчики.
Итак, мы создали полезную утилиту CopyKoi8, которая позволяет копировать произвольные файлы из одного каталога в другой, сохраняя структуру внутренних подкаталогов.Таким образом, мы существенно упростили операции пункта 2 исходной задачи.
Несмотря на то что утилита не отличается богатством функций и не слишком экономит время пользователя при реальной работе (однако гарантирует от возможных ошибок при переписывании файла не в тот подкаталог!), она представляет собой законченное решение.
Более того, в дальнейшем мы создадим более "автоматические" процедуры копирования и преобразования, хотя и данный режим ручного выбора файлов будет нам иногда необходим. Мы реализовали наиболее общий случай, причем сформированный нами код на 90% пригодится и для других режимов работы. Теперь можно передать созданную утилиту для эксплуатации пользователю (себе самому) и приступить к ее дальнейшему функциональному развитию.