Поговорим о программировании
Размышления бывшего программиста. Часть 5
В этой статье на простом примере проиллюстрированы ключевые тезисы о стиле разработки программ, опровергающие утверждение, приведенное в качестве эпиграфа. Более того, этот материал подготовлен в качестве практического примера реализации идей, высказанных в цикле статей «Поговорим о программировании. Размышления бывшего программиста». Общий смысл современного подхода к разработке приложений заключается в переходе от последовательного выполнения трех основных этапов разработки (проектирование, кодирование и отладка) к параллельному.
Точнее, принцип этой технологии заключается в пошаговой реализации проекта, опирающейся на следующие основные положения:
Использование методики «снисходящего проектирования» (другие названия: «пошаговое совершенствование», «иерархическое») — от компонентов приложения верхнего уровня к компонентам нижнего уровня.
Пошаговая разработка. На каждом шаге выполняется тестирование созданного программного фрагмента. Фрагмент проверяется не только в рамках работы всей программы, но и в случае необходимости — с использованием специальных текстовых конструкций. Последнее необходимо, если функциональность созданного кода не полностью задействована в рамках данной конкретной программы.
Делая шаг, нужно иметь в виду следующий, а может быть и еще один. Так же как горнолыжник, проходя трассу слалома, должен видеть сразу несколько ворот вперед и думать, как он пройдет вторые (лучше третьи), а не первые ворота.
Одна из ключевых идей — максимально быстрое создание работающего варианта приложения, выполняющего хотя бы одну полезную для пользователя функцию. Далее наращивайте функциональность приложения исходя из соображений практической потребности в них и скорости реализации.
Качественное оформление программы по ходу работы (присвоение наименований переменным, комментарии, отступы и т.д.).
Оперативное выделение в ходе разработки категории «повторно используемых» компонентов, формирование набора таких компонентов (этот вопрос мы будем рассматривать во второй части статьи).
В статье показано, что использование правильной методики позволяет при высокой скорости разработки поучать эффективные решения. Кроме того, при такой работе «с колес» можно добиться минимальных потерь в виде «ненужного» кода (кода, который был написан, но впоследствии удален за ненадобностью).
Здесь мы будем говорить о разработке локального, небольшого приложения. Разумеется, для создания даже средних систем этап «бумажной» прорисовки проекта является необходимым, и его качество в решающей степени определяет будущий результат. Однако при внимательном изучении вопроса легко увидеть, что многие приведенные выше положения вполне годятся и для создания крупных проектов. Просто там под «шагом» будут подразумеваться не десять строк кода, а другие, более объемные компоненты проекта.
Постановка задачи
Задача связана с процедурой поддержки (обновления) моего персонального Web-узла. Хотелось бы сразу предупредить, что я — довольно неопытный Web-мастер и поэтому, вполне вероятно, даже не догадываюсь о существовании готовых средств решения задач, о которых пойдет речь далее. Но, во-первых, в данном случае мы обсуждаем вопрос технологии разработки программ, а не инструменты работы с Web. А во-вторых, даже если мы «изобретем велосипед», созданное нами приложение может оказаться полезным для реализации аналогичных задач.
Работаю я со своим Web-сервером следующим образом: его разработку я веду на персональном компьютере в каталоге MySourceWebSite, а пользователи Интернета имеют дело с его копией, находящейся на каком-то удаленном сервере с именем Vusial2000. Периодически производится обновление содержимого сервера: сначала я выполняю все операции с файлами в каталоге MySourceWebSite, а потом переписываю обновления на Visual2000 с помощью FTP-клиента.
Проблема перезаписи заключается в том, что на локальном компьютере я работаю с файлами в кодировке Windows-1251, а на Web-сервере они должны существовать в кодировке KOI-8 (сохраняя при этом строку <Meta charset=windows-1251>). Это выглядит довольно странно, однако таковы были указания Web-провайдера, а проведенные мною различные эксперименты подтвердили их правильность.
Поскольку мой FTP-клиент не умеет перекодировать файлы в момент перезаписи, я вынужден держать у себя также копию Web-сайта в кодировке KOI-8 в подкаталоге MyKoi8WebSite. В результате процедура перезаписи обновлений может быть представлена в виде следующих шагов:
В первую очередь определяю список файлов, которые нужно переписать на Web-узел. Это выполняется с помощью операции Find в Windows Explorer с поиском по дате. Обычно число обновленных файлов составляет 10-20. Читать этот список с экрана не очень удобно, поэтому я вручную переписываю его на лист бумаги (я не нашел возможности импорта списка в виде файла или вывода на печать, хотя, утилиты, которые могут это делать, безусловно, существуют).
Открыв два окна Windows Explorer, выполняю копирование файлов из MySourceWebSite в MyKOI8WebSite.
С помощью утилиты Coder перекодирую (по переписанному мною списку) файлы в MyKOI8WebSite из Windows-1251в KOI-8.
Соединяюсь с Web-сервером и с помощью FTP-клиента копирую туда файлы из MyKOI8WebSite.
Поскольку за один сеанс (а это происходит в среднем два раза в месяц) обновляется 10-20 файлов, операция перезаписи занимает не более 10 минут (не считая, естественно, времени перекачки через модем, которое не всегда предсказуемо). Конечно, это несоизмеримо с затратами на создание и коррекцию HTML-файлов, которые для такого объема могут легко занять весь рабочий день.
И тем не менее процедуру перезаписи было бы неплохо автоматизировать, поскольку при ее выполнении нужно напрячь внимание (не те файлы переписали, какие-то забыли, неверно перекодировали и пр.), а делаю я это обычно в конце рабочего дня.
Думаю, гораздо лучше выглядел бы следующий вариант: вызвал утилиту, указал в окошке дату начала обновления и нажал кнопку «Переписать файлы из MySourceWebSite». И потом только смотри, как утилита соединилась с удаленным FTP-сервером, провела авторизацию, начала копировать только нужные файлы, одновременно выполняя их перекодировку, и в конце выдала отчет о проделанной работе.
Вот и попробуем создать такое приложение. Честно говоря, не уверен, что мне удастся реализовать этот план до конца, хотя бы потому, что я никогда не имел дело с программированием FTP-доступа.
Обращаю внимание читателей на следующий момент: этот текст будет точно отражать весь ход реализации проекта. В момент написания этой фразы у меня нет никаких заготовок, и поэтому мы начнем с запуска VB 6.0 и выбора опции New File.
Этап 1: копирование и перекодировка файлов по заданному списку
Замечание 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. Теперь разместим элементы управления, которые нужны нам для начала работы. Пока представляется необходимым иметь следующие элементы:
Label lblFrom — Имя главного каталога, откуда будут копироваться файлы
Label lblTo — Имя главного каталога, куда будут копироваться файлы
Label lblCopy — Число файлов, скопированных без перекодировки
Label lblConv — Число файлов, скопированных с перекодировкой
Label lblReplace — Число измененных файлов (ранее существовавших)
Command cmdCopy — Команда на выполнение копирования (необходимость перекодировки будет определяться автоматически по расширению файла)
Command cmdExit — Команда на завершение утилиты
DirListBox dirList — Окно для выбора каталога
FileListBox filList — Список файлов выбранного каталога
Два примечания:
Пока будем работать с жестко заданными именами каталогов (откуда и куда копируются файлы). Лично мне этого вполне достаточно. Потом реализуем возможность выбора каталогов.
Для удобства ориентации на форме установим для меток Caption = Name (при запуске программы нужные значения устанавливаются программно).
Теперь сформируем программный код для данного шага отладки:
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
Несколько примечаний:
Сейчас довольно часто не проводят начальной установки переменных, считая, что эта операция уже выполнена в момент компиляции. Тем не менее программное обнуление счетчиков я сделал, так как это способствует лучшему восприятию программы и гарантирует от возможных неприятностей. А вдруг вы потом захотите объявить переменные статическими и использовать форму в виде компонента другого приложения?
Я сразу выделил установку меток со значениями счетчиков в отдельную процедуру, так как очевидно, что такая операция понадобится впоследствии и при перезаписи файлов.
Процедура Form_Unload содержит не очень нужный в данный момент вывод сообщения. Этим я хочу подчеркнуть, что данная процедура нам понадобится в дальнейшем: код, который нужно будет выполнить при завершении утилиты (а он скорее всего появится), необходимо записать именно здесь, а не в cmdExit_Click. Ведь закрыть форму можно и путем нажатия системной кнопки Close.
По ходу отладки утилиты мы будем использовать вывод информации с помощью MsgBox. После проверки фрагментов кода будем исключать их, превращая в комментарии. Не стоит сразу удалять эти строки — они могут пригодиться в будущем, к тому же они хорошо показывают ход отладки приложения.
Запустите проект и убедитесь, что все названия меток сформированы верно.
Шаг 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:\Windows)
Итак, мы увидели, что:
правильно задали начальное имя каталога PathFrom;
текущее имя не содержит косой черты в конце.
Внимание! Чтобы избежать проблем с формированием полных имен файлов, всегда будем задавать в переменных 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
Несколько примечаний:
Мы поставили MsgBox, чтобы проверить, в каком виде выдается имя файла. Под именем файла можно подразумевать как идентификатор внутри каталога, так и идентификатор, включающий полный путь к файлу. Некоторые элементы управления имеют свойства для обоих вариантов, FileListBox работает только с первым.
Далее следует проверка на тот случай, когда в списке нет выделения, а пользователь щелкнет кнопку.
Запустите проект, посмотрите, в каком виде выдается имя файла, и убедитесь, что проверка отсутствия выделения работает правильно.
Шаг 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
Несколько примечаний:
В окончательном варианте запрос на замену существующего файла можно будет при желании убрать либо предусмотреть опцию — спрашивать подтверждение или нет. Хотя при перезаписи текстовых файлов уже существующий файл автоматически удаляется, предпочтительнее сделать операцию удаления в явном виде.
В окончательном варианте мы обязательно создадим протокол нашей работы, где будем для каждого файла указывать, осуществлялась ли замена (для этого формируем переменную sReplace).
Внимание! Теперь начинаем экспериментировать с процедурами удаления и копирования файлов в каталоге КУДА.
Для этого создадим временный файл для тестирования, например 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
Пока продолжаем развитие нашей утилиты только для случая простого копирования файлов. Однако уже сейчас мы вставили конструкцию If ... Then ... Else... End If, чтобы подчеркнуть, что есть некий общий код для обоих случаев и что нужно выполнять некоторые парные операции (например, изменять счетчики и формировать переменную sCopy). Хотя в часть «перекодировать» мы сейчас не попадаем, тем не менее в комментарии уже указали, что операция будет выполняться для TXT- и HTM-файлов.
Запустите проект и убедитесь, что программа правильно производит копирование файлов, предупреждая об уже существующих файлах и верно изменяя счетчики. Попробуйте скопировать файл из подкаталога, которого нет в каталоге КУДА, — должна появиться ошибка с сообщением «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% пригодится и для других режимов работы. Теперь можно передать созданную утилиту для эксплуатации пользователю (себе самому) и приступить к её дальнейшему функциональному развитию.
Ваш Андрей Александрович Колесов.
Оставить комментарий
Ваш комментарий будет опубликован после модерации.