How do I avoid both global variables and magic numbers?
I know and understand that global variables and magic numbers are things to avoid when programming, particularly as the amount of code in your project grows. I however can't think of a good way to go about avoiding both.
Say I have a pre-determined variable representing the screen width, and the value is needed in multiple files. I could do...
doSomethingWithValue(1920);
But that's a magic number. But to avoid that, I'd d开发者_运维百科o...
const int SCREEN_WIDTH = 1920;
//In a later file...
extern const int SCREEN_WIDTH;
doSomethingWithValue(SCREEN_WIDTH);
And now I'm using a global variable. What's the solution here?
In your second example, SCREEN_WIDTH
isn't really a variable1, it's a named constant. There is nothing wrong with using a named constant at all.
In C, you might want to use an enum if it's an integer constant because a const object isn't a constant. In C++, the use of a const object like you have in the original question is preferred, because in C++ a const object is a constant.
1. Technically, yes, it's a "variable," but that name isn't really "correct" since it never changes.
I would recommend defining the constant within a namespace in a header file. Then the scope isn't global and you don't need to redefine it (even with the extern keyword) in multiple places.
Why do you need to hard-code the screen width in the first place? Where does it come from? In most real applications, it comes from some system API,which tells you which resolution you're currently running, or which resolutions the system is capable of displaying.
Then you just take that value and pass it to wherever it is needed.
In short, on this line: doSomethingWithValue(SCREEN_WIDTH);
you're already doing it. SCREEN_WIDTH
might be a global in this particular example, but it doesn't have to be, because the function isn't accessing it as a global. You're passing the value to the function at runtime, so what the function sees isn't a global variable, it's just a plain function argument.
Another important point is that there's typically nothing wrong with immutable global data.
Global constants are typically fine. The problem occurs when you have mutable global state: objects that can be accessed throughout all of the application, and which might have a different value depending on when you look. That makes it hard to reason about, and causes a number of problems.
But global constants are safe. Take for example pi. It is a mathematical constant, and there's no harm in letting every function see that pi is 3.1415..... because that's what it is, and it's not going to change.
if the screen width is a hard-coded constant (as in your example), then it too can be a global without causing havoc. (although for obvious reasons, it probably shouldn't be a constant in the first place9
The main problem with global variables is when they're non-const. Non-changing globals aren't nearly such a concern as you always know their value anywhere they're used.
In this case, one sane approach is to create a constants namespace and put the constant values there, for reference anywhere in your program. This is much like your second example.
They have to be defined somewhere. Why not put the defines in a .h file or in a build file?
It's not a variable, it's constant, which is resolved at compile time.
Anyhow, if you don't like such a "floating" constant, you could put it in a namespace or whatever to collect all the constants of that type together. In some cases you may also consider an enum to group related constants.
Even better, if this can apply to your situation, avoid using a fixed predetermined screen width and use the correct APIs to retrieve it at runtime.
If a global is a must then it's typically a good idea to wrap it in a function and use the function to get the value.
Another post that might help
While the global in your second case is a fairly innocuous one, unless you're designing this for something where you're sure the screen width won't change, I'd use something to obtain the screen width dynamically (e.g., GetSystemMetrics
on Windows, or XDisplayWidth
on X).
One way to avoid this would be to set up your program as an object and have a property on the object (my_prog.screen_width). To run your program, have main() instantiate the object and call a ->go method on the object.
Java does this. A lot. Half-decent idea.
Bonus features for your program's expansion:
- When you make your program customizable some day, you can have it set the property in the constructor instead of recompiling everything.
- When you want to run two instances of your program next to each other in the same process, they can have different settings.
It's not a huge deal for a quick one-off program, though.
It's important to recognize that even global constants can sometimes cause problems if they need to be changed in future. In some cases, you may decide that you simply aren't going to let them change, but other times things may not be so easy. For example, your program may contain a graphic image which is sized to match the screen; what happens if it needs to be adjusted to run on a different-sized screen? Merely changing the named constant won't necessarily fix the graphic image that is embedded in the program. What if it needs to be able to decide at run-time what size screen to use?
It's not possible to deal with every conceivable contingency, and one shouldn't try too hard to protect against things that simply Aren't Going To Happen. Nonetheless, one should be mindful of what things can change, so as to avoid boxing oneself into a corner.
When designing a class, having a virtual readonly property which returns an unchanging value will allow future extensions of the class to return different values. Using a property rather than a constant may have some performance implications, but in some cases the flexibility will be worth it. It might be nice if there were a way to define virtual constants, and I see no reason it wouldn't be theoretically possible for .net to allow it, (include the constant values in the table with virtual method pointers) but so far as I know it doesn't.
精彩评论