Are Global Variables Really that bad ?

I make no claim that what I do is "right." What I do is the way I like it.
Mark

In the end that's all that matters ... as long as it works reliably for the developers and the end users.

FWIW here's part of the modDefinitions standard module containing the kind of global variable I do use in one of my databases ...

attachment.php


Again its just what I do (for items referenced repeatedly by the program)
... but less so than in the past
 

Attachments

  • Capture.PNG
    Capture.PNG
    36.8 KB · Views: 689
...I almost always, for instance, write a cUser class that has rich data--like date/time of login, machine name, security info, prefs--about the current user. This class would be created automatically using WScript.Network.Username and drawing other data from a table.

When I first became aware of the existence of Class Modules, I struggled with finding a practical uses for them. The cUser Class you describe is one of the situations I thought of.

While is was trying to figure out if the juice was worth the squeeze, I stumbled across Type variables instead and went that route. I would like to say I chose that route for efficiency reasons but truth be told, it was because it was MUCH easier to figure out!

All that being said, in this particular case, does one course have any advantages over the other? Specifically data stability (scope) and system resources are the two main advantages that come to mind.
 
does one course [Type or Class] have any advantages over the other? Specifically data stability (scope) and system resources are the two main advantages that come to mind.

A Type cannot support any validation of its component values beyond the datatype. A Class supports Properties with Get and Let procedures that can do pretty much anything.

A Class can have Methods.
 
Apologies Galaxiom, I am not a programmer. By “methods” I assume you mean Subs and Functions?

Class Modules have intrigued me from the moment I first read about them. I have invested some time in trying to learn how they work and how to apply them, and the quest continues - in regards to learning how they work. My brain simply isn’t wired right when it comes to OOP.
 
Gent, a Class has properties and methods, not functions or subs or global variables, when it is well defined. Here is the rub:

Inside the module that defines the class, those things are private functions or subs and their entry points are defined using the normal Function and Sub declarations. However, from the outside of the class, you invoke them differently. The variables in the class module's declaration area are declared normally, but you then have to arrange for Property Get and Property Set "functions" to involve the hidden elements that the class REALLY uses when it does something.

So then, if set up correctly, the class-obj.XYZ = 3 method ACTUALLY DOES call a subroutine that sets the private variable XYZ to the indicated value. The class-obj.Move(X,Y) really DOES call a subroutine to move coordinates in some way.

But the SYNTAX by which you use it suddenly makes the class object behave like a form or control or whatever, with the same VBA syntax as any other encapsulated object. And this is done because of course, if you have more than one of these class items, using a global runs afoul of over-defined global names - including global Function or Sub names. This multiplicity of definition pretty much guarantees massive headaches.

But if you have multiple objects of that class, each object has its own private copy of the properties or other content, and is qualified by the name of the class object you created as a member of the class. Which is EXACTLY why you can talk about FormA.Recordsource and FormB.Recordsource and in each case, know exactly which recordsource you were discussing.

This is one ASPECT of enclosure and encapsulation. There's a LOT more to it than that, but it should be enough to explain what you need to know to get past the confusion.
 
Sorry Doc, there are numerous errors in your post. I am sure you know better and just posted without reviewing what you must have typed at phenomenal speed. You should try again.
 
G, it has been years since I did one of these and it wasn't implemented in VBA.

Gent, take my previous description with a grain of salt because I DO respect Galaxiom and don't care to get into a big dispute over something that to me was an explanation of WHY more than HOW. Even if I'm wrong technically for HOW it is done with VBA, I am right conceptually about WHY you do it.

When building a class object definition, you convert private subroutine calls (defined inside the class module) into Methods of the object and you convert simple Property assignment operations into functional calls (also inside the module) that affect the actual variable declarations inside the object. The details of how that is done do not matter for the discussion at hand.

You do it so that if you have two objects of the same class, you use the names of the objects to differentiate between the variables declared inside the class definition for each object. And you have two because when you instantiated the two objects, you instantiated copies of any required data structures associated with those objects.

So, for example, there probably IS some sort of internal variable within Access's definition of a Form object that is called "Width" (or, who knows, internally it would be called FormWidth... a rose by any other name). You cannot see that actual variable because the class object has code that intervenes when you attempt to set or get the value associated with that property. That intervention can filter stuff, raise errors, or actually do what you asked it to do. Since the object is a "black box" you don't really know - but you know it has to be there in SOME form.

Oh, by the way... since all variables internal to a properly constructed class declaration are Private, we adroitly side-step the "Global Variables" issue by using class objects.
 
No worries folks, I was a little concerened about hijacking the thread but Doc brought it back on point.

To offer a less than informed perspective, I have never liked the idea of a Global unless of course it actually IS a constant. MarkK’s example of Pi is a good one, although I am sure The is already a built in function for it.

In my case, I have never needed them and I liked the idea that they went out of scope once the Module that used them terminated.
 
Gent, take my previous description with a grain of salt because I DO respect Galaxiom and don't care to get into a big dispute over something that to me was an explanation of WHY more than HOW. Even if I'm wrong technically for HOW it is done with VBA, I am right conceptually about WHY you do it.

Yes. Doc was conceptually more or less correct but it wasn't expressed so well and the details were messed up a bit.

"Methods" are generally Public Subs. (That is just what are called from the outside of the object.)

A Public Function can also behave as a Method but then it has to use function syntax (parentheses around the parameter group etc) to get the return value. It can get very confusing especially if there is a single parameter because parentheses around a parameter alters how it behaves. Best stick to using Subs as Methods.

Normally one would use a Property to record or return a value from a class object. A Property value is usually stored as a Private variable preventing access to it other than via the Property. The Public Property GET and LET are used to read and write to and from the Private variable which provides tight control over what will be accepted. The Variable must have a different name from the Property.

The main purpose of a Class object is to support multiple instances.
 
Just for clarity, G, in the system where I implemented the object, the sub wasn't declared as Public because this particular language had a different keyword for this case. It has been so long, I don't remember ALL of the details. Nor does it matter.

As to "not expressed so well" - I must admit the post to which you objected was composed near midnight local time. The fingers still work OK but the brain begins to shut down by then.
 
The biggest issue with extending the scope of variables is that you need to keep careful track of their use. As long as you are careful, there are no issues. However, if you use functions, with passed arguments, and define variables within the functions, then there is much less chance/likelihood of such errors.

By the same token, I prefer to use byval arguments, rather than byref, to avoid the possibility of inadvertently changing the variables.

With all that in mind, using public variables sensibly can make development much easier in some cases.
 
The biggest issue with extending the scope of variables is that you need to keep careful track of their use. As long as you are careful, there are no issues. However, if you use functions, with passed arguments, and define variables within the functions, then there is much less chance/likelihood of such errors.

By the same token, I prefer to use byval arguments, rather than byref, to avoid the possibility of inadvertently changing the variables.

With all that in mind, using public variables sensibly can make development much easier in some cases.

There is nothing you have said with which I disagree.

Best,
Jiri
 
I see this is still going on. I'll try to avoid beating a dead horse here. (According to the Woody Allen movie What's Up, Tiger Lily, that would make me a sadistic sodomistic necrophile... but I digress.)

Jiri was emphatic in expressing feelings over the idea that globals have their place. Galaxiom tends to avoid globals. Plog and Ridders have their positions. I am a known pragmatist and have guidelines about when I use them and when I don't. However, something said in passing needs addressing.

When you are dealing with a team of programmers, each responsible for constructing parts of a project, you CATEGORICALLY NEVER allow use of a global for any part of any programmer's piece. ALL repeat ALL assignments to "code up this function" or "write this subroutine" will include the strictest possible admonition to NEVER depend on a global - with one exception.

That exception is that the team's leader lays out what will be allowed as a global variable and NOBODY ELSE will create one. EVER.

The key here is coordination among people who have high intelligence and vivid imaginations. When one person is the entire development team, coordination with regard to globals is reasonably easy. When you actually DO have a team, you will NOT achieve coordination unless you lay down hard law on which globals will and will not be allowed.

I've done that more than once on teams I have led (in other languages). The moment you allow someone to say "I need this to be global" then you have opened up the path to unbridled global scope - and in so doing, you lose the ability to re-use your modules in other projects. As a software engineer, you cannot afford to write non-reusable code. Your most expensive commodity is your time and to waste it by making too many one-off projects due to modules that defy reuse is to cheat your own wallet.

This has been my experience as a project / team leader. My project experience over a period of over forty years is why I am very careful about globals. Not to the point of exclusion. But very careful of them in ANY language.
 
Some of you are taking this way too seriously. If I didn't know better, I'd think you were arguing about surrogate vs natural keys:)

You probably don't need another opinion but I thought I should share my original problem with globals which actually was fixed by A2007 or possibly A2010 and that problem was that Global variables used to loose their value if the app encountered an unhandled error. I don't know about you, but I don't bother to put 10 lines of error trapping code into procedures that have only a few lines of code UNLESS, I know the procedure has the potential to raise an error. Of course I use extensive error trapping when I know errors will be raised but how likely are you to get an error when you have 10 lines of code that are clearing search boxes? So, since this was prior to TempVars, my solution was to create a hidden form where all "global" variables were defined as controls. The upside of this turned out to be that making the form visible during testing enabled me to monitor the variables as they changed and to actually step in and change values to alter logic paths when necessary. That in particular made it much easier to test the application security since I didn't have to keep loging out and logging back in as a different user. So, even today, in the era of TempVars I still primarially use the form method. If I do use TempVars or Globals, I ALWAYS define them in a single standard module to make it easy for me to find them. My use of "globals" (any of the three methods) is very limited and as someone else already mentioned usually limited to things relating to the logged in user and security. I also use the form as a way of passing arguments to forms and reports. Rather than have queries that reference form fields on each individual form, I copy the value to my generic hidden form so that I can use the same query regardless of what form opened a particular form or report.

I know that we all think that we write clean, clear, concicse code and that anyone who reads it will have no trouble understanding it. I hate to burst your bubble but when we are writing an app, we have so much internal information that simply never makes it into coments or documentation that those who follow in our footsteps are frequently at a loss as to why certain design decisions were made. Judicious use of comments helps (that doesn't mean you should comment every single line of code. Use common sense if you want people to actually read the comments and make sure to change them if the procedure's logic changes.). Sometimes documentation overviews help - assuming people actually read them but more important is using standards that will help people who come in cold, figure out what is going on. For example, it is important that you name your globals and TempVars and Form contols in such a was that the average reader can actually "see" their use and so be wary of changing them. Knowing he is dealing with a Global will probably give your successor pause for thought and he should realize that changing it could easily affect some other part of the app and so he needs to do some research prior to making any changes.

Coupling and Cohesion are terms that predate OOP. Coupling describes the connections BETWEEN objects and Cohesion describes the connections WITHIN an object. The general rule is that you want procedures to be loosely coupled (not dependent on each other unnecessarily including passing very few variables) and highly cohesive (all the code within the procedure should relate to the same process). For example, you would never use the same procedure to write payroll checks and FTP your backups because both happen every other Monday. That means that Global variables which are not precicsely defined and correctly used can cause pathalogicol coupling causing procedureB to fail because someone made a change to a variable that was intended to benefit procedureA. I'm sure you've all run into a software bug and while scratching your head said, how the *** did that change cause that effect? The answer will very likely come down to the developer not understanding (or ignoring) the principles of coupling and cohesion. Keep in mind that very few applications go their entire productive lives managed by their original developer. This is why defensive programming is so necessary. Your goal as a developer should't be to be "elegant", it should be to prevent future errors if possible.

@MarkK, what do you do when you have data that needs to be shared by several procedures? You could of course pass all the values areound as arguments. If you pass them by value, the original values never get altered except by the original procedure. If you pass them by reference, any module in the chain can modify them. Both are valid points of view. But the second method is essentially the same has having global variables but with a narrower scope.

@Jiri,
One would expect people to test their creations before offering them at AWF.
I'm not sure how much time you have to spend doing pro bono (had to have some Latin, somewhere) work but I don't have that much. Expecting me to build a test bed and create tables with usable data is going above and beyond. If the OP includes a suitable database and I do end up writing queries or code, I do test it but otherwise, he gets air code or a working sample from one of my apps where he would need to at a minimum, change all the variable names.
 
@MarkK, what do you do when you have data that needs to be shared by several procedures?
I really don't see this happen very much. Almost all code I write is in a class, so the class itself either knows how to get at the data it needs, or it exposes other classes which instantiate automatically, and which also do their own data access. As such, I rarely find myself passing data around to disparate procedures. To answer this more specifically, can you describe a case? Then I can say how I would handle that case.

I typically expose a few global classes that are costly to construct, like you, from a hidden form. System settings, for instance, I commonly expose as a global class with named properties so its members are all available via intellisense, which simplifies other code. I'll also commonly create and expose a global cUser class which in a bigger system might itself expose a cUserPreferences class, similar to the cSysSettings, with named properties available via intellisense.

Also, keeping track of classes and their exposed properties at runtime is super easy using the IDE's locals window, which evaluates Property Get procedures automatically, and shows child classes as expandable nodes in a tree view.

Otherwise I never--and I mean never--use standard module system-wide globals to transfer simple data types between data producers and data consumers as an intermediate step in a coded process. And it's not that I think others shouldn't, but to me it just always works out better if that would-be global is a member of a class.

Mark
 
I read Pat's viewpoint and I agree with most of it. Using a form as a variable-keeper is an interesting debugging trick that never occurred to me. Thanks for that one, Pat!

It has been so long since I took ANY programming language and theory courses that I had forgotten the "coupling and cohesion" terms - though I used them conceptually. It had become so ingrained that their use became automatic and at that level, you don't name the principles. You just use them. Thanks for the reminder of the nomenclature, too, Pat.
 
Some of you are taking this way too seriously. If I didn't know better, I'd think you were arguing about surrogate vs natural keys:)

You probably don't need another opinion but I thought I should share my original problem with globals which actually was fixed by A2007 or possibly A2010 and that problem was that Global variables used to loose their value if the app encountered an unhandled error.

That problem has not been fixed, Pat (as of A2013 that I am using). But as I pointed out earlier, it only exists if the app is not compiled into .accde.


I don't know about you, but I don't bother to put 10 lines of error trapping code into procedures that have only a few lines of code UNLESS, I know the procedure has the potential to raise an error. Of course I use extensive error trapping when I know errors will be raised but how likely are you to get an error when you have 10 lines of code that are clearing search boxes? So, since this was prior to TempVars, my solution was to create a hidden form where all "global" variables were defined as controls. The upside of this turned out to be that making the form visible during testing enabled me to monitor the variables as they changed and to actually step in and change values to alter logic paths when necessary. That in particular made it much easier to test the application security since I didn't have to keep loging out and logging back in as a different user.

I am not clear about the "upside". Are you saying that this could not be done with global variables without the form ? Eg. via the watch window ? I am not really clear about the benefit of spawning a form just to house a bunch of variables. Why not to put them in a parameter table?

So, even today, in the era of TempVars I still primarially use the form method. If I do use TempVars or Globals, I ALWAYS define them in a single standard module to make it easy for me to find them. My use of "globals" (any of the three methods) is very limited and as someone else already mentioned usually limited to things relating to the logged in user and security.

I personally see nothing wrong in using them also to pass parametric data between modules, in defining application settings, and in managing a session. Also, I am not sure why you would use globals in a standard module when you went into the trouble of creating a hidden form for them. But I guess that's just me.

I know that we all think that we write clean, clear, concicse code and that anyone who reads it will have no trouble understanding it. I hate to burst your bubble but when we are writing an app, we have so much internal information that simply never makes it into coments or documentation that those who follow in our footsteps are frequently at a loss as to why certain design decisions were made. Judicious use of comments helps (that doesn't mean you should comment every single line of code. Use common sense if you want people to actually read the comments and make sure to change them if the procedure's logic changes.). Sometimes documentation overviews help - assuming people actually read them but more important is using standards that will help people who come in cold, figure out what is going on. For example, it is important that you name your globals and TempVars and Form contols in such a was that the average reader can actually "see" their use and so be wary of changing them.

No argument there. If I write code to be maintained by someone else, I make a habit of commenting the correct use of the global variable in the special standard module I put them in. But in general I am convinced that code with globals is more readable and maintanable than one that relies on the use of hidden forms (or Windows API) to pass data.

Knowing he is dealing with a Global will probably give your successor pause for thought and he should realize that changing it could easily affect some other part of the app and so he needs to do some research prior to making any changes.

But, Pat, isn't that the case with pretty much everything? It may be an obscure column in a table; it can be a user-defined structure or object, it can be the use of an event. Why to single out globals? I don't know how you, but when I go over someone's code I don't start changing things until I feel reasonably sure I have solid grasp of what is going on. And actually, I have gone through professional work where all globals where prefixed with "g" or "gl" and placed in a separate module with other globals. So, against all the dire warnings of the naysayers, there is I believe a lot of common sense out there in using the globals.

Pat Hartman said:
Coupling and Cohesion are terms that predate OOP. Coupling describes the connections BETWEEN objects and Cohesion describes the connections WITHIN an object. The general rule is that you want procedures to be loosely coupled (not dependent on each other unnecessarily including passing very few variables) and highly cohesive (all the code within the procedure should relate to the same process). For example, you would never use the same procedure to write payroll checks and FTP your backups because both happen every other Monday. That means that Global variables which are not precicsely defined and correctly used can cause pathalogicol coupling causing procedureB to fail because someone made a change to a variable that was intended to benefit procedureA. I'm sure you've all run into a software bug and while scratching your head said, how the *** did that change cause that effect? The answer will very likely come down to the developer not understanding (or ignoring) the principles of coupling and cohesion. Keep in mind that very few applications go their entire productive lives managed by their original developer. This is why defensive programming is so necessary. Your goal as a developer should't be to be "elegant", it should be to prevent future errors if possible.

This type of argument against the use of globals is why I opened the post. To expose it as group-think or prejudice. It's an argument from "excessive design" that I have not yet seen supported by a single compelling example. Also, it is interesting to see you joining Mark and Galaxiom when you said this earlier:

Pat Hartman said:
]Some of you are taking this way too seriously. If I didn't know better, I'd think you were arguing about surrogate vs natural keys:)

So frankly, I don't think there is danger of "pathological coupling" from the use of global variables. I don't see any reason to label poor programming that way but more to the point, the globals in and of themselves do not present any more of a problem than say object variables. Maybe, there is more issues with the former but that would be because startup programmers usually don't do OOP. But bottom line: you don't prove anything by simply asserting something, especially not when you start from a false premise (i.e. that global variables include an invitation to programmers to use them thoughtlessly and/or in preference to a lesser scope).

Interesting that no-one has pointed out in the nearly three dozen posts here that Access is, for better or worse, a RAD tool. And yet nearly everyone would concede that globals are as a rule the simpler solution. Surely, there has to be some reason why Microsoft insist on leading us into temptation. (Worse even, the old "global variables" were extended into "public variables" in standard modules -A2003?- so you can now choose how to sin). But friends, I am done here so I won't speculate on what it is.

Thanks to all who participated here.

Best,
Jiri
 
This type of argument against the use of globals is why I opened the post. To expose it as group-think or prejudice.
Isn't this statement prejudicial? This is not a thread about globals, it's a prejudicial thread about others' prejudices; a religious thread, by design.
#amirite
Mark
 
Are you saying that this could not be done with global variables without the form
You can use the immediate window but first you have to figure out what variables you want to view. The form simplifies the process since they are all together and if you are not using tab view, you can see both forms at once so you don't have to have two monitors.

Also, I am not sure why you would use global s in a standard module when you went into the trouble of creating a hidden form for them.
Not every application needs a bunch of global s. It is a design decision I make early on. I've built enough applications to know which works best but the security components ALWAYS stay on a hidden login form. Whether I create another hidden form to control reporting and/or batch processing depends on how complex the reporting requirements will be. Apparently the Access apps I create are significantly different from much of my mainframe work in the past in that the Access apps are interactive and surprisingly little batch processing is required. I did recently create a pretty complicated EDI process to create 835 medical billing transactions. In that case, I ended up using a global variable to hold a recordset object. I couldn't get a TempVar to work and obviously, you can't store an object in a textbox on a form, so that left a Global. If you've ever worked with EDI, you can envision the problem of having to create dozens of separate records to hold small segments of data. The records get written out as they are built by various procedures but all the base data comes from the single record in the recordset.

But, Pat, isn't that the case with pretty much everything?
Review my discussion of Coupling and Cohesion. KNOWING a specific variable is global because of a naming standard gives it a greater context.

To expose it as group-think or prejudice.
I don't believe I ever said Global s were inherently "bad". I did point out an implementation problem specific to Access that causes them to loose their state and for that reason and that reason only, I found a different way (the global form since this was before TempVars). However the concept of Coupling is very important to understand and I actually think this is misunderstood by people who create Classes in Access for everything. I've acquired custody of a significant number of Access apps over the years and I can say categorically that the very worst app I ever took charge of was created by a professional programmer rather than an amateur. The programmer had apparently decided that he knew more than the Access team and insisted on enforcing an OOP style on all his code. There were literally thousands of useless lines of code in the app and every time I had to dive 9 levels through classes to find a single variable, I cursed his offspring from now to eternity.

So frankly, I don't think there is danger of "pathological coupling" from the use of global variables
You live an insular life. Perhaps I've spent too many years consulting. I get called in to clean up lots of messes. I try to avoid those assignments in favor of new development but sometimes they cannot be avoided. I'll give you an example of pathological coupling. Somehow I managed to get a trial version of SQL Server installed on my laptop. When I went to upgrade it I ran into issues so I had to delete it in order to install the full version. I just went to control panel and started uninstalling SQL Server. But, apparently there was a sequence that needed to be followed when uninstalling. I got to a point where I could not reinstall or repair because some part of the app existed and yet I could not complete the uninstall because some part was missing. Does that make any sense? Of course not. It makes sense that an install might have prerequisites but it makes no sense that an uninstall would. Whether this was caused by a "global" or not doesn't matter. It was caused by some piece of information that was causing two processes to be connected when they shouldn't be. The point of the repair is to reinstall "missing" or "damaged" parts and yet repair wouldn't work.
 

Users who are viewing this thread

Back
Top Bottom