How Enums Spread Disease – And How to Cure It (2012)
codecraft.co
codecraft.co
The solution of creating a bunch of tuples (or structs)...well, that's just creating a bunch of objects. The language and paradigm already has a way of dealing with these that's a bit more flexible and powerful than tuples.
Essentially in Java that's actually what you're doing, and if you override the constructor of the enum you get a nice version of what the author is describing (but with more flexibility than a straight up Map)
(oh, bthornbury covered this in his comment)
My recent facebook post is, verbatim: Java enums are shitty classes.
Applying polymorphism often creates an nasty inversion. If the situation is such that you need the algorithm to mutate "outside" data based on "inside" (the object) information, you end up calling a method on the object that "passes in the world." This is a particularly terrible form of coupling since it obscures the straight-line path and allows new code to break old code when it gets used out of context.
The trick the article describes is more like what you would do if you were modeling this problem in SQL and decided to normalize the data into an additional table: Each enum now points to a static "program" containing various true-false properties - e.g. trucks are large, cars are not. Instead of checking the enum directly across all business logic, you can use it to look up an associated property. Subsequently, your test doesn't have to be updated to account for new types of vehicles.
Algebraic data types (such as Haxe's enum) that can do a compile-time coverage check and allow parameterization are another way to attack this problem.
The idea behind the solution is definitely ok: you define a type with a bunch of immutable properties describing it. But surely there must be more elegant/easier to read/easier to understand ways to do this than to have a bunch of macros/includes inside enums and whatnot? Moreover the TUPLE macro is declared twice with the same arguments (and then the arguments are repeated once more) so that means extending the type has to be done in 2 places which is exactly one of the things warned about earlier.
I felt like the OP made a great point about enums and I agree.
Not that I think macros are a good thing– 99% of the time they're best avoided– but dismissing them because 'performance isn't important' is basically a non sequitur.
Furthermore, `__LINE__` and `__FILE__` style macros are a pretty safe subset of macros that aren't really representative of the problems with macros. Compare:
#include <stdlib.h>
#include <stdio.h>
#include "my_macro_library.h"
int main()
{
int x = 0;
printf("%i", increment(x));
printf("%i", __LINE__);
return EXIT_SUCCESS;
}
There are a ton of different implementations of an `increment` macro which are going to cause undefined behavior here. Contrast with the `__LINE__` macro: it's implemented by the compiler and has unsurprising, well-defined behavior.This isn't really an argument in favor of macros so much as an argument against minimally-featured languages like C.
To be clear, I'm not against using macros in every case. I think a lot of the danger of macros in C comes from C's syntax: macros become a much more powerful tool when you're using a homoiconic language like Lisp. There are a few different projects which have attempted to write languages with C-like semantics and Lisp-like syntax, but sadly none have gained traction.
Also consider inheiritance and enums. Derived classes have to jump through hoops to extend enum space e.g. enum { tractor = truck+1, crane, bulldozer}; where truck was the last enum value in the base class.
With the tuple method, you just add some more tuples.
enum class VehicleType
{
Car,
Bicycle
};
using tuple = std::tuple< std::string, int, int >;
using map = std::map< VehicleType, tuple >;
const map VehicleTypes =
{
{ VehicleType::Car, tuple( "car", 4, 10 ) },
{ VehicleType::Bicycle, tuple( "bicycle", 3, 20 ) }
};
const tuple& get_it( VehicleType type )
{
static tuple def( "unknown", -1, -1 );
auto it = VehicleTypes.find( type );
return it == VehicleTypes.end() ? def : it->second;
}
std::string getVehicleTypeName( VehicleType type )
{
return std::get< 0 >( get_it( type ) );
}Isn't that kind of the definition of an Enum vs. Struct vs. Class/Object? And the problem lies with what enums are being used for in some cases, rather than enums being a bad practice?
In Java, you can take this a stop further, and use enums basically as enumerated instances of a class. Example:
public enum Stuff { Stuff1(param1), Stuff2(param2);
private Object param;
private Stuff(Object param){
this.param = param;
}
public getParam(){
return this.param;
}
}Now you can call Stuff1.getParam();
Isn't it half the point of java's enums? (the other half being type-safe enums).
This article really lacks of OOP concept. In the first example the enum is a property on the 'vehicule' object, it would be way better to add all the properties needed to that class instead of creating a weird 'VehicleTypeTuple' struct...
- constructors, private fields, methods, addressing the "separations of concerns" - how about Enum.valueOf(enumClass, text.toUpperCase()) to avoid the "classic shadow array of string literals"?
Here is an example of what can be achieved by Java enums
enum Type {
ANIMAL(null),
MAMMAL(ANIMAL),
DOG(MAMMAL) {
public void makeNoise() {
System.out.println("Woof");
}
},
CAT(MAMMAL) {
public void makeNoise() {
System.out.println("Meow");
}
};
private Type parent;
Type(Type parent) {
this.parent = parent;
}
public boolean isA(Type type) {
if(this == type) return true;
if(parent != null) return parent.isA(type);
return false;
}
public void makeNoise() { }
}
assert Type.DOG.isA(Type.MAMMAL) == true;
assert Type.CAT.isA(Type.DOG) == false;
Type.CAT.makeNoise(); // "Meow"There is very little reason to use an enum over classes in this example. It's rare that I've seen a Java class style enum that wouldn't have been better served by classes (including some that I've written myself and regretted!)
I got bit pretty hard before trying to design an annotation-driven convention for a library I was writing (I had been writing a lot of Python for a while) and realized I'd have to squash a bunch of my carefully arranged classes into an enum. You run into similar problems when marshalling your objects to, say, a Thrift structure definition.
If the fact that enums are closed ever presents a problem, then it's a good indication that you shouldn't be using enums. That's not a problem with enums, it's just a bad design decision that needs to be reversed.
Agreed that Vehicle is a poor choice for an enum, because it's something that isn't really closed. But if you're modelling something like states of a TCP connection, there's nothing dangerous about it being closed.
In C# you can use extension methods to keep all your switch-cases in one place, but it takes a little more work to enforce this with private visibility. I haven't thought through this completely, but my first attempt might be to replace the enum with a class with a private constructor and expose public static const instances, or maybe even hide the instances behind a factory method. I haven't tried this in practice, however, so I'm not sure what the implications of that approach are.
There are also the downsides of the table approach. What does avg_km_per_liter mean for e-cars? With multiple classes they can have different fields.
Everything is a tradeoff.
So, I guess, that's to say, this is a little bigger than just enums.