Как дать скотине в глаз – критический обзор кода электронного администратора
В главе 6, в разделе «Кодовая полиция», я объяснял, как стать кодовым полицейским. Сохранять объективность при чистке собственного кода очень сложно, но я постараюсь. Связав в названии этого приложения свой код со скотным двором, я надеялся подсказать вам, что я собираюсь делать. Все, что я хочу, – это чтобы вы разобрались в процессе критического обзора кода и обратили внимание на некоторые моменты, которые играют существенную роль в процессе проверки кода, написанного подотчетной вам группой.
Имея перед глазами исходный код, вам будет легче читать это приложение. Как я уже говорил в приложении А, скачать код можно с сайта http://www.piter.com.
Контекст и происхождение программного продукта
Код электронного администратора я написал вскоре после прекращения работы над неким веб-проектом, призванным облегчить административную волокиту, сопровождающую любой процесс разработки программного обеспечения. Поскольку времени на написание кода, который мне позже предстояло самому же раскритиковать, категорически не хватало, для иллюстрации нескольких архитектурных принципов я привлек материалы упомянутого проекта. Эксперимент этот, принесший мне немало забавных впечатлений, кажется, удался.
Не скажу, что код, о котором идет речь, может по своему качеству соревноваться с коробочными продуктами, но, в общем, он не так уж плох и с моими административными функциями справляется успешно.
Правила игры
Анализировать код я намерен по методике, изложенной в главе 6. Итак, как я уже говорил, хороший код отличается следующими особенностями.
• Он пишется в соответствии со стандартами программирования, принятыми для конкретного языка. В таком случае применяемые разными программистами методики конструирования объектов, обусловленные архитектурой, не будут слишком разниться.
• Внутри объектов соблюдается строгая связность. Объект – это несколько больше, чем просто группа процедур; он должен выполнять конкретную функцию. Как известно, сердце не дышит, а легкие не качают кровь.
• Взаимозависимость между объектами по возможности минимизируется. В большинстве случаев (в отсутствие существенных доводов в его пользу) взаимозависимость не приводит ни к чему хорошему – она лишь усложняет сопровождение. На последующую изоляцию взаимозависимых объектов затрачиваются серьезные финансовые и временные ресурсы.
Я обращу ваше внимание на ряд слабых сторон кода. По большей части они обусловливаются сжатыми временными рамками и тем обстоятельством, что инструмент, к которому этот код относится, я проектировал исключительно под себя. Пока что я ни разу не утверждал, что безгрешен, поэтому мои самоистязания вряд ли кого-нибудь удивят.
Следовал ли я стандартам?
В основном я следовал стандартам – по крайней мере, мне так кажется. VB я пользуюсь, начиная с версии 1.0, и с годами отношения с кодом складывались у меня (наверное, так же как и у вас) по-разному – был и удачный, и провальный опыт. С моей точки зрения, следование стандартам VB выражается в попытках привести объектно-ориентированные понятия в соответствие с этим языком, который, по правде говоря, отнюдь не полностью поддерживает объектно-ориентированную парадигму.
В главе 4 я изложил понятие задачи, или задания, – основного организующего принципа программного обеспечения. Просмотрев мой код, вы найдете в нем объект под именем clsTasks и вспомогательный объект clsTask. В этих двух модулях классов инкапсулированы пользовательское взаимодействие и все данные, обрабатываемые программой в связи с заданиями. Формы frmTask и frmTasks, ответственные за обработку заданий на стороне графического пользовательского интерфейса, являются дочерними объектами объекта clsTasks. Все прочие объекты, например clsToday, при обработке заданий обращаются к локальным экземплярам clsTasks. Эта схема довольно удачно, как мне кажется, иллюстрирует методику многократного использования объектов.
Модульные объявления объекта clsTasks выглядят так:
'-Закрытые объекты и события
Private mo_DataService As clsDataService '–данные объекта
Private mo_PickList As clsPickList '–список отбора для форм
Private WithEvents mfjasks As frmTasks '–все задания, связанные с frmTasks
Private WithEvents mfjask As frmTask '–отдельное задание, связанное с frmTask
Private mo_DataGrid As DataGrid
Private WithEvents mo_DataProvider As clsDataProvider '–основные данные
Private ml_CurTaskID As Long '–выбранный идентификатор задания
Private ms_Project As String '–применяется с frmProject
Private mo_ProgConfig As clsProgConfig
Private ms_TaskFilter As String
Private mo_Task As clsTask
Private mb_NeedRefresh As Boolean
Private ms_Resource As String
Private ms_Source As String '–открытые объекты и события
Public Event TaskUpdatedO
И что мы имеем? Много комментариев, среди которых нет ни одного, который описывал бы назначение центрального объекта. Плохой код и бездарный кот! И каким же образом человек со стороны сможет понять, зачем этот объект нужен, если нет никаких описаний?! Для этого придется основательно изучать код. Я-то его знаю вдоль и поперек, а вот свежий взгляд наткнется на труднопреодолимое препятствие.
Будь вы менеджером, вы бы, конечно, настояли на введении заголовка модуля с указанием его автора и даты создания. Кроме того, вы, вероятно, потребовали бы от программиста составить обзор модуля, обозначив в нем имена открытых процедур и механизм «жизнеобеспечения» ими объекта.
Нельзя, однако же, не признать некоторые достоинства вышеприведенного фрагмента кода. Обратите внимание на имена переменных – m в них идентифицирует область действия (модуль), а следующий символ обозначает тип переменной (Соответствует длинной переменной, s – строковой, b – логической, и т. д.).
Теперь рассмотрим конкретную процедуру, инициирующую процесс отображения формы задания:
Public Sub Show(Optional sResource As String ="")
If (mf_Tasks Is Nothing) Then
SetHourglass
Set mf_Tasks = New frmTasks Load mfjasks
Set mo_DataGrid = mf_Tasks .grdTasks
'-load tasks
LoadTaskGrid
'-Load resource combo
mo_PickList.LoadPickList mf_Tasks.cboResource. PIC_RESOURCE
' -configure task list
ms_Resource = sResource
mf_Tasks.Configure ms_Resource
If sResource «» "" Then ' -установка источника данных для отображения отдельного задания
ms_TaskFilter = "Assigned =" & Chr$(39) & sResource & Chr$(39)
mo_DataProvider.Filter ms_TaskFi1ter
mo_DataProvider.Sort «Status»
End If
SetReady
End If
With mf_Tasks
WindowState = 0
Show
ZOrder 0
End With
End Sub
Здесь, несмотря на немногочисленность комментариев по именам вызываемых процедур, можно определить их назначение – таким образом, в некотором отношении этот код можно признать самодокументированным. С другой стороны, комментарии модульного уровня опять же отсутствуют – о том, что это ключевая подпрограмма с открытой областью действия, код ничего не говорит.