[HN Gopher] When are enums not a code smell?
       ___________________________________________________________________
        
       When are enums not a code smell?
        
       Author : danny00
       Score  : 26 points
       Date   : 2021-11-21 10:57 UTC (1 days ago)
        
 (HTM) web link (softwareengineering.stackexchange.com)
 (TXT) w3m dump (softwareengineering.stackexchange.com)
        
       | [deleted]
        
       | bob1029 wrote:
       | 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.
        
         | gjulianm wrote:
         | You can use flag enums for the logical facts. Not always, but
         | sometimes they can be useful and make comparisons/checks
         | easier.
        
       | echelon wrote:
       | 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.
       | 
       | Enums (sum types) are one of the best patterns.
        
         | couchand wrote:
         | That's not what flagging is for?
        
           | echelon wrote:
           | This is really bad advice.
           | 
           | 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.
        
             | couchand wrote:
             | Then downvote. Flagging an article you simply disagree with
             | wastes the moderation team's time.
        
               | gary_0 wrote:
               | You can't downvote submissions on HN.
               | 
               | 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.
        
       | danpalmer wrote:
       | 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.
        
         | jamil7 wrote:
         | Yeah, I use them all the time in Swift to model UI state, I
         | find it's a pretty elegant way to avoid invalid states and
         | simplifies UI code a lot.
        
           | danpalmer wrote:
           | Yeah they're wonderful for that.
           | 
           | 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.
        
             | jamil7 wrote:
             | 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.
        
         | amarshall wrote:
         | Python enums with static type-checking via Mypy are pretty
         | pleasant, though.
        
       | mindvirus wrote:
       | 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.
        
       | [deleted]
        
       | exdsq wrote:
       | Enums aren't a code smell.
        
       | bo1024 wrote:
       | 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; }
        
         | shikoba wrote:
         | Where is File_not_found?
        
       | theknocker wrote:
       | If you make heavy use of enums in a class oriented language, your
       | code is not as clean as you think it is.
       | 
       | Everyone in this thread sounds like they've been writing c# for
       | exactly three years.
        
       | simiones wrote:
       | 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.
        
         | Zababa wrote:
         | 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.
        
         | couchand wrote:
         | 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...)
        
       | richardwhiuk wrote:
       | Design patterns are often heavily weighted towards the languages
       | in which they were devised and don't generally apply.
       | 
       | Lots of the classic OOP design patterns are because Java/C++ is
       | very rubbish.
        
       | fivelessminutes wrote:
       | > "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.
        
         | raverbashing wrote:
         | 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.
        
           | Supermancho wrote:
           | 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.
        
         | aardvark179 wrote:
         | 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.
        
           | dwaltrip wrote:
           | 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.
        
             | aardvark179 wrote:
             | 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.
        
         | dang wrote:
         | 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."
         | 
         | https://news.ycombinator.com/newsguidelines.html
        
         | userbinator wrote:
         | Seeing sentences like that are very illuminating to explain why
         | a lot of software turns into bloated monstrosities.
        
         | bena wrote:
         | I'd never heard of the phrase "frog in a well" before. I like
         | it, I don't think I ever had a term for that phenomenon before.
        
           | marcosdumay wrote:
           | 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.
        
             | Jensson wrote:
             | I think it is originally from China, I see it a lot in
             | Chinese stories and never seen it anywhere else (except
             | here now).
        
               | marcosdumay wrote:
               | Hum... I know it from Portuguese literature.
               | 
               | I was able to find some references from ancient India,
               | but nothing about it coming from there.
        
           | smegsicle wrote:
           | "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."
        
         | AndrewBissell wrote:
         | Somehow I knew it must have been a dependency injection
         | advocate who came up with the idea that enums are a code smell.
        
           | quickthrower2 wrote:
           | 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.
           | 
           | Let your competitor drown in EnumClassFactorys .
        
             | AndrewBissell wrote:
             | 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.
        
               | userbinator wrote:
               | But then they wouldn't sound "authoritative" enough. The
               | dogma-cult that keeps its leader well-fed wouldn't
               | survive.
               | 
               | I think the best authoritative-sounding counter-quip is
               | "Best practices are best not practiced."
        
         | adamkl wrote:
         | In Mark's defence, our views on software development can change
         | over time as we learn and grow.
         | 
         | 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").
        
         | kaetemi wrote:
         | That guy must be so afraid of bitfields.
        
         | quickthrower2 wrote:
         | "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.
        
           | dahfizz wrote:
           | > 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.
        
         | cle wrote:
         | Can you elaborate on your shallow dismissal?
        
           | dtech wrote:
           | 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.
        
           | flaviu1 wrote:
           | I'd expect it to go something like this:
           | 
           | - 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.
        
             | oflannabhra wrote:
             | 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).
        
             | bryanrasmussen wrote:
             | >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.
        
             | masklinn wrote:
             | > - "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
        
               | aardvark179 wrote:
               | 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.
        
               | couchand wrote:
               | 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.
        
               | jaywalk wrote:
               | As someone who works in C# and finds enums useful, I'd be
               | curious to know why you describe them as literally
               | useless.
        
               | masklinn wrote:
               | 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.
        
               | Semaphor wrote:
               | But how is that an issue? I guess when you are casting
               | random integer to the enum-type without any checks?
        
               | masklinn wrote:
               | > 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.
        
           | strulovich wrote:
           | 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)
        
           | kaetemi wrote:
           | 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.
        
           | mannykannot wrote:
           | 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.
        
           | coldacid wrote:
           | Mark Seemann started it first.
        
       | mcraiha wrote:
       | 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.
        
       | notacoward wrote:
       | The key IMO is mentioned in one of the comments.
       | 
       | > [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?
        
       | trinovantes wrote:
       | Never knew enums are even considered to be code smell
       | 
       | Linters can/should already detect when a switch on enum doesn't
       | handle all cases
        
         | chris37879 wrote:
         | 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.
        
         | Lutger wrote:
         | For some languages, like D, that can also be expressed in the
         | language itself: https://dlang.org/spec/statement.html#final-
         | switch-statement
        
         | IggleSniggle wrote:
         | Exactly. Indeed, the whole point of using switch+enum is
         | frequently to get exhaustiveness checking on your conditional!
        
         | klyrs wrote:
         | Me neither, but now I'm picturing a horrible mess of an IOCCC
         | entry that abuses enums beyond all reason...
        
         | jeffbee wrote:
         | Aside from linters, at least some very common C++ compilers
         | will refuse to compile a non-exhaustive switch statement.
        
           | todd8 wrote:
           | 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).
        
         | gwbas1c wrote:
         | Or in a unit test
        
       | bigbillheck wrote:
       | I really hope that 'enums are a code smell' is the worst
       | technical opinion I see today because I'm not sure I could take
       | much worse.
        
       | gwbas1c wrote:
       | 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.
        
       | stathibus wrote:
       | 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.
        
       | brazzy wrote:
       | _laughs in Java, where enums ARE polymorphic classes_
        
       | zzbzq wrote:
       | The only time they ARE a code smell is when the values are only
       | used one place and they could have been in-lined for readability.
        
       ___________________________________________________________________
       (page generated 2021-11-22 23:02 UTC)