Ways to avoid the needs and the musts of global variables??

Started by
86 comments, last by swiftcoder 13 years, 3 months ago
Thanks mate. It's restored the faith a bit to have an old sparring partner put his head above the parapet.

And I never actually expect to win one of these things. I do believe I'm right (or at least I have one version of arguably many rights), but I clearly never present my point the correct way as my main thrust is "get more done with less" which I would've thought should go down better that this! :)
------------------------------Great Little War Game
Advertisement
This thread might have run out of steam but let me throw in my bit.

I avoid global variables by not having any global variables.

Global variables are bad because globally mutable state is really really bad. What doesn't have state? Functions. Wait I need state somewhere! Static variables. No one complains about calling rand() from anywhere, though of course it stores state. The state is static, it can only be touched in that module (local, not global).

So the answer to the "global logger" scenario is:
void log_init(settings);void log_print(stuff);void log_shutdown();


If you need more than one, then it's not global anymore, and you need to handle the state.

If you need unit tests, just make an implementation of that module header that does nothing or has sane defaults.
Anthony Umfer
Ugh.. I shouldn't, but sleep isn't calling me yet so...

Quote:Original post by Rubicon
What I'm really on about is to write some code to turn the wheels and make it a method called CCar::TurnWheels(float dt);

As opposed to thinking about what refactoring I can do to enable me to inherit CWheel from CCarPart and then consider a design pattern that best serves a wheel turning scenario and then unit testing whether it worked or not. By the time you've read that sentence, I would have my wheels turning. Well, ok. Ish! :)


Here that sound? It's the sound of everything you just said wooshing over your head because you are CLEARLY commenting on things you do not understand and that alone makes your advice suspect at best.

I mean, seriously, that sentence (for lack of a better term) was nothing more than technical gibberish at best.

Lets try and take each thing you apprently have a problem with here;
- Refactoring; a process which happens AFTER THE FACT to remove redundant code and clean up intent. This isn't something you'd do before writing the 'TurnWheel' function anyway.

- Inhertiance; often abused, sure, but modern thinking has very much pushed away from this to composition. You'll probably say this is nothing new and I'd agree and beyond short related chains I prefer to avoid it anyway. Frankly anyone who knows anything wouldn't advocate CWheel:CCarPart anyway unless there was very solid reasoning for linking the two types. Maybe the uni graduates you have a bee in your bonnet about would try to do such a thing but not those of us who you also seem to think are 'wrong'.

- Design Patterns; seriously, stop even trying with this you are just showing your ignorance constantly. Design Patterns are nothing more than a common language used to talk about things. A good programmer would be aware of them but wouldn't say "I need to do X, what pattern can I use?" they would say "I need to do X, how can I solve it?" and then later, when discussing it with someone else might say "Yeah, I used a flyweight for that" or "I hooked it up as an observer to do....".

- Unit testing; Again, something you don't understand and yet feel qualified to 'poo poo' as much as possible. If you knew anything about it you'd realise that a unit test for something like that could be a simple as;

-- Set wheel to known rotation
-- Apply function with known time step
-- Check that wheel position is at expected location

Or; set number, call function, compare number.

Finally, as I think I mentioned before; you do not unit test everything. Many things, by their nature, can't be tested. Other things should be tested because you need to check they are still performing to spec. Writing a test for a critical component is generally trivial and testing in general is VITAL for any large project where a bug could be introduced so it can be caught early.

So, here is a suggestion from one professional to another; if you want to rubbish as subject at least have the decency to learn about it first. Attacking something from a position of ignorance (which is what you are doing) just makes you, the attacker, appear ignorant.

Don't want to learn about it?
Fine.
What to continue saying "Well, I don't use this"?
Fine.
Just, you know, drop the "its clearly rubbish, despite the fact I don't know anything about it" line.
Quote:Original post by CadetUmfer
No one complains about calling rand() from anywhere, though of course it stores state. The state is static, it can only be touched in that module (local, not global).


You know, this makes me wonder how 'rnd' would react when being called from multiple threads at around the same time?
I wonder if the CRT is thread safe in that case...

Quote:Original post by CadetUmfer
This thread might have run out of steam but let me throw in my bit.

I avoid global variables by not having any global variables.

Global variables are bad because globally mutable state is really really bad. What doesn't have state? Functions. Wait I need state somewhere! Static variables. No one complains about calling rand() from anywhere, though of course it stores state. The state is static, it can only be touched in that module (local, not global).

So the answer to the "global logger" scenario is:
*** Source Snippet Removed ***

If you need more than one, then it's not global anymore, and you need to handle the state.

If you need unit tests, just make an implementation of that module header that does nothing or has sane defaults.

Or, store the state in private member variables of an object, "init" it in the constructor, "shut_down" in the destructor/dispose, and have a well defined API. Create these objects where needed. Then invert that dependency.

Now it's impossible to call "Log" before "Init". Yay!
Quote:Original post by phantom
You know, this makes me wonder how 'rnd' would react when being called from multiple threads at around the same time?
I wonder if the CRT is thread safe in that case...


Thread safety is not something that needs to be checked for or handled in code. That's a documentation issue.

For example, the .NET CRT specifies that all static functions are thread safe, member functions aren't. If you call a instance functions from different threads, that's on you. (Well debug mode will probably catch you because they put checks in for it). In C, rand() just isn't thread safe, and they're not babying you if you forget.

So if you want your logger to be thread safe, you document that, and put locks/etc INSIDE the module. The interface doesn't change and the calling code knows nothing about it.

Quote:Original post by return0
Or, store the state in private member variables of an object, "init" it in the constructor, "shut_down" in the destructor/dispose, and have a well defined API. Create these objects where needed. Then invert that dependency.

Now it's impossible to call "Log" before "Init". Yay!


What? My modules often have a static bool init (or more often a state enum) that you can check in your global functions to make sure that the module is in the proper state to execute the function.

It doesn't need to be a "private member variable of a global singleton instance" to do that.
Anthony Umfer
Quote:Original post by CadetUmfer
In C, rand() just isn't thread safe, and they're not babying you if you forget.


Sorry, I probably wasn't being clear; the quoted bit was what I was simply pondering on. I'd never looked into what the C Runtime Time had to say with regards to thread safety of that function and had never considered the 'static' nature of its operation in that way.

Nothing more than a pondering on my part [smile]

(For the record, when it comes to designing my own stuff I'm well aware of the thread safety requirements of things [smile])

Quote:Original post by CadetUmfer
Quote:Original post by phantom
You know, this makes me wonder how 'rnd' would react when being called from multiple threads at around the same time?
I wonder if the CRT is thread safe in that case...


Thread safety is not something that needs to be checked for or handled in code. That's a documentation issue.

For example, the .NET CRT specifies that all static functions are thread safe, member functions aren't. If you call a instance functions from different threads, that's on you. (Well debug mode will probably catch you because they put checks in for it). In C, rand() just isn't thread safe, and they're not babying you if you forget.

So if you want your logger to be thread safe, you document that, and put locks/etc INSIDE the module. The interface doesn't change and the calling code knows nothing about it.

Quote:Original post by return0
Or, store the state in private member variables of an object, "init" it in the constructor, "shut_down" in the destructor/dispose, and have a well defined API. Create these objects where needed. Then invert that dependency.

Now it's impossible to call "Log" before "Init". Yay!


What? My modules often have a static bool init (or more often a state enum) that you can check in your global functions to make sure that the module is in the proper state to execute the function.

It doesn't need to be a "private member variable of a global singleton instance" to do that.

I'm not advocating a singleton, or a global instance. That would be insane. I'm advocating using an object, and injecting it where required. Then you don't need to "check static state flags" in global functions because you know it's in a good state. The runtime guarantees it.
Quote:Original post by phantom
- Design Patterns; seriously, stop even trying with this you are just showing your ignorance constantly. Design Patterns are nothing more than a common language used to talk about things. A good programmer would be aware of them but wouldn't say "I need to do X, what pattern can I use?" they would say "I need to do X, how can I solve it?" and then later, when discussing it with someone else might say "Yeah, I used a flyweight for that" or "I hooked it up as an observer to do....".


Just going to jump in on this point. I'm currently in my 3rd year of a computer science degree at a university I'd consider decently reputable. Given what I've seen of my fellow students, many of them probably WOULD fall into the sort of thinking that results in "I need to do X, what pattern can I use?" In fact, I think I've overheard almost exactly that quote during an in-class exercise, and both my midterm and final in my 3rd-year level software engineering course had an exam question (if not more than one!) that was basically "you need your program to do X, which of the following patterns would be best used here?" Now, mind you, the professor never explicitly encouraged this kind of thinking - but it would be difficult, given the material presented, to develop any other kind.

If this goes on at other institutions, then Rubicon's example, though obviously exaggerated and invalid at face value, is nevertheless an accurate portrayal of the way many of these people think. If my experience is anything to judge by, then many new graduates are not going to realize that design patterns are descriptive instead of prescriptive. My point is that if Rubicon's sole experience with "design patterns" is through these graduates, I would say that he's not to entirely blame for his warped perspective of them and the distaste for them that accompanies it. One might argue that he is to blame for not going out and educating himself on the matter, however. I had much the same view of design patterns before I learned better (by reading a thread here on GDNet, oddly enough).
Quote:Original post by return0
I'm not advocating a singleton, or a global instance. That would be insane. I'm advocating using an object, and injecting it where required. Then you don't need to "check static state flags" in global functions because you know it's in a good state. The runtime guarantees it.

Sure that's great. But we're talking about replacing something that was in global scope. The decision to put it in global scope was probably made because so many objects need access to it.

Yeah, you can have one single instance (managed/owned sanely, not as a singleton) and pass that around if you need that flexibility. But if that flexibility isn't worth it, use global functions with local state.

I equate it to logging in .NET. There are lots of nice logging frameworks that will do all sorts of cool stuff via dependency-injection. There's also Trace.WriteLine, which will write to a file or a console or a custom TraceListener that you make, so it covers 80% of the use cases.
Anthony Umfer

This topic is closed to new replies.

Advertisement