Originally shared by Marjan Venema

Originally shared by Marjan Venema

How to store enums without losing your coding freedom

Enums are nice! You have found out about enumeration types and you clearly see the advantages they hold over plain, tired, old integers. They are specific, self documenting, type safe – you can’t pass one type of enum when another is expected. Oh yes, you…
http://softwareonastring.com/2014/08/17/how-to-store-enums-without-losing-your-coding-freedom

Comments

  1. LOL: I did basically the same in both Delphi and C# (:
    Great article, very well explained.

    ReplyDelete
  2. don't care about enums, love the identifier names! ((:

    ReplyDelete
  3. Dorin Duminica They are the WordPress gravatar types :)

    ReplyDelete
  4. Me likey this.

    However, a couple of points: the real problem with storing integers is that using Ord is rather poor form. Also, using the assigned values is, in my opinion, poor form because both are signs of laziness.

    I did have the problem and this is the solution I've come up with:

    Type
      TMyEnum = ( myEnum1, myEnum2,myEnum3 );

    const cMyEnum1 = 1;
               cMyEnum2 = 2;
               cMyEnum3 = 3;

    ....

    function ToInteger( AnEnum : TMyEnum ) : Integer;
    begin
       case AnEnum of
          myEnum1 : Result := 1;
          myEnum2: Result :- 2;
          myEnum3 : Result := 3;
      else
          Result := 1;
    end;

    Now this function can be overloaded for all kinds of enums and you only need a function each to return to your enum from the value.

    This, I have to say, works well especially if you are on anything before Delphi 2010 and is my suggested solution.

    It is true that remembering all of the values can be problematic, but surely - being us awesome programmers - we love consistency so each kind of value will have the same kind of concept, i.e. 1 for stuff that is pending, 2 for stuff that we are actually doing, etc. so it's not too bad if you keep consistent.

    I just thought I'd add my 2c :)

    Kind Regards,

    A

    ReplyDelete
  5. Andrea Raimondi why not:

    type
      TMonkeyEnum = (meCool = 1, meVeryCool, meSoCool); // everything following "meCool" is incremented by one

    function MonkeyEnumToInteger(const Value: TMonkeyEnum): Integer;
    begin
      Result := Integer(Value);
    end;

    function IntegerToMonkeyEnum(const Value: Integer): TMonkeyEnum;
    begin
      if (Value >= Integer(meCool)) and (Value <= Integer(meSoCool) then begin
        Result := TMonkeyEnum(Value);
      end else begin
        raise Exception.CreateFmt('oh snap! IntegerToMonkeyEnum(Value=%d) is out of bounds, you got bugs, happy hunting!!', [Value]);
      end;
    end;

    ReplyDelete
  6. Because, as the post clearly explains, if you move one of the values, you are well screwed :)

    A

    ReplyDelete
  7. Andrea Raimondi Thanks. Yes, your conversion functions achieve the same as my ToString and FromString functions: independence from the (compiler assigned) ordinal value of the enum in code. And yes, for Delphi versions without the extended RTTI such conversion functions are the best you can achieve if you want to maintain type safety elsewhere in your code. Personally I prefer specific function names to overloads as I feel it makes my code more explicit and thus readable. Still don't like converting to and from Integers though. Even if you can come up with a consistent numbering scheme, you'd still have to remember the values and I'd rather use my limited brain power to do other stuff. Especially when encountering my enum values out in the wild in some ini file or database ;) Storing enums as strings counters just about every drawback I can think of...

    ReplyDelete
  8. Hi!

    I think the main problem I see with it stems from the fact that where I work we have lots of what should be enums (and are really constants) and the number of data fixes I have to do.

    We sometimes uncover a bug that will have us do some data fixes, in those cases it's easy to mis-spell an enum value, whereas you have no problems with numbers. or at least a much lessened problem, because a mis-spelt number will usually either be too big (say a 10 instead of a 1) or wrong (say a 1' instead of 10).

    Also, don't underestimate the power of remembering the sequence: it really does help, trust me. I have managed to build one in my mind and it usually matches. I still reach for the constants, mind, but at least I know in what part of the process I am in an overwhelming majority of the time.

    All in all, I think it really depends on what you're doing with it.
    Also, and this might seem a minor point but it's not, consider the case of mobile devices: having strings means that it takes more bytes to store a value, which may be ok on massive systems but can become a problem on a lonesome and not-very-well-equipped Android phone.

    A

    ReplyDelete
  9. A few observations:

    1. Enums can and do change. Storing the enum by name makes simple name changes much more difficult. Storing the enum by ordinal value makes reordering the enum problematic as well.

    2. Enums have a habit of turning into classes. See http://sourcemaking.com/refactoring/replace-type-code-with-class Storing the enum by name makes it much more difficult to perform this refactoring.

    It may sound like more work but I think a better approach to storing enums is to create a separate table that maps the enum to the database:

    Id (PKey)
    EnumValue integer (maps to ordinal value of enum elements)
    EnumDescription varchar(20) (human readable and entirely separate from the enum names)

    Tables that need to refer to the enum use the id as a foreign key. The application will have to map the values of the table to the enum it represents.

    This solutions has a few advantages:
    1. The enum names can be changed without affecting the database.
    2. The enum descriptions can be changed without needing a recompile.
    3. The enum ordinal values can be changed and only one table in the database needs to be updated.
    4. In the event that the enum is refactored into a class hierarchy the table is a suitable starting point for either table per hierarchy or table per class mapping strategies.

    ReplyDelete
  10. Kenneth Cochran

    Yep. Enum name changes do indeed need to be dealt with, but is still to be preferred over the mess you get with ordinal values. And it need not be too difficult. Didn't show it in the blog post as it was getting too long already.

    Enum names can be changed without affecting the database when storing enum values as names just as well. You just need a proper way - in code - to deal with the old names coming back in from the database.

    Having a translation table is one way for a "dealing with name changes" scheme as well. The simplest way would be to use a second shadow enum type for that (one that keeps the old names) and a static array that links old and new names indexed on the shadow enum type. Way better than a case statement.

    Database translation tables are only interesting if you already store everything in a database. The system I deal with on a daily basis doesn't use one...

    And even then, putting "code stuff" external to the code means that the functioning of my code has dependencies on external content. That makes alarm bells go off in my head.

    Where you see an advantage in being able to change the ordinal values of enums and "only one table needs to be changed", I see a disadvantage in having to change anything outside the code at all when I change an enum. It's something that can and, by Murphy's law, will be forgotten. Probably when it is least convenient.

    If you are going to use a translation table, code it. And make sure that you cannot change the (to be) stored values without a unit test raising an alarm.

    Enum descriptions are something I tend to put in static arrays indexed by the enum type. Haven't yet had the desire to put them external to the code, but I can see the advantage of it. I would then probably put them in some resource or text files. Not in a database (because most of the time I don't use one).

    Haven't personally converted an enum to a class yet in all my coding life... But maybe that is because of the kind of applications I deal with, or because I don't mind tiny classes? Dunno. Maybe I'll know after reading that refactoring article. Thanks for that.

    ReplyDelete
  11. Sorry that link should have http://sourcemaking.com/refactoring/replace-type-code-with-subclasses
    But there are a few other refactoring that replace an enum with one or more classes.

    ReplyDelete

Post a Comment