|
Boost : |
From: John Max Skaller (skaller_at_[hidden])
Date: 2001-08-10 22:22:46
williamkempf_at_[hidden] wrote:
> > Yes, I believe every single use of static variables
> > and singletons is BAD DESIGN.
>
> Well, I don't agree, and I've yet to read a book on design that does
> either.
Object Oriented Software Construction (Bertrand Meyer).
Many regard this as the seminal lay book on design, including me.
> Oh, I know about re-entrancy and it's issues, but they do *NOT*
> preclude "globals" in good designs. In fact, there are several
> classes of designs that simply can't be coded with out the use of
> global data.
Show me one. I've never seen one. I've never used
global data (not since the '70's anyhow). I've never missed it.
I've had no end of problems working on other people's code
that did, and I've always solved the problems by uncovering
the design flaws by eliminating the global variables.
> Depends on what you mean here. Been too long since I did Pascal, but
> I believe it had an answer for this issue.
Of course it has an 'answer': it has nested scopes,
and the 'outer' scopes can be considered 'global'.
> Eiffel I'm a tad more familiar with in the
> recent past, so I can prove you wrong right now. This is what
> the "once" feature is used for.
Sorry: the original Eiffel didn't have this feature.
You're right, they messed up because they were forced to deal
with interfacing to badly designed interfaces.
> Even a construct as simple as (pseudo code for any language):
>
> main() // or language equivalent
> {
> foo global_foo;
> bar(global_foo); // pass to all routines/objects that need
> // the global data
> }
>
> Results in "global" data with the same re-entrancy issues.
No it doesn't. With this design, you have EXPLICIT
control over whether the foo data is shared or not.
Checkout 'bar':
bar(global_foo)
see? you're PASSING the data to bar. So I can write:
foo gd1;
foo gd2;
bar(gd1);
bar(gd2);
and if the code worked in the first case, it now
is guarranteed to work for two cases. I can even
make these two calls thread and it is GUARANTEED
TO WORK. It is intrinsically thread safe.
(assuming of course you don't call any other
routines which use static data)
Note that in ML, for example, you have
'implicit coupling' with your environment:
let x = 1 in
let f y = x + y
Here, f is coupled to 'x'. However, used functionally,
'x' is a constant. if x is a variable, we have implicit
coupling of variables: however the situation is not
as bad as in C, since the coupling is to a 'nearby'
variable in the environment: if you really want to do this,
at least you aren't forced to make the variable top level.
> Re-entrancy issues reveal problems with _shared_ data, not problems
> with global data, class statics or block statics.
I agree. But the difference is that global data is
ALWAYS shared. Passing a pointer to an object means EVERY routine
will work for whatever is passed to it. You may need to
copy the data, or create several 'global data' objects,
or even factor the object into subobjects, but the point
is that you have explicit control over this, and you will
find out from the compiler when you have failed to
pass the data correctly.
> The solutions to
> the problem are the same no matter the form of the shared data.
No. They're NOT the same at all.
> To claim that "global" data is a bad design because of re-entrancy
I didn't claim that: I simply cited re-entrancy
as an example of one of the issues: it isn't the only one,
nor is avoiding global data enough.
> logically follow that designs using shared data are bad designs as
> well.
Yes, but that isn't my claim at all. Obviously,
we want sharing. The point is to pass the data to be
shared _explicitly_, not implicitly.
Global variables provide _implicit_ coupling.
It is very hard to have two copies of them when you
don't want sharing. You have a massive interface change
to cope. Bad design because it is fragile.
Here's an example of the extreme's I go to. And why.
I'm writing a compiler. I need some state for the lex like lexer.
I could use global variables. Do I? NO WAY. I parameterise
EVERY SINGLE rule with an object. Why? Do I need to
lex two files simultaneously? No, not at the moment.
Why go to all that trouble? [And it was a LOT
of extra work to change { TOKEN } to { fun state -> TOKEN }
for every single token]
Well, I just knew it was the right thing to do.
So I did it. Then, I had to add source references in:
to every single token (*boring*):
{ fun state -> TOKEN (state#get_srcref) }
Very laborious. Why not just read a global variable?
(the srcref is the current file name and line number).
Then I addded #include files to the language and it all
paid off. Because in an include file, there are TWO
files open at once, so you just can't fit it into
a single global variable. You'd have to make a stack,
and push and pop the global variable. MUCH easier
to just create a new object with the new filename,
and pass that to the recursive invocation of the lexer
than processes the include file.
This is just one _positive_ example of
how slavish adherence to the NO GLOBAL VARIABLES
rule pays off in the long run.
[Note again: global _variables_. Global constants are OK.
Sometimes, they have to be initialised. That is a problem,
and there are different solutions in different languages:
Ocaml, for example, enforces ordered linkage, and prohibits
forward references, C++ is more flexible but unsafe.
The problem of initialising things in the right order --
and detecting if there is such as order -- isn't the same
as insisting there is only one of something, and it isn't
the same as failing to pass a _variant_ argument explicitly.]
-- John (Max) Skaller, mailto:skaller_at_[hidden] 10/1 Toxteth Rd Glebe NSW 2037 Australia voice: 61-2-9660-0850 New generation programming language Felix http://felix.sourceforge.net Literate Programming tool Interscript http://Interscript.sourceforge.net
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk