Поговорим о программировании. Размышления бывшего программиста
Несколько советов по написанию кода
Совет 1. Не забывайте о циклах
Совет 2. Не забывайте о параметризации
Хотя здесь приведены примеры на Visual Basic, речь идет о достаточно общих программных конструкциях. Надеюсь, что синтаксис примера поймут все программисты.
Совет 1. Не забывайте о циклах
Один из «столбов», на которых держится технология программирования, является идея замены линейных последовательностей операций на циклы. В качестве второй фундаментальной идеи нужно упомянуть выделение в виде процедур повторно используемого кода. Однако эти базовые положения в последнее время довольно часто забываются.
Например, часто встречаются такие конструкции для перекодировки строчных символов (конечно, тут приводится утрированный пример):
Select Case OneSymbol$ Case "а" OneSymbol$ = "А" Case "б" OneSymbol$ = "Б" ...
(хорошо, что в русском языке всего 33 буквы). Хотя все это легко описывается в четыре строки кода:
SourceSet$ = "аб..." ResultSet$ = "АБ..." iSym= Instr(SourceSet$, OneSymbol$) If ISym > 0 Then OneSymbol$ = Mid$(ResultSet$, iSym, 1)
Не говоря уже о более простом варианте, учитывающем специфику данных кодов для Win-1251:
iSym = Asc (OneSymbol$) if (iSym <= 255) And (iSym >=224) Then _ OneSymbol$ = chr$(iSym - 32)
Достоинство первого варианта заключается не только в краткости. Еще важнее, что его легко можно использовать для любой перекодировки символов, меняя исходные и результирующие таблицы, заданные в виде переменных SourceSet$ и ResultSet$.
Тут хотелось бы отметить два момента:
- такой неэффективный код часто пишут далеко не новички в программировании;
- речь идет совсем не о «чистой» эстетике, а о сугубо технологических вещах (но нужно напомнить, что эффективные решения чаще всего являются и внешне очень симпатичными).
Я с большим уважением отношусь к американскому журналу Visual Basic Programer's Journal, но меня в свое время просто шокировал код, приведенный в статье «Настройки для Word» (специальный выпуск журнала Getting Started with Visual Basic, Summer 1998, vol.2, № 2). Статья была адресована, скорее, начинающим, но это только усугубляло тяжелое впечатление. Тем более что она была написана достаточно известным автором.
Речь шла о хранении в системном реестре информации о последних использованных документах Word (расширении списка по сравнению со стандартным вариантом). Автор предлагал код для работы с 25 файлами, сопровождая это следующим текстом в конце статьи: «Вы можете расширить приведенную здесь подпрограмму, добавив дополнительный строчки кода. 50, 75 или 100 документов — вы можете сделать это сами. Я ограничил число 25-ю из-за ограниченного места в журнале и поддержки уровня нужного быстродействия».
Статья содержала несколько процедур, одна из которых включала такой код формирования списка файлов:
strRegistry = "HKEY_CURRENT_USER\..." lstRegistry.AddItem System.PrivateProfileString _ ("", strRegistry, "File1") . . . ... lstRegistry.AddItem System.PrivateProfileString _ ("", strRegistry, "File25")
Листинг этого кода занимал полстраницы журнала. Однако еще более ужасный код был приведен в процедуре, которая корректировала содержимое реестра, — для 25 позиций листинг занимал еще две страницы, напечатанных довольно мелким шрифтом. Понятно, что для 50 файлов потребуется пять журнальных страниц. Но ведь все это можно написать и в таком виде:
Public Const RegFile$ = "" Public Const strRegistry As String = _ "HKEY_CURRENT_USER\Software\Microsoft\Office\8.0\Common\RecentFiles" Public Const MaxList = 25
Public Sub FileCloseAddToRecentFiles()
' коррекция списка файлов в реестре strThisFile = ActiveDocument.FullName strOldFile$ = strThisFile$ For i% = 1 To MaxList% KeyN$ = "File" + LTrim$(Str$(i%)) strFile$ = System.PrivateProfileString(RegFile$, _ strRegistry, KeyN$) System.PrivateProfileString(RegFile$, strRegistry, _ KeyN$) = strOldFile$ If strThisFile$ = strFile$ Or strFile$ = "" Then Exit For strOldFile$ = strFile$ Next End Sub
Private Sub UserForm_Initialize() ' формирование списка Dim FileName$, KeyN$, i% i% = 0 Do i% = i + 1 KeyN$ = "File" + LTrim$(Str$(i%)) FileName$ = System.PrivateProfileString(RegFile$, _ strRegistry, KeyN$) If FileName$ = "" Then Exit For lstFiles.AddItem FileName$ Loop End Sub
Этот код содержит всего 33 строки вместо почти 350, приведенных в журнале. Любое число файлов (100, 1000) можно задать коррекцией одной строки кода:
Public Const MaxList = XXX
Я уже не говорю о методических «проколах» приведенного в статье решения. Например, зачем вообще такую частную информацию хранить в реестре (из-за этого, кроме всего прочего, действительно падает быстродействие при увеличении списка). И нельзя использовать дубликатное (в каждой процедуре) определение констант — нужно использовать общие переменные. Кстати, обратите внимание, что переход от системного реестра к частному INI-файлу выполняется простой заменой двух констант:
Public Const RegFile$ = "C:\Office97\WordCust.ini" Public Const strRegistry As String = "RecentFiles"
Совет 2. Не забывайте о параметризации
Под параметризацией понимаетcя максимальное разделение обрабатываемых данных и самого кода, что обеспечивает гибкую адаптацию программ на возможные изменения данных. Ведь то, что сейчас кажется константой, в ближайшем будущем может оказаться переменной, причем получаемой из какого-то внешнего файла.
В связи с этим приведу еще один пример преобразования «плохого» кода в «хороший». Недавно в одной из онлайновых дискуссий я наткнулся на такой вопрос. Пользователь методом записи макрокоманды создал следующий код копирования стилей документов Word, содержащий 6 строк такого вида (для каждого стиля):
Application.OrganizerCopy Source:= _ "C:\WINDOW95\Application Data\Microsoft\Templates\Normal.dot", _ Destination:="C:\MyDocs\File1.doc", Name:="МойСтиль", Object:= _ wdOrganizerObjectStyles ... ' далее идут еще 5 пять таких же операторов с другими Name:=
Вопрос заключался в том, как сделать, чтобы эту макрокоманду можно было использовать для любого документа, а не только конкретного файла. В качестве весьма примечательной позиции (чего там думать — трясти надо) вопрос сопровождался заявлением о том, что его автор бился над проблемой полдня и времени на чтение книг по программированию у него нет.
На мой взгляд, данный пример интересен прежде всего тем, что для поиска ответа было бы крайне желательно выполнить преобразование кода в «приличный» вид. Для начала следует выделить очевидные общие параметры данной программы:
SourceFile$ = "Normal" DestFile$ = "C:\MyDocs\File1.doc"
Application.OrganizerCopy Source:= SourceFile$, _ Destination:=DestFile$, Name:="МойСтиль", Object:= _ wdOrganizerObjectStyles
Тут мы не только сократили размер кода, но самое главное четко определили суть проблемы. Автор спрашивал о каких-то стилях, но на самом деле вопрос был в другом: как узнать имя файла активного документа? Ответ на такой вопрос найти гораздо проще.
Но продолжая тему правильного кодирования, нужно продолжить наши преобразования исходной программы. В качестве второго шага я бы сделал такой вариант:
SourceFile$ = "Normal" DestFile$ = "C:\MyDocs\File1.doc" ' далее последовательность кода операторов такого вида: StyleName$ = "МойСтиль": Gosub MyOrganizerCory StyleName$ = "ВашСтиль": Gosub MyOrganizerCory ...
MyOrganizerCory: Application.OrganizerCopy Source:= SourceFile$, _ Destination:=DestFile$, Name:=StyleName$, Object:= _ wdOrganizerObjectStyles Return
Даже для шести разных стилей заметное снижение числа строк и улучшение «читаемости» кода гарантированы. Еще лучше заменить конструкцию GoSub на обычную процедуру (GoSub должна исчезнуть в будущей VB.NET):
Call MyOrganizerCory ("МойСтиль") ... Sub MyOrganizerCory (StyleName$) Application.OrganizerCopy Source:= SourceFile$, _ Destination:=DestFile$, Name:=StyleName$, Object:= _ wdOrganizerObjectStyles End Sub
А теперь сделаем последний шаг — преобразуем линейную последовательность кода в цикл:
StyleName$ = Array("МойСтиль", "ВашСтиль", ...)
For i = LBound(StyleName$) To UBound(StyleName$) Call MyOrganizerCory (StyleName$(i)) Next
Следует подчеркнуть, что последний вариант не только сокращает код, но, что самое главное, обеспечивает параметризацию программы. Например, этот код будет очень просто адаптировать, если названия стилей будет вводиться из какого-то внешнего файла.