Make invalid states unrepresentable - someone
Some problems:
What can we do? Leverage the compiler more:
Go does not make this easy for us, unfortunately. We do not have good enum types, nor generics. What can we do? Let's do a thought experiment: be as extreme as possible with the Go type system to increase safety / purity of our application.
Active validation has problems:
Prefer dedicated constructors for types
Then, we know that create foo has validated inputs:
Problem, what about someone just doing Name{someBadString}
as a raw caller? Instead, we make Name
an interface
and prevent exporting its raw struct. The only way to get a Name
type is via the constructor, which validates inputs.
Zero values of important types can bypass their constructor requirements we mentioned above. Anyone can create a Name{someBadString}
from anywhere, bypassing our controls.
Moreover, sometimes the definition of what "allowed zero values" are is complex, as we've had many issues with using nil vs. empty slices in serialization paths.
Maybe we can mark values as complete or incomplete. Types with fields that are allowed to be zero values shouldn't affect the zero-value-ness of that type, or we should have more granular control over them.
Let's define a Complete
interface which is met by types that must be complete.
The above returns false is the value is the zero value and is invalid, or for complex types, if any of its inner methods that satisfy Complete
return false.
Some of these ideas can be used to implement nice forking logic depending on fork versions! Super-structs such as beacon state could benefit from this if we have a notion of completeness depending on fork object
There are two categories of validation errors in applications such as Prysm:
and our dev build:
Pretty radical idea, but it's an idea nonetheless. This approach panics in development, helping us set up CI jobs that fail before they reach prod.
nil
as the expression of absence is pretty terrible in our design, as it can mean an accidental omission, or it could be on purpose and make our completeness checks tricky. Other languages that do not have nil
instead use optional types. Here's what they look like in Go:
Why? Well we can express absence safely without nil pointer dereferences, and because option types are non-nilable, we will receive compile time errors if we forget to add them to a struct:
Compile time error below! We forgot Value
Instead,
We can also protect our option values from invalid states by adding the type constraint to our None
constructor and checking for completeness when building an option: