[HN Gopher] Ask HN: Do You Use Enum for Yes, No, Unset?
___________________________________________________________________
Ask HN: Do You Use Enum for Yes, No, Unset?
I've seen different arguments on this topic and thought I'd ask HN
if you all have a standard for this. Say you have a form that's
filled out and saved to a DB. One required field is a dropdown with
Yes and No. But the field begins as blank to show that a value
hasn't been selected yet, ensuring that the User pays attention to
the field. Do you use a Nullable Bit (DB) and Nullable Bool
(Code)? OR an Enum: Yes, No, Unset? You wouldn't use a boolean to
represent states like say New, Processing, Done. But do you
consider NULL a separate "state"? Thanks
Author : whitakerch
Score : 16 points
Date : 2022-06-17 19:38 UTC (3 hours ago)
| dathinab wrote:
| In some cases I encode the enum in the field name like
| `isThisOrElseThat BOOL` if you have clear opposites it often
| doesn't make sense to do so, but if it's non clear opposite
| either A or B choices or nullable and NULL isn't equal to false
| it can be helpful.
|
| It also is annoying to use.
|
| So if the DB supports a reasonable overhead enumeration that's a
| good choice, too (but that's not always the case, e.g. sqlite).
| marginalia_nu wrote:
| I'd probably avoid using NULL in DB to convey a borderline-value
| for the simple reason that NULL has weird semantics in SQL and it
| can be pretty confusing.
|
| If you're not using NULLs in DB, it's weird to translate an enum
| into a nullable Boolean.
|
| In the end, there is very little downside to using enums in this
| case. It's by far the least confusing option.
| SigmundA wrote:
| There are two common "nulls" in this situation, the "unset" one
| and the "unknown" one. Unset would imply no one has ever set the
| value, unknown means someone has set the value to unknown which
| is sometimes an important distinction.
|
| Javascript this is easy undefined can represent unset, and null
| can represent unknown. This is very useful for updating the state
| as well, undefined means do not change the value while null means
| update the value to unknown.
|
| I have used code (enums) for yes/no/unknown that allow null value
| for unset as well in relational db's. If I had a choice I prefer
| JavaScripts data model of a boolean that can be undefined or null
| as well.
|
| A nullable boolean in general has been better for a tristate than
| enums, but like I said many times I need a quadstate.
| dvh wrote:
| I use integer surrogate keys everywhere. So 1 yes, 2 no, 3 unset,
| 4 yes but no every other Friday, 5 use this option for customer
| x, 6 no with notes, 7 zebra, 8...
| marginalia_nu wrote:
| With some 4.2 billion integers to choose from, you can pretty
| much put everything in one enum. Genius!
| Volundr wrote:
| I'm pretty sure I've worked on this codebase...
| disintegore wrote:
| Yes/no/unspecified is a good match for something like
| `Option<bool>` or `bool?`. Their usage covers every case and
| nothing needs to be elucidated.
|
| With that said, in a language that supports null and has no
| compile-time mechanism to detect potential null dereferencing
| (like modern C# for instance) I might use an enum.
| adzm wrote:
| For SQL there is usually an optimization where nullable columns
| are simply a bit set; same with boolean/bit columns. This does
| sound like a good use of a nullable bit.
|
| You may want to distinguish things further in the future such as
| adding a new column and you can determine if it was
| unset/undefined by the user versus null because the user never
| saw it. In that case an enum makes sense.
|
| Personally I think the logic maps directly to nullable bit.
| IceMetalPunk wrote:
| JS/TS dev here. I tend to stick with a nullable boolean. For me,
| "no value" is usually pretty equivalent with "null" in general,
| and the data type I choose to use depends only on the non-null
| possibilities.
| olliej wrote:
| You could be using Objective-C with NSBoolean, so you have @YES,
| @NO, and nil :D
| rvdginste wrote:
| I use the convention that an 'null' value is a value that was not
| explicitly set by the user.
|
| So in C#, I would always use a nullable boolean in this case
| (choices yes and no). If the field is a required value, I would
| annotate the field as such.
|
| When using the field in an ORM, the required annotation would
| lead to a not-null field in the database.
|
| When using the field in a dto, the required annotation could be
| used for validation of incoming input.
|
| If the user should have the choice "yes", "no", "unknown", I
| would use a nullable enum to express this. An 'null' value means
| that the value was not explicitly set. The enum value "unknown"
| indicates that the user explicitly chose the "unknown" value.
|
| So, in both cases the 'null' is not a separate state, it simply
| indicates that no explicit value was given to the field.
| DubiousPusher wrote:
| I work in a slightly different context but have dealt with a
| similar problem. I build Unity3D apps and I write a lot UI for
| the editor that affects how things are serialized. I've stopped
| serializing enums due to being burned too many times. When
| people:
|
| -Add entries not at the bottom -Rename entries -Remove entries
| -Rearrange entries
|
| So instead I made a kind of data-driven enum that you create
| through the editor. Each entry (called a key) in the "enum" is
| backed by a guid. You can associate a name with each key.
|
| When you want to save info like on/off/unset. You create an enum.
| Then you declare a key in your data model. The UI will then show
| that key as a drop-down with the mapped names as the choices. But
| in the end, the associated guid is what is recorded.
|
| Not only does this largely solve the, add, rename, re-arrange
| problems, it solves some other persistent issues as well. I've
| gotten designers to pickup this tool so they stop using ints and
| string to signal events. This way, they define their signal once
| as a data-driven enum and then they see it as a choice throughout
| the app rather than as something they have to type in matching.
|
| It also causes engineers to write components which are more
| configurable for designers. Instead of checking a literal token
| like:
|
| if (currentEvent.appState == MyAppStateEnum.Start) doSomething()
|
| They declare the Key and check its value: public Key
| appPhaseToDoSomething;
|
| if (currentEvent.appState == appPhaseToDoSomething) doSomething()
|
| Since appPhaseToDoSomething is exposed as a dropdown in the
| editor, it means a designer can change the phase when something
| might happen without an engineer. And engineers basically have to
| write in this modular way because there's no token to check.
| jeroenhd wrote:
| I've seen some terrible implementations in Java abuse Boolean as
| a tri state value: true, false, null.
|
| However, this always caused bugs because you'd end up with
| terrible code patterns. For one, what does "unset" mean? Does it
| equal false, because it wasn't enabled? What's the default? I
| think "unset" is a state to be generally avoided because it's
| bound to create problems for you down the line.
|
| Personally, I prefer enums for anything that's not an actual
| boolean. A boolean should, in my opinion, always be a yes/no
| variable. Other states should probably be more specific.
|
| I've seen people use booleans and other types to represent
| limited options (i.e. animal.IsCat to determine if an animal is a
| cat or a dog. I'd go so far as to create enums that will only
| have two or even just one option if expansion is expected soon
| enough, with an optional state encoded in the type system as
| well. If your database can't store optional/maybe values, I'd add
| an enum variable.
|
| Of course I'd make an exception if you're terribly resource
| constrained. If you need to save every bit, document the hell out
| of it and optimize your code any way you want.
| aliswe wrote:
| In C# I would definitely use the nullable "bool?" then you can
| use null coalescing operator to default to either true or false:
| "value ?? false"
| throwaway81523 wrote:
| In the code, it's considered something of a smell to use a bool
| at all. Look up "Boolean blindness". Basically instead of a bool
| field saying whether the person wants the deluxe upgrade, you'd
| have an enum whose values are Regular | Deluxe. How you would
| represent that in a db would depend on how your implementation
| works.
| sshine wrote:
| Yes, I'd have an enum with Yes, No and Unset.
|
| Many languages have a typed "no value" value that is composable
| with other types: Maybe<T>, Option<T>, Nullable<T>.
|
| In other situations I might have an Option<Yes, No>, i.e. when I
| want a non-null value to indicate that it isn't Yes or No. The
| reason why I don't want a null, in general, is that it's the
| billion dollar mistake:
|
| https://hackernoon.com/null-the-billion-dollar-mistake-8t5z3...
|
| But if the user can actually choose "Unset", then that's a
| choice, too.
|
| "Unset" might, for example, have implications that other null-ish
| answers don't have in the future.
|
| As for SQL: I might go for a NULL for performance, knowing that
| if another neither-yes-or-no option with different implications
| came, I might have to migrate the NULLs.
| nybble41 wrote:
| When the final record should have either Yes or No, but the
| there is an intermediate state where the answer could be
| missing, you want something like a Maybe<Bool> which you can
| map to a simple Bool as part of validation. Ideally you'd use a
| functor-style parameter for this, for example in Haskell:
| data Record f = Record { ..., someField [?] f Bool, ... }
| type PartialRecord = Record Maybe type CompleteRecord =
| Record Identity getCompletedRecord [?] PartialRecord
| - Maybe CompleteRecord getCompletedRecord (Record { ...,
| someField, ... }) = Record <$> ... <*> (Identity <$>
| someField) <*> ...
|
| This way you can define your record type once and handle all
| the missing fields in a uniform way, and functions can easily
| indicate whether they expect a partial record full of Maybes or
| a complete record with no potentially missing fields. You can
| use the same type definition for other things, too, by
| substituting different functors in place of Maybe or Identity.
| For example, `Record ToString` where `ToString a` is a newtype
| over `(a - String)` could be a record of functions describing
| how to render each field as a string.
|
| In the DB you would need to store incomplete records in a
| separate table, since they have different validation rules.
| Queries against the main table should be able to assume that
| all the records are complete.
| jrockway wrote:
| I don't have general advice, but you do have 3 states to
| represent there; not acted upon by user, yes, and no. That's what
| enums are.
|
| I think "we'll pass in a pointer to the actual data and if it's
| unset the pointer won't point to a valid memory location" is a
| weird pattern that the industry should probably stop doing. So no
| *bool type here.
|
| Database nulls are maybe reasonable, but mean many things that an
| explicit enum doesn't. "This column was added after the fact and
| we didn't want to set a default value", "this column is
| optional", etc. The more ambiguous the meaning, the more mistakes
| that can be made.
|
| (I think someone is going to say, "don't write the form to the
| database until it's valid", but you have to have some way to save
| work in progress between website visits or "my Internet died",
| right? If you just dump a JSON blob in local storage, you still
| have the same representation problem; the field has 3 possible
| values, and you can't just pretend like it only has two because
| there is a built-in type that has two possible values.)
| j1elo wrote:
| TypeScript is nice because its null type is explicit. So if a
| type claims to be bool, _it is really bool_ (either true or
| false, nothing else).
|
| For a nullable variable of type T (including boolean), the type
| must be explicit about it: let choice: T |
| null;
|
| Another nice alternative is an Option type, like Rust has. I
| think it would be something like Option<bool>.
|
| But those (a null, or a None, respectively) would be only choices
| if internally, at the code level, the Unset case must be handled
| as some kind of extraordinary scenario. If it was possible for
| users to make a conscious decision to leave it unset, i.e. if
| Unset is part of the valid choices offered by the user-facing
| API, then I'd encode it as a proper possible state, in an enum.
| gnabgib wrote:
| And it's terrible because there's both `null` and `undefined`
| which have largely the same meaning but aren't equal. Depending
| on what you're interacting with, you may need to use one over
| the other or coerce. Especially common with developers from
| other languages who don't even know of the gotcha
| NovemberWhiskey wrote:
| Neither?
|
| If the user responses are in a table of (field_id,
| value_selected, ...) then you just use an outer join vs the
| available field_id for the form and get back NULLs automatically
| for the unpopulated selections.
|
| That way if you (say) add new selections to forms that are
| partially complete, reasonable things happen without any further
| logic.
|
| Why record data to say you don't have data?
| nescioquid wrote:
| The "ternary boolean" strikes me as a smell because it has to do
| with how you initialize your code/state and enforce pre- and
| post-conditions.
|
| If you allow users to complete the form without choosing T/F on
| your field, what happens? If you fail or prevent the submission,
| then I would not write the schema to capture the value as a
| nullable field. If you do accept the form without the user
| selecting T/F, then you are defaulting to either T/F and the
| field should likewise not be nullable.
|
| If the value _is_ really nullable and you are using the presence
| of null to decide anything, then it seems reasonable to enumerate
| the state space explicitly with an enum so that the next dev
| riding by on a horse doesn 't mistake your ternary boolean for a
| simple null.
|
| You can make null work in any of those cases, it just seems like
| a headache and potential hazard, but could also just be personal
| aesthetics on my part.
| varispeed wrote:
| You need a third state to know that user has not made a choice.
| If you don't want to make a choice for the user, for instance,
| you don't want to default to F or T, then third state tells
| exactly that user has to make a choice.
| dathinab wrote:
| I would say using null as 3rd state is only ok iff it's part
| of the domain logic and the 3rd state is literally no choice
| was made, i.e. nothing, i.e. null.
| nescioquid wrote:
| Agreed. Sorry if I wasn't clear, but I consider that to fall
| into the "value _is_ really nullable and you are using the
| presence of null to decide anything... " scenario, in which
| case, this is important to the domain, so explicitly model it
| so the next dev doesn't trip over the special meaning of null
| in this case.
|
| Again, personal judgment and aesthetics, probably.
| sprite wrote:
| I would just use NULL
| culpable_pickle wrote:
| This is slightly different and a funny, but you reminded me of
| https://thedailywtf.com/articles/What_Is_Truth_0x3f_
| [deleted]
___________________________________________________________________
(page generated 2022-06-17 23:02 UTC)