Skip to main content
Published / updated

Reading Legacy Code

Before you start

You need: Articles 01–06. This one puts them together on an unfamiliar codebase.

Time: about 50 minutes, plus the practice.

Learning objective

Take an unfamiliar VB.NET application and, without changing it, explain what one screen does and where its logic lives.

Topics

  • A survey order for an unfamiliar codebase
  • Code-behind and the event model
  • Handles, WithEvents, and AddHandler
  • Tracing a button click to the database
  • VB.NET to C# translation reference
  • Reading before changing

Terminology

TermMeaning
Legacy applicationSoftware still in production that predates current practice and has no active feature roadmap
Code-behindThe .vb file holding the logic for a form or page, paired with a designer file
MaintenanceFixing defects and making small changes, without redesigning
RegressionSomething that used to work and no longer does, caused by a change
Connection stringThe configuration value telling ADO.NET which server, database, and credentials to use

The distinction that matters to your job is between maintenance and redesign. On a maintenance task, the smallest change that fixes the problem is the correct one. Improvements you noticed belong in a list you hand to your lead, not in the same commit.

Survey order

Do not start reading at line 1 of the file you were pointed at. Build a map first, in this order.

1. Solution structure. Open the .sln and list the projects. One project is a small tool. Twelve projects with a .Data, .Business, and .Web split tell you where to expect what.

2. Configuration. Read Web.config or App.config. Connection strings tell you how many databases are involved. appSettings keys are usually the feature switches nobody documented.

3. Compiler options. Check Option Strict in project properties. Off means run-time type failures are possible everywhere and you should trust the compiler less.

4. Entry points. For Web Forms, the .aspx pages. For WinForms, the startup form. For a service, Sub Main. This is the list of things a user can actually do.

5. Data layer. Find where SqlConnection appears. That tells you whether data access is centralised in a few classes or scattered through the UI.

6. Only then, the screen you were asked about.

Use Find in FilesCtrl+Shift+F. Set Look in to Entire Solution and, for these, File types to *.vb.

Search forTells you
New SqlConnectionHow scattered data access is
On ErrorWhere unstructured error handling survives
Option StrictWhich files override the project setting
TODO, HACK, FIXMEKnown debt the last developer left you

Ctrl+Shift+F, not Ctrl+F. Ctrl+F searches the current file only, and on an unfamiliar codebase that is the wrong question. Results open in the Find Results window; double-click a line to jump to it, and press F8 to walk through the hits.

Use Find in Files rather than Find All References for this survey. Find All References needs the symbol to resolve, and in a project with Option Strict Off a late-bound call has no symbol to resolve — a plain text search finds it, and the smarter tool does not.

A high SqlConnection count spread across UI files means business logic and data access are mixed, and a change in one screen will not be reusable in another.

Code-behind and the event model

A Web Forms page is three files:

StudentList.aspx markup and control declarations
StudentList.aspx.vb code-behind — the logic
StudentList.aspx.designer.vb generated control fields — do not edit

WinForms follows the same shape with .vb and .Designer.vb.

The .designer.vb file is regenerated by Visual Studio. Editing it by hand loses your changes the next time someone opens the designer. When a control field seems to be missing, it is a designer-file problem, not a code problem.

Handles, WithEvents, and AddHandler

VB.NET wires events three ways, and a legacy application usually contains all three.

' 1. Handles clause — the common form. The field must be declared WithEvents.
Protected WithEvents btnSearch As Button

Protected Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
LoadStudents()
End Sub
' 2. One handler, several events
Protected Sub Filter_Changed(sender As Object, e As EventArgs) _
Handles ddlClass.SelectedIndexChanged, ddlSection.SelectedIndexChanged

LoadStudents()
End Sub
' 3. AddHandler — wired at run time, invisible to a name search
AddHandler grdStudents.RowCommand, AddressOf Students_RowCommand

The third form is the one that costs you time. A handler attached with AddHandler has no Handles clause, so searching for the control name will not find it. When you cannot find what responds to a control, search for AddHandler and for AddressOf.

Handles binds by the field name, not the method name. Renaming the method is safe; renaming the control field breaks every Handles clause referring to it.

In Web Forms, also check AutoEventWireup in the .aspx page directive. When True, methods named Page_Load are wired by convention with no Handles clause at all.

Tracing a button click

The exercise: a user clicks Search on the student list and the wrong students appear. Find where the logic is, without changing anything.

1. Find the control in the markup.

<asp:Button ID="btnSearch" runat="server" Text="Search" />
<asp:GridView ID="grdStudents" runat="server" AutoGenerateColumns="False">

2. Find the handler. Search the code-behind for btnSearch. Look for Handles btnSearch.Click; if there is none, search for AddHandler btnSearch.

3. Read the page lifecycle. In Web Forms, Page_Load runs before the click handler on every postback. A Page_Load that reloads the grid without an IsPostBack check will overwrite whatever the handler did:

Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadStudents() ' correct — only on first load
End If
End Sub

A missing IsPostBack check is the single most common Web Forms defect. The symptom is exactly "my filter does nothing" or "the grid resets itself".

4. Follow the call chain down.

btnSearch_Click
└─ LoadStudents()
└─ StudentService.Search(schoolId, term)
└─ StudentRepository.Search(...)
└─ SqlCommand → Student table

Use Go to definition (F12) at each step rather than searching by name — a Module function with no receiver is otherwise very hard to place.

5. Note where the filter value actually comes from. txtSearch.Text, a Session variable, a ViewState entry, and a query-string value are four different sources, and legacy screens mix them.

6. Write down the chain before you touch anything. That written chain is what you attach to the ticket, and it is what the capstone assignment asks for.

VB.NET to C# reference

Useful in both directions: for translating an example you found in C#, and for explaining VB.NET code to a colleague who only reads C#.

VB.NETC#
Dim x As Integer = 5int x = 5;
Nothingnull (and default for value types)
Me / MyBasethis / base
&+ for strings
AndAlso / OrElse&& / ||
And / Or& / |
Not!
<>!=
Is / IsNot== / != on references
Mod / \% / integer /
Sub / Functionvoid / typed method
ByVal / ByRef(default) / ref
Optional p As Integer = 0int p = 0
ParamArrayparams
Sharedstatic
Friendinternal
MustInherit / MustOverrideabstract
NotInheritable / NotOverridablesealed
Overridable / Overridesvirtual / override
Shadowsnew (member hiding)
Inherits / Implements: Base / : IInterface
Property X As Integerpublic int X { get; set; }
Select Caseswitch
For i = 0 To 4for (i = 0; i <= 4; i++)
Dim a(4) As Integerint[] a = new int[5];
List(Of T)List<T>
Function(x) x.Namex => x.Name
CType(o, Student)(Student)o
TryCast(o, Student)o as Student
Try/Catch/Finallysame
Catch ex As E When condcatch (E ex) when (cond)
Usingusing
AddressOf MM (method group)
NameOf(x)nameof(x)
' comment// comment
line break;

Three rows deserve care when translating:

  • For ... To is inclusive. For i = 0 To 4 is i <= 4, five iterations.
  • Dim a(4) allocates five elements, not four.
  • CType versus TryCast. CType throws on failure and also performs conversions (CType("5", Integer) works); TryCast returns Nothing and only works on reference types.

Reading before changing

Before you edit anything in an unfamiliar module, be able to answer:

  • What calls this? Use Find all references, not a text search.
  • What does it call? Follow each call one level down.
  • What state does it read that is not a parameter — Session, ViewState, a Module variable, a static cache?
  • What does it write besides its return value — a database row, a file, a global?
  • Is it wired to an event, and does anything else handle the same event?
  • Is there a test? If not, what manual steps prove it works today?

If you cannot answer the last one, establish it before changing anything. A change you cannot verify is a change you cannot defend.

When something looks wrong, use the version history before deleting it. A strange-looking condition is often a deliberate fix for a real incident, and the commit message usually says so.

Errors you will hit

What you seeCauseWhat to do
A handler that never runsMissing Handles clause, or the control was renamedCheck the Handles and the designer file
A control the code-behind cannot seeThe .designer.vb is out of syncOpen the page in the designer to regenerate it
Two handlers for one buttonHandles plus an AddHandler somewhere elseSearch for AddHandler
Search finds nothing you expectSearched the wrong scopeSet Find in Files to Entire Solution
A method that seems unusedCalled by name through late bindingSearch the whole solution for the string

Late binding defeats Find All References. With Option Strict Off, a method can be invoked by a name built at run time, so the only reliable search is a plain text search for the name.

Common mistakes

  • Reading the reported file first and never building a map of the application
  • Searching for a control name and concluding nothing handles it, when AddHandler was used
  • Editing a .designer.vb file by hand
  • Missing that Page_Load runs before the click handler on every postback
  • Assuming a call with no receiver is local, when it is a Module member
  • Translating For i = 0 To n as i < n
  • Using CType where TryCast was meant, turning a null check into an exception
  • Deleting odd-looking code before reading its history
  • Fixing unrelated things you noticed, in the same change

Practice

Pick one screen in an existing VB.NET application. Without editing anything, produce a written trace: the markup control, the handler and how it is wired, every method called down to the SQL statement, and every piece of state read that is not a parameter. Note whether Page_Load guards with IsPostBack.

Then take a C# method of about twenty lines and translate it to VB.NET by hand, without a converter tool. Compile it. The errors you get are the parts of the mapping you have not internalised yet — most people discover the inclusive For ... To here.

Finally, run the AI drill from the course: ask an assistant to explain an unfamiliar legacy procedure, then verify every claim it makes against the code. Note anything it stated confidently that was wrong. That verification habit is the point of the exercise, not the explanation.

You can now

  • Survey an unfamiliar VB.NET application in a defined order
  • Use Find in Files across the whole solution to map data access and error handling
  • Trace any screen from a control back to the database
  • Translate between VB.NET and C# without a converter
  • Say what to read before changing anything

Review questions

  1. Why can searching for a control's name fail to find its event handler?
  2. What breaks when you rename a control field that has Handles clauses pointing at it?
  3. Why does a missing IsPostBack check make a search filter appear to do nothing?
  4. What is the difference between CType and TryCast?

Next: Maintenance project