Should an enumeration be constant in the lifetime of the solution?

Asked

Viewed 1,443 times

15

Modern languages often have a type of enumeration that is usually a range of related constants. Its members are usually constant. In most languages this is even guaranteed by the compiler.

But constancy in this case means that your values will not change during the execution of the application.

Besides this it should be obvious that no programmer will change the values of their members from one version to another. No one will change:

enum Direcao { Norte = 0, Sul = 1, Leste, 2, Oeste = 3 }

for

enum Direcao { Sul = 0, Norte = 1, Leste, 2, Oeste = 3 }

It would be crazy, of course. But what about changing the enumeration? Adding new members? Would that break her semantics? Technically it is possible to create a:

enum Acoes { Falar = 0, Cantar = 1, Gritar = 2 }

And then in future version modify it to:

enum Acoes { Falar = 0, Cantar = 1, Gritar = 2, Sussurrar = 3, Calar = 4 }
  • But should it? What problems could it cause?

  • If it is not a good idea, what solution should be adopted when it is known that the enumeration can "grow"?

  • And what to do if an enumeration has been created, used extensively in your application and it has now been discovered that new members are needed?

  • If I have a range of values that will be obtained at runtime (from an XML or DB, for example), in languages that is possible, it would be appropriate/would make sense to create an enumeration dynamically (with reflection, for example)?

  • It makes a difference if the enumeration is used to treat a set of flags? To be used with or:

enum Opcoes { Nenhuma = 0, Primeira = 1, Segunda = 2, Terceira = 4, Quarta = 8 }

That is, it would bring bigger or smaller problems if the programmer later added members Quinta = 16, Sexta = 32, etc..?

In a way this last doubt is: there are exceptions?

I’m talking about use for any language, but if there’s something important and different for some language, I’m particularly interested in C#.

Bonus point: Is there any language that can guarantee this? Or can it be guaranteed? I know there are languages that only compile one switch if all members of the enumeration used in it are evaluated in any way. This would help but not solve the problem.

  • 2

    As there are those who do not understand the reasons for closure, I need to explain that the question is essentially one. Despite having clearly 5 parts, they are related and in the background are helping to clarify the doubt. You can answer relatively briefly. And it doesn’t even need to be answered separately.

  • Clarify this last paragraph: guarantee what? That the enumeration does not change from one version of the program to another? (nonsense, only if the implementation was in hardware...) or you refer to something else?

  • It was more of a tease, so it’s not even in the question. It could be a check on the project if it had before a way, could not move anymore. It would not be a guarantee indeed. Just an indicator that it couldn’t be done. A field private is said to be guaranteed that it cannot be accessed externally to a class. But in practice this is possible. The compiler ensures that you cannot access it by accident. It would be the same, a guarantee that no accidents are made. I have ideas how to do this but I admit that I would not implement in a language. It would be very complicated.

  • I get it. In fact, absolute guarantees do not exist (after all software was made to be changed - hence the "soft"), but I miss the computer programs being more "self-aware". Not only does "this code come out and enter this newer one", but the realization that the system already existed in an older version, and a certain insight into its structure to help establish the implications of the change. To what extent this is feasible, I can’t say, but it doesn’t hurt to push the boundaries a little until you see where it goes... :)

  • I know that if we sat down to discuss some ideas, we would go days without sleep :)

4 answers

12


Constancy

Enumerations are only constants, more precisely a range of constants. Members must be constants and the enumeration must be constant.

Some languages define well what is a constant, others do not, but should. C# defines a constant as a value that must be immutable for the whole life of the solution. Indeed the documentation of enum clearly states that adding new members can be problematic. Especially when they are used in switch. The problem is not unique to C#.

There is even a more academic reason. By open-close, the entity must be closed for change and open for extension. Then the enumeration must be closed for change. For other reasons it is also closed to extension. It would make little sense. Causes polymorphism problems.

As has already been said, enum should be used to facilitate the life of the programmer, to avoid loose magic number. But if the value changes, there are cases that semantics can change together. Or it can bring unexpected results. You can’t treat information that can change as constant. Information that is only guaranteed that does not change during an implementation should not be considered constant. And some modern languages differentiate this (readonly in the C# and final in Java). Constancy is something else. enum is constant, not just reading. And there is a practical reason for this.

If the information is not constant, do not use it as if it were. There are other ways to represent information not contained. Use an immutable list, create a class with member data read only, but don’t use enum.

Examples

You can risk creating one enum Semaforo { Verde = 0, Amarelo = 1, Vermelho = 2 }. There will hardly be another color but if there is the semantics will change and the whole application will have to be changed. This is good, forcing a change into something that must be changed is positive.

In enum Cor { Azul = 0, Verde = 1, Vermelho = 2, Roxo = 3 }. It might be a good idea but will never need to add one Amarelo = 4? If you change, the semantics will probably not be changed. But problems may occur. This new element may not be dealt with. It seems to me to be more a list than an enumeration. Of course, you have to analyze the specific situation.

Another clear example: enum CreditCard { Visa = 0, MasterCard = 1 }. The chance of the application accepting another credit card flag is huge. This is clearly a list, possibly even changeable, and not an enumeration.

Usually enumerations are closer to mechanisms than business rules, but not always.

If you want to change an enumeration in Runtime is even worse. It is clear that it should be a changeable list. Java has the Enumset that helps in these cases.

The biggest concern should be its use in switchs. Some languages will even prevent compilation without treating all members. This is good, but there may be versioning problems. If it is guaranteed that it will not be used in switch the problem is minor but can be even worse because nothing will complain.

So it’s forbidden to use enum if he can change?

No need to overdo it. It has language style tones (usually C/C++ or other lower level) that allow this naturally. There it is worth understanding the problem and documenting. The enumeration user should be aware that it can be extended. So is not prohibited add a new value to the enumeration, it is only problematic and should be avoided. When you cannot avoid it, you should document very well, in advance, that she can be expanded with new members. You should only be careful not to use clear lists as enumerations.

And if I ever created an enumeration it was to be fixed and now need to add a new member?

Depends on where it’s used:

  • Public API - In most cases the only correct solution is to create a new API with this new enumeration. Unless you can guarantee that the rest of the API is unaffected in the way it interacts externally and that the enumeration was only used where the new member will not cause problems (perhaps because it was documented how it should be used and how it would be wrong), you cannot add anything at risk of breaking the API.
  • Small internal system of a single programmer - Let’s face it, this gives easy to solve with current tools, no problem change.
  • Relatively large internal system with large and variable team - Here fits a procedure:
  1. Create a new enumeration with the new member.
  2. Document this new enumeration, document (perhaps in code, with attribute/annotation of your obsolescence) that the old one can no longer be used.
  3. As far as possible find all occurrences of the old one and try to switch to the new one. Ask other developers for help.
  4. If possible, try logging in to use the old one to assess where more is being used. And try to fix these cases.
  5. When guaranteed no more use of the old, destroy it.

Completion

There is nothing that is forbidden. But there are things that must be avoided to the fullest. And it is necessary to know what to do when a rule must be broken. But just try to use enumerations to replace magic numbers in a well-defined range.

One reference of who understands (in the end).

See also that a enum is less useful than it seems: What is the advantage of using the ENUM type?.

11

TL;DR

Assuming an enumeration used to map domains defined in the business rules of a system, it does not need to be constant, but must respect the business rules and, if necessary, change along with them.

Because we use enumeration

The enumeration is used mainly for two reasons:

  1. Make life easier for the developer who doesn’t need to consult the system manual at all times to remember what values a field can assume and the meaning of those values.
  2. Reinforce the validity of constants at compile time. This avoids literals spread across the code that inevitably may lag behind the actual values used in the system.

We should change an enumeration?

When we use enumerations to map database values, we must ensure that they track values in the database.

These values usually come from a domain defined in the system documentation. The precaution of not changing them will be the analyst’s and not the developer’s.

If at a given time the responsible analyst decides that the code 7 no more shall be the equivalent of Parcela Paga and now should be Parcela em Atraso, he shall bear the consequences of the decision:

  1. In the database (update in tables, procedures, triggers, views, queries)
  2. Change in us Enums and refactoring of the impacted code
  3. Updating descriptions and fields on screens
  4. Etcetera, etcetera, etcetera.

The advantage of using an enumeration provided by the programming language is that if you change the description of it, you can easily find the affected points because they will no longer compile.

In addition, several Ides allow you to locate all points where a certain value is used not only by its name, but by analyzing the code semantics.

Techniques to deal with increasing enumerations

Polymorphism

Enumerations replace very simple constants. However, if the developer stops using features that the language offers and uses Enumas if they were constant, filling the code with IFs, the growth of the set values will be more prone to errors.

In Java, for example, instead of simply grouping IFs like this:

if (TipoFuncionario.Celetista == functionario.getTipo()) {
  //validar celetista
} else if (TipoFuncionario.QuadroPermanente == functionario.getTipo()) {
  //validar quadro permanente
} else ....

We could add a parameter to the constructor of Enum to force the developer to declare a validator for each added value.

public enum TipoFuncionario {

    Celetista(new CeletistaValidator()), 
    QuadroPermanente(new QuadroPermanenteValidator()), 
    Surfista(new SurfistaValidator());

    private FuncionarioValidator validator;

    private TipoFuncionario(FuncionarioValidator validator) {
        this.validator = validator;
    }

    public FuncionarioValidator getValidator() {
        return validator;
    }

}

So the whole place could just call the validation that:

funcionario.getTipo().getValidator().validate(funcionario);

Note that to add new values it is not necessary to change the existing code.

Note: This example was extracted from another answer from me here in the OR.

Define individual values for each constant

The Java language has a serious problem with Enums: it is not possible to define a value for each constant as quoted in the question.

You can even get an integer based on the order of the constant, but relying on the order of the constants declaration does not seem to me anything consistent.

So instead of doing something like:

public enum MeuEnum { valor1, valor2, valor3}

We should always associate value with a constant to map values of a domain:

public enum MeuEnum {
    valor1("V1"), valor2("V2"), valor3("V3");

    String id;
    private MeuEnum(String id) { 
        this.id = id; 
    }

    public String getId() {
        return id;
    }
}

In the example above, we can map values of text or number domains, without worrying about the order or the name used for each element of the Enum.

Cases where we shouldn’t use enumerations

There is a rule (Rule of Thumb) sort of like this:

If you’re gonna change, parametrize!

If a domain is constantly changing, perhaps an enumeration is not the most appropriate response.

It would probably be better to create a table in the database and allow the configuration of some features of each element.

This is much better than refactoring and recompiling code frequently.

Final considerations

Change is inevitable.

If very frequent, a more generic solution than Enums may be recommended.

Otherwise, the ideal is to use an OO approach according to the language used to reinforce the due treatment of each element at compile time.

And finally, we must bear in mind that the change of a Enum is usually the result of change in business, which must be properly analyzed to cause the least impact on the system.

Some analysts have the habit of never delete an element from a domain, just depress it and add a new option to be used in new versions of the system. I believe that it is not always possible, but it is a good approach to be able to maintain historical data.

10

What problems could this [modification of an enumeration] cause?

It depends on how this enumeration is modified. If the object uses enumeration to persist database data, for example, adding new elements may result in a problem if the integer number related to the value is not specified, or is modified.

If not specified, the programmer can add new values to the middle of the sequence. From his example:

enum Acoes { Falar, Cantar, Gritar }

Thus specifying, the language assumes the default value for Falar like 0, Cantar as 1 and Gritar as 2. Modifying as follows:

enum Acoes { Falar, Cantar, Gritar, Sussurrar, Calar }

It does not result in problems, since the elements were placed at the end. Already this modification:

enum Acoes { Falar, Cantar, Calar, Sussurrar, Gritar }

Makes Gritar have a new default value (4). By bringing the database data, records that were previously saved as Gritar appear as Calar.

If it is not a good idea, what solution should be adopted when it is known that the enumeration can "grow"?

As said before, two, one:

  • Specify the values of each enumeration item;
  • Do not change the initial order by adding new elements only at the end of the statement.

And what to do if an enumeration has been created, used extensively in your application and it has now been discovered that new members are needed?

The previous topic already answers this.

If I have a range of values that will be obtained at runtime (from an XML or DB, for example), in languages that is possible, it would be appropriate/would make sense to create an enumeration dynamically (with reflection, for example)?

No. This is to distort the meaning of enumeration, which exists to work with a set of fixed and limited values.

It is even possible to do this, but it would compromise the security of the data, and consequently of the whole system.

It makes a difference if the enumeration is used to treat a set of flags? To be used with or

In compiled and executed code, everything turns int or float. So, no.

  • 1

    +1. "This is to distort the meaning of enumeration, which exists to work with a set of fixed and limited values."

7

Modern languages often have an enumeration type that is usually a range of related constants.

Translation: Programming languages derived from C often have a type of enumeration that is usually a range of related constants.

The most notable difference appears in ML-inspired functional languages such as Haskell. In them algebraic types (Adts) can be used to describe enums constants:

data Color = Red | Green | Blue

and can also be used to describe data structures with more than one "case". In an analogy C is like a Union with a built-in Enum used as a "tag".

-- Uma árvore é ou uma folha contendo um inteiro
-- ou um nó interno que contem uma chave inteira e duas sub-árvores.
data Tree = Leaf Int | Node Int Tree Tree

To use Adts, these languages provide Pattern matching, which is a switch type that ensures that the branches are executed correctly (it is not possible to extract the "right child" from within the "sheet" branch")

-- Função que soma os elementos da árvore
sumTree tree =
    case tree of
        Leaf x = x
        Node x t1 t2 = x + (sumTree t1) + (sumTree t2)

Getting back to your questions:

Moreover it should be obvious that no programmer will change the values of its members from one version to another.

The main characteristic of an Enum is that the values are different and that we can make a switch on them. If we only care about it the value the compiler uses internally makes no difference.

If it is not a good idea, what solution should be adopted when it is known that the enumeration can "grow"?

Consider a data type with multiple cases (for example, leaf vs tree node) over which we want to perform various functions (for example, inserting and searching in the tree).

If we use an implementation with Adts/enums it’s easy to introduce new functions without tampering with the previous ones but to introduce a new case to the type we need to tinker with all existing implementations to treat the new case in the switch.

On the other hand, if we use an object-oriented implementation it is easy to add a new case to the type (create a new class implementing all existing methods) but it is difficult to create a new function (we need to add the method implementation in all existing classes).

In English this dichotomy is usually known as "Expression problem".

And what to do if an enumeration has been created, used extensively in your application and it has now been discovered that new members are needed?

The simplest solution is to simply add a new member to Enum and let the compiler warn you at all switch to forget to treat one of the cases. In C and in languages with a switch similar sometimes it is difficult to make the compiler generate these messages consistently, especially if Enum is converted to int in the middle of the process, but in Haskell certainly this is extremely natural to do.

If I have a range of values that will be obtained at runtime (from an XML or DB, for example), in languages that is possible, it would be appropriate/would make sense to create an enumeration dynamically (with reflection, for example)?

I find it wiser to separate the internal values used by the compiler from the values of the external representation. I would create a function to convert numbers in XML to elements in my Enum.

Does it matter if the enumeration is used to treat a set of flags? To be used with or binary.

In that case you’ll never make one switch or if-else about a value of enumeration. For me this is a real Enum and it’s just a coincidence that in C can use enums to set these flags.

  • Excellent complement and counterpoint.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.