Вступление
Иногда, особенно в более сложных структурах классов, я в некотором роде чувствую неуверенность в том, могут ли существовать неоткрытые круговые зависимости.
Поэтому я подумал, как мне легко проверить их существование.
Идея, лежащая в основе
Мне пришла в голову идея использовать центральное место, где я автоматически регистрирую все соответствующие объекты, когда они создаются, и удаляю их, когда они уничтожаются.
При желании / необходимости можно также хранить здесь контекстную информацию для каждого объекта, чтобы иметь возможность различать экземпляры объекта во время оценки.
Слабые ссылки
Конечно, вы можете использовать слабые ссылки в целом, но, возможно, вы не сможете / не будете этого делать в зависимости от проекта.
(Де) активировать мониторинг
Для этого используется условная компиляция. Это также гарантирует, что код для продуктивных сред не будет затронут.
Чтобы создать константу условной компиляции для всего проекта, необходимо использовать диалог свойств проекта VBE:
Для Conditional Compilation Arguments
переменная MONITOR_CYCLIC_DEPENDENCIES
можно указать и установить на 0
или же 1
.
Пример:
MONITOR_CYCLIC_DEPENDENCIES = 1
Обработка ошибок
Конкретная обработка ошибок наверняка может / должна быть реализована.
Именование
На днях я ответил на другой вопрос, связанный с соглашениями об именах.
Я тоже так поступил и некоторое время назад решил использовать Pascal Casing (MyProcedureName
) для имен объектов и процедур и Camel Casing (myVariableName
) для имен параметров и переменных.
Я всегда пишу константы с заглавной буквы и разделяю слова подчеркиванием (MY_CONSTANT_NAME
).
Я всегда предваряю свои комментарии '//
, что мне визуально приятнее.
Чтобы избежать автоматического повторного регистрирования моих литералов в VBA, я стараюсь найти хорошие имена, чтобы различать их, и поэтому избегаю необходимости написания одних и тех же литералов в разных регистрах.
Иногда это невозможно, хороший пример: value
, который используется как Value
из некоторых библиотек, поэтому я не могу назвать переменную value
.
Для таких особых случаев, как этот, я смирился с использованием фактически бессмысленного префикса: x
. Это не имеет отношения к префиксу как таковому.
В x
на самом деле только визуально гарантирует, что мои глаза чувствуют себя комфортно с кодом. Это конечно чисто субъективно, но меня это устраивает.
Примеры, в которых я использую этот «префикс», например:
xValue
xIndex
xItem
xResult
Это компромисс, на который я пришел.
Реализация
CyclicDependenciesMonitor
Важно: этот класс использует Attribute VB_PredeclaredId = True
Option Explicit
Private Type TState
MonitoredObjects As Collection
End Type
Private this As TState
Private Sub Class_Initialize()
Reset
End Sub
'// Reset object monitoring for another run
Public Sub Reset()
Set this.MonitoredObjects = New Collection
End Sub
'// Add an object for monitoring
Public Sub AddObject(ByVal objectToMonitor As Object)
this.MonitoredObjects.Add _
ObjPtr(objectToMonitor) & " (" & TypeName(objectToMonitor) & ")", _
CStr(ObjPtr(objectToMonitor))
End Sub
'// This procedure allows to store additional
'// context information if desired for debugging
Public Sub AddContextInformationToObject(ByVal monitoredObject As Object, _
ByVal contextInformation As String)
On Error Resume Next
Dim xResult As String
xResult = this.MonitoredObjects(CStr(ObjPtr(monitoredObject)))
If xResult = vbNullString Then Exit Sub
xResult = xResult & " " & contextInformation
RemoveObject monitoredObject
this.MonitoredObjects.Add xResult, CStr(ObjPtr(monitoredObject))
End Sub
'// Remove an object from monitoring
Public Sub RemoveObject(ByVal objectToMonitor As Object)
this.MonitoredObjects.Remove CStr(ObjPtr(objectToMonitor))
End Sub
'// Determine information from leftover objects
Public Function LeftoverObjects() As String
LeftoverObjects = TypeName(Me) & " - " & "Objects left over: " & _
this.MonitoredObjects.Count & vbNewLine
If this.MonitoredObjects.Count = 0 Then Exit Function
LeftoverObjects = LeftoverObjects & vbNewLine & _
"<Memory Adress> (<TypeName>) <Provided context Information>" & vbNewLine & _
"-----------------------------------------------------------" & vbNewLine
Dim xItem As Variant
For Each xItem In this.MonitoredObjects
LeftoverObjects = LeftoverObjects & xItem & vbNewLine
Next xItem
End Function
Встраивание кода в пользовательские классы
Для демонстрации я создал два определяемых пользователем класса, один с возможностью добавления данных контекста для внутренней отладки.
UserClass
Option Explicit
Private Type TState
SampleStoredObject As Object
End Type
Private this As TState
Private Sub Class_Initialize()
#If MONITOR_CYCLIC_DEPENDENCIES Then
CyclicDependenciesMonitor.AddObject Me
#End If
'// Place here the code that your class needs in this procedure
End Sub
Private Sub Class_Terminate()
'// Place here the code that your class needs in this procedure
#If MONITOR_CYCLIC_DEPENDENCIES Then
CyclicDependenciesMonitor.RemoveObject Me
#End If
End Sub
'// This is a sample procedure that receives and stores an object
'// to provoke a cyclic dependency
Public Sub SampleStoreObject(ByVal objectToStore As Object)
Set this.SampleStoredObject = objectToStore
End Sub
ДругойUserClass
Option Explicit
Private Type TState
SampleStoredObject As Object
SampleContextData As String
End Type
Private this As TState
Private Sub Class_Initialize()
#If MONITOR_CYCLIC_DEPENDENCIES Then
CyclicDependenciesMonitor.AddObject Me
#End If
'// Place here the code that your class needs in this procedure
End Sub
Private Sub Class_Terminate()
'// Place here the code that your class needs in this procedure
#If MONITOR_CYCLIC_DEPENDENCIES Then
CyclicDependenciesMonitor.RemoveObject Me
#End If
End Sub
'// This is a sample procedure that receives and stores an object
'// to provoke a cyclic dependency
Public Sub SampleStoreObject(ByVal objectToStore As Object)
Set this.SampleStoredObject = objectToStore
End Sub
'// This procedure could have been intentionally created to store contextual information,
'// but it could also be an existing procedure that is now also used for this purpose
Public Sub SampleStoreContextData(ByVal contextData As String)
this.SampleContextData = contextData
#If MONITOR_CYCLIC_DEPENDENCIES Then
CyclicDependenciesMonitor.AddContextInformationToObject Me, contextData
#End If
End Sub
Используй это
Как видите, код создает три объекта двух разных типов.
Object1
а также Object3
затем ссылаются друг на друга, вызывая циклическую зависимость.
После попытки уничтожить объекты, существующие циклические зависимости определяются и выводятся.
Комментирование одной из этих строк предотвращает существование циклических зависимостей:
object1.SampleStoreObject object3
object3.SampleStoreObject object1
ClientTestModule
Option Explicit
Public Sub TestObjectMonitoring()
#If MONITOR_CYCLIC_DEPENDENCIES Then
CyclicDependenciesMonitor.Reset
#End If
Dim object1 As UserClass
Set object1 = New UserClass
#If MONITOR_CYCLIC_DEPENDENCIES Then
'// Add some context data for monitoring
'// (however, this is not necessary and just for debugging purposes)
CyclicDependenciesMonitor.AddContextInformationToObject object1, "Foo"
#End If
Dim object2 As UserClass
Set object2 = New UserClass
Dim object3 As AnotherUserClass
Set object3 = New AnotherUserClass
'// The context data for monitoring will be added by a class method of the object
'// (however, this is also not necessary)
object3.SampleStoreContextData "Bar"
'// Provoke a cyclic dependency:
object1.SampleStoreObject object3
object3.SampleStoreObject object1
'// The attempt to destroy the objects by removing the references
Set object1 = Nothing
Set object2 = Nothing
Set object3 = Nothing
#If MONITOR_CYCLIC_DEPENDENCIES Then
'// Check whether monitored objects still exist, which have not been destroyed
Debug.Print CyclicDependenciesMonitor.LeftoverObjects()
#End If
End Sub
Выход
CyclicDependenciesMonitor - Objects left over: 2
<Memory Adress> (<TypeName>) <Provided context Information>
-----------------------------------------------------------
2357270190368 (UserClass) Foo
2357270190512 (AnotherUserClass) Bar
Или в случае отсутствия циклической зависимости:
CyclicDependenciesMonitor - Objects left over: 0
Вопросов
Что вы думаете об этом?
Подход полезен / работоспособен?
Есть ли у вас предложения по улучшению?