Writing Procedures

Team-Fly    

 
Visual Basic .NET Unleashed
By Paul Kimmel
Table of Contents
Chapter 5.  Subroutines, Functions, and Structures

Writing Procedures

Basic procedure declarations haven't changed dramatically between VB6 and Visual Basic .NET. Procedures that do not have a return value are still defined with the keyword Sub and procedures that return a value are still defined with the keyword Function.

You will notice a few subtle changes when implementing and using subroutines and functions. The first such change is the use of parentheses for calling subroutines and functions. Always use parentheses when calling a function or a subroutine, but you don't need to add the Call keyword. We will cover additional changes to procedures in the subsections that follow.

Writing Subroutines

A subroutine is defined by typing the keyword Sub followed by a name and parentheses. The bounds of the procedure are constrained by the End Sub statement. Together the Sub Name and End Sub statements define the procedure block, or the extent of a particular sub.

 Sub MySub() End Sub 

Any temporary variables defined in a procedure block have procedure scope. That is, variables with procedure scope are only accessible when the program's execution point is within the subroutine. Except for Static variables, procedurally scoped variables don't maintain their values between successive calls to the procedure.

Tip

VB6 supported adding the Static modifier to a procedure heading if you wanted all variables in that procedure to be static. Visual Basic .NET does not support this modifier in the procedure heading. If you want every variable in a procedure to be static, you must use the Static modifier on each variable.


In recent years , best practices have been identified and innovations have evolved from these best practices. The section "Defining Procedure Arguments" covers further specific implementation details regarding subroutines and functions. For now, let's examine the subroutine from the perspective of constraining procedure complexity. (The discussions in the next two sections, "Best Practices" and "Innovations in Programming," apply equally to subroutines and functions.)

Best Practices

For many years there have been several well-reasoned best practices that describe what constitutes a good procedure. Unfortunately these best practices haven't been adopted universally . With innovations in programming, specifically refactoring, it's possible that we will have a forum and a language for demonstrating and teaching these best practices to new and experienced developers.

The strategies described here can be reduced to several common maxims about software development. I will introduce each maxim in a separate statement followed by my comments about the problem presented by the maxim .

  • Maxim 1: Systems grow exponentially in complexity.

    Advances in software development languages, such as the broad adoption of object-oriented languages, allow us to problem-solve at a much higher level of abstraction. Yet, software demand still outpaces the ability of people to implement increasingly complex solutions.

  • Maxim 2: Programming is the hardest thing people try to do.

    I have heard many disagreements to this assertion, but there are few if any other endeavors that touch every other industry and combine engineering, mathematics, aesthetics, psychology, art, intercommunication skills, and raw creative talent.

  • Maxim 3: Short- term memory is limited to juggling very few similar but related concepts at a given time.

    Short-term memory keeps us from juggling more than a couple of pieces of information at a time. Without strategies for limiting what must be considered and resolved at a single moment, our capacity to solve problems diminishes to the point of overwhelming frustration.

  • Maxim 4: Software development projects have a high failure rate.

    Software projects fail because software is difficult to build. As a group , programmers are still working in environments where few of the necessary roles are fulfilled by trained and experienced professionals, and programmers are doing the bulk of the work with little formal process or design.

  • Maxim 5: The greatest cost of ownership is incurred during the maintenance of a system, after it has been developed and put into initial production.

    It has been estimated that 60% of the cost of ownership of software is incurred after a system has gone into production. Cost of ownership represents a significant risk to any software endeavor and should be considered when choosing an implementation strategy.

  • Maxim 6: Long procedures are bad. Short procedures are good.

    (This maxim focuses on proceduresthe first five maxims were related to software in general.) Long procedures don't take into account limited short-term memory capacity, are likely to be significantly more difficult than short procedures, and are less likely to be reused.

The high incidence of failure in our industry is evidence that the way in which developers as a group are currently building software has shortcomings. Further, it seems to be apparent that we have discovered better ways of building software but haven't widely adopted many of them. The following bulleted list describes best practices to implement Maxim 6.

  • Provide procedure names using whole words (or at a minimum, standardized abbreviations), combining a noun and verb to indicate the behavior of the procedure and what the procedure acts on.

  • Keep procedures short. A predominant number of procedures should be less than five lines of code. If procedures are long, they are less likely to be reusable and more likely to be difficult to understand. (Use the refactoring extract method described in Chapter 8.)

  • Keep parameter lists short. A long parameter list may indicate the absence of a key element in the problem domain. That is, a class is probably missing in the solution domain.

  • Limit the use of temporary variables. Temporary variables make procedures longer and add to the clutter and confusion. Use the refactoring "Replace Temp with Query" introduced in Chapter 3. Optimizing compilers may place simple functions inline, but bear in mind that complexity costs more than the overhead of a function call.

  • Declare variables, if needed, immediately before first use, limiting the number of explanatory comments.

  • Use access specifiers to promote encapsulating implementation details, limiting the number of methods a consumer of classes has to consider at any point during development. (Refer to Chapter 7 for more on encapsulation.)

Employing the best practices described in this list will help keep you out of trouble at the procedural level. There are additional strategies to address other problems related to developing complex systems, which will be introduced in later chapters as appropriate.

Innovations in Programming

Apparently the term best practices wasn't catchy enough. Writers like Grady Booch were talking about best practices as early as the 1990s (Booch, 1994). Recently Kent Beck's Extreme Programming (XP) and William Opdike's Refactoringwhich are catchierare gaining a following. Specifically, refactoring, an aspect of XP, describes programming best practices in the context of all code. Instead of prescribing the things you should do to proactively prevent code from becoming obtuse, refactoring describes curatives that can be employed in a systematic manner to recondition code into a condition closer to an ideal. Possibly the best benefit of refactoring is that it introduces a forum in which code quality can be discussed and taught in a constructive and consistent manner.

As mentioned in the introduction, refactorings are introduced in this book where appropriate.

Writing Functions

All of the guidelines for writing good subroutines apply to functions. The only difference between a function and a subroutine is that a function is designed to return a value. VB6 return values were assigned to the function name. In Visual Basic .NET, the Return statement is used to return the value from a function. The basic syntax of a function is

 Function  Name  () As  DataType  Return  Value  End Function 

For example, a function that returns an integer would be implemented as follows :

 Function GetInteger() As Integer   Return 1 End Function 

Note

The precedence for introducing the Return statement is that it is close to the actual Ret instruction found at the machine-language level, and other languages have employed a Return statement (or similar construct) successfully for years.


Use functions when you need to return a value, but in general avoid using functions simply to return an error code. Error codes are used much less frequently since the adoption of exception handlers. Where you would have employed a function with a return error code in VB6, use a subroutine and throw an exception in VB .NET.

Using the Return Statement

To demonstrate the Return statement, Listing 5.1 defines a recursive factorial algorithm. Calculating N -factorial means returning the multiplicative value of all the digits from 1 to N. For example, 5 factorial (written in mathematics as 5!) is the value of 1 * 2 * 3 * 4 * 5, or 120.

Listing 5.1 Calculating N-factorial using the Return statement
  1:  Public Class Form1  2:  Inherits System.Windows.Forms.Form  3:   4:  [ Windows Form Designer generated code ]  5:   6:  Public Sub Check(ByVal Value As Long)  7:  If (Value > 0) Then Exit Sub  8:  Const Msg = "Factorial value must be greater than 0"  9:  Throw New Exception(Msg)  10:  End Sub  11:   12:  Public Function Factorial(ByVal Value As Long) As Long  13:  Debug.Assert(Value > 0)  14:  Check(Value)  15:   16:  If (Value > 1) Then  17:  Return Value * Factorial(Value - 1)  18:  Else  19:  Return Value  20:  End If  21:   22:  End Function  23:   24:  Private Sub Button1_Click(ByVal sender As System.Object, _  25:  ByVal e As System.EventArgs) Handles Button1.Click  26:   27:  TextBox1.Text = Factorial(CLng(TextBox1.Text))  28:   29:  End Sub  30:  End Class 

Listing 5.1 defines a recursive Factorial function that calls itself when needed to aid in solving N -factorial. (Refer to the section "Working with Recursion" for more about recursive algorithms.) Lines 13 and 14 combine an Assert with a Check subroutine. The Assert is used to alert the developer to problems with the algorithm. In this instance, the assertion will let the programmer know if a consumer of the Factorial class is passing bad data. When you build the release version, all Assert statements are disabled. Assertions are written for the benefit of the developer. In addition to an assertion, you usually need to write runtime error-checking code. The Check method plays the role of runtime error handler.

Note

Line 13 uses an assertion to ensure that callers are passing values greater than 0 to the Factorial function. Using assertions to encourage callers to pass appropriate values to procedures is referred to as a contract program. The test value of the Assert method constitutes the contract. In Listing 5.1, the contract is Value > 0.

Contract programming is an excellent way to ensure that consumers use methods in ways consistent with the needs of the algorithm.


The Check subroutine doesn't return an error code. Instead it throws an exceptionline 9if the value passed to the Factorial function is less than 1. Notice on the other hand that Button1_Click doesn't throw an exception when converting the value of TextBox1 to a Long. If CLng(TextBox1.Text) fails, an InvalidCastException is thrown. The default behaviorraising an InvalidCastException (see Figure 5.1)is the behavior we want, so no pre-type checking is necessary.

Figure 5.1. InvalidCast Exception can be raised when we try to convert from one data type to another as shown (running the executable outside of the IDE).

graphics/05fig01.jpg

The recursive Factorial function returns Value if Value is equal to 1. If Value is greater than 1, the current Value is multiplied by Factorial(Value-1). Figure 5.2 shows the stack frame when 5 is passed to the Factorial procedure. The stack shows successive calls to Factorial with the value of the parameter being decreased by one each time Factorial is called.

Figure 5.2. Stack memory showing recursive calls to the Factorial function.

graphics/05fig02.jpg

Throw an Exception Instead of Returning an Error

We could have returned a Boolean value in the Check subroutine. For example, Check could have been implemented as a function returning a Boolean as the result of Return Value < 1. However, this would require every user to check the return value and implement a response to the return value. Unfortunately, programmers can and sometimes do ignore return error codes or Booleans.

Unlike error codes, exceptions can't be ignored, but the exception only needs to be checked when an error condition actually exists. Chapter 3 covers the fundamentals of exceptions. Line 9 demonstrates how we can throw an exception to indicate an error state in our code. As far as the Check subroutine is concerned , we are defining what constitutes an error, although if the code itself will cause an exception, we need do nothing.


Team-Fly    
Top
 


Visual BasicR. NET Unleashed
Visual BasicR. NET Unleashed
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 222

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net