Programming in Visual Basic

Chapter 3
Procedures, Modularity, and Error Handling

Post Graduate Program in Computer Science Engineering
Programming in Visual Basic   •   Post Graduate

3. Procedures, Modularity, and Error Handling

Introduction and Context

Large Visual Basic applications cannot be developed reliably as one continuous sequence of statements. They require named units of behaviour, clearly defined interfaces, controlled data flow, and systematic responses to abnormal conditions. Procedures provide those units. Modularity organizes them into understandable and maintainable components, while error-handling mechanisms protect the application from failures arising during execution.

In Visual Basic, procedures include `Sub` procedures, functions, properties, event handlers, and constructors. They may receive information through parameters, return values, modify caller-owned variables, or expose controlled access to object state. These capabilities support abstraction: a caller needs to know what a procedure does and how to invoke it, but not necessarily how it performs its task internally.

Error handling is equally important. A program that works only when every file exists, every input is valid, and every external service responds is not a robust program. Professional software anticipates failure, detects it, records useful information, releases resources, and either recovers or communicates the problem appropriately. This chapter develops these ideas through Visual Basic syntax, design principles, examples, and an applied case.

Learning Objectives

After completing this chapter, the learner should be able to:

  • distinguish among subroutines, functions, properties, constructors, and event procedures;
  • design procedures using optional parameters, parameter arrays, `ByVal`, and `ByRef` arguments;
  • apply method overloading, modules, and extension methods appropriately;
  • explain recursion and identify its termination and resource requirements;
  • structure exception handling with `Try...Catch...Finally` and create custom exceptions;
  • use assertions, logging, and meaningful exception messages;
  • manage files, database connections, and other disposable resources with `Using` blocks;
  • evaluate modular designs for cohesion, coupling, maintainability, and reliability.

Procedures as Units of Behaviour

A procedure is a named block of executable code. It has a declaration, an implementation, and an invocation interface. A `Sub` performs an action without returning a value:

```vb

Public Sub DisplayMessage(ByVal message As String)

Console.WriteLine(message)

End Sub

```

A function returns a value and is normally used when the operation represents a computation or query:

```vb

Public Function CalculateTax(ByVal amount As Decimal,

ByVal rate As Decimal) As Decimal

Return amount * rate

End Function

```

The return type is part of the function declaration. A function should generally avoid surprising side effects. For example, a function named `CalculateTax` should not silently update a database or alter unrelated global state. Separating commands from queries makes programs easier to understand and test.

A procedure may be marked `Public`, `Private`, `Protected`, or `Friend`. Accessibility expresses the intended boundary of a component. Private implementation procedures should remain private when callers have no reason to depend on them. Restricting visibility reduces coupling and preserves the freedom to change internal implementation.

Parameters and Interfaces

Parameters are the formal inputs declared by a procedure. Arguments are the actual values supplied at invocation. Their types should be explicit because strong typing detects many errors before execution.

By default, Visual Basic passes arguments `ByVal`. The procedure receives a copy of the reference or value supplied by the caller. For value types such as `Integer`, changing the parameter does not change the caller's variable:

```vb

Sub Increment(ByVal value As Integer)

value += 1

End Sub

```

`ByRef` passes a variable by reference, allowing the procedure to replace the caller's value:

```vb

Sub Increment(ByRef value As Integer)

value += 1

End Sub

```

`ByRef` can be useful when a procedure must return more than one result, but it introduces a side effect in the caller's scope. It should therefore be used deliberately. A common alternative is to return a value object or a tuple. For reference types, `ByVal` prevents replacement of the caller's reference, but the object itself may still be mutated. This distinction is essential: passing an object `ByVal` does not make the object immutable.

Optional Arguments and Parameter Arrays

An optional parameter has a default value and may be omitted by the caller:

```vb

Public Function FormatName(ByVal firstName As String,

ByVal lastName As String,

Optional ByVal title As String = "") As String

If title = "" Then

Return $"{firstName} {lastName}"

End If

Return $"{title} {firstName} {lastName}"

End Function

```

Optional parameters should be used when the omitted value represents a sensible default. Excessive optionality can make an interface ambiguous and difficult to document. Named arguments improve clarity when several optional arguments exist.

A `ParamArray` permits a variable number of arguments. It must be the final parameter and is received as an array:

```vb

Public Function SumAll(ParamArray values() As Integer) As Integer

Dim total As Integer = 0

For Each value In values

total += value

Next

Return total

End Function

```

Parameter arrays are convenient for aggregation and formatting utilities, but they weaken compile-time specification of the number of arguments. They should not replace a strongly typed collection when the operation is conceptually collection-based.

Properties

Properties expose object state through controlled `Get` and `Set` accessors. They are not simply public fields; accessors can validate, calculate, or protect internal representation.

```vb

Public Class Account

Private _balance As Decimal

Public ReadOnly Property Balance As Decimal

Get

Return _balance

End Get

End Property

Public Sub Deposit(ByVal amount As Decimal)

If amount <= 0D Then Throw New ArgumentOutOfRangeException(NameOf(amount))

_balance += amount

End Sub

End Class

```

A property should represent a logical attribute and should normally execute quickly. Expensive operations, network calls, or database queries hidden behind ordinary property access make code surprising. Use an explicit method when an operation performs significant work.

Modularity and Reusable Design

Modularity divides a system into units with defined responsibilities. A module may be a class, structure, interface, namespace, or a Visual Basic `Module`. A class supports object instances and state; a standard module provides shared members without requiring an object instance.

```vb

Public Module MathUtilities

Public Function Clamp(value As Decimal,

minimum As Decimal,

maximum As Decimal) As Decimal

If minimum > maximum Then Throw New ArgumentException("Invalid range.")

Return Math.Max(minimum, Math.Min(maximum, value))

End Function

End Module

```

A module is suitable for stateless utility functions, constants, and application-wide procedures. It should not become a dumping ground for unrelated global state. Classes are preferable when behaviour belongs to an identifiable entity or when dependencies should be injected and tested independently.

Two useful design measures are cohesion and coupling. Cohesion describes how closely the responsibilities within one component belong together. High cohesion is desirable. Coupling describes how strongly components depend on one another. Low coupling is generally preferable because it limits the consequences of change. A well-designed procedure has a focused purpose, a small and meaningful interface, and minimal hidden effects.

Overloading

Method overloading provides several procedures with the same name but different parameter signatures:

```vb

Public Overloads Function ParseAmount(text As String) As Decimal

Return Decimal.Parse(text)

End Function

Public Overloads Function ParseAmount(text As String,

culture As Globalization.CultureInfo) As Decimal

Return Decimal.Parse(text, culture)

End Function

```

The compiler selects the best matching signature. Overloading is appropriate when operations are conceptually identical but require different input forms. It becomes harmful when overloads have inconsistent semantics or differ only in obscure ways. Optional parameters and overloads can sometimes solve the same problem; a design should choose the clearer interface rather than accumulate alternatives.

Extension Methods

An extension method adds callable behaviour to an existing type without modifying its source code. It is declared in a module, uses the `Extension` attribute, and places the extended type as its first parameter:

```vb

Imports System.Runtime.CompilerServices

Public Module StringExtensions

<Extension>

Public Function IsNullOrBlank(value As String) As Boolean

Return String.IsNullOrWhiteSpace(value)

End Function

End Module

```

After the namespace is imported, the method may be called as `text.IsNullOrBlank()`. Extension methods improve readability for broadly useful, stateless operations. They should not be used to disguise complex business logic or create competing methods that make resolution confusing.

Recursion and Control of Repeated Work

Recursion occurs when a procedure calls itself. Every recursive algorithm requires a base case and a progression toward that case. Without both, the procedure may continue indefinitely until the call stack is exhausted.

```vb

Public Function Factorial(n As Integer) As Long

If n < 0 Then Throw New ArgumentOutOfRangeException(NameOf(n))

If n = 0 Then Return 1

Return n * Factorial(n - 1)

End Function

```

The recursive form is natural for tree traversal and divide-and-conquer algorithms. However, recursion consumes stack space for each call, and a deep input may cause a stack overflow. An iterative algorithm is often preferable for simple linear repetition. A recursive solution should therefore be evaluated for readability, maximum depth, performance, and the possibility of repeated subproblems. Memoization or dynamic programming can avoid recalculating identical recursive states.

Exceptions as a Failure Model

An exception represents an abnormal condition that interrupts ordinary control flow. Examples include invalid arguments, missing files, unavailable services, arithmetic errors, and violated application rules. Visual Basic provides structured exception handling through `Try`, `Catch`, `Finally`, and `Throw`.

```vb

Try

Dim text = File.ReadAllText(path)

Process(text)

Catch ex As FileNotFoundException

logger.LogError(ex, "Input file was not found: " & path)

ShowMessage("The requested file is unavailable.")

Catch ex As IOException

logger.LogError(ex, "Input/output failure.")

ShowMessage("The file could not be read.")

Finally

UpdateStatus("Operation finished")

End Try

```

`Catch` blocks should be ordered from specific to general. Catching `Exception` may be appropriate at an application boundary for logging and controlled termination, but it is usually poor practice inside a low-level method if the method cannot recover. A caught exception should be handled, translated, or rethrown; silently ignoring it conceals defects.

Use `Throw` to preserve the original exception and stack trace when rethrowing. `Throw ex` resets important diagnostic context in many situations. When translating an implementation exception into a domain-level exception, preserve the original as the inner exception:

```vb

Throw New OrderProcessingException("Unable to save the order.", ex)

```

Exceptions should not normally be used for routine branching, such as testing whether a string is empty. Predictable conditions are better represented by validation and ordinary control flow.

Custom Exceptions

A custom exception expresses a meaningful domain failure:

```vb

Public Class InsufficientCreditException

Inherits Exception

Public ReadOnly Property Required As Decimal

Public ReadOnly Property Available As Decimal

Public Sub New(required As Decimal, available As Decimal)

MyBase.New($"Required credit: {required}; available credit: {available}.")

Me.Required = required

Me.Available = available

End Sub

End Class

```

Custom exceptions are valuable when callers need to distinguish a business condition from infrastructure failure. They should have descriptive names, useful data, and appropriate constructors. They should not expose confidential information through messages or logs.

Assertions and Validation

An assertion states a condition that the programmer believes must be true at a particular point. In Visual Basic, `Debug.Assert` is useful for detecting programming errors during development:

```vb

Debug.Assert(total >= 0D)

```

Assertions document invariants, but they are not a substitute for validating external input. Assertions may be disabled in production configurations, whereas user input, files, and service responses must always be checked. Public procedures should validate their arguments and throw suitable exceptions such as `ArgumentNullException` or `ArgumentOutOfRangeException`.

Logging

Logging creates a durable record of significant execution events. An effective log entry generally includes time, severity, operation, correlation or request identifier, and relevant diagnostic context. Common severity levels include trace, debug, information, warning, error, and critical.

Logs should support diagnosis rather than reproduce every line of execution. Passwords, access tokens, payment data, and other sensitive information must not be recorded. In distributed applications, structured logging is preferable to unstructured concatenated text because fields can be searched and aggregated. Logging should occur at meaningful boundaries: when an operation starts or completes, when a recoverable condition occurs, and when an exception reaches a boundary where it is handled or terminated.

Resource Management with Using Blocks

Files, streams, database connections, and similar objects often implement `IDisposable`. Such objects hold resources that are not reliably released by garbage collection alone. A `Using` block ensures `Dispose` is called even when an exception occurs:

```vb

Using reader As New StreamReader(path)

Dim content As String = reader.ReadToEnd()

Process(content)

End Using

```

The block is conceptually transformed into a `Try...Finally` structure. Multiple resources may be nested or declared in one statement, with disposal occurring in reverse order. A `Using` declaration is also available in modern Visual Basic versions:

```vb

Using connection As New SqlConnection(connectionString)

connection.Open()

ExecuteQuery(connection)

End Using

```

Ownership must be clear. A method that creates a disposable object should usually dispose it, while a method that merely receives a caller-owned object should not dispose it unless the contract explicitly transfers ownership. Resource management includes timely disposal, bounded lifetimes, cancellation of lengthy operations, and avoiding connection or file-handle leaks.

An Integrated Procedure Design

Consider a service that imports customer orders from a text file. A robust design might contain:

  • a public orchestration method that coordinates the workflow;
  • a private parser that validates one record;
  • a domain object representing an order;
  • a custom exception for invalid business data;
  • a logger at the application boundary;
  • a `Using` block for the file stream.

The orchestration method should not contain every parsing detail. It should establish the workflow, decide which failures can be recovered, and report the final outcome. The parser should have a narrow responsibility and should not display user-interface messages. This separation permits independent testing and allows the same parser to be used by a scheduled job, web service, or desktop application.

Illustrative Case Study: Reliable Invoice Import

Background

A fictional manufacturing company, Northbridge Components, receives daily invoice files from suppliers. Its initial Visual Basic application read the entire file in one procedure, inserted records into a database, and displayed a generic “Import failed” message when an error occurred.

Situation and Problem

The application occasionally left database connections open, accepted negative quantities, and provided no indication of which record was invalid. Operators sometimes retried a complete file, producing duplicate invoices. The technical problem was therefore also an operational and governance problem.

Analysis

The original procedure had low cohesion and high coupling. File access, parsing, validation, database operations, user messaging, and error handling were mixed together. It used shared mutable variables and caught all exceptions without preserving their details. Because resources were manually closed along only the successful path, failures could leak connections.

Approach and Solution

The redesigned solution divided responsibilities into an `InvoiceImporter`, `InvoiceParser`, repository, and logger. The parser returned a validated invoice or raised `InvalidInvoiceException` containing the supplier identifier and line number. The importer used a `Using` block for the file reader and database connection. Specific exceptions were handled close to the operation that could recover from them; unexpected exceptions were logged with a correlation identifier and allowed to reach the application boundary.

Assertions checked internal assumptions such as nonnegative calculated totals during testing, while public validation handled all external data. A unique invoice key and transactional database operation prevented duplicate insertion when an import was retried. The importer accepted an optional cancellation token and used a parameterized repository method rather than constructing SQL text through string concatenation.

Outcome

The redesigned application produced precise operator messages, released resources on successful and failed paths, and permitted invalid records to be isolated for correction. The modular structure also made parser tests independent of the database.

Learning Points

The case demonstrates that modularity is not merely an aesthetic preference. It supports testing, controlled recovery, observability, security, and operational continuity. Exception handling is most effective when paired with clear procedure boundaries and explicit resource ownership.

Common Errors, Risks, and Professional Considerations

Frequent mistakes include using `ByRef` without documenting side effects, exposing mutable fields, placing all functionality in a standard module, catching and ignoring exceptions, logging confidential data, and disposing an object that belongs to the caller. Another error is using a property for a slow or failure-prone operation, thereby hiding important control flow.

Exception messages should be useful to developers and safe for users. Internal paths, SQL statements, credentials, and stack traces should not be displayed in public interfaces. Authorization and privacy requirements also apply to logs and diagnostic files. In regulated systems, logs may require retention controls, access restrictions, tamper evidence, and documented deletion policies.

A procedure should be short enough to understand as one unit, but length alone is not a reliable quality measure. More important are responsibility, naming, error semantics, testability, and dependency boundaries. Automated testing should include valid input, boundary values, invalid input, exceptions, repeated calls, and resource-release paths. Static analysis and code review can identify unreachable handlers, excessive complexity, and unsafe exception practices.

Current Development Practices

Modern Visual Basic applications increasingly use dependency injection, asynchronous procedures, cancellation, structured logging, and centralized observability. Asynchronous methods must still apply the same principles: validate inputs, propagate meaningful exceptions, avoid blocking calls, and dispose asynchronous resources appropriately. Extension methods and reusable libraries can improve consistency, but shared abstractions should be introduced only when their contracts are stable.

AI-assisted programming tools can generate procedure templates and suggest exception handlers, but generated code requires review. An automatically inserted broad `Catch` block may conceal failures, and generated logs may expose sensitive values. Human developers remain responsible for contracts, security, resource ownership, and the consequences of recovery decisions.

Best Practices

1. Give every procedure one clear primary responsibility.

2. Prefer explicit, strongly typed interfaces and meaningful names.

3. Use `ByVal` by default; use `ByRef` only when caller mutation is intentional.

4. Choose overloads and optional parameters for clarity, not convenience alone.

5. Keep modules cohesive and minimize global mutable state.

6. Validate external input and use assertions for internal invariants.

7. Catch exceptions only where recovery, translation, or responsible logging is possible.

8. Preserve inner exceptions and stack traces.

9. Use `Using` for disposable resources and define ownership clearly.

10. Exclude secrets and personal data from logs.

11. Test exceptional paths as carefully as successful paths.

Chapter Summary

Procedures provide named, testable units of behaviour in Visual Basic. Functions return values, subroutines perform actions, and properties expose controlled state. Parameters define procedure contracts; `ByVal`, `ByRef`, optional arguments, and `ParamArray` affect how information crosses those contracts. Overloading and extension methods support expressive reuse when applied carefully.

Modularity depends on high cohesion, low coupling, appropriate accessibility, and clear ownership. Recursion requires a base case and must be evaluated against stack and performance constraints. Structured exceptions separate normal execution from abnormal conditions, while custom exceptions express domain-specific failures. Assertions expose violated internal assumptions, logging supports diagnosis, and `Using` blocks ensure timely disposal of resources. Together these practices produce Visual Basic systems that are easier to test, operate, secure, and maintain.

Key Terms

Procedure: A named block of executable code with a defined interface.

Subroutine: A procedure that performs an action without returning a value.

Function: A procedure that returns a value.

Parameter: A formal input declared by a procedure.

Argument: An actual value supplied when a procedure is called.

ByVal: Parameter passing that prevents replacement of the caller’s variable.

ByRef: Parameter passing that permits a procedure to modify the caller’s variable.

Optional parameter: A parameter with a default value that may be omitted.

ParamArray: A final parameter that accepts a variable number of arguments.

Property: A controlled access mechanism for object state.

Module: A shared Visual Basic container for related members.

Overloading: Defining procedures with the same name but different signatures.

Extension method: A method that adds callable behaviour to an existing type.

Recursion: A technique in which a procedure calls itself.

Exception: An abnormal condition that interrupts normal execution.

Custom exception: An application-defined exception representing a meaningful failure.

Assertion: A statement of an expected internal condition.

Logging: Recording execution and diagnostic information for later analysis.

IDisposable: An interface whose `Dispose` method releases owned resources.

Using block: A construct that guarantees disposal of a disposable object.

Review Questions

Short Answer Questions

1. What is the difference between a `Sub` procedure and a function?

2. Why is `ByVal` generally preferred as the default parameter-passing mode?

3. What conditions are required for a safe recursive procedure?

4. Why should specific exception handlers precede a general `Catch` block?

5. What responsibility does a `Using` block provide?

Long Answer Questions

1. Explain how cohesion, coupling, accessibility, and procedure boundaries contribute to modular Visual Basic design.

2. Compare optional parameters, overloaded methods, and `ParamArray` arguments, including appropriate use cases for each.

3. Describe structured exception handling, including validation, custom exceptions, rethrowing, logging, and application boundaries.

4. Explain the difference between assertions and validation, and discuss why assertions cannot replace validation of external input.

5. Discuss resource ownership and show how `Using` blocks protect files, streams, and database connections during exceptions.

Analytical / Application Questions

1. A procedure receives a `List(Of Customer)` `ByVal` and adds a customer to the list. Explain what the caller can observe and why this does not contradict `ByVal` semantics.

2. Design a modular invoice-import workflow that identifies invalid records, avoids duplicate insertion, logs failures safely, and releases all resources.

3. A developer catches `Exception`, writes only “Error occurred” to a log, and returns an empty result. Analyze the risks and propose a more reliable design.

22. Multiple Choice Questions with Answers

Question 1

Which parameter-passing mode allows a procedure to replace the caller’s variable directly?

A. ByVal

B. ByRef

C. Optional

D. ParamArray

Correct Answer: B. ByRef

Question 2

Which design best represents a suitable use of a property?

A. A property that silently performs a lengthy network request

B. A property that exposes validated, controlled object state

C. A property that accepts an unlimited number of arguments

D. A property that catches every exception and ignores it

Correct Answer: B. A property that exposes validated, controlled object state

Question 3

What is required for a recursive procedure to terminate safely?

A. A shared mutable variable only

B. A `ParamArray` parameter

C. A base case and progress toward it

D. A general `Catch` block

Correct Answer: C. A base case and progress toward it

Question 4

Why should exception handlers generally be ordered from specific to general?

A. The compiler requires all handlers to be identical

B. A general handler could otherwise intercept exceptions intended for specific handlers

C. Specific exceptions cannot be logged

D. General exceptions always indicate recoverable errors

Correct Answer: B. A general handler could otherwise intercept exceptions intended for specific handlers

Question 5

Which statement about `ByVal` and reference types is correct?

A. ByVal makes the referenced object immutable

B. ByVal prevents the procedure from replacing the caller’s reference, but the object may still be mutated

C. ByVal always copies the entire object

D. ByVal is equivalent to ByRef for all types

Correct Answer: B. ByVal prevents the procedure from replacing the caller’s reference, but the object may still be mutated

Question 6

What is the principal purpose of a `Using` block?

A. To overload a method automatically

B. To guarantee disposal of a disposable resource

C. To disable all exceptions

D. To convert a Sub into a Function

Correct Answer: B. To guarantee disposal of a disposable resource

Question 7

Which practice best preserves diagnostic information when rethrowing the current exception?

A. Throw ex

B. Throw

C. Return Nothing

D. Catch without logging or rethrowing

Correct Answer: B. Throw

Question 8

When is a custom exception most appropriate?

A. For every ordinary conditional branch

B. When callers need to distinguish a meaningful domain failure

C. Whenever a loop reaches its final iteration

D. To replace all argument validation

Correct Answer: B. When callers need to distinguish a meaningful domain failure

Question 9

What is the main purpose of `Debug.Assert`?

A. To validate untrusted user input in production

B. To document and detect violated internal assumptions during development

C. To dispose database connections

D. To provide variable-length arguments

Correct Answer: B. To document and detect violated internal assumptions during development

Question 10

Which characteristic describes a strong modular design?

A. High coupling and broad global state

B. Low cohesion and hidden side effects

C. High cohesion, limited interfaces, and low unnecessary coupling

D. One procedure containing the entire application workflow

Correct Answer: C. High cohesion, limited interfaces, and low unnecessary coupling

Programming in Visual Basic
Chapter 3 of 14