我一直在开发一个调度应用程序,现在我已经完成了中间层。它几天内没有改变,所以我觉得它已经准备好接受审查了。我只有这一套感觉很脏。它肯定接近箭头代码,但没有短路,我不知道如何进一步改进它。
我的Schedule
类封装了一个ScheduleEntries
集合,并提供了添加条目、删除条目和级联更改的方法(以及侦听底层集合更改的方法)。调用CascadeChanges
时,将搜索条目集合以查找脏记录。然后,这些记录在以后的周期中级联到相应的记录。必须满足一些条件,以确保更改被级联到正确的未来条目。目前,我牺牲了大量的性能,以获得更干净、更易读的代码。如何改进这一方法?
Public Sub CascadeChanges()
Dim innerEntries As SmartScheduleEntries
Set innerEntries = Me.Entries
'??? use group id to cascade changes?
Dim entry As SmartScheduleEntry
For Each entry In Me.Entries
If entry.IsDirty Then
Dim innerEntry As SmartScheduleEntry
For Each innerEntry In innerEntries
If innerEntry.Store = entry.Store Then
If (innerEntry.Cycle.Year = entry.Cycle.Year _
And innerEntry.Cycle.Number > entry.Cycle.Number) _
Or innerEntry.Cycle.Year > entry.Cycle.Year Then
With innerEntry
If .WeekDay = mOldWeekDay And .Week = mOldWeek And .Team = mOldTeam Then
.Team = entry.Team
.Week = entry.Week
.WeekDay = entry.WeekDay
End If
End With
End If
End If
Next innerEntry
End If
Next entry
RaiseEvent OnCascadeChanges
End Sub
这是我的两个测试用例。(我一直在使用橡胶鸭对所有这些进行单元测试。)
'@TestMethod
Public Sub CascadeShouldUpdateFuture()
On Error GoTo TestFail
Arrange:
Dim mock As SmartSchedule
Set mock = Mocks.MockFullSchedule
Dim originalDay As VbDayOfWeek
originalDay = mock.Entries(1).WeekDay
Dim shouldBeChanged As New SmartScheduleEntries
Dim entry As SmartScheduleEntry
For Each entry In mock.Entries
If entry.WeekDay = originalDay And entry.Store = 6003 Then
shouldBeChanged.Add entry, entry.ID
End If
Next
Act:
mock.Entries(1).WeekDay = vbFriday ' make a change to first record
mock.CascadeChanges
Assert:
For Each entry In shouldBeChanged
Assert.AreEqual vbFriday, entry.WeekDay, "Cycle: " & entry.Cycle.ToString
Next
TestExit:
Exit Sub
TestFail:
If Err.Number <> 0 Then
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Else
Resume TestExit
End If
End Sub
'@TestMethod
Public Sub CascadeShouldNotUpdatePast()
On Error GoTo TestFail
Arrange:
Dim mock As SmartSchedule
Set mock = Mocks.MockFullSchedule
Dim originalDay As VbDayOfWeek
originalDay = mock.Entries(1).WeekDay
Dim shouldNotBeChanged As New SmartScheduleEntries
Dim entry As SmartScheduleEntry
For Each entry In mock.Entries
If entry.WeekDay <> originalDay And entry.Store <> 6003 Then
shouldNotBeChanged.Add entry, entry.ID
End If
Next
Act:
mock.Entries(1).WeekDay = vbFriday ' make a change to first record
mock.CascadeChanges
Assert:
For Each entry In shouldNotBeChanged
Assert.AreNotEqual vbFriday, entry.WeekDay, "Cycle: " & entry.Cycle.ToString & "; Store: " & entry.ToString
Next
TestExit:
Exit Sub
TestFail:
If Err.Number <> 0 Then
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Else
Resume TestExit
End If
End Sub
对于上下文,下面您将找到相关的类。我很高兴收到关于这些的批评,但我对它们很满意。
Option Explicit
Private WithEvents mEntries As SmartScheduleEntries
Public Event OnAddEntry(ByRef entry As SmartScheduleEntry)
Public Event OnRemoveEntry(ByRef entry As SmartScheduleEntry)
Public Event OnCascadeChanges()
Private mOldWeek As CycleWeek
Private mOldWeekDay As VbDayOfWeek
Private mOldTeam As String
Public Property Get Entries() As SmartScheduleEntries
Set Entries = mEntries
End Property
Public Property Set Entries(ByVal value As SmartScheduleEntries)
Set mEntries = value
End Property
Public Sub AddEntry(ByVal entry As SmartScheduleEntry)
mEntries.Add entry, entry.ID
RaiseEvent OnAddEntry(entry)
End Sub
Public Sub RemoveEntry(ByVal entry As SmartScheduleEntry)
mEntries.Remove entry
RaiseEvent OnRemoveEntry(entry)
End Sub
Public Sub Validate()
'todo: implement Validate()
RaiseNotImplementedError "Validate"
End Sub
Public Sub CascadeChanges()
Dim innerEntries As SmartScheduleEntries
Set innerEntries = Me.Entries
'??? use group id to cascade changes?
Dim entry As SmartScheduleEntry
For Each entry In Me.Entries
If entry.IsDirty Then
Dim innerEntry As SmartScheduleEntry
For Each innerEntry In innerEntries
If innerEntry.Store = entry.Store Then
If (innerEntry.Cycle.Year = entry.Cycle.Year _
And innerEntry.Cycle.Number > entry.Cycle.Number) _
Or innerEntry.Cycle.Year > entry.Cycle.Year Then
With innerEntry
If .WeekDay = mOldWeekDay And .Week = mOldWeek And .Team = mOldTeam Then
.Team = entry.Team
.Week = entry.Week
.WeekDay = entry.WeekDay
End If
End With
End If
End If
Next innerEntry
End If
Next entry
RaiseEvent OnCascadeChanges
End Sub
Public Sub CleanEntries()
Dim entry As SmartScheduleEntry
For Each entry In mEntries
entry.IsDirty = False
Next
End Sub
Private Sub Class_Initialize()
Set mEntries = New SmartScheduleEntries
End Sub
Private Sub mEntries_Add(ByRef entry As SmartScheduleEntry)
' ReRaises event
RaiseEvent OnAddEntry(entry)
End Sub
Private Sub mEntries_ItemChanged(ByRef outWeek As CycleWeek, ByRef outWeekDay As VbDayOfWeek, ByRef outTeam As String)
mOldWeekDay = outWeekDay
mOldWeek = outWeek
mOldTeam = outTeam
End Sub
Private Sub mEntries_Remove(ByRef entry As SmartScheduleEntry)
' ReRaises Event
RaiseEvent OnRemoveEntry(entry)
End Sub
Private Sub RaiseNotImplementedError(ByVal procName As String)
Err.Raise vbObjectError + 1, TypeName(Me) & "." & procName, "Not implemented yet."
End Sub
Option Explicit
Public Enum ScheduleEntryError
ReadOnlyPropertyError = vbObjectError + 3333
End Enum
Public Enum CycleWeek
weekOne = 1
WeekTwo
End Enum
Private Type TScheduleEntry
ID As Long
GroupID As Long
Cycle As Cycle
Team As String
Store As Integer
WeekDay As VbDayOfWeek
Week As CycleWeek
IsDirty As Boolean
End Type
Private this As TScheduleEntry
Public Event OnWeekDayChange(ByRef outDay As VbDayOfWeek)
Public Event OnWeekChange(ByRef outWeek As CycleWeek)
Public Event OnTeamChange(ByRef outTeam As String)
Public Property Get ID() As Long
ID = this.ID
End Property
Public Property Let ID(ByVal value As Long)
If this.ID = 0 Then
this.ID = value
Else
RaiseReadOnlyError "ID"
End If
End Property
Public Property Get GroupID() As Long
GroupID = this.GroupID
End Property
Public Property Let GroupID(ByVal value As Long)
If this.GroupID = 0 Then
this.GroupID = value
Else
RaiseReadOnlyError "GroupID"
End If
End Property
Public Property Get IsDirty() As Boolean
IsDirty = this.IsDirty
End Property
Public Property Let IsDirty(ByVal value As Boolean)
this.IsDirty = value
End Property
Public Property Get Team() As String
Team = this.Team
End Property
Public Property Let Team(ByVal value As String)
Dim old As String
old = this.Team
this.Team = value
this.IsDirty = True
RaiseEvent OnTeamChange(old)
End Property
Public Property Get Store() As Integer
Store = this.Store
End Property
Public Property Let Store(ByVal value As Integer)
this.Store = value
this.IsDirty = True
End Property
Public Property Get Cycle() As Cycle
Set Cycle = this.Cycle
End Property
Public Property Set Cycle(ByVal value As Cycle)
Set this.Cycle = value
this.IsDirty = True
End Property
Public Property Get Week() As CycleWeek
Week = this.Week
End Property
Public Property Let Week(ByVal value As CycleWeek)
Dim old As CycleWeek
old = this.Week
this.Week = value
this.IsDirty = True
RaiseEvent OnWeekChange(old)
End Property
Public Property Get WeekDay() As VbDayOfWeek
WeekDay = this.WeekDay
End Property
Public Property Let WeekDay(ByVal value As VbDayOfWeek)
Dim old As VbDayOfWeek
old = this.WeekDay
this.WeekDay = value
this.IsDirty = True
RaiseEvent OnWeekDayChange(old)
End Property
'read-only property
Public Property Get SetDate() As Date
Dim result As Date
' vbMonday == 2, and our week starts on Monday.
' If DayOfWeek == vbMonday, it is the startdate, we should add zero days.
' In other words, Add (2 - 2) to startdate if it's Monday.
If this.Week = weekOne Then
result = DateAdd("d", this.WeekDay - 2, this.Cycle.StartDate)
Else
result = DateAdd("d", this.WeekDay - 2 + 7, this.Cycle.StartDate)
End If
SetDate = result
End Property
Public Function ToString() As String
ToString = this.Cycle.ToString & "," & this.Team & "," & this.Store & "," & this.Week & "," & this.WeekDay & "," & this.IsDirty
End Function
Private Sub RaiseReadOnlyError(ByVal procName As String)
Err.Raise ScheduleEntryError.ReadOnlyPropertyError, TypeName(Me) & "." & procName, "Property Is ReadOnly."
End Sub
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "SmartScheduleEntries"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Private mCollection As Collection
Private WithEvents mEntryListener As SmartScheduleEntry
Attribute mEntryListener.VB_VarHelpID = -1
Public Event Added(ByRef entry As SmartScheduleEntry)
Public Event Removed(ByRef entry As SmartScheduleEntry)
Public Event ItemChanged(ByRef outWeek As CycleWeek, ByRef outWeekDay As VbDayOfWeek, ByRef outTeam As String)
Public Function Add(ByRef entry As SmartScheduleEntry, ByVal Key As Long)
mCollection.Add entry, CStr(Key)
RaiseEvent Added(entry)
End Function
Public Function Remove(ByVal entry As SmartScheduleEntry)
mCollection.Remove IndexOf(entry)
RaiseEvent Removed(entry)
End Function
Public Function Item(ByVal index As Variant) As SmartScheduleEntry
Attribute Item.VB_UserMemId = 0
Set mEntryListener = mCollection(index)
Set Item = mEntryListener
End Function
Public Function Count() As Long
Count = mCollection.Count
End Function
' returns index of item if found, returns 0 if not found
Public Function IndexOf(ByVal entry As SmartScheduleEntry) As Long
Dim i As Long
For i = 1 To mCollection.Count
If mCollection(i).ID = entry.ID Then
IndexOf = i
Exit Function
End If
Next
End Function
Public Function NewEnum() As IUnknown
Attribute NewEnum.VB_UserMemId = -4
Set NewEnum = mCollection.[_NewEnum]
End Function
Private Sub Class_Initialize()
Set mCollection = New Collection
End Sub
Private Sub Class_Terminate()
Set mCollection = Nothing
End Sub
Private Sub mEntryListener_OnTeamChange(ByRef outTeam As String)
RaiseEvent ItemChanged(mEntryListener.Week, mEntryListener.WeekDay, outTeam)
End Sub
Private Sub mEntryListener_OnWeekChange(ByRef outWeek As CycleWeek)
RaiseEvent ItemChanged(outWeek, mEntryListener.WeekDay, mEntryListener.Team)
End Sub
Private Sub mEntryListener_OnWeekDayChange(ByRef outDay As VbDayOfWeek)
RaiseEvent ItemChanged(mEntryListener.Week, outDay, mEntryListener.Team)
End Sub
Option Explicit
Private Type TCycle
StartDate As Date
EndDate As Date
Year As Integer
Number As Integer
End Type
Private this As TCycle
Public Property Get Year() As Integer
Year = this.Year
End Property
Public Property Let Year(ByVal value As Integer)
this.Year = value
End Property
Public Property Get Number() As Integer
Number = this.Number
End Property
Public Property Let Number(ByVal value As Integer)
this.Number = value
End Property
Public Property Get StartDate() As Date
StartDate = DateValue(this.StartDate)
End Property
Public Property Let StartDate(ByVal value As Date)
this.StartDate = value
End Property
Public Property Get EndDate() As Date
EndDate = DateValue(this.EndDate)
End Property
Public Property Let EndDate(ByVal value As Date)
this.EndDate = value
End Property
Public Function ToString() As String
ToString = this.Year & "-P" & Format(this.Number, "00")
End Function
Public Sub SetFromString(ByVal value As String)
Dim arr As Variant
arr = Split(value, "-P", 2)
Me.Year = arr(0)
Me.Number = arr(1)
End Sub
发布于 2014-12-08 18:03:35
是的,您的箭头代码很脏,可以分解成其他方法。它们现在可能只在一种方法中使用,但是随着代码的扩展,您会发现已经定义了这些方法是很方便的。我发现,将每一种方法保持在一两种控制结构中是有帮助的。请使用比我所用的更好的名称,因为我不完全理解你的产品。
Public Sub CascadeChanges()
Dim entries As SmartScheduleEntries
Set entries = Me.Entries
Dim entry As SmartScheduleEntry
For Each entry in entries
If entry.IsDirty Then CascadeEntry entry, entries
Next entry
RaiseEvent OnCascadeChanges
End Sub
Private Sub CascadeEntry(ByVal inputEntry As SmartScheduleEntry, _
ByVal entries As SmartScheduleEntries)
Dim entry As SmartScheduleEntry
For Each entry In entries
If OughtCascade(inputEntry, entry) And IsOutDated(entry) Then
DoCascade inputEntry, entry
End If
Next entry
End Sub
Private Function IsOutDated(ByVal entry As SmartScheduleEntry) As Boolean
IsOutDated = (entry.WeekDay = mOldWeekDay And _
entry.Week = mOldWeek And _
entry.Team = mOldTeam)
End Function
您可能希望抽象出OughtCascade
的各种比较,但我确实知道哪些比较与抽象有关。所有的比较都是简单的性质,因此缺乏短路评价具有边际成本。回顾一下Scheduler类,并不是所有这些方法都属于该类。下面两个可以移植到您的SmartScheduleEntry
类。
Private Function OughtCascade(ByVal entryFrom SmartScheduleEntry, _
ByVal entryTo SmartScheduleEntry) As Boolean
OughtCascade = (entryFrom.Store = entryTo.Store) And _
((entryFrom.Cycle.Year = entryTo.Cycle.Year) And _
(entryFrom.Cycle.Number < entryTo.Cycle.Number) Or _
(entryFrom.Cycle.Year < entryTo.Cycle.Year))
End Function
Public Sub DoCascade(ByRef entryFrom As SmartScheduleEntry, _
ByRef entryTo As SmartScheduleEntry
With entryTo
.Team = entryFrom.Team
.Week = entryFrom.Week
.WeekDay = entryFrom.WeekDay
End With
End Sub
IsDirty
是肮脏的再看一看您的代码,我就开始怀疑IsDirty
成员了。我相信物业应评估该项目是否肮脏,而不是从会员那里读取。它似乎是导致锅炉板代码在让属性的其他成员。
公共属性设WeekDay(ByVal value As VbDayOfWeek) Dim old As VbDayOfWeek old = this.WeekDay this.WeekDay = value this.IsDirty = True RaiseEvent OnWeekDayChange(old) End Property
问题是Get IsDirty
依赖于Let WeekDay
和其他属性的代码。Get IsDirty
应该独立于它没有具体引用的任何方法。隔离IsDirty
可能需要完全重新设计您的结构。鉴于IsDirty
似乎是HasMutated的同义词,请考虑使您的SmartScheduleEntry
类不可变。
发布于 2014-12-09 21:42:57
我以为你知道比用魔术数字更好
对于mock.Entries中的每个条目,If entry.WeekDay = originalDay和entry.Store = 6003然后shouldBeChanged.Add条目,entry.ID结束如果Next
6003
商店有什么特别之处?
这是什么错误?
公共Enum ScheduleEntryError ReadOnlyPropertyError = vbObjectError + 3333末端Enum
魔术数字错误?
我发现这对解释这里的逻辑是有用的,否则的话就没有意义了。
“只读属性公共属性将SetDate()作为Date结果作为Date”vbMonday == 2,我们的一周从周一开始。‘如果DayOfWeek == vbMonday是开始日期,我们应该增加零天。‘换句话说,如果是星期一,在开始日期加上(2-2)。如果this.Week = weekOne则结果= DateAdd("d",this.WeekDay - 2,this.Cycle.StartDate),则this.WeekDay = DateAdd("d",this.WeekDay-2+ 7,this.Cycle.StartDate)结束,如果SetDate =结果结束属性
不管怎么说,对于VB来说,代码看起来很漂亮。
https://codereview.stackexchange.com/questions/72033
复制相似问题