Method declares a ByRef or Out parameter
ID |
vbnet.maintainability.no_out_ref_params |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Api Design |
Language |
VB.NET |
Description
Reports a Sub or Function that is not declared Private and that declares a parameter as
ByRef, including the <Out> ByRef form used to express an output-only parameter. Three cases
are not reported: Private members, since the argument is about the published surface;
<DllImport> declarations, where the platform signature dictates the parameter passing; and
methods whose name begins with Try, since the TryParse-style pattern - a Boolean result
plus one ByRef output - is the established convention for an operation that is allowed to fail
without throwing. An Overrides member is not reported either, for the same reason as
<DllImport>: the signature is fixed by the base class, so the ByRef cannot be removed here
without breaking the override. The cost is that a ByRef first introduced by an overridable
member is only reported on the base declaration, not on the overrides that inherit it.
Rationale
A ByRef parameter makes the caller’s variable part of the method’s contract, and everything
that follows from that is awkward. The call site cannot pass an expression, a property, a
constant or the result of another call, so callers are forced to declare a local for the sole
purpose of receiving the value; the call stops composing, and code that would read as one
expression becomes three statements. It also makes the order of effects observable: the method
may write the parameter before it fails, so a caller has to know whether a thrown exception
leaves the variable updated, half-updated or untouched, and nothing in the signature answers
that. For a Function, a ByRef parameter means the operation has two results with no stated
relationship - which one is authoritative when they disagree, and is the ByRef value even
meaningful when the return value indicates failure - and that question lands on every caller.
ByRef is also the harder shape for consumers outside the language: dynamic callers, most
interop layers and any binding over the API have to model an in-out slot rather than a value.
A method that returns what it computed, or a small type that carries the several values
together, has none of these properties and is understandable from the signature alone.
The following code illustrates the pattern detected by this rule:
Public Class Calculator
' FLAGGED: Method declares a ByRef or Out parameter
Public Sub Swap(ByRef first As Decimal, ByRef second As Decimal)
Dim temp = first
first = second
second = temp
End Sub
Remediation
Return the value instead of writing it through a parameter. Where an operation produces several
values, return a type that holds them together - a small Structure, a class, or a tuple - and
name its members for what they mean. Where the operation may fail, either throw, or use the
Try pattern deliberately: name the method TryX, return Boolean, and leave the single
ByRef output as its only out-parameter.
' Before: the caller must declare a local, and cannot pass an expression
Public Function Apply(amount As Decimal, ByRef remainder As Decimal) As Decimal
' After
Public Structure SplitResult
Public ReadOnly Whole As Decimal
Public ReadOnly Remainder As Decimal
End Structure
Public Function Apply(amount As Decimal) As SplitResult