shared variable - bad ?

dpirckrstu

New Member
hi team,

i learned and work on progress for close to one year now. I learn much of it from handbook and existing code in my application.
i joined new company now and they do not support shared variables. they say it is not good program logic to use shared variables.
but my previous application, i use all shared variable in progamming.

anyone can tell me why it is bad to use shared variables. is there a handbook for good programming practise?

thanks,
Diego.
 

RealHeavyDude

Well-Known Member
Forgive me for being drastic:

Shared variables is a design pattern dating back from the earliest version of Progress (back in the 1980s) which have no place in a modern application design. Although their usage may be tempting in the end they cause more problems than they solve (OTOH):

  • They make your code intransparent (how can you be sure about the value stored on it when "everybody" may access and change it)?
  • The make your code that uses it dependent on other code, not at compile time but even worse, at runtime (a code that has defined the variable as new must have been executed before your code is executed.
  • And this one is really bad: They compromise encapsulation which is one of the core design patterns of modern application design.
  • They compromise re-usability (which comes with encapsulation).
  • They scatter your code.
  • ...
As for every rule, there might be rare exceptions, but in general they are not bad practice, they are worst practice.

Heavy Regards, RealHeavyDude.
 

kolonuk

Member
We use shared variables, but only to indicate the user of the session, or if we are in debug mode or something like that. Rather than passing these values from program to program via parameters, it makes sense for us to do it this way...
 

Marian EDU

Member
it makes more sense to keep them 'encapsulated' in a 'static' object (persistent procedure or static class)... if you don't use OO then you'll need 'getter' procedures/functions, like in getCurrentUser() - functions are more appealing but those have to be defined using in-super.
 

philipped

New Member
I'm using a shared variable to find the current user to. The only reasy i'm using this method to find the current user is that's how it was made by my predecessor. I never asked myself if there is another way to achieve this.

So I think now is a good time to do so. I'll start by looking at the _MyConnection table. Looks interesting..
 

kolonuk

Member
@Marian: Correct me if i'm wrong, but it seems obvious that using persistent procedures or static classes creates a dependency on something outside the program as much as a global variable does?

For something like the userid, to me this seems ideal...
 

Marian EDU

Member
dependency yes, everything a object use outside of it's scope make it dependent of the other object it use... if it's a 'core' object you can use classes, if something that can be plug-in define an interface, in any case you keep the logic and state encapsulate where it belongs (the 'owner' object).

user id is most probably to be set in something like user context/session after it passed authentication on 'authentication service' in place, once set it shouldn't be possible to changed it freely while when using shared variables you don't have any control over this.

while it might look more convenient and it's perpetuating because at some point that was the only option, imho there should be no place for shared variables for any new development... mind you that simply take those shared variables and make them public properties in a static object with read/write access to everyone won't change anything in that regard :)
 

tamhas

ProgressTalk.com Sponsor
Think about it a little ... if you have a shared variable or a global shared, you really have only good discipline to be sure that it contains the intended value. If some day you find the wrong value, e.g., it shows up blank in a place where you expect it to be set, you have no clue where the problem exists because it could be anywhere that the variable is accessed. Make it a parameter and you know exactly where it came from and can trace back until you find the error. Put it in a static object or superprocedure and again you will know exactly where and when and how it gets its value while retaining the lighter signature of the call.
 

TomBascom

Curmudgeon
Think about it a little ...

If you insist...

... if you have a shared variable or a global shared, you really have only good discipline to be sure that it contains the intended value. If some day you find the wrong value, e.g., it shows up blank in a place where you expect it to be set, you have no clue where the problem exists because it could be anywhere that the variable is accessed.

So far so good... but not really very compelling.

Make it a parameter and you know exactly where it came from and can trace back until you find the error.

It being a parameter doesn't help you at all with the enforcement of valid values or the tracing aspect. It tells you nothing about where or when any value was assigned. Nor does it help you ensure that it is valid.

Put it in a static object or superprocedure and again you will know exactly where and when and how it gets its value while retaining the lighter signature of the call.

"Lighter signature"? Where? As compared to what?

How is it that merely making something an object or putting it in a super-procedure lets me know exactly when and how something got a particular value?

It doesn't.

The only indisputable advantage to making it an object is that you can enforce rules about it's value (for instance, perhaps you have a rule that a number can only be an odd number). With variables (shared or otherwise) and parameters the only enforceable rules are those imposed by the data type. Even if you need such an advantage you don't get it for free -- you still have to code it. But at least you know that it will actually be enforced if you go to the trouble.

I suppose too that you could, in theory anyway, introduce some tracing features into an object and keep track of who has been fiddling with it for traceability purposes. But you don't get that simply by creating an object either.

NEW GLOBAL SHARED is, essentially, a static object without any special rules. If that's all you need then why mess around?

That was a somewhat rhetorical question and, I think, there are 2 serious answers:

1) What you need today isn't always what you need tomorrow -- if your requirements change and you someday need to add tracing capabilities or want to enforce rules about the value of xyzzy you have a nice central location to do that.

2) You cannot use SHARED stuff inside classes. So if you start to mess around with any of the .NET interface stuff or if you just like OO in general you're going to have to cross this bridge. Plus more and more of the interesting code that PSC provides is starting to be delivered as OO code. There's a whole new data dictionary API for instance. And it's OO. So like or not OO is going to be increasingly in our lives and we may as well come to terms with it.

Finally, it is actually very easy to create a static object that acts like a global shared variable:

Code:
/* gsv.cls
 */

using Progress.Lang.*.

class lib.gsv:
  define public static property xyzzy as character get. set.
end class.

And in any program where you want to use "xyzzy":
Code:
using lib.gsv.

/* ... */

gsv:xyzzy = "pflugh".

(Notice that you don't have to "NEW gsv" anywhere...)

This is actually less typing than "define new shared variable xyzzy as character no-undo." The only thing that you lose is compatibility with v9 and earlier. (Not that anyone should be enabling the perpetuation of ancient, obsolete and unsupported releases...)
 

tamhas

ProgressTalk.com Sponsor
It being a parameter doesn't help you at all with the enforcement of valid values or the tracing aspect. It tells you nothing about where or when any value was assigned. Nor does it help you ensure that it is valid.

Quite the contrary. With a shared variable, you have no idea where it might have changed. As a parameter, if it is bad in A which was called from B, one know it was either set bad in B or was passed bad to B. One can follow that chain back until one finds the problem.

"Lighter signature"? Where? As compared to what?

To pass it as a parameter, it must be a parameter. To fetch it from a persistent source, the parameter doesn't need to be there. Therefore, fewer parameters and thus a lighter signature.

How is it that merely making something an object or putting it in a super-procedure lets me know exactly when and how something got a particular value?

Things like user ids are often put in objects or SP with only getter methods. They are therefore read only and *can't* be munged up by anyone else. If there is a setter method, one knows that has to have been used to set it and one can trace where those are.

NEW GLOBAL SHARED is, essentially, a static object without any special rules. If that's all you need then why mess around?


For starters because it isn't read only.

If you make that

define public static property xyzzy as character get.

and initialize some other way than a set, e.g., through the constructor ... often possible for context variables ... then it is readonly and can't be messed with.
 

TomBascom

Curmudgeon
It being a parameter doesn't help you at all with the enforcement of valid values or the tracing aspect. It tells you *nothing* about where or when any value was assigned. Nor does it help you ensure that it is valid.

Quite the contrary. With a shared variable, you have no idea where it might have changed. As a parameter, if it is bad in A which was called from B, one know it was either set bad in B or was passed bad to B. One can follow that chain back until one finds the problem.

You have no more of an idea just because you passed it as a parameter. You still have to track back through the call stack (just as you would for a shared variable) looking for assignments. In fact you've made it *more* difficult because there is no reason to use the same name on both sides of the call -- so now instead of searching for XYZZY you have to figure out what it's name was on the other side of the call -- all the way up the stack. No simple "grep" commands will work. And don't forget that it could have been changed as a side-effect if it was ever passed on as an input-output parameter to some other procedure which has since returned with a changed value. It isn't even /just/ the call stack that you have to look at.

So, actually, by making it a parameter you are making things more difficult. You're not improving them at all.

How is it that merely making something an object or putting it in a super-procedure lets me know exactly when and how something got a particular value?

Things like user ids are often put in objects or SP with only getter methods. They are therefore read only and *can't* be munged up by anyone else. If there is a setter method, one knows that has to have been used to set it and one can trace where those are.

That is not "merely" putting something in a SP or making it an object.

It is, however, a good reason #3 why someone might want to go that route. Too bad you didn't mention it as a worthy potential benefit in the first place.


NEW GLOBAL SHARED is, essentially, a static object without any special rules. If that's all you need then why mess around?


For starters because it isn't read only.

I don't recall specifying a need for read-only as part of "if that is all you need".
 

tamhas

ProgressTalk.com Sponsor
You still have to track back through the call stack (just as you would for a shared variable) looking for assignments.

In the sort of legacy code in which shared variables are used, it is common to have many call stacks. With a shared variable, you have no idea if someone has messed with it deep in one of those stacks, regardless of where you first discovered the problem. With a parameter, typically it will be an input parameter unless one intends to change it. One doesn't need to follow any call stack beginning with passing as an input parameter since there is no way the value, even if altered below can come back along that chain.

Perhaps we should make a strong distinction between two uses since they have somewhat different issues. One typical use ... like a user id or fiscal period or effective date is context information, typically set by the menu system or in a top level start up routine and not changed after that. This value might be the default for some other value that one might override, e.g., current login date being the default for order date and one might override order date, but they login date is not supposed to change during the execution of the program set. The second use is for passing values back and forth between procedures in a program set.

The former case is inherently read-only and doing something that enforces that read-only character is not only going to provide protection but make what is happening and possible more clear. There is nothing about a shared variable that keeps a programmer from making a mistake or being clever and changing a value which shouldn't be changed, but an SP with only a getter IP or a class with a read-only property will make it impossible for that to happen.

In the latter case,, the shared variable is part of the contract between specific procedures, not a diffuse piece of context. If those procedures are immediately connected, e.g., one runs the other, making the variable a parameter makes that contract clear and explicit, whereas with a shared variable one has to deduce the connection by reading the code. If the communication is occurring between procedures which are not closely connected, then one has a design problem. One needs then to put the value in some central accessible place like an SP or static class, again so that the relationship is clear. With shared variables, it isn't just that one can't tell who did what when something goes wrong but that one has no idea how the value gets to be what it is without searching the entire program set. With an SP or static class one can quickly determine who uses and who sets.
 

Marian EDU

Member
just to comment a bit on static objects since all this can be misleading imho... properties equals state and apart 'constants' (read-only static properties) there are very few (if any) cases where a static object have any state information.

due to the single-threaded nature of 4GL we tend to use shared objects as we are the only one that can access them at a given time, while this is true now things might change with all that .Net inclusion :)

instead of adding state to static objects i'll go for using 'helper' properties in base classes for easy access to most common 'shared data'... i was coding in this editor but it starts to be way to much text and I need to refine a bit the 'context' concept i'm thinking about :)
 

TomBascom

Curmudgeon
Writing a mountain of text claiming facile advantages that change with every post doesn't make it true.

Shared variables are bad and objects are better. I agree with that.

Being able to make an attribute enforceably read-only is a good thing. I agree with that.

Having clear contracts is good. But it isn't easy or automatic and it doesn't happen magically just because you start passing parameters or using objects. It happens when the architect (or developer) has a clear and complete concept about what they are building.
 

tamhas

ProgressTalk.com Sponsor
Clear concepts on the part of the architect and good structures for expressing clear contracts tend to reinforce one another. Shared variables means fuzzy contracts both in the actual code and probably in the mind of the architect. Moving to parameters, superprocedures with context values, static objects and the like help to sharpen up the contract in the code and help the architect to get a clear idea of structure.
 

suny

ProgressTalk.com Sponsor
I think so, shared variable = goto.
If you find the second good, you can use the first, too :)

(I have to admit, I use goto in Excel Visual Basic and I (sometimes) used new global shared, during MFG/PRO developing. Do not imitate me.)
 
Top