6 ms·
How Enums Spread Disease – And How to Cure It (2012)
- karmakaze 12y agoThe title is a bit misleading--maybe 'plain enums'. In Java for instance, all the solutions can all be encapsulated within the language's 'enum' type.
- geonik 12y agoI second that; Java has solutions for all problems described by this post. - 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"
- ajanuary 12y agoThis demonstrates a different problem - enums are closed. If I want to add an Owl, I can't do it without editing Type. This might break the API contract (if you own the library) or not be possible (if it's a third part library). 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!)
- jdmichal 12y agoFor this example, I agree that it makes very little sense. The question you really need to ask yourself whenever you are making an `enum` is: Does it make sense that this type is restricted to a predefined selection of instances? There are times when the legitimate answer to this question is "yes", and in those cases go ahead and make an `enum` and enjoy its benefits.
- devonkim 12y agoHistorically, I've used rich enums in Java to be able to use switch statements to reason about a system state and as poor man's type pattern matching. Sometimes the switch is handy for making a state machine clearer, and sometimes it makes it easier to explain the relationships of these states as an ordered list. Another important use of enums is if you're trying to create a rich set of annotations that take parameters, which would force you into dropping a lot of the Java OOP typing available. I can't have a closure or even an anonymous class as an argument to parameterized annotations - and this is definitely by design. 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.
- peeters 12y agoThe main point of an enumerated type is that it should be closed. You control all instances of the type, so that you and the compiler can benefit from the fact that it's closed. 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.
- deleted 12y ago[deleted]
- bthornbury 12y agoThis is a good look at the dangers of diffusing semantic knowledge in general. His approach is much more true to OOP paradigms. 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();
- masklinn 12y ago> In Java, you can take this a stop further, and use enums basically as enumerated instances of a class. Isn't it half the point of java's enums? (the other half being type-safe enums).
- powertower 12y agoSo in other words, only use Enums to declare a set of 1-dimensional items that can't be broken down further (i.e., item is a property, not an object). Otherwise, you are going to run into trouble. 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?
- masklinn 12y agoDepends on the language. Java enums can hold arbitrary attributes (the "cure" is built-in), and algebraic data types go significantly further as variants can have completely different fields.
- gkanapathy 12y agoSeems to me that the whole point of OOP is programming is to encapsulate this information. An enum is basically just a degenerate class. If you're finding that you are doing a case on your enum all over the place, you should probably be defining them as objects (or possibly subclasses). 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.
- altcognito 12y ago> The solution of creating a bunch of tuples (or structs)...well, that's just creating a bunch of objects. 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)
- spacemanmatt 12y ago> An enum is basically just a degenerate class My recent facebook post is, verbatim: Java enums are shitty classes.
- westoncb 12y agoThat's how I thought about it when reading. I kept wondering whether I was missing something, but: the things to be modeled by programs have a continuum of complexities, but there are only a small number of language constructs to use for modeling, which are each more/less suitable based on the complexity of the thing to be modeled (for one thing). The 'language constructs' from simple to complex are something like: enum, struct, class. It sounds like the author wants something that's between struct and class, though personally, I don't see why doesn't just use a class/objects.
- chipsy 12y agoThe meaning is basically different. An enumerated value doesn't always carry around additional state, neither is it expected to carry around implementation information. 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.
- Enthouan 12y agoI guess the way to solve this disease is just to use Enum for what they are supposed to be used for. Which is storing a state, that's it. 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...
- arielby 12y agoThis is the standard style of programming in functional programming languages. While it isn't object-oriented (being its dual), I don't see why is it so bad (I wouldn't like a single `Vehicle` class to have methods handling lane use in each state).
- stinos 12y agoI was nodding in agreement throughout the article until the solution was presented, or at least the implementation of it. Maybe it's truly genius and am I just not understanding it properly, but the fact I had to go over it multiple times to grasp wtf is going one isn't exactly a good sign, I think. 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.
- JoeAltmaier 12y ago2 places is better than N places. At the cost of slightly heavier enum declarations (2 places), you get rid of tests throughout the code for particular enum values. 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.
- stinos 12y agoYes of course inheritance (if you can even call it that) is problematic and indeed one, if not the whole, point of the solution is having to add an antry means having to do it in one place only, but my question remains: does it have to be with a macro? I threw something together and this seems sort of equivalent but (imo) with less of the uglyness? 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 ) ); }
- kerkeslager 12y agoIt has been a while since I wrote Java, but if my memory is correct, Java has a much cleaner solution to this. In Java an enum is just a class with a fixed number of instances, each with a constant name. You can make the instances private and expose all of your behavior through the interface of the class, using a "factory" method to find the correct instance (I say factory in quotes because it doesn't create the instance, it just finds the correct instance, which already exists). There's nothing stopping you from doing a switch-case against the instances, but at least if someone does that it will be limited to the enum class. And with some discipline you can define all the properties in the enum's constructor so no switch-cases are necessary. This mitigates all the OP's concerns, I think. 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.
- spacemanmatt 12y agoOne of the antipatterns I see in db schemas is over-use of just one field to indicate status or action, if not both. It's the same basic deal of overloading enums as the article demonstrated -- every instance increases your pain of complexity. So, I guess, that's to say, this is a little bigger than just enums.
- jpatte 12y agoThe solution presented here basically consists of implementing a VehicleType class with a bunch of static instances without using the class syntax. I can't decide if this was a deliberate decision from the author or if he just didn't realize this.
- JoeAltmaier 12y agoMaybe to be C-compatible?
- jblow 12y agoThis does not read to me like good advice from an experienced programmer.
- justicezyx 12y agoCommon sense in OOP rebranded as some magic attack on enum?...
- yuriks 12y agoIn my experience, most of the evils of enums can be traced back to the lack of exhaustive pattern matching in C++/Java. When programming in Rust or Haskell, enums are used much more often, and the exhaustiveness enforcement by the compiler means that these kinds of use cases tend to be encapsulated inside helper functions, which end up being equivalent to the members of the "tuples" presented by the author. Any additions or changes to the enum will automatically make the compiler force you to update these functions.
- anon4 12y agoShouldn't this all be in a config file or vehicle database in the first place and not hardcoded?
- qznc 12y agoWhy not simple subclasses? E.g. class Truck extends Vehicle. From the article, the argument would be that "everything in one file" is desired, so you can e.g. parse it from other or for other languages. 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.
- CountSessine 12y agoOften called an X-macro http://en.m.wikipedia.org/wiki/X_Macro http://en.m.wikipedia.org/wiki/X_Macro