VB6 anti-pattern
VB6 global module state
Public variables in .bas modules, implicit default form instances and hidden controls used as variables, so behavior depends on what any form did earlier in the session.
- FAMILY
- VB6
- CUTOVER RISK
- High
- Hides business logic or coupling that a rewrite will drop unless it is extracted explicitly.
- MODERN MAPPING
- Explicit request context and injected services
- GENERATED ARTIFACT
- state-cells.json
Before and after
legacy source → modern mapping
Legacy: exhibits the pattern
1' modGlobals.bas2Public gCurrentBranch As String3Public gUserLevel As Integer4Public gLastQuoteNo As Long56' frmQuote.frm7Private Sub cmdRate_Click()8 If gUserLevel < 3 And CCur(txtDiscount.Text) > 5 Then9 MsgBox "Supervisor approval required"10 Exit Sub11 End If12 gLastQuoteNo = gLastQuoteNo + 113 lblPremium.Caption = Format(CalcPremium( _14 gCurrentBranch, CCur(txtSumInsured.Text), _15 CCur(txtDiscount.Text)), "#,##0.00")16 ' implicit load of frmSummary default instance17 frmSummary.txtHiddenPremium.Text = lblPremium.Caption18End Sub
Modern: Explicit request context and injected services
1public sealed record QuoteContext(2 string Branch, // was gCurrentBranch3 int AuthorityLevel); // was gUserLevel45public async Task<Quote> RateAsync(6 QuoteContext ctx, QuoteInput input)7{8 if (ctx.AuthorityLevel < 3 && input.DiscountPct > m)9 throw new ApprovalRequiredException("supervisor");1011 long quoteNo = await _sequences12 .NextAsync("quote"); // was gLastQuoteNo + 113 decimal premium = _rating.Calculate(14 ctx.Branch, input.SumInsured, input.DiscountPct);15 return new Quote(quoteNo, premium);16}
Why it is dangerous in modernization
VB6 desktop applications were single-user processes, and global state was cheap: a Public gUserLevel As Integer in a module is visible to every form and class. Forms add more state. Referencing frmSummary.txtTotal anywhere silently loads the default instance of frmSummary, running its Form_Load, and invisible text boxes on forms are routinely used as variables to pass values between screens. Business rules read these globals and hidden controls, so a premium calculation can depend on which branch the user picked on a login screen an hour ago. Moving that logic to a multi-user web service without making the state explicit causes the worst kind of defect: one user's branch, discount authority or running counter leaks into another user's request.
How PYD ENTERPRISES detects it
The VB6 parser builds a symbol table across all modules, forms and classes in the .vbp project, including the controls declared in .frm files. Every Public and Global module variable, every Static local, and every control property used as data storage is a state cell. Dataflow analysis records which procedures write each cell and which read it, and the call graph gives the event order that can lead from a write to a read. Implicit form loads are detected wherever a form's members are referenced without an explicit instance. Rules extracted from procedures that read global state get those cells listed as inputs.
Maps to: Explicit request context and injected services
Per-user values such as branch and authority level become fields of a request or session context passed explicitly to services, typically from the authenticated identity. Shared reference data such as rate tables becomes an injected, read-only service with its own cache policy. Counters and sequence numbers move to the database. Hidden controls disappear: whatever they carried becomes a property of a request or a resource.
Generated specification fragment
1{2 "project": "QuoteDesk.vbp",3 "stateCells": [4 { "cell": "gCurrentBranch", "kind": "module public",5 "writers": ["frmLogin.cmdOK_Click", "frmBranch.lstBranch_Click"],6 "readers": ["frmQuote.cmdRate_Click", "modRating.CalcPremium"],7 "scope": "per user session" },8 { "cell": "gLastQuoteNo", "kind": "module public",9 "writers": ["frmQuote.cmdRate_Click"],10 "scope": "shared counter, move to database sequence" },11 { "cell": "frmSummary.txtHiddenPremium.Text",12 "kind": "hidden control",13 "readers": ["frmSummary.cmdBind_Click"],14 "scope": "pass as request property" }15 ]16}
Every generated element links back to the source file and line range shown in the legacy pane. Review and sign off before building against it.
Why a counter in a module is dangerous on a server
gLastQuoteNo = gLastQuoteNo + 1 produces unique numbers only because each user runs a separate copy of the program, and the starting value is loaded from the database at login. Put the same logic in a web service and every request shares one variable: two concurrent quotes can receive the same number, and a restart resets the sequence. The engine classifies each global by how it is written (per-session value, shared counter, cache of reference data) so the right replacement is chosen, not just a mechanical move to a static field.
Hidden controls and implicit loads
Assigning frmSummary.txtHiddenPremium.Text loads frmSummary if it is not loaded yet, which runs its Form_Load event, which in many applications queries the database and sets other globals. A single line can therefore trigger a chain of side effects. PYD ENTERPRISES marks each implicit load in the call graph and includes the triggered Form_Load in the slice of any rule that depends on it, so reviewers can see the whole chain behind a value.
Money in VB6 types
The example uses CCur for monetary values, which is right: Currency is a scaled 64-bit integer with four decimal places. Many VB6 applications use Double or Single instead, sometimes in the same calculation, and a rate table in Double carries binary rounding error into every premium. The engine reports the numeric type of every variable on a monetary path, which tells you where the new decimal-based service will legitimately differ in the last cent and where it should match exactly.
Related patterns
Usually found in the same codebase.
- VB6Critical
VB6 On Error Resume Next
Maps to Explicit exceptions and result types
- .NETHigh
WebForms ViewState and code-behind logic
Maps to SPA front end over a stateless API that rereads authoritative data
- 4GLMedium
Informix 4GL screen logic
Maps to Request schema plus server-side validation and correction rules
- CICSCritical
CICS pseudo-conversational state
Maps to Stateless REST resource with explicit versioning
Find every VB6 global module state in your codebase.
Run PYD ENTERPRISES in your private VPC and get each occurrence with file, line range, modern mapping and equivalence fixtures, ready for your architects to review.