Empty implementations often hide unfinished logic
A constructor that only calls inherited adds no value and can be removed
Deeply nested branches increase cognitive load and defect rates
Methods exceeding 10 decision points are hard to test and maintain reliably
Three levels of nested loops create O(n^3) complexity and are difficult to reason about
Deprecated units can block upgrades and increase maintenance cost
`with` obscures binding and can introduce subtle bugs
`AnsiString` often introduces encoding bugs in modern Unicode codebases
`Variant` weakens type safety and can hide conversion/runtime errors
ShortString is a legacy fixed-length type that limits interoperability with modern Unicode APIs
Real48 is a legacy 6-byte floating-point type with limited precision and no hardware support
Raw memory management is error-prone and bypasses Delphi's type safety
Untyped pointers bypass compile-time checks and are a frequent source of memory errors
Re-entering the message loop causes reentrancy bugs and unpredictable UI state
Sleep blocks the calling thread, freezing the UI or wasting thread-pool resources
The Tag property is untyped and fragile, leading to hard-to-trace bugs when used for logic
Halt and RunError terminate the process immediately without cleanup, risking data loss
Mutating list items in a loop without BeginUpdate causes excessive UI repaints and flicker
Storing a component reference without FreeNotification risks dangling pointers when the referenced component is destroyed
Version conditionals targeting very old Delphi versions add noise without practical benefit
Untyped var/const parameters bypass compile-time type checking and can cause subtle corruption
Inline assembler is non-portable across CPU architectures and hard to maintain
Using LowerCase/UpperCase for comparison allocates a temporary string unnecessarily
Legacy PChar functions are error-prone and replaced by modern string operations
Creating UI controls in constructors bypasses the form designer and complicates layout
Deprecated components like TWebBrowser and TDDEServerConv lack modern support
Overgrown classes correlate with poor cohesion and regressions
Shared mutable globals create hidden coupling and race risks
Large event handlers couple business logic to the UI, hindering testability and reuse
Classes with more than 30 methods likely violate the single responsibility principle
Deep hierarchies via TCustom-prefixed ancestors increase coupling and fragility
A method heavily accessing another object's members likely belongs on that object
Public mutable fields bypass invariants and validation
Long methods increase defect risk and reduce reviewability
Signals technical debt, but not always immediate risk
Very large units are harder to understand and change safely
High-arity signatures are difficult to use correctly and evolve
Unstructured jumps hinder readability and control-flow reasoning
Unnamed literals obscure intent and make policy changes harder
Commented-out code clutters the source and is better tracked by version control
Methods with many local variables are hard to follow and often need decomposition
Boolean expressions with many operators are hard to read and error-prone to modify
Repeated literals make changes error-prone and increase the risk of inconsistency
A uses clause with many units indicates high coupling and makes the unit fragile to changes
Unreachable statements waste reader attention and may indicate a logic error
Many return points make control flow harder to reason about and test
Nested procedures reduce readability and cannot be unit-tested independently
Methods where over half the lines are comments often contain stale documentation
Hardcoded Windows paths break portability and complicate deployment
Unused parameters add noise and may indicate incomplete implementations
Units with an implementation section but no code are dead weight in the project
Listing the same unit twice adds noise and can mask refactoring mistakes
Files with more than 50 methods are hard to navigate and likely have too many responsibilities
Repeated string concatenation in loops causes O(n^2) memory allocations
FindComponent and FieldByName perform linear searches; calling them inside loops is O(n*m)
Repeated property getter calls may execute logic each time; caching avoids redundant work
Creating and destroying objects per iteration wastes allocations and increases GC pressure
Large fixed-size arrays on the stack risk stack overflow, especially in recursive methods
Calling Length or Count in every loop iteration re-evaluates the expression unnecessarily
Format with a single %s substitution adds overhead with no formatting benefit over concatenation
Many concatenation operators on one line cause repeated memory allocations
Untyped TList requires manual casting and lacks ownership semantics, increasing error risk
Sorting inside a loop multiplies the cost dramatically; sort once after all mutations
Many try blocks in one method suggest exceptions used as flow control, which is slow
Assigning value types to Variant inside loops causes repeated heap allocations
Converting a value to a string and back wastes CPU cycles with no net effect
Passing large records or arrays by value copies the entire structure on each call
Calling the same function three or more times wastes cycles when the result is stable
Copy(S, I, 1) allocates a temporary string when direct indexing S[I] is faster
Many log calls in a single method add I/O overhead and clutter log output
Creating or executing TRegEx inside a loop recompiles the pattern on each iteration
Executing SQL in a loop multiplies database round-trips and degrades throughput
Creating TMemoryStream per iteration wastes allocations when one stream can be reused
Swallowing exceptions hides failures and complicates recovery
Nil assignment after free reduces accidental use-after-free patterns
Missing `inherited Destroy` can leak resources and corrupt cleanup
Parsing failures via exceptions create brittle control flow and runtime faults
Object lifetime without guaranteed cleanup frequently leaks resources
Calling Exit in a function without setting Result can return undefined values
The absolute keyword aliases memory locations and is error-prone in modern Delphi
Catching all exceptions without a type makes debugging and recovery much harder
Re-raising the caught exception variable resets the stack trace, losing diagnostic information
Exit inside a finally block silently swallows any pending exception, masking failures
A case statement without an else branch silently ignores unexpected values
Freeing the same object twice causes access violations or heap corruption
Reading a variable before assignment yields unpredictable values and subtle bugs
Integer multiplication or truncating casts can silently overflow, causing wrong results
Unsynchronized access to global state from threads causes data races and corruption
Objects created without a matching Free leak memory and OS handles
Dividing by a variable without a zero guard risks runtime exceptions
Omitting inherited Create skips ancestor initialization and can corrupt object state
Identical branches are almost always copy-paste errors that mask intended logic
Assigning a variable to itself is a no-op and indicates a copy-paste bug
Catching generic Exception hides specific failures and makes debugging harder
Indexing a string without a Length guard risks an index-out-of-range exception at runtime
A case branch with no statements silently ignores matching values and may hide bugs
Chaining member access on nil-returning functions causes access violations
While True or repeat/until False without Break/Exit will hang the application
:= inside an if or while condition is almost certainly a typo for = comparison
Inc/Dec on small integer types can silently overflow without range checking enabled
Calling
Comparing signed and unsigned integers can produce unexpected results with negative values
Entering a critical section without a matching Leave risks deadlocks
Objects created before try/except without cleanup in the except block leak on exception
Embedded secrets are high-impact and immediately exploitable
Concatenation is a common SQL injection precursor
Risky file APIs can bypass safe path handling and validation
Weak randomness is unsafe for tokens, keys, or security decisions
Process execution APIs become high-risk when arguments can be influenced by untrusted input
HTTP, FTP, and Telnet transmit data in cleartext, risking interception
Passing user-controlled variables as format strings to Format() can cause crashes or information disclosure
User-controllable file paths can escape intended directories and access arbitrary files
MD5 and SHA1 are vulnerable to collision attacks and should not be used for security
Hardcoded IPs reduce flexibility and can expose internal network topology
LoadLibrary with a variable path enables DLL hijacking if the search order is exploited
Direct registry access ties code to Windows and can expose sensitive configuration data
Disabling SSL validation exposes connections to man-in-the-middle attacks
Direct typecasts skip runtime type checking and can cause access violations or data corruption
Storing passwords without hashing is a critical vulnerability if the data store is compromised
Building XML by concatenation allows injection of malicious elements
Concatenating user input into LDAP filters enables directory injection attacks
Unsanitized user data in logs can forge entries or inject control characters
Predictable temp file names allow attackers to pre-create symlinks (TOCTOU attack)
Move/CopyMemory without SizeOf can overwrite adjacent memory and enable exploits
Deserializing untrusted streams can instantiate arbitrary classes and execute code
Hardcoded connection strings expose server addresses and credentials in source
DES, RC4, and Blowfish are cryptographically broken and offer inadequate protection
Handlers that process requests without auth checks expose functionality to unauthenticated users
Sending raw exception messages to clients leaks internal paths, types, and stack traces
Launching external commands is command injection the moment any argument is user-influenced
A secret in source ships to every developer machine and every backup; in VB6, = is both assignment and comparison, and either way the literal is the leak
Concatenated SQL plus user input is injection; ADO with assembled WHERE clauses is the most common security hole in legacy VB6
Raw-memory and process APIs (CopyMemory, WriteProcessMemory, GetProcAddress) crash on a wrong length and bypass every safety the language has
Http/ftp/telnet URLs in VB6 source feed WinInet or browser controls in plaintext; localhost is exempt
Kill deletes files, accepts wildcards, and asks nothing; with any user-influenced path it is arbitrary file deletion
Consistent naming improves readability and follows Delphi conventions
Interfaces should follow the I prefix convention for discoverability
Comparing to True/False is redundant and reduces readability
Assigned() is the idiomatic Delphi way to check for nil and works with both objects and interfaces
Extremely long single-line declarations reduce readability and complicate diffs
Consistent UPPER_CASE naming for constants improves scan-ability and convention compliance
A shared prefix on enum values clarifies membership and prevents name collisions
Comparing with '' is less intention-revealing than Length or IsEmpty checks
Names over 40 characters impair readability and complicate code navigation
Mixing camelCase and PascalCase in one class confuses contributors
Unscoped unit names like SysUtils are ambiguous and deprecated in modern Delphi
More than 2 consecutive blank lines waste screen space and reduce code density
Trailing spaces cause noisy diffs and serve no purpose
Mixing tabs and spaces for indentation causes inconsistent rendering across editors
Lines over 120 characters cause horizontal scrolling and are harder to review
Type-encoding prefixes are redundant in a strongly typed language and reduce readability
Without Option Explicit a misspelled variable silently becomes a new Variant instead of a compile error
It swallows every error after it — the VB6 equivalent of an empty except block
Unstructured jumps hide control flow; On Error GoTo is exempt because it is VB6's structured error handling
Variants defer type errors to runtime; Dim a, b As Long types only b and makes a a Variant without any warning
CreateObject and As Object resolve members at runtime, so a typo becomes error 438 in production instead of a compile error
DoEvents re-enters the message loop mid-procedure — the same re-entrancy trap as Application
Obsolete construct removed after VB6; one of the first things a migration has to untangle
An untyped Function or Property Get returns Variant, spreading the typing trap to every call site
An untyped parameter is a Variant and ByRef by default, so the callee can silently reassign the caller's variable
A bare End terminates the process without unload events or cleanup — and in a compiled executable a leftover Stop does the same
+ concatenates strings until an operand is a numeric Variant or Null — then it silently adds, or yields Null
Procedures past ~60 lines accumulate the local state and branching that make legacy VB6 unreviewable; same threshold as the Delphi rule so mixed codebases are judged alike
An empty body is either dead scaffolding or a handler that silently swallows the event it was wired to
Work markers are debt the compiler cannot see; surfacing them keeps the backlog honest
A module past ~800 code lines has become the place where everything lands; splitting it is the first step of any untangling
Unexplained numeric literals; the Const that fixes the finding is also the documentation the number never had
Nesting past four levels hides which condition guards which statement; inverting guards flattens it
A Select Case with no Case Else does nothing for unexpected values — and the unexpected value is usually the bug report
More than five parameters is usually a missing Type or class — and with VB6's default ByRef, every one is a variable the callee might rewrite
While
IIf is a function, not a conditional — both branches run every time, so a guard inside it guards nothing
Without an Exit Sub above the handler label, the successful path runs straight into the error handling — one of the most common real bugs in VB6
Public/Global variables in a
An unhandled error walks up the call chain and terminates the program on the user's screen; long procedures doing real work deserve a handler
DefInt A-Z changes what undeclared names mean by first letter, invisibly and per module
Arrays in this module start at 1 while every other module starts at 0 — an off-by-one waiting at each module boundary
UCase(a) = UCase(b) allocates two strings per comparison and mishandles locale-specific casing
CallByName resolves a member from a string at runtime — no compile check, no rename safety, and user-influenced strings choose which method runs
SendKeys types into whatever window has focus at that instant; it breaks when a dialog steals focus and trips UAC/UIPI on modern Windows
As String * N pads with spaces and truncates silently, so equality checks fail in ways that depend on the data
Dim s$ is the pre-As spelling of types; explicit but only readable by those who know the six-symbol table
Dim x As New re-creates the object the moment it is touched after Set x = Nothing, so lifetime checks lie and teardown code resurrects what it released
A loop on rs
ReDim Preserve copies the whole array every call; growing one element at a time inside a loop is quadratic
A locally opened ADO/DAO recordset or connection that is never closed holds locks and pool connections until garbage collection
A transaction never committed or rolled back in its procedure keeps its locks until the connection dies
Open
An Open without a Close keeps the handle and its lock for the life of the process; the error path is where it usually escapes
Close without a file number closes every open file in the process, including ones another procedure was still writing
App
Removing from a collection while For Each walks it skips elements or raises error 5 mid-loop, whichever the data decides