Ensure maintainability & testability in file-processing application - vb.net

Problem
I want to refactor an old application which has grown over time and is awful to maintain/test. Basically the app needs to read an file an process it lines.
The file to be read can be compared to a CSV-file, where the first field determines the type of the line, something like this:
1,abcd,...
1,3423,...
2,"abc",...
5,test,...
Currently the application works like this (pseudo-code):
For Each line in file.getLines()
if (line.StartsWith("1,") then
' Process line of type 1
elseif (line.StartsWith("2,") then
' Process line of type 2
...
End If
Next
This obviously is a nightmare to maintain and test. Therefore I'd like refactor this whole thing and thought of doing it like this:
Create a parser for each line-type
Let each parser register itself to a handler, which will raise an event for each line
Code Idea
Handler:
Public Class ParserHandler
Public Event Parse(RepLine As ReportLine)
Public Event FileFinished()
Public Sub RaiseParse(RepLine As ReportLine)
RaiseEvent Parse(RepLine)
End Sub
Public Sub RaiseFileFinished()
RaiseEvent FileFinished()
End Sub
End Class
Parser:
Public Class FirstParser
Public Sub New(Handler As ParserHandler)
AddHandler Handler.Parse, AddressOf Parse
AddHandler Handler.FileFinished, AddressOf FileFinished
End Sub
Public Sub Parse(RepLine As ReportLine)
If Not RepLine.Type = 1 Then
Return
End If
' Process
End Sub
Private Sub FileFinished()
' Final stuff, eg. insert into DB
End Sub
End Class
Main:
Dim ParserHandler As New ParserHandler()
Dim FirstParser As New FirstParser(ParserHandler)
Dim SecondParser As New SecondParser(ParserHandler)
...
For Each line in file.getLines()
' Extract Id and construct ReportLine-object here
ParserHandler.RaiseParse(ReportLine)
Next
I think this looks way better than the original version, but I'm still not convinced that this is really the way to go. In case of testing the parsers with unit tests, I still have to create a handler first. This might be ok, but feels wrong as the parser should be testable separately (is it wrong?).
I like the use of events in this case, as not every parser needs to implement every possible event of the handler, which prevents me from just having an IParser interface.
What do you think of this approach, is it good to go? Are there any best practices/design pattern I should look into for a clean solution to this problem?

Related

Can I make a message box appear if a textbox time is 5 minutes before system time?

I am trying to create a reminder system for my break schedule (because for some reason the tech company I work for does not have one in our scheduling system /facepalm)
I can make it work IF the time I write in the textbox for my break matches the system time. What I would like it to do is change it so it pops up once, 5 minutes before and then again when it matches the system time. Here is what i have so far for the on time reminder:
Sub Timer1Tick(sender As Object, e As EventArgs)
DaClock.Text = Format(Now, "h:mm")
For the sake of simplicity ^
If Coffee.Text = "DaClock.Text" And reminder.Checked = True Then
Coffee.Text = (Coffee.Text + " Over")
Coffee.Enabled = False
MsgBox("Break Time", MsgBoxStyle.Information, "break")
Else If Coffee.Text = DaClock.Text Then
Coffee.Text = (Coffee.Text + " Over")
Coffee.Enabled = False
End If
End Sub
I find that I am not able to figure out how to get the reminder to pop up 5 minutes before.
edit Oh it may not be relevant but DaClock is an invisible Label
Update
I was able to set up a secont label and a string with 5 minutes added so the event will trigger 5 minutes early but now i am having formatting issues:
Dim MyTime As String
MyTime = TimeOfDay.AddMinutes(5)
D5Clock.Text = Format(MyTime, "h:mm")
But all it shows in the Label is h:mm. If i choose not to format it and shows normally, (eg: 6:30:54 PM) but the formatting is important to make sure my break entries trigger as we are only using the h:mm (eg 6:30) in the fields.
While I can respect what you're doing, and especially that you progressed and just straight out beaten the shit out of your own challenge, I would like to suggest an alternative (explanations follows):
Public NotInheritable Class Reminder
Private Shared _instance As Reminder
Private Sub New()
'hehehe Private
End Sub
Public Shared Function GetInstance() As Reminder
If _instance Is Nothing Then _instance = New Reminder
Return _instance
End Function
Private Async Sub RemindMe(ByVal time As DateTime)
Dim waiting As Boolean = True
While (waiting)
Await Task.Delay(60000) 'This means "Check once every minute"
If time > Now Then
MessageBox.Show("Wake up, it's " & Now.ToShortTimeString & " !!")
waiting = False
End If
End While
End Sub
Public Sub Dispose()
Me.Dispose()
End Sub
End Class
So... what exactly is this unholy thing? It's a Singleton. It's a class which has only one instance, at all time - that's why the Sub New() is private: I don't want people to be able to instantiate this class! Except for you. You can get ONE, only one instance, by using the public function GetInstance() (if you ask for more than one instance, you'll always get the first one, so ask away if you feel like it).
Then you can ask it to remind you to do stuff by giving it a time when to awaken. It keeps track of your reminders on different threads, so don't worry about these hogging all your main thread's cpu. This is just a skeleton code snippet, though, and I suggest you alter it with custom messages and the like.
Don't forget to Dispose() of it if you want to keep your memory happy. It'll die eventually anyway when you dispose of it's parent object, but it's a healthy habit nonetheless.
It may or may not be something which will help you, and don't mind me if it's not something you want to deal with, but I felt like you might like it. Have fun!
Figured it out by running two clocks simultaneously and calling each one so it will happen 5 minutes early and then again on time.
D5Clock.Text = Format(TimeOfDay.AddMinutes(5), "h:mm")
DaClock.Text = Format(TimeOfDay, "h:mm")
whew

VBA Settings Dialog using MVP - do I need a model?

I've been reading up on many examples of MVP (Model-View-Presenter) and their variations (Passive view, Supervising view) to try and make my solutions more robust (and reusable) in VBA (using Excel as the host in this instance). The problem I've found is finding good, simple examples in VBA that are not complete overkill for the (hopefully) simple examples I need.
I'm attempting to create a "settings" dialogue that stores certain configuration in a worksheet (this is my "repository").
Here's my main procedure, triggered by the user:
Private Sub ShowImportSelector()
Dim importPresenter As DataImportPresenter
Set importPresenter = New DataImportPresenter
importPresenter.LoadConfig
If importPresenter.Show = -1 Then Exit Sub
importPresenter.SaveConfig
' begin processing...
If (CStr([Settings.SelectedVersion].Value2) = "QQ") Then
' ...
End If
End Sub
Here is my "presenter" (here I use range names for the source, and config destination):
Option Explicit
Private m_importForm As FImport
Private Sub Class_Initialize()
Set m_importForm = New FImport
End Sub
Public Sub LoadConfig()
m_importForm.SetAvailableVersions "tblVERSION"
m_importForm.SetAvailableSalesOrgs "tblSALESORG"
m_importForm.SetAvailableCategories "tblCATEGORY"
m_importForm.ToolName = "Forecast"
End Sub
Public Sub SaveConfig()
[Settings.SelectedVersion].Value2 = m_importForm.SelectedVersion
[Settings.SelectedSalesOrg].Value2 = m_importForm.SelectedSalesOrg
[Settings.SelectedCategory].Value2 = m_importForm.SelectedCategory
End Sub
Public Function Show() As Integer
m_importForm.Show vbModal
Show = m_importForm.Result
End Function
And now the "View" (a VBA Form):
Option Explicit
Private m_selectedVersion As String
Private m_selectedSalesOrg As String
Private m_selectedCategory As String
Private m_toolName As String
Private m_dialogueResult As Long
Public Property Get ToolName() As String
ToolName = m_toolName
End Property
Public Property Let ToolName(ByVal value As String)
m_toolName = value
ToolNameLabel.Caption = value
End Property
Public Property Get Result() As Long
Result = m_dialogueResult
End Property
Public Property Get SelectedVersion() As String
SelectedVersion = m_selectedVersion
End Property
Public Property Get SelectedSalesOrg() As String
SelectedSalesOrg = m_selectedSalesOrg
End Property
Public Property Get SelectedCategory() As String
SelectedCategory = m_selectedCategory
End Property
Public Sub SetAvailableVersions(ByVal value As String)
VersionSelector.RowSource = value
End Sub
Public Sub SetAvailableSalesOrgs(ByVal value As String)
SalesOrgSelector.RowSource = value
End Sub
Public Sub SetAvailableCategories(ByVal value As String)
CategorySelector.RowSource = value
End Sub
Private Sub SaveSelections()
m_selectedVersion = VersionSelector.value
m_selectedSalesOrg = SalesOrgSelector.value
m_selectedCategory = CategorySelector.value
End Sub
Private Sub CloseButton_Click()
m_dialogueResult = -1
Me.Hide
End Sub
Private Sub ImportButton_Click()
SaveSelections
m_dialogueResult = 0
Me.Hide
End Sub
At this point, I have become confused with the possible directions I could go in terms of adding a model to the above - question is: is this even needed for this simple example?
MVP architecture makes cleaner code, but cleaner code isn't the primary purpose of MVP; achieving loose coupling, higher cohesion, and testability is.
If loosely-coupled components and unit-testable logic isn't a requirement, then full-blown MVP is indeed overkill, and having the "model" exposed as properties on the "view" is definitely good enough, as it already helps making your "presenter" not need to care about form controls. You're treating the form as the object it's begging to be, and pragmatically speaking this could very well be all you need. I'd make the Show method return an explicit Boolean though, since it's implicitly used as such.
On the other hand, if you are shooting for decoupling and testability, then extracting the model from the view would only be step one: then you need to decouple the presenter from the worksheet, and maybe introduce some ISettingsAdapter interface that abstracts it away, such that if/when the configuration needs to go to a database or some .config file, your presenter code doesn't need to change in any way... but this requires designing the interfaces without having any particular specific implementation in mind, i.e. something that works without changes regardless of whether the data is on a worksheet, in some flat file, or in some database table.
MVP demands a paradigm shift: MVP isn't procedural programming anymore, it's OOP. Whether OOP is overkill for your needs depends on how much coupling you're willing to live with, and how frail this coupling is making your code in the face of future changes. Often, abstraction is enough: using named ranges instead of hard-coded range addresses is one way of improving the abstraction level; hiding the worksheet behind an adapter interface implemented by a worksheet proxy class (whatever you do, never make a worksheet module implement an interface: it will crash) is another - depends where your threshold for "overkill" is, but if you do achieve full decoupling and write the unit tests, nobody can blame you for going overboard: you're just following the industry best-practices that every programmer strives for, improving your skills, and making it much easier to later take that code and rewrite it in .NET, be it VB or C#. I doubt anyone would argue that full-blown MVP is overkill in .NET/WinForms.

VBA - Passing variable between modules

I'm new to VB6, and trying to write some macro in use for CorelDraw.
I have a variable that need to be passed from Class module to Standard module, in my Class Module "SaveOptClass" I have a public variable called IsSaved and it's set on the class module:
Public IsSaved As Boolean
Public Sub SaveFile()
If <some triggers> Then
IsSaved = True
End If
In Standard module:
Sub DoSave()
Dim SaveClass As SaveOptClass
Set SaveClass = New SaveOptClass
If SaveClass.IsSaved = True Then
ActiveDocument.Save
Else
Form1.Show
End If
End Sub
Basically I'm trying to pass "IsSaved" boolean value from class module to standard. (If IsSaved is true, save the document or else display a form.)
I have tested that the boolean is True when I executed the code, but I can't get the state to pass to the other module.
Is there something I miss here? Thanks in advance.
As already answered by #shahkalpesh the problem is that you're not using a meaningful instance of SaveOptClass.
In my opinion the best way to design this kind of dependency is by mean of a parameter in the routine is using it and avoid as much as possible the use of global variables.
In your case this brings to this rewriting:
' in someOtherModule
Public Sub DoSave(saveOptObj as SaveOptClass)
If saveOptObj.IsSaved Then ' = True is unnecessary
ActiveDocument.Save
Else
Form1.Show
End If
End Sub
The client code could be:
private saveOptObj as SaveOptClass
Public Sub SaveFile()
If <some triggers> Then
saveOptObj.IsSaved = True
End If
' ....
someOtherModule.DoSave(saveOptObj)
' ...
Consider also, at this point, a renaming of DoSave, given that the actions taken suggest different semantics. In similar cases is preferable moving the If Else logic in the caller. Anyway, if you prefer to group actions with different semantics in the same routine you'd better use namings like DoSaveOr<SomethingElse>.

Is it possible to dynamically specify the type of event when using AddHandler?

I am trying to write a clever little function here that will add a specified delegate as an event handler to all the controls in a collection for any dynamic event. What I'm trying to do is write this as a completely generic function so that I could possibly use it in various different projects (perhaps including it in some sort of tools library).
Basically I want to specify a group of controls, the delegate to handle the event, and the type of event to handle. The problem that I'm running up against is that I can't figure out how to dynamically specify the event at run time.
Here's my 'work-in-progress' sub:
Private Sub AddHandlerToControls(controlList As ControlCollection, eventToHandle As EventHandler, eventHandlerDelegate As Func(Of Object, EventArgs), Optional filterList As List(Of Type) = {})
For Each controlInList As Control In controlList
If controlInList.HasChildren Then
AddHandlerToControls(controlInList.Controls, controlInList.MouseEnter, eventHandlerDelegate, filterList)
End If
If filterList.Count > 0 Then
If filterList.Contains(controlInList.GetType) = False Then
Continue For
End If
End If
AddHandler controlInList.MouseEnter, eventHandlerDelegate
Next
End Sub
Ideally I would like to use the eventToHandle parameter there at the end in the AddHandler statement instead of specifically using controlInList.MouseEnter. Like this:
AddHandler eventToHandle, eventHandlerDelegate
That way I could call this function dynamically in a form.load method, and call it sort of like how I did earlier in the sub where it's recursively calling itself for child controls. Somehow say "for this list of controls I would like to use this delegate as the 'MouseEnter' event handler". Like So:
AddHandlerToControls(Me.Controls, control.MouseEnter, MouseEnterHandlerDelegate, new List(Of Type) {TextBox, ComboBox})
This could just be wishfull thinking, I'm starting to think that this isn't quite possible at this level of 'genericness', but it's an interesting enough problem that I thought I should at least ask.
Edit for solution:
Jon Skeet's suggestion of using Reflection ended up working for me. Here's the final function:
Private Shared Sub AddHandlerToControls(controlList As Control.ControlCollection, eventToHandle As String, eventHandlerDelegate As MethodInfo, Optional filterList As List(Of Type) = Nothing)
For Each controlInList As Control In controlList
If controlInList.HasChildren Then
AddHandlerToControls(controlInList.Controls, eventToHandle, eventHandlerDelegate, filterList)
End If
If Not filterList Is Nothing Then
If filterList.Contains(controlInList.GetType) = False Then
Continue For
End If
End If
Dim dynamicEventInfo As EventInfo = controlInList.GetType.GetEvent(eventToHandle)
Dim handlerType As Type = dynamicEventInfo.EventHandlerType
Dim eventDelegate As [Delegate] = [Delegate].CreateDelegate(handlerType, eventHandlerDelegate)
dynamicEventInfo.AddEventHandler(controlInList, eventDelegate)
Next
End Sub
And how I call it and the delegate used:
AddHandlerToControls(Controls, "MouseClick", GetType(MainFrm).GetMethod("MouseClickEventDelegate"), New List(Of Type) From {GetType(TextBox), GetType(ComboBox)})
Shared Sub MouseClickEventDelegate(sender As Object, eventArgs As EventArgs)
sender.SelectAll()
End Sub
This allowed me to set all text boxes and combo boxes on my form (there's quite a few) to select all text when clicked into, in about 20 lines of code. The best part is that if I add any in the future, I won't have to worry about going back to add this handler, it'll be taken care of at run time. It may not be the cleanest solution, but it ended up working pretty well for me.
Two options:
Specify a "subscription delegate" via a lambda expression. I wouldn't like to guess at what this would look like in VB, but in C# it would be something like:
(control, handler) => control.MouseEnter += handler;
Then you just need to pass each control to the delegate.
Specify the event name as a string, and use reflection to fetch the event and subscribe (Type.GetEvent then EventInfo.AddEventHandler).

Weird behavior using the Observer pattern

Ok, so I have an application that reads another processes memory. I initially had multiple scanning threads for the various areas I needed to read. This was processor intensive so I decided to go with the observer pattern. All was well except that I am having a weird behavior.
Here is what is happening
I have 2 radars (overlay and mapped) Both have a watcher class that attaches to the memory scanner and is notified on a new list of mobs.
so I open radar 1 (mapped) it attaches it's watcher to the scanner and waits for mob list update notifications
Open radar 2 (overlay). same thing happens and another watcher is attached.
all is well and good so far
Now there are properies on the mobs in the list, one of which is IsFilteredOut. This property is set in the radar code after it receives the list.
Now the weird behavior is that no matter what I do, the second radar to be opened changes all the properties of the mobs in the list of both radars. It is as if I am passing the list by ref, but I am not. I actually create a new instance of the moblist class every time I pass the list.
Here is the notify code. As you can see I create a new instance of the moblist class each pass.
Private Sub NotifyMobListUpdated(ByVal Mobs As List(Of MobData))
If Mobs IsNot Nothing Then
For Each w As Watcher In _watchers
If w.Type And WatcherTypes.MobList = WatcherTypes.MobList OrElse w.Type And WatcherTypes.All = WatcherTypes.All Then
w.MobListUpdated(New MobList(Mobs))
End If
Next
End If
End Sub
This is where it is handled in the Watcher class
''' <summary>
''' IWatcher MoblistUpdated Implementation
''' </summary>
''' <param name="Mobs">The Updated mob list</param>
''' <remarks></remarks>
Public Sub MobListUpdated(ByVal Mobs As MobList) Implements IWatcher.MobListUpdated
Try
PostNewMobList(Mobs)
Catch ex As Exception
End Try
End Sub
Public Sub PostNewMobList(ByVal Mobs As MobList)
_sync.Post(New SendOrPostCallback(AddressOf OnNewMobList), Mobs)
End Sub
Private Sub OnNewMobList(ByVal state As Object)
Dim mobs As MobList = TryCast(state, MobList)
Try
If mobs IsNot Nothing Then
RaiseEvent NewMobList(mobs)
End If
Catch ex As Exception
End Try
End Sub
This error is driving me nuts and any help would be greatly appreciated.
Thanks
I actually create a new instance of the moblist class every time I pass the list.
Which only prevents the list from changing, not the list elements. You'd have to clone the element objects as well. I don't have a clue with radars and mobs do, you might want to consider using Send instead of Post.