I recently needed to add some code to our project that pulled some information out of configuration. The configuration is broken into sections and values, and I need to give the API a section name and a key name to retrieve the configuration value:
string maxCouponLimit = configManager.GetValue("storeOptions", "maxCouponLimit");
The "storeOptions" configuration section in the config file had several other config values, so when I searched the solution for "storeOptions", I found several dozen references of the exact same code getting different values out of the same section. This was a prime candidate for the Replace Magic Number with Symbolic Constant refactoring. When I look around the Framework Class Library, I couldn't find any constants, however. Why is this?
Refactoring to a constants class
Let's say I want to put the "storeOptions" value in a constants class:
public static class ConfigurationSections
{
public const string STORE_OPTIONS = "storeOptions";
}
I have defined both a constant and a readonly static field for the configuration value. To illustrate how constants work first, I'll change all references to "storeOptions" to use "ConfigurationSections.STORE_OPTIONS" instead. As it turns out, Joe from the accounting team also uses this configuration section in his assemblies, so I work with Joe to change all of his references to go to the constants class also.
Now for a change
As we add more and more configuration values throughout development, it becomes clear to our team that the "storeOptions" section name has become a little misleading and we want to rename the section to "storePaymentOptions". Since Joe's assembly references our configuration assembly, all we need to do is change the constant value, rebuild, and redeploy, right? Wrong. When we redeploy, Joe's assembly (which he can't rebuild yet for some organizational reasons) breaks, and still references "storeOptions". How is this possible when both of our assemblies are pointing to the same value?
Compile time versus run time
The problem is that the value of constants are determined at compile time. That means that the value for the constant is actually substituted in the IL during compilation, so the final IL code never references the original constant. If I change the constant value, every single assembly referencing the constant will need to be recompiled. That's not what I'd like to do. I just want to change the value and have all dependent assemblies pick up the new value.
Instead of using a constant, let's use a static field instead:
public static class ConfigurationSections
{
public static readonly string StoreOptions = "storeOptions";
}
Static field values are determined at run-time instead of compile time. I can change the value of the static field, only recompile the configuration assembly, and now all dependent assemblies referencing my static field will get the correct value!
Conclusion
So the moral of the story is to always use static fields instead of constants. The compile-time nature of constants is one of those "gotchas" that can really kill you with crazy errors at deployment time. Additionally, static fields should be named using PascalCasing. Constants should be reserved for values that can and will never change.
No comments:
Post a Comment