by whitakerch on 6/17/22, 7:38 PM with 41 comments
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
by nescioquid on 6/17/22, 8:30 PM
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.
by jeroenhd on 6/17/22, 8:58 PM
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.
by SigmundA on 6/17/22, 8:34 PM
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.
by jrockway on 6/17/22, 8:42 PM
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.)
by DubiousPusher on 6/17/22, 9:46 PM
-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.
by marginalia_nu on 6/17/22, 9:13 PM
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.
by disintegore on 6/17/22, 8:38 PM
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.
by IceMetalPunk on 6/17/22, 7:41 PM
by sshine on 6/17/22, 8:41 PM
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.
by rvdginste on 6/17/22, 9:46 PM
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.
by j1elo on 6/17/22, 9:07 PM
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.
by culpable_pickle on 6/17/22, 8:48 PM
by 8note on 6/18/22, 2:18 AM
I don't think I've used it to describe true or false though. Possibly as a third option, but the names for the options end up well described, not just true and false.
Looking back, I'd much prefer to make lots of types, describing the object along the way. In typescript it would be real easy to describe too, just &-ing the steps' results to be main object along the way
by adzm on 6/17/22, 8:39 PM
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.
by dathinab on 6/17/22, 8:50 PM
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).
by throwaway81523 on 6/17/22, 10:11 PM
by NovemberWhiskey on 6/17/22, 8:47 PM
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?
by stevefan1999 on 6/18/22, 3:00 AM
by aliswe on 6/17/22, 8:59 PM
by dvh on 6/17/22, 8:34 PM
by sprite on 6/17/22, 8:30 PM
by olliej on 6/17/22, 8:40 PM
by 0xfacfac on 6/18/22, 12:44 AM