Question

I'm considering refactoring some Java code that passes around objects that implement the interface Map<String, Object>. The strings are all (as far as I know) from some fixed list of string literals. But as I said, I don't know which particular strings are used. I think it would be better if the Maps used an enum for the keys instead so that we get more type safety and the possibilities are forced to be listed in the source code.

The Maps are used in enough places that it is not practical to change all that code at once. If it's ever going to happen, it needs to happen gradually. How can this change be performed one section of code at a time, while still leaving the code in a working state?

Était-ce utile?

La solution

If you really need to introduce this gradually, you will temporarily have two kind of maps in your program: one Map<String, Object> type, and one Map<MyEnum, Object> type. A conversion from the latter to the former is straight-forward, but a conversion from the former to the latter may not be possible as long as MyEnum does not contain all the required keys.

So I guess your best bet is to identify subsections or classes in your code where you can introduce Map<MyEnum, Object> in isolation, and where the conversion of Map<String, Object> into Map<MyEnum, Object> is not required, whilst the reverse conversion does not result in an unacceptable performance hit. Just call a conversion function at the borders between the already refactored code and the unrefactored parts, as long as the refactoring is not complete.

Since we do not know the structure of your actual code, I guess there is not much more which can be recommended here, and how viable this approach is. Of course, there is always the standard recommendation for such a kind of refactoring - provide enough automated regression tests before you start. Note also that once you change the type of Map<String, Object> to Map<MyEnum, Object> for one variable, the compiler will tell you if you forgot to refactor a place in you code where now either MyEnum is required, or a conversion between those types. So I guess the refactoring is relatively safe and not too error-prone.

You can actually utilize the compiler error messages and unit tests for making different attempts for partial refacorings, and, whenever an attempt fails, undo all changes in source control and start with a different part of the codebase again. Ola Ellnestam and Daniel Brolund made a systematic approach of this refactoring technique and called it "The Mikado Method". I haven't read their book, so I cannot tell you if it is worth the price, but maybe the general idea is sufficient for you case.

Autres conseils

Your other option is to use a value object, combined with exposed properties representing your enum values.

  1. Create a value object that wraps your string value
  2. Overload your value object casting so you can cast between a value object and its string value (you should be able to use a value object anywhere a string can be used and vice versa).
  3. Expose constant or static members from your value object with your desired future enum names and values (ValueObject.String1)
  4. As you have time, convert each section of code to use the exposed properties, instead of the underlying string values if (stringValue.Equals("string#1") would become if(stringValue.Equals(ValueObject.String1)
  5. Once you are confident that you have changed an adequate amount of code, replace the value object with a true enum, using the same exposed property names of the value object

That should avoid breaking changes, while also gradually moving you closer to an enum solution. The casting on the value object will let your changes work with legacy code, while the exposed properties let you slowly refactor towards an enum solution.

Licencié sous: CC-BY-SA avec attribution
scroll top