简体   繁体   English

如何避免全局变量和幻数?

[英]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 do... 但是要避免这种情况,我会...

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 variable 1 , it's a named constant . 在第二个示例中, SCREEN_WIDTH实际上不是变量 1 ,而是一个命名常量 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. 在C语言中,如果它是一个整数常量,则可能要使用一个枚举,因为const对象不是常量。 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. 在C ++中,最好像在原始问题中一样使用const对象,因为在C ++中,const对象是常量。

1. Technically, yes, it's a "variable," but that name isn't really "correct" since it never changes. 1.从技术上讲,是的,它是一个“变量”,但是该名称并不是真正的“正确”,因为它永远不会更改。

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. 然后,该范围就不是全局的,并且您无需在多个地方重新定义它(即使使用extern关键字)。

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. 在大多数实际应用程序中,它来自某些系统API,该API告诉您当前正在运行的分辨率或系统能够显示的分辨率。

Then you just take that value and pass it to wherever it is needed. 然后,您只需获取该值,然后将其传递到所需的任何地方即可。

In short, on this line: doSomethingWithValue(SCREEN_WIDTH); 简而言之,在这一行: 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. 在此特定示例中, SCREEN_WIDTH可能是全局SCREEN_WIDTH ,但不一定是全局变量,因为该函数未将作为全局变量进行访问 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. 以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. 这是一个数学常数,而且也让中每个函数看到pi为3.1415任何伤害.....因为这是它什么,它不会改变。

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 (尽管出于明显的原因,它最初可能不应该是一个常数9

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? 为什么不将定义放在.h文件或构建文件中?

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. 更好的是,如果这适合您的情况,请避免使用固定的预定屏幕宽度,并使用正确的API在运行时检索它。

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 另一则可能有帮助的帖子

虽然在第二种情况下,全局是一个无害的全局,但是除非您为确定屏幕宽度不会改变的东西设计该全局,否则我将使用某些东西来动态获取屏幕宽度(例如, GetSystemMetrics on Windows,或X上的XDisplayWidth )。

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). 避免这种情况的一种方法是将程序设置为对象,并在该对象上具有属性(my_prog.screen_width)。 To run your program, have main() instantiate the object and call a ->go method on the object. 要运行程序,请让main()实例化该对象并在该对象上调用-> go方法。

Java does this. Java执行此操作。 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. 如果有一种方法可以定义虚拟常量,那可能会很好,而且我认为没有理由在理论上不允许.net允许它(包括带有虚拟方法指针的表中的常量值),但到目前为止我知道不是。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM