Domanda

During development phase, there are certain variables which need to be the fixed in the same run, but may need be modified over time. For example a boolean to signal debug mode, so we do things in the program we normally wouldn't.

Is it bad style to contain these values in a constant, i.e. final static int CONSTANT = 0 in Java? I know that a constant stays the same during run time, but is it also supposed to be the same during the whole development, except for unplanned changes, of course?

I searched for similar questions, but did not find anything that matched mine exactly.

È stato utile?

Soluzione

In Java, static final constants can be copied, by the compiler, as their values, into code which uses them. As a result of this, if you release a new version of your code, and there is some downstream dependency that has used the constant, the constant in that code will not be updated unless the downstream code is recompiled. This can be a problem if they then make use of that constant with code that expects the new value, as even though the source code is right, the binary code isn't.

This is a wart in the design of Java, since it's one of very few cases (maybe the only case) where source compatibility and binary compatibility aren't the same. Except for this case, you can swap out a dependency with a new API-compatible version without users of the dependency having to recompile. Obviously this is extremely important given the way in which Java dependencies are generally managed.

Making matters worse is that the code will just silently do the wrong thing rather than producing useful errors. If you were to replace a dependency with a version with incompatible class or method definitions, you would get classloader or invocation errors, which at least provide good clues as to what the problem is. Unless you've changed the type of the value, this problem will just appear as mysterious runtime misbehavior.

More annoying is that today's JVMs could easily inline all the constants at runtime without performance penalty (other than the need to load the class defining the constant, which is probably being loaded anyway), unfortunately the semantics of the language date from the days before JITs. And they can't change the language because then code compiled with previous compilers won't be correct. Bugward-compatibility strikes again.

Because of all this some people advise never changing a static final value at all. For libraries which might be distributed widely and updated in unknown ways at unknown times, this is good practice.

In your own code, especially at the top of the dependency hierarchy, you will probably get away with it. But in these cases, consider whether you really need the constant to be public (or protected). If the constant is package-visibility only, it's reasonable, depending on your circumstances and code standards, that the entire package will always be recompiled at once, and the problem then goes away. If the constant is private, you have no problem and can change it whenever you like.

Altri suggerimenti

Anything in your source code, including const declared global constants, might be subject to change with a new release of your software.

The keywords const (or final in Java) are there to signal to the compiler that this variable will not change while this instance of the program is running. Nothing more. If you want to send messages to the next maintainer, use a comment in source, that's what they are there for.

// DO NOT CHANGE without consulting with the legal department!
// Get written consent form from them before release!
public const int LegalLimitInSeconds = ...

Is a way better way to communicate with your future self.

We need to distinguish two aspects of constants:

  • names for a values known at development time, which we introduce for better maintainability, and
  • values that are available to the compiler.

And then there's a related third kind: variables whose value does not change, i.e. names for a value. The difference between an these immutable variables and a constant is when the value is determined/assigned/initialized: a variable is initialized at runtime, but the value of a constant is known during development. This distinction is a bit muddy since a value may be known during development but is actually only created during initialization.

But if the value of a constant is known at compile-time, then the compiler can perform computations with that value. For example, the Java language has the concept of constant expressions. A constant expression is any expression that consists only of literals of primitives or strings, operations on constant expressions (such as casting, addition, string concatenation), and of constant variables. [JLS §15.28] A constant variable is a final variable that is initialized with a constant expression. [JLS §4.12.4] So for Java, this is a compile-time constant:

public static final int X = 7;

This becomes interesting when a constant variable is used in multiple compilation units, and then the declaration is changed. Consider:

  • A.java:

    public class A { public static final int X = 7; }
    
  • B.java:

    public class B { public static final int Y = A.X + 2; }
    

Now when we compile these files the B.class bytecode will declare a field Y = 9 because B.Y is a constant variable.

But when we change the A.X variable to a different value (say, X = 0) and recompile only the A.java file, then B.Y still refers to the old value. This state A.X = 0, B.Y = 9 is inconsistent with the declarations in the source code. Happy debugging!

This doesn't mean that constants should never be changed. Constants are definitively better than magic numbers that appear without explanation in the source code. However, the value of public constants is part of your public API. This isn't specific to Java, but also occurs in C++ and other languages that feature separate compilation units. If you change these values, you will need to recompile all dependent code, i.e. perform a clean compile.

Depending on the nature of the constants, they might have led to incorrect assumptions by the developers. If these values are changed, they might trigger a bug. For example, a set of constants might be chosen so that they form certain bit patterns, e.g. public static final int R = 4, W = 2, X = 1. If these are changed to form a different structure like R = 0, W = 1, X = 2 then existing code such as boolean canRead = perms & R becomes incorrect. And just think of the fun that would ensue were Integer.MAX_VALUE to change! There is no fix here, it's just important to remember that the value of some constants really is important and cannot be changed simply.

But for the majority of constants changing them is going to be fine as long as the above restrictions are considered. A constant is safe to change when the meaning, not the specific value is important. This is e.g. the case for tunables such as BORDER_WIDTH = 2 or TIMEOUT = 60; // seconds or templates such as API_ENDPOINT = "https://api.example.com/v2/" – though arguably some or all of those ought to be specified in configuration files rather than code.

A constant is only guaranteed to be constant for the life of the application runtime. As long as that is true, there is no reason to not take advantage of the language feature. You just need to know what the consequences are for using a constant vs. compiler flags for the same purpose:

  • Constants take up application space
  • Compiler flags do not
  • Code turned off by constants can be updated and changed with modern refactoring tools
  • Code turned off by compiler flags cannot

Having maintained an application that would turn on drawing bounding boxes for shapes so that we could debug how they were drawn, we ran into a problem. After refactoring, all the code that was turned off by compiler flags wouldn't compile. After that, we intentionally changed our compiler flags to application constants.

I'm saying that to demonstrate that there are trade-offs. The weight of a few booleans wasn't going to make the application run out of memory or take up too much space. That might not be true if your constant is really a large object that essentially has a handle to everything in your code. If it doesn't take care to remove all references it holds to an object after it is no longer needed, then your object may be the source of a memory leak.

You need to evaluate the use case and why you would want to change the constants.

I'm not a fan of simple blanket statements, but in general your senior colleague is correct. If something is bound to change often, it might need to be a configurable item. For example, you could probably convince your colleague for a constant IsInDebugMode = true when you want to protect some code from being broken. However, some things may need to change more often than you release an application. If that is the case, you need a way of changing that value at the appropriate time. You can take the example of a TaxRate = .065. That may be true at the time you compile your code, but due to new laws it can change before you release the next version of your application. That is something that needs to be updated either from some storage mechanism (like file or database)

The const, #define or final is a compiler hint (note that the #define isn't actually a hint, its a macro and significantly more powerful). It indicates that the value will not change over the execution of a program and various optimizations may be done.

However, as a compiler hint, the compiler does things that may not always be expected by the programmer. In particular, javac will inline a static final int FOO = 42; so that anywhere that FOO is used, the actual compiled byte code will read 42.

This isn't too big of a surprise until someone changes the value without recompiling the other compilation unit (.java file) - and the 42 remains in the byte code (see is it possible to disable javac's inlining of static final variables?).

Making something static final means that it is that and forever more will be that and changing it is a Really Big Deal - especially if its anything but private.

Constants for things like final static int ZERO = 0 isn't a problem. final static double TAX_RATE = 0.55 (aside from being money and double is bad and should be using BigDecimal, but then its not a primitive and thus not inlined) is a problem and should be examined with great care for where it is used.

As the name suggests, constants should not change during runtime and in my opinion constants are defined to not change for a long term (you may look at this SO question for more information.

When it comes to the need of flags (e.g. for development mode) you should instead use a config file or startup parameter (many IDEs support configuring startup parameter on a per-project-base; refer to the relevant documentation) to enable this mode - this way you keep the flexibility to use such a mode and you cannot forget to change it everytime the code goes productive.

Being able to be changed between runs is one of the most important points of defining a constant in your source code!

The constant gives you a well-defined and documented (in a sense) location to change the value whenever you need to during the lifetime of your source code. It is also a promise that changing the constant at this location will actually change all occurences of whathever it stands for.

As an adverse example: it would not make sense to have a constant TRUE which evalutes to true in a language which actually has the true keyword. You would never, ever, not even once, declare TRUE=false except as a cruel joke.

Of course there are other uses of constants, for example shortening code (CO_NAME = 'My Great World Unique ACME Company'), avoiding duplication (PI=3.141), setting conventions (TRUE=1) or whatever, but having a defined position to change the constant is certainly one of the most prominent ones.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
scroll top