Saturday, May 24, 2014

Refactoring & Scope of Variables

Recently, I had to enhance some legacy codes running into thousands and thousands of lines of code within a loop. In order to facilitate ease of customisation, I decided to refactor the codes breaking chunks of codes into procedures. However, I was concerned with the visibility of the visibility in these procedures of variables declared in the calling procedure.

A google search led me to this valuable article at

Object Pascal Variable Scope

 As mentioned in some of the previous articles understanding Object Pascal variable scope is one of key elements in building applications with Delphi/Object Pascal.

 Scope of Variables and Constants
The term scope refers to the availability of a variable or constant declared (or used) in one part of a program to other parts of a program.
Unless we specify otherwise, changing the value of a variable named, let's say, SomeNumber in one procedure (function) will not affect another variable with the same name in another procedure (function).
Since Delphi requires us to declare variables, it's a lot harder to fall into the trap caused by side effects accidentally. As we know by now, every variable used in some procedure has to be declared in the var section of the event handler.
In general, we declare a variable where we want to use it. For example, if we want to use a variable in an event handler, we declare the variable within the event handler.

Local Scope (+ variable declaration and initialization)
Most variables have local scope, which means that the variable is visible only within the code block in which it is declared (usually: Event Handler for some method). In particular, an event handler will not normally have access to the value of a variable in another event handler.
If we want to be sure a variable is local within an event handler, we have to declare it in the var section inside the event handler. Since we must declare a variable before we can use it, if we can use a variable without declaring it locally, we know that there is a variable with greater scope with the same name somewhere around project.
Let us look at the first example:
1. Start Delphi, this will give us (by default) new application with one blank form.
2. Double click somewhere on the form (to create OnCreate event handler)
3. Write down this code:
procedure TForm1.FormCreate(Sender: TObject);

4. If you try to run your project now, you will be prompted with: "Undeclared Identifier: 'SomeNumber'" error. This means that we haven't declared SomeNumber variable in our project (note: entire project, not FormCreate event handler).
5. To declare SomeNumber variable as double type change your code to:
procedure TForm1.FormCreate(Sender: TObject);
var SomeNumber: double;

6. Run your project, message box will appear with some strange (value of the memory region where variable is stored) number. Delphi will also give us "Variable 'SomeNumber' might not have been initialized" warning. This means that, before using declared variable, it is a good practice to initialize it (just to be sure). For that purpose add this line of code before ShowMessage...
SomeNumber := 123.45;

7. Now, when you run your project message box will display 123,45 (no errors, no warnings). Finally, we can see why local variables are called local...
8.Add one TButton component to form and double-click it to create Buttons OnClick event handler. Add the following code (so that OnClick looks like):
procedure TForm1.Button1Click(Sender: TObject);

Again we have "Undeclared Identifier: 'SomeNumber'" error. This is what local variables are all about: even if we have declared (and initialized) SomeNumber variable in OnCreate event handler of the form, SomeNumber is not accessible in the OnClick handler of TButtton. We simply cannot use SomeNumber (with 123.45 value) in the OnClick (at least for now). This means that SomeNumber from OnCreate and SomeNumber from OnClick are two different variables (that can, of course, hold different values) 9. Don't close this project, jet. We will need it again...
10. Add the following line before ShowMessage in the OnClick event handler (we will need it later, don't worry about this for now)
   Sharing variables across procedures (event handlers)
Occasionally we will want to share the values of variables (and constants) across event handlers or across units. For example, if an application is designed to perform a calculation involving one SomeNumber at a time, that SomeNumber should be available to all procedures in a unit.
Depending on where we declare a variable, the variable can be thought of as a true global variable accessible by any other code in the application, or a unit-level variable accessible by any code in the unit.
   Unit level variables - unit level scope
We put the declaration statements for unit-level variables in a var section in the unit's implementation section. Unit-level constants are declared in a const section.
Let's look at the second example:
0. We will be modifying our first example (be sure to have it)
1. Add declaration of SomeNumber, so that implementation code of the unit looks like:
{$R *.DFM}
  SomeNumber: Double;
2. Run your program. As you can see, we don't have "Undeclared Identifier: 'SomeNumber'" error in OnClick handler of the TButton. When program starts message box will appear with 123.45 value. When you click on the Button1 message box will display 555.55; that's why we need step 10 in the first example - we have initialized SomeNumber to 555.55 in the OnClick event of the Button1. What's this? We have two SomeNumber variables in our project and they both hold different values.
Obviously, we have to be careful when assigning values to unit-level variables. Although we can use the same variable (or constant) name for both local and unit-level variables, this is not a good idea. Any var (or const) declaration contained in a procedure takes precedence over global (unit-level) declarations. Duplicating the names makes the global variable invisible to the procedure (Delphi doesn't tell us whether a global variable has been defined with the same name as a local variable). That is why SomeNumber holds the 123,45 value in the OnCreate event handler of the form (we cannot use global variable SomeNumber in the OnCreate procedure)
Note 1: If you really have to use two variables with the same SomeNumber name (one global and one local), you can access the global SomeNumber variable value in the forms OnCreate procedure with the call to unit1. SomeNumber (unit1 is the name of the unit with the global SomeNumber variable). That is, something like
will change value of the global variable SomeNumber inside OnCreate event handler of the form (remember that there is a SomeNumber variable local to this procedure which will stay unchanged)
Note 2: As global SomeNumber is global to the unit we can access (more important: change) its value from any other procedure inside this unit. However click to Button 1 will reset SomeNumber value to 555.55. Better way to initialize global variables is inside initialization section of the unit.
   Global variables - program level scope
If we want to create true global variables (or/and constants) in a project, we have to place the declaration in the interface section of the unit. Variables declared in the interface section will be visible (accessible) to any unit which uses that unit.
For example, to change SomeNumber variable value that is declared in Unit1 from Unit2, use this statement:
Be sure to add Unit1 to the uses clause of Unit2.    Conclusion
That's it. I hope you have had the power to come to the end of this article. As we can see, there is much to be stated about variable scope in Object Pascal. Of course, there is more: static variables (or typed constants) are something we could name "constant variables". I'll be dealing with static variables in some of the future articles...

No comments:

Welcome to Delphi... Delphi... Delphi

I have been a Delphi Developer since Delphi 3 when I finally decided on Delphi in 1996 as my programming language of choice for the Windows 32 environment. So what have I created with Delphi ?

Would you believe that I had single-handedly created a full ERP2 system comprising ERP+CRM where ERP=Sales Distribution+MRP+ Procurement Management+Planning & Production +Finacial Management + Human Resources Management System.

Since 15th February 2009, we have visitors from more than 60 countries including Malaysia, United States, Brazil, Italy, Australia, India, Turkey, Russian Federation, Spain, Indonesia, Hungary, South Africa, Germany, Mexico, Argentina, Singapore, Saudi Arabia, Colombia, Czech Republic, Canada, France, Croatia,Thailand, Bulgaria, Slovenia, Hong Kong, Poland, Sri Lanka, Chile, Japan, Austria, Ukraine, Azerbaijan, Ireland, Tunisia, Greece, Taiwan, Egypt, Bolivia, Paraguay, Iran, Islamic Republic , Morocco, Angola, Belgium, Portugal, Norway, Venezuela, United Arab Emirates, Algeria, Korea, Republic Of, Slovakia, Georgia, Lebanon, Macedonia, Sweden, Philippines, Vietnam, Dominican Republic