Are hard-coded STRINGS ever acceptable?

后端 未结 9 974
臣服心动
臣服心动 2021-01-07 04:21

Similar to Is hard-coding literals ever acceptable?, but I\'m specifically thinking of \"magic strings\" here.

On a large project, we have a table of configuration o

9条回答
  •  悲哀的现实
    2021-01-07 04:45

    In my experience, this kind of issue is masking a deeper problem: failure to do actual OOP and to follow the DRY principle.

    In a nutshell, capture the decision at startup time by an appropriate definition for each action inside the if statements, and then throw away both the config_options and the run-time tests.

    Details below.

    The sample usage was:

    if (config_options.value('FOO_ENABLED') == 'Y') ...
    

    which raises the obvious question, "What's going on in the ellipsis?", especially given the following statement:

    (Of course, this same option may need to be checked in many places in the system code.)

    Let's assume that each of these config_option values really does correspond to a single problem domain (or implementation strategy) concept.

    Instead of doing this (repeatedly, in various places throughout the code):

    1. Take a string (tag),
    2. Find its corresponding other string (value),
    3. Test that value as a boolean-equivalent,
    4. Based on that test, decide whether to perform some action.

    I suggest encapsulating the concept of a "configurable action".

    Let's take as an example (obviously just as hypthetical as FOO_ENABLED ... ;-) that your code has to work in either English units or metric units. If METRIC_ENABLED is "true", convert user-entered data from metric to English for internal computation, and convert back prior to displaying results.

    Define an interface:

    public interface MetricConverter {
        double toInches(double length);
        double toCentimeters(double length);
        double toPounds(double weight);
        double toKilograms(double weight);
    }
    

    which identifies in one place all the behavior associated with the concept of METRIC_ENABLED.

    Then write concrete implementations of all the ways those behaviors are to be carried out:

    public class NullConv implements MetricConverter {
        double toInches(double length) {return length;}
        double toCentimeters(double length) {return length;}
        double toPounds(double weight)  {return weight;}
        double toKilograms(double weight)  {return weight;}
    }
    

    and

    // lame implementation, just for illustration!!!!
    public class MetricConv implements MetricConverter {
        public static final double LBS_PER_KG = 2.2D;
        public static final double CM_PER_IN = 2.54D
        double toInches(double length) {return length * CM_PER_IN;}
        double toCentimeters(double length) {return length / CM_PER_IN;}
        double toPounds(double weight)  {return weight * LBS_PER_KG;}
        double toKilograms(double weight)  {return weight / LBS_PER_KG;}
    }
    

    At startup time, instead of loading a bunch of config_options values, initialize a set of configurable actions, as in:

    MetricConverter converter = (metricOption()) ? new MetricConv() : new NullConv();
    

    (where the expression metricOption() above is a stand-in for whatever one-time-only check you need to make, including looking at the value of METRIC_ENABLED ;-)

    Then, wherever the code would have said:

    double length = getLengthFromGui();
    if (config_options.value('METRIC_ENABLED') == 'Y') {
        length = length / 2.54D;
    }
    // do some computation to produce result
    // ...
    if (config_options.value('METRIC_ENABLED') == 'Y') {
        result = result * 2.54D;
    }
    displayResultingLengthOnGui(result);
    

    rewrite it as:

    double length = converter.toInches(getLengthFromGui());
    // do some computation to produce result
    // ...
    displayResultingLengthOnGui(converter.toCentimeters(result));
    

    Because all of the implementation details related to that one concept are now packaged cleanly, all future maintenance related to METRIC_ENABLED can be done in one place. In addition, the run-time trade-off is a win; the "overhead" of invoking a method is trivial compared with the overhead of fetching a String value from a Map and performing String#equals.

提交回复
热议问题