> "WARNING As a rule of thumb, enums are code smells and should be refactored to polymorphic classes. [8]" Seemann, Mark, Dependency Injection in .Net, 2011, p. 342
Gosh, what a load of baloney from a frog in a well.
- There are many many languages without classes that make good use of enums
- Doing this refactoring is excessively verbose
- Someone making bold claims like this about a language feature that's never been considered error prone and been around since the beginning ought to provide some really good evidence to back things up.
There's cases where enums are good, and there's cases where polymorphic classes are good. Dismissing one of them by default is wrong, since both are useful at different times.
edit: and the citation there to Martin Fowler et al., Refactoring: Improving the Design of Existing Code doesn't even match the claim being made. That book mentions
- "The first part of this problem is that switch statement. It is a bad idea to do a switch based on an attribute of another object. If you must use a switch statement, it should be on your own data, not on someone else's." -- sure, I agree
- "Often you find the same switch statement scattered about a program in different places. If you add a new clause to the switch, you have to find all these switch, statements and change them. The object-oriented notion of polymorphism gives you an elegant way to deal with this problem." -- fair enough
This is a long way from "enums are a code smell," and honestly feels like padding the citation count. Another thing to note is that the second edition of Refactoring: Improving the Design of Existing Code says:
> Even in our more wild-eyed youth, we were never unconditionally opposed to the conditional. Indeed, the first edition of this book had a smell entitled “switch statements.” The smell was there because in the late 90’s we found polymorphism sadly underappreciated, and saw benefit in getting people to switch over.
> These days there is more polymorphism about, and it isn’t the simple red flag that it often was fifteen years ago.
> - "Often you find the same switch statement scattered about a program in different places. If you add a new clause to the switch, you have to find all these switch, statements and change them. The object-oriented notion of polymorphism gives you an elegant way to deal with this problem." -- fair enough
Even that seems like it could easily be way overkill e.g. if you have multiple switches which, say, generate a label from an enum, the first step is probably to add a utility function / method, not to migrate the whole thing over to polymorphism.
Although there is one thing to be said about context:
* OP works in C#, whose enums are literally useless (they’re like C’s)
* apparently even in Java (which at least has type-safe enums even if not sum types), `switch` is unable to check for completeness
Generally the issue with the switches scattered about (or more generally, and concern about conditions scattered about the codebase) is that the body of each case is different. Of course if all the bodies are the same the easier option is a utility method.
A C# enum is, like a C enum (as they were explicitly introduced to be compatible with those), just a bunch of constants for integers.
So when you have an enum-typed value, odds are good that it’s one of the named ones but there’s no mechanism anywhere preventing it to be any other integer of the underlying type.
> But how is that an issue? I guess when you are casting random integer to the enum-type without any checks?
Any caller can send any garbage (if you're publishing a package / API), likewise a dependency can return any garbage, etc... C#'s enums are entirely indicative.
Switch statements are not required to be exhaustive, but switch expressions are. I’d have to check but I think it was found that changing the exhaustiveness requirement would break too much source code. The compiler will still insert some some sort of default case because somebody might add to the enumerator, and it might be in a separate compilation unit.
In Swift specifically, switch statements are exhaustive, so adding a new case to an enumeration that is switched on will automatically give you compilation errors at every point in the code base where that enum is matched against.
Which, in my experience is a huge advantage rather than the supposed disadvantage Fowler is saying it is (although I could see that being the case in many other languages).
>This is a long way from "enums are a code smell,"
originally the meaning of code smell was not that code that had the smell was bad, but there was a chance it was and should be examined. For this reason of course it is useful to get rid of code smells so that people don't feel the need to investigate hey is this smelly code actually bad code.
But all that said not sure if an enum is a code smell.
on edit: this at any rate was the rationale behind the phrase code smell I was first introduced to.
For me it’s the following:
- going for polymorphic classes instead of a few switches can cause bad coupling of knowledge. (
For example, instead of a switch on what to log in module A and the same for module B, now you’re mixing code from these two modules due to a shared constant.
Sometimes these polymorphic classes make sense, but my experience is that it’s a minority, and it’s better to err on the side of some simple enums.
Also note that Uncle Bob in Clean Code pretty much directly opposes in one of the chapters about logic and data separation (can’t find the chapter right now since I don’t have the book anymore)
This might have been valid in 00's Java/C#. However, most modern languages and even Java 16+ have good Enum support and exhaustiveness checks, making them very convenient and safe to express a limited set of possible values.
In contrast, an alternative like polymorphism + a visitor is safe, but excruciatingly verbose and hard to follow and modify.
The OO story generally falls apart once you go outside of the module/library, and want to actually treat objects like generic objects, and have new custom behaviour for some types of objects.
Either you end up with 'scary' enums, or with dynamic_cast and 'scary' null pointers.
Fivelessminutes's dismissal is indeed shallow, but then again, the rule being dismissed looks like shallow dogma (though we are not seeing it in context.)
Enums are essentially categorical variables, and there is nothing wrong with that: they are widely used in statistics and other real-world applications (arguably, even, booleans are a case of such.)
As a junior programmer, I was once assigned to work on an example of what could happen as a result of following this advice: an explosion of subclasses for the sake of avoiding a few conditional clauses. Algorithms had been broken up so that the common parts could be put in base classes, and you had to jump all around the code to see what was going on. Worse, objects could change their categorization during their lifetime, which required the substitution of a new object for the previous one. This complexity was contagious; once this one type was implemented in this manner, a lot of other things had to follow suit.
This was one of the experiences that turned me into a skeptic with regard to the proposition that there's no problem that cannot be simply solved by being more object-oriented.
It's a reference to a story about a group of frogs that adapt to living a hellish life in a well, and when given the means to escape decide to destroy it instead of escaping.
The story itself seems to be ancient, but I couldn't find where it originates from.
"Kupa manduka is a Sanskrit phrase for the frog in the well that imagines the well, its home, to be the whole world and, therefore, becomes pompous. It is a term of derision for the intellectually complacent, for someone who thinks he knows everything there is to know."
The more insidious problem is the card carrying believer in X saying “!X is harmful”
See this pattern all the time in both technical and people side all the time.
“You should never estimate using time”
“Don’t start new work until all the teams sprint commitments are completed”
Etc.
In a word it is arrogance with a twist of ignorance.
Ignorance that complex systems of humans and code don’t follow perfect dogmatic rules.
It might be ok for junior devs to be a bit like this but always add a pinch of “most of the time, but when experienced you’ll know when to ignore the rule”
Ignoring some unwritten or written dogma is often what makes for a competitive advantage.
Every one of these language toolbox books should come with a disclaimer for new devs up front about how the mark of a good craftsmen is knowing when to use the tools, when to bend or break their rules and guidelines, etc. I know it certainly might have saved me a fair bit of grief and oddly written code.
“Should” is way too strong. “You should consider” is appropriate.
An enum is essentially an encoding over integers. I guess this depends on your programming language, so humour me.
If you have an integer whose meaning is a category (as opposed to a count or measure) - does this need refactoring into classes?
Probably not most of the time.
For example you import a CSV and one of the fields is a categorisation. Let’s say county code. Your code may occasionally care if you are in an EU country.
You could have a class per country and override isEU as needed but this is clumsy and ugly in my opinion.
You’d also need a switch statement, in a factory method to create these from the csv data.
A lot of the time it’s easier to follow code where the switch statement is in its intuitive place.
Highly OO abstracted code can be very hard to reason about too.
> Highly OO abstracted code can be very hard to reason about too.
Definitely. Coming up with all the right design patterns and abstractions can feel quite clever and satisfying. But when someone else needs to debug that code, having to jump through layers and layers of object, interfaces, factories, etc to find out what a method call is actually doing is a pain.
I find it funny how Java (and maybe .NET, though the example is .NET - but probably "extended" from Java) seems to have catalyzed the worse types of cargo-culting, OO "overdose" and similar pseudo-good practices.
Then we end up with "corporate java code" that wants to throw out what was the basis of computing (if statements, switches) and turn all into some crazy type hierarchy.
Not so much .NET/Java but Structured Programming (1972 Edsger W. Dijkstra, Ole-Johan Dahl, and Tony Hoare) has prompted the gradual backlash against switches. (https://youtu.be/eEBOvqMfPoI?t=1512, re: Duff's Device). Senior developers should watch the whole thing to understand how block scope and functions and classes relate.
Modern development has drifted toward lots of small functions (which causes a mess because modern IDEs dont have supporting features like this: https://youtu.be/baxtyeFVn3w?t=1786, re: gtoolkit). A switch requires you to look inside and reason about the internals of a function to understand how it will behave before exiting, as the compiler. This doesn't matter so much if you dont care about early exits (no branch prediction, etc). Either way, it's extra cognitive overhead and error prone for a small improvement in readbaility. It's the line where programming becomes tricky programming.
As a thought experiment, imagine there is ANOTHER keyword that acts like an if/else (or switch with mandatory breaks). If A. it's terse, B. it's structured, C. it's readable, would that be better?
We primarily use switch because it's easy to read (versus a ton of if{}/else{}), as it gives us almost-block scoping without having to define another indirection/function. Python has this switch equivalent in match/case, but you have to use Python to get it.
Switch use is a syntax problem. Lots of software is colored by bad syntax and language design^.
^ I think most people realize that type hierarchies are a brittle/limited form of mixin. Even Java has softened to this a bit (aspects, etc) but will likely never be fun to use.
I seem to remember the Martin Fowler advice that this apparently cited being slightly more nuanced, and it’s not totally wrong. If you have a simple enumerator and many switch statements depending on it then it is quite error prone if you have to add a value (depending on your language it may even be a silent failure), but a class will force you to implement the bits of behaviour you need as methods, and gives a good template for when you add another subclass. It’s not always the right thing to do, you’re trading the flexibility for others to add new types for the old flexibility of it being easy to add new behavioural decision based on a fixed set of types.
I think in a lot of cases it’s definitely worth stopping and thinking, and making sure you are implementing the right thing if you find yourself using enums heavily, or using a class hierarchy as an enumerator replacement.
If I’m understanding correctly, languages like rust avoid that problem by throwing a compile error when a match statement doesn’t cover all possible cases.
Of course, a catch-all would then somewhat reintroduce the problem.
Yes. The problem you then have is that adding additional values to the enum then either breaks compatibility or relaxes that guarantee. Since Java tries to run old code for as long as possible the only option for switch statements is to ensure there is a fallback so that you at least know there is a problem.
Ok, but please don't post unsubstantive comments here. In particular, shallow dismissals and calling names are against the site guidelines. "A good critical comment teaches us something."
Do you mean when specific flags are passed to the compiler? I wasn’t aware of that. C++ has defined behavior for non-exhaustive switch statements, just like a non-exhaustive if statement (that is an if statement missing an else clause).
Compilers have (optional - it's kinda good practice to enable them though) warnings about things that are perfectly within spec and not undefined behavior, but usually a bad idea.
A switch statement with a default: label is exhaustive. The warning is only about switches without a default. There is also a warning about exhaustive switches with a default. Makes sense - the first is apparently supposed to handle all the cases, but doesn't. The second is apparently supposed to handle some cases and not some others, but given that it's actually exhaustive, any enumerator added later should probably get its own label instead of falling under the previously unused default label.
They're only a code smell in languages that treat them poorly.
Most modern languages have enums as just a matter of course for the language. I'm using Dart rather a lot these days for Flutter, and I absolutely love their enums. I can extend one and use it like a pseudo type union, or for a thing like `Environment.development`, I can extend that so that any `Environment` variable automatically has properties like `apiUri` or `name` or whatever other thing that changes per environment, but the set of them is still well known ahead of time.
It is also good to remember that different programming languages have different features for enums. e.g. in some languages you cannot print the enum as string, and in some languages you cannot choose the underlying type of the enum.
Modern enums in languages with powerful compilers are one of my favourite features and I repeatedly find them to be a marker of high quality codebases.
In Python there has historically been a strong tendency to use strings as enums, which combined with Python's generally weak typing, can lead to all sorts of issues.
I also write a fair bit of Swift at the moment, and in Swift codebases I've found that plenty of use of enums, particularly those with attached values, leads to easy to use and correct code, as well as quite concise code. It's much better than codebases that go hard on classes.
Very much like Elm (and/or Haskell). Our whole iOS architecture is inspired by the Elm architecture – it's a pleasure to work with, and Swift the language makes it feel really at home.
We're doing something very similar on macOS and iOS. The only rough edge I'm finding with this setup is integrating with Core Data feels a bit clunky/verbose.
Ah yeah I could imagine this. I haven't used CoreData much, but my understanding is that most of the framework support for it encourages bindings in a traditional MVC sort of approach. I know there is some SwiftUI support but it looks like you have to hand over a lot of control.
> Modern enums in languages with powerful compilers are one of my favourite features and I repeatedly find them to be a marker of high quality codebases.
Mega +1. I LOLed when I saw the post. My favorite feature of enums is that you limit the set of values - and thus get a lot of free checks. Not to mention being able to write case statements without default - which catches future bugs (C++).
Generally the enum vs polymorphic class debate is actually a subset of a better known trade-off between OO style inheritance and FP style use of sum types.
The trade-off is between ease of extension in two cases:
- adding new types to the sum type / new subclasses
- adding new methods that can operate on all types / subclasses
In a classic OO design, it's easy for any client to add new subclasses that implement the same API as the existing classes. However, adding a new API means that all subclasses need to be changed.
Conversely, in an FP design, adding a new type to the sum type (a new enum value) means that all places pattern matching on the sum types (switching on the enum) need to be changed. However, adding a new operation that works with the existing types is trivial.
To give a more specific example, let's say we want to model an expression tree.
An OO design might look like this:
abstract class BaseExpression {
abstract evaluate()
subexpressions()
}
class SumExpression extends BaseExpression {
evaluate() = map(evaluate, subexpressions()).Sum()
}
class NumberExpression extends BaseExpression {
int x
subexpressions() = [x]
evaluate() = x
}
An FP design might look like
type ExpressionTree a = SumExpresssion [ExpressionTree] | NumberExpression a
evaluate :: ExpressionTree a -> a
evaluate SumExpression xs = sum map evaluate xs
evaluate NumberExpression x = x
subexpressions :: ExpressionTree a -> [a]
subexpressions SumExpression xs = map union xs
subexpressions NumberExpression x = [x]
Equivalently, an enum based design would have
enum ExprType {
Sum
Number
}
struct SumExpression {
ExprType
*void[] Subexpressions
}
struct NumberExpression {
ExprType
int x
}
evaluate(e) {
switch(e->ExprType) {
case Sum: (SumExpression)e.Subexpressions.map(evaluate).Sum()
case Number: (NumberExpression)e.x
}
}
subexpressions(e) {
switch(e->ExprType) {
case Sum: return (SumExpression)e.subexpressions
case Number: void*[]{(NumberExpression)e.x}
}
}
Now, if we want to add support for MultiplyExpression, in the OO program we add a new file and we define evaluate() for MultiplyExpression. In the FP program we have to modify the definitions of ExpressionTree, SumExpression and NumberExpression.
Conversely, if we want to add support to print an ExpressionTree, in the FP version we just add a new function in a new file and describe how to print each type of ExpressionTree. In the OO program, we need to modify the implementations of each existing BaseExpression subclass to add support for printing.
I think it's usually refered as "the expression problem". Here's a good post about it: https://eli.thegreenplace.net/2016/the-expression-problem-an.... The idea is that when you have existing data and operations on them, how can you add more data and more operations without controlling the original code.
There's also a part about early and late binding. In statically typed FP programming, the compiler will warn you where you forgot enum cases when you add one. This is because this information is known at compile time. If you want to generate classes at runtime while keeping the same code, you may prefer the late-binding approach of having the class itself handle how it reacts to an operation.
This is called the Expression Problem because, as you allude to in your example, it comes up all the time in compilers and interpreters when dealing with expressions.
If you're looking for an elegant solution (the first I've ever seen), check out the work by Kiselyov on tagless final encodings.
The core idea is to invert the interface. Instead of the expression types being objects that implement an interface consist ing of operations, or the expression types being enum values and the operations being total functions of those enums, the interface is the abstract logic of the operation, and the interface methods are the expression types.
Adding new operations is easy, it's a new interface implementation. Adding new expression types is easy, too. You create a new interface for the new type, and implement that interface for each compatible operation.
It does require typeclasses or traits or something like that, and if you want the full power you need language support for higher-kinded types. Look at the papers for full examples, but here's a teaser in pseudocode:
interface BoolLogic {
fn bool(bool) -> Self;
fn if(Self, Self, Self) -> Self;
}
struct Eval {
value: Anything
}
implement BoolLogic for Eval {
fn bool(value) -> Eval {
Eval { value }
}
fn if(condition, consequent, alternate) -> Eval {
if (conditon.value)
consequent
else
alternate
}
}
struct Print {
text: String
}
implement BoolLogic for Print {
fn bool(value) -> Print {
let text = value.toString()
Print { text }
}
fn if(condition, consequent, alternate) Print {
let text = "if (" +
condition.text +
") {" +
consequent.text +
"} else {" +
alternate.text +
"}"
Print { text }
}
}
interface NumLogic {
fn num(int) -> Self
fn add(Self, Self) -> Self
fn eq(Self, Self) -> Self
}
implement NumLogic for Eval {
fn num(value) -> {
Eval { value }
}
fn add(left, right) -> Self {
let value = left.value + right.value
Eval { value }
}
fn eq(left, right) -> Self {
let value = left.value == right.value
Eval { value }
}
}
fn munge<T: BoolLogic + NumLogic>() -> T {
T.if(T.eq(T.add(T.nu.(2), T.num(2)), T.num(4)), T.num(42), T.num(0))
}
If you're curious I'd highly recommend reading and implementing the papers yourself. Have fun writing a typechecker, it's surprisingly easy! (with HKTs...)
I find this pattern pretty useful when you have a lot of small utility programs and don't want to litter the code 36 different main classes. Like it's a frickin bizarre pattern, but it's undeniably pretty tidy.
enum ToolLauncherMain {
convertTheThing(this::doFoo, "Convert the bin to the dat"),
sendSomeMessage(this::doBar, "Pass a message to the stuff-doer"),
help(this::printHelp, "Print this message");
private final Consumer<String[]> runner;
private String description;
public UtilRunner(Consumer<String[]> r, String desc) {
runner = r;
description = desc;
}
public static void main(String... args) {
try {
valueOf(args.length == 0 ? "help" : args[0]).runner.apply(args);
} catch (IllegalArgumentException ex) {
printHelp();
}
}
private void printHelp() {
for (var value : values()) {
System.out.printf("%s - %s\n", value, value.description);
}
}
// ...
}
In a functional language like Haskell or O'Caml, it feels like half of what you write involves enums (sum types). They're fantastic in those contexts and I really miss them in python. But OO is a different approach to data and I suspect the "code smell" advice comes from not wanting to mix the two paradigms. For example
Functional:
data Switch = On | Off
function flip: Switch -> Switch
flip s = case s of
On -> Off
Off -> On
Object Oriented:
abstract class Switch { Switch flip(Switch); }
class On : Switch { flip(s) { return new Off(); }
class Off : Switch { flip(s) { return new On(); }
Weird mixture:
enum State {On, Off};
class Switch {
State myState;
Switch flip(Switch s) {
if (s.myState == Off) { s.myState = On; return s ; }
if (s.myState == On) { s.myState = Off; return s; }
Someone else also pointed out that a code smell doesn't mean something is always bad - only that it's likely to be bad. An enum that's rarely or never the subject of an if/switch check or pattern match is probably OK. For example...
* A type-safe and self-describing alternative to a boolean argument
* A scheduler priority or group
* A state in a state machine
OTOH, an enum that appears often in conditionals is likely to be error prone and classes or interfaces should be considered instead. But it's really all pretty context-dependent. The question is: which approach creates more coupling and/or requires more points in the code to be updated to remain correct when a new value is added?
> An enum that's rarely or never the subject of an if/switch check or pattern match is probably OK.
If its never, then it is probably pointless.
If it is rare, it is probably OK irrespective of language.
If it is common, and it is a language with strong OOP support, and the conditionals repeat each other, you should probably consider whether you are using the enum as an inverted class and implementing common behavior that would make more sense in methods via redundant and hard to maintain conditionals on the enum. (If not, you should also consider if the enum is appropriate but the conditionals should be abstracted to a function.)
I disagree, because using an enum value as an index into an array or map (e.g. the scheduler-class example, or any other kind of grouping/routing) can still be useful even when there's no if/switch in sight. These patterns occur very frequently IMX. In many of these cases proper subranges would be even better and I wish more languages supported those, but an enum is still better than resorting to naked integers or (ick) strings to represent a small set of discrete values.
> OTOH, an enum that appears often in conditionals is likely to be error prone and classes or interfaces should be considered instead.
That's surprising. Isn't the whole point of an enum to switch/match on it?
> The question is: which approach creates more coupling and/or requires more points in the code to be updated to remain correct when a new value is added?
As other people mentioned when talking about the expression problem (https://news.ycombinator.com/item?id=29306511), it depends. If you're going to add new types, enums are better. If you're going to add new operations, polymorphic objects are better. If you're going to add both, hope that you're doing Clojure or Haskell.
In C#, enums basically add typing to a predefined set of entities or statuses. This is why they basically are just numbers under the hood.
Practically, though, they are important when dealing with serialization: Writing state to disk, database, or over the wire. Because they quickly typecast to a number or a string, you don't have to deal with the complexity of serializing or deserialzing.
Having recently inherited a C# codebase where enums weren't used, what I can say is that "polymorphic classes" to represent a predefined set of things is a smell. This manifests itself in cases where we had tables with a limited set of constants, and were loading them through joins. It was much more efficient to remove the join and turn the foreign key into an enum. In another case, the "object" is fully serialized in a contract to JSON. It's much more efficient to just serialize a single value in the contract as well.
This is a great example of why the "clean code" movement is so destructive. Writers (bloggers, textbook authors, professors, whatever) can very easily take advantage of inexperienced developers who are dying for someone to tell them what is and isn't the right way to do things, and virtually all "code smell" style blanket rules are detrimental to learning. In this case and in many other cases, the rules doesn't tie back in any way to what the code actually needs to do.
I very strongly disagree. Clean code principles have a lot of good rules to teach to new coders, and it's possible to get an intuition of when and where to apply clean code principles (or any other principles), and when not to. Arguably, you need to obtain such an intuition at some point - otherwise you're just applying random rules left and right without thought for whether that's the right thing to do in context.
The problem is not that all principles are bad. The problem is that there are so many people writing down stupid rules, and neither the good nor the bad ones get concretely justified, so it's very difficult for new programmers to discriminate between them. This is how we end up with new CS grads who wrap every new bit of functionality inside a class with getter/setter methods and write dozens of useless unit tests.
For me, enums are just a way to eliminate canonical magic strings throughout. They also have the happy side effect of storing as an integer in the database, but I do sometimes apply a .ToString() in those situations where users will be looking at the data frequently.
There are actually very few cases where I would say enums should not be used. The most obvious one being open-ended types where the list of members will always be growing (e.g. if you made each user of a system a member). You should also not use enums to convey a compounding of logical facts - these get nasty really fast in my experience. Sometimes it is better to just have 5-10 boolean flags on a type than to try and cram every logical combination of those things into canonical enumeration members.
I'm going to flag this submission. This is horrible Stack Overflow post with bad answers.
Languages have different types of "enums". Enums in languages like Rust are extraordinarily powerful sum types that create compact memory footprints, strong compile time guarantees, and can form the basis of expressive and correct state machines.
> Flagging is the closest to a "dislike" option available. But it is suggested to use it for items that are off topic.
> The better way to make sure items you find to be "positive" make the front page is to upvote everything you think deserves to get there. Upvote the positive, ignore the negative, and the negative will fade from view.
Officially, there's https://news.ycombinator.com/newsfaq.html but I wish it went into more detail about what the HN norms are regarding upvoting/downvoting/flagging.
I'm in the minority I guess on not liking enums. The main thing I've run into repeatedly is difficulty to add to them without breaking things. Even if linters catch non-exhaustive switch case statements, there's always the if statement that it misses. Client compatibility is a big challenge, so I usually end up sending them as strings on the wire anyway.
Did you link to this SE post so we could laugh at it?
Never smells: You are using a language with discriminated unions (before or after 90s)
Smells: You are using a dynamically typed language, and you used a hack to implement them
Sometimes smells: You are using a 90s language with a horrible implementation of enums
Smells: You are an OOP charlatan and they do not fit your invalid world view and your enums have methods on them plus some other bezerk shit. plus you have cognitive dissonance because you were told objects are expensive and you should really only pass ints around willy nilly and everything else should be god objects
I don't have a SE account so I'm going to provide the answer he's searching for to get out of the switch statement: dynamic object construction.
He's got code that looks like this:
switch(type) {
case BidPrice:
case BidSize:
MatchSellOrders();
The solution to ditching the switch statement is to create small classes whose names and locations conform to a convention so they can be inferred from the type value:
class BidPriceOrders : MatchSellOrders {}
class BidSizeOrders : MatchSellOrders {}
The corollary to switch statements is "primitive obsession" and that's the name for the code smell that includes enums. An enum doesn't have behaviour. It's just primitive data with an expressive symbol and namespace. The switch exists to couple behaviour to the symbol, both of which exist independently of each other because of poor design. The basic idea of OOP is all about grouping data with the methods that operate on it so that they live and evolve together as objects.
A switch is a middleman that has to understand the type and the behaviour as things that live and evolve separately. It's a point of coupling that needs to be maintained independently of the data and methods. In other words: it's superfluous code; unnecessary complexity that any code-base would benefit from not having to deal with.
So the idea is to replace the enum with an object without having to introduce the switch. He's right that you need a factory, but he's wrong that you need a switch statement to implement the factory. You can also just stick a static "factory method" on the abstract base class that defines the generic object type to keep the code / overhead as low as possible. The pseudo-code implementation is:
abstract class BidType {
static create(typeFlag: BigTypeFlag): BidType {
const className = `${BidTypeClassPath}/${typeFlag}Orders`;
if(!notExists(className)) {
throw new IllegalArgumentException('Unsupported bid type');
}
return new className();
}
}
const bidType = BigType::create(typeFlag);
Every language will have a different mechanism that allows you to dynamically instantiate an object but that's the basic idea.
For defining bitmasks and the like in (embedded) C I'm mostly using enums instead of preprocessor macros. Looks better in syntax highlighting and works better in autocompletion.
93 comments
[ 3.1 ms ] story [ 159 ms ] threadGosh, what a load of baloney from a frog in a well.
- There are many many languages without classes that make good use of enums
- Doing this refactoring is excessively verbose
- Someone making bold claims like this about a language feature that's never been considered error prone and been around since the beginning ought to provide some really good evidence to back things up.
There's cases where enums are good, and there's cases where polymorphic classes are good. Dismissing one of them by default is wrong, since both are useful at different times.
edit: and the citation there to Martin Fowler et al., Refactoring: Improving the Design of Existing Code doesn't even match the claim being made. That book mentions
- "The first part of this problem is that switch statement. It is a bad idea to do a switch based on an attribute of another object. If you must use a switch statement, it should be on your own data, not on someone else's." -- sure, I agree
- "Often you find the same switch statement scattered about a program in different places. If you add a new clause to the switch, you have to find all these switch, statements and change them. The object-oriented notion of polymorphism gives you an elegant way to deal with this problem." -- fair enough
This is a long way from "enums are a code smell," and honestly feels like padding the citation count. Another thing to note is that the second edition of Refactoring: Improving the Design of Existing Code says:
> Even in our more wild-eyed youth, we were never unconditionally opposed to the conditional. Indeed, the first edition of this book had a smell entitled “switch statements.” The smell was there because in the late 90’s we found polymorphism sadly underappreciated, and saw benefit in getting people to switch over.
> These days there is more polymorphism about, and it isn’t the simple red flag that it often was fifteen years ago.
Even that seems like it could easily be way overkill e.g. if you have multiple switches which, say, generate a label from an enum, the first step is probably to add a utility function / method, not to migrate the whole thing over to polymorphism.
Although there is one thing to be said about context:
* OP works in C#, whose enums are literally useless (they’re like C’s)
* apparently even in Java (which at least has type-safe enums even if not sum types), `switch` is unable to check for completeness
So when you have an enum-typed value, odds are good that it’s one of the named ones but there’s no mechanism anywhere preventing it to be any other integer of the underlying type.
Any caller can send any garbage (if you're publishing a package / API), likewise a dependency can return any garbage, etc... C#'s enums are entirely indicative.
Which, in my experience is a huge advantage rather than the supposed disadvantage Fowler is saying it is (although I could see that being the case in many other languages).
originally the meaning of code smell was not that code that had the smell was bad, but there was a chance it was and should be examined. For this reason of course it is useful to get rid of code smells so that people don't feel the need to investigate hey is this smelly code actually bad code.
But all that said not sure if an enum is a code smell.
on edit: this at any rate was the rationale behind the phrase code smell I was first introduced to.
Sometimes these polymorphic classes make sense, but my experience is that it’s a minority, and it’s better to err on the side of some simple enums.
Also note that Uncle Bob in Clean Code pretty much directly opposes in one of the chapters about logic and data separation (can’t find the chapter right now since I don’t have the book anymore)
In contrast, an alternative like polymorphism + a visitor is safe, but excruciatingly verbose and hard to follow and modify.
Either you end up with 'scary' enums, or with dynamic_cast and 'scary' null pointers.
Enums are essentially categorical variables, and there is nothing wrong with that: they are widely used in statistics and other real-world applications (arguably, even, booleans are a case of such.)
As a junior programmer, I was once assigned to work on an example of what could happen as a result of following this advice: an explosion of subclasses for the sake of avoiding a few conditional clauses. Algorithms had been broken up so that the common parts could be put in base classes, and you had to jump all around the code to see what was going on. Worse, objects could change their categorization during their lifetime, which required the substitution of a new object for the previous one. This complexity was contagious; once this one type was implemented in this manner, a lot of other things had to follow suit.
This was one of the experiences that turned me into a skeptic with regard to the proposition that there's no problem that cannot be simply solved by being more object-oriented.
The story itself seems to be ancient, but I couldn't find where it originates from.
I was able to find some references from ancient India, but nothing about it coming from there.
See this pattern all the time in both technical and people side all the time.
“You should never estimate using time”
“Don’t start new work until all the teams sprint commitments are completed”
Etc.
In a word it is arrogance with a twist of ignorance.
Ignorance that complex systems of humans and code don’t follow perfect dogmatic rules.
It might be ok for junior devs to be a bit like this but always add a pinch of “most of the time, but when experienced you’ll know when to ignore the rule”
Ignoring some unwritten or written dogma is often what makes for a competitive advantage.
Let your competitor drown in EnumClassFactorys .
I think the best authoritative-sounding counter-quip is "Best practices are best not practiced."
An enum is essentially an encoding over integers. I guess this depends on your programming language, so humour me.
If you have an integer whose meaning is a category (as opposed to a count or measure) - does this need refactoring into classes?
Probably not most of the time.
For example you import a CSV and one of the fields is a categorisation. Let’s say county code. Your code may occasionally care if you are in an EU country.
You could have a class per country and override isEU as needed but this is clumsy and ugly in my opinion.
You’d also need a switch statement, in a factory method to create these from the csv data.
A lot of the time it’s easier to follow code where the switch statement is in its intuitive place.
Highly OO abstracted code can be very hard to reason about too.
Definitely. Coming up with all the right design patterns and abstractions can feel quite clever and satisfying. But when someone else needs to debug that code, having to jump through layers and layers of object, interfaces, factories, etc to find out what a method call is actually doing is a pain.
Then we end up with "corporate java code" that wants to throw out what was the basis of computing (if statements, switches) and turn all into some crazy type hierarchy.
Modern development has drifted toward lots of small functions (which causes a mess because modern IDEs dont have supporting features like this: https://youtu.be/baxtyeFVn3w?t=1786, re: gtoolkit). A switch requires you to look inside and reason about the internals of a function to understand how it will behave before exiting, as the compiler. This doesn't matter so much if you dont care about early exits (no branch prediction, etc). Either way, it's extra cognitive overhead and error prone for a small improvement in readbaility. It's the line where programming becomes tricky programming.
As a thought experiment, imagine there is ANOTHER keyword that acts like an if/else (or switch with mandatory breaks). If A. it's terse, B. it's structured, C. it's readable, would that be better?
We primarily use switch because it's easy to read (versus a ton of if{}/else{}), as it gives us almost-block scoping without having to define another indirection/function. Python has this switch equivalent in match/case, but you have to use Python to get it.
Switch use is a syntax problem. Lots of software is colored by bad syntax and language design^.
^ I think most people realize that type hierarchies are a brittle/limited form of mixin. Even Java has softened to this a bit (aspects, etc) but will likely never be fun to use.
Mark, for example, is now primarily a functional programmer and makes good use of F#’s type system:
https://blog.ploeh.dk/2015/08/10/type-driven-development/
So he might even agree with you these days (on the “baloney” part, but maybe not the “frog in a well”).
I think in a lot of cases it’s definitely worth stopping and thinking, and making sure you are implementing the right thing if you find yourself using enums heavily, or using a class hierarchy as an enumerator replacement.
Of course, a catch-all would then somewhat reintroduce the problem.
https://news.ycombinator.com/newsguidelines.html
Linters can/should already detect when a switch on enum doesn't handle all cases
A switch statement with a default: label is exhaustive. The warning is only about switches without a default. There is also a warning about exhaustive switches with a default. Makes sense - the first is apparently supposed to handle all the cases, but doesn't. The second is apparently supposed to handle some cases and not some others, but given that it's actually exhaustive, any enumerator added later should probably get its own label instead of falling under the previously unused default label.
Most modern languages have enums as just a matter of course for the language. I'm using Dart rather a lot these days for Flutter, and I absolutely love their enums. I can extend one and use it like a pseudo type union, or for a thing like `Environment.development`, I can extend that so that any `Environment` variable automatically has properties like `apiUri` or `name` or whatever other thing that changes per environment, but the set of them is still well known ahead of time.
In Python there has historically been a strong tendency to use strings as enums, which combined with Python's generally weak typing, can lead to all sorts of issues.
I also write a fair bit of Swift at the moment, and in Swift codebases I've found that plenty of use of enums, particularly those with attached values, leads to easy to use and correct code, as well as quite concise code. It's much better than codebases that go hard on classes.
Very much like Elm (and/or Haskell). Our whole iOS architecture is inspired by the Elm architecture – it's a pleasure to work with, and Swift the language makes it feel really at home.
Mega +1. I LOLed when I saw the post. My favorite feature of enums is that you limit the set of values - and thus get a lot of free checks. Not to mention being able to write case statements without default - which catches future bugs (C++).
The trade-off is between ease of extension in two cases: - adding new types to the sum type / new subclasses - adding new methods that can operate on all types / subclasses
In a classic OO design, it's easy for any client to add new subclasses that implement the same API as the existing classes. However, adding a new API means that all subclasses need to be changed.
Conversely, in an FP design, adding a new type to the sum type (a new enum value) means that all places pattern matching on the sum types (switching on the enum) need to be changed. However, adding a new operation that works with the existing types is trivial.
To give a more specific example, let's say we want to model an expression tree.
An OO design might look like this:
An FP design might look like Equivalently, an enum based design would have Now, if we want to add support for MultiplyExpression, in the OO program we add a new file and we define evaluate() for MultiplyExpression. In the FP program we have to modify the definitions of ExpressionTree, SumExpression and NumberExpression.Conversely, if we want to add support to print an ExpressionTree, in the FP version we just add a new function in a new file and describe how to print each type of ExpressionTree. In the OO program, we need to modify the implementations of each existing BaseExpression subclass to add support for printing.
There's also a part about early and late binding. In statically typed FP programming, the compiler will warn you where you forgot enum cases when you add one. This is because this information is known at compile time. If you want to generate classes at runtime while keeping the same code, you may prefer the late-binding approach of having the class itself handle how it reacts to an operation.
If you're looking for an elegant solution (the first I've ever seen), check out the work by Kiselyov on tagless final encodings.
The core idea is to invert the interface. Instead of the expression types being objects that implement an interface consist ing of operations, or the expression types being enum values and the operations being total functions of those enums, the interface is the abstract logic of the operation, and the interface methods are the expression types.
Adding new operations is easy, it's a new interface implementation. Adding new expression types is easy, too. You create a new interface for the new type, and implement that interface for each compatible operation.
It does require typeclasses or traits or something like that, and if you want the full power you need language support for higher-kinded types. Look at the papers for full examples, but here's a teaser in pseudocode:
If you're curious I'd highly recommend reading and implementing the papers yourself. Have fun writing a typechecker, it's surprisingly easy! (with HKTs...)Lots of the classic OOP design patterns are because Java/C++ is very rubbish.
Functional:
Object Oriented: Weird mixture:> [lots of] switches on enums are a code smell
Someone else also pointed out that a code smell doesn't mean something is always bad - only that it's likely to be bad. An enum that's rarely or never the subject of an if/switch check or pattern match is probably OK. For example...
* A type-safe and self-describing alternative to a boolean argument
* A scheduler priority or group
* A state in a state machine
OTOH, an enum that appears often in conditionals is likely to be error prone and classes or interfaces should be considered instead. But it's really all pretty context-dependent. The question is: which approach creates more coupling and/or requires more points in the code to be updated to remain correct when a new value is added?
If its never, then it is probably pointless.
If it is rare, it is probably OK irrespective of language.
If it is common, and it is a language with strong OOP support, and the conditionals repeat each other, you should probably consider whether you are using the enum as an inverted class and implementing common behavior that would make more sense in methods via redundant and hard to maintain conditionals on the enum. (If not, you should also consider if the enum is appropriate but the conditionals should be abstracted to a function.)
I disagree, because using an enum value as an index into an array or map (e.g. the scheduler-class example, or any other kind of grouping/routing) can still be useful even when there's no if/switch in sight. These patterns occur very frequently IMX. In many of these cases proper subranges would be even better and I wish more languages supported those, but an enum is still better than resorting to naked integers or (ick) strings to represent a small set of discrete values.
That's surprising. Isn't the whole point of an enum to switch/match on it?
> The question is: which approach creates more coupling and/or requires more points in the code to be updated to remain correct when a new value is added?
As other people mentioned when talking about the expression problem (https://news.ycombinator.com/item?id=29306511), it depends. If you're going to add new types, enums are better. If you're going to add new operations, polymorphic objects are better. If you're going to add both, hope that you're doing Clojure or Haskell.
Practically, though, they are important when dealing with serialization: Writing state to disk, database, or over the wire. Because they quickly typecast to a number or a string, you don't have to deal with the complexity of serializing or deserialzing.
Having recently inherited a C# codebase where enums weren't used, what I can say is that "polymorphic classes" to represent a predefined set of things is a smell. This manifests itself in cases where we had tables with a limited set of constants, and were loading them through joins. It was much more efficient to remove the join and turn the foreign key into an enum. In another case, the "object" is fully serialized in a contract to JSON. It's much more efficient to just serialize a single value in the contract as well.
There are actually very few cases where I would say enums should not be used. The most obvious one being open-ended types where the list of members will always be growing (e.g. if you made each user of a system a member). You should also not use enums to convey a compounding of logical facts - these get nasty really fast in my experience. Sometimes it is better to just have 5-10 boolean flags on a type than to try and cram every logical combination of those things into canonical enumeration members.
Languages have different types of "enums". Enums in languages like Rust are extraordinarily powerful sum types that create compact memory footprints, strong compile time guarantees, and can form the basis of expressive and correct state machines.
Enums (sum types) are one of the best patterns.
While the commentary in this HN thread is good, those that click the link directly and don't know any better will be misinformed.
I seems bad to let this stay on the front page.
From https://news.ycombinator.com/item?id=24123981 user 'pwg' says:
> Flagging is the closest to a "dislike" option available. But it is suggested to use it for items that are off topic.
> The better way to make sure items you find to be "positive" make the front page is to upvote everything you think deserves to get there. Upvote the positive, ignore the negative, and the negative will fade from view.
Officially, there's https://news.ycombinator.com/newsfaq.html but I wish it went into more detail about what the HN norms are regarding upvoting/downvoting/flagging.
Everyone in this thread sounds like they've been writing c# for exactly three years.
Never smells: You are using a language with discriminated unions (before or after 90s)
Smells: You are using a dynamically typed language, and you used a hack to implement them
Sometimes smells: You are using a 90s language with a horrible implementation of enums
Smells: You are an OOP charlatan and they do not fit your invalid world view and your enums have methods on them plus some other bezerk shit. plus you have cognitive dissonance because you were told objects are expensive and you should really only pass ints around willy nilly and everything else should be god objects
He's got code that looks like this:
The solution to ditching the switch statement is to create small classes whose names and locations conform to a convention so they can be inferred from the type value: The corollary to switch statements is "primitive obsession" and that's the name for the code smell that includes enums. An enum doesn't have behaviour. It's just primitive data with an expressive symbol and namespace. The switch exists to couple behaviour to the symbol, both of which exist independently of each other because of poor design. The basic idea of OOP is all about grouping data with the methods that operate on it so that they live and evolve together as objects.A switch is a middleman that has to understand the type and the behaviour as things that live and evolve separately. It's a point of coupling that needs to be maintained independently of the data and methods. In other words: it's superfluous code; unnecessary complexity that any code-base would benefit from not having to deal with.
So the idea is to replace the enum with an object without having to introduce the switch. He's right that you need a factory, but he's wrong that you need a switch statement to implement the factory. You can also just stick a static "factory method" on the abstract base class that defines the generic object type to keep the code / overhead as low as possible. The pseudo-code implementation is:
Every language will have a different mechanism that allows you to dynamically instantiate an object but that's the basic idea.