Live data from Hacker News

Applying “make invalid states unrepresentable”

kevinmahoney.co.uk

61–70 of 193 posts

Re: Applying “make invalid states unrepresentable”

#61
post #49

I like the concept but I’ve seen a fair few examples of where the developers and users clearly had differing opinions about which states are invalid! Dates are a rich vein of examples. Some users will happily consider “25th December” to be a date, without any year, because it might be the name of a folder in which they store their Christmas stuff. More seriously, genealogists or historians may want to record “25th De…

[deleted]

Re: Applying “make invalid states unrepresentable”

#62
this is a nice idea, but as mentioned in other comments, i think the most important goal when designing a schema is to design to make the queries you'll frequently be making simple and efficient. extensibility comes second and if you can make some invalid states impossible to represent, then that's definitely a bonus.

Re: Applying “make invalid states unrepresentable”

#63
This is sound advice for sure, and I think it applies much more broadly (or do I mean deeply?) than just for databases.

For instance, one micro-application of it that makes a lot fo sense to me is the const-ness of variables in languages like C. Since a normal variable can be overwritten, and that affects the use and semantics of that variable, marking them as const whenever possible really helps in my opinion.

For instance, take this micro-snippet of code from Redis [1]:

    int time_independent_strcmp(char *a, char *b) {
        char bufa[CONFIG_AUTHPASS_MAX_LEN], bufb[CONFIG_AUTHPASS_MAX_LEN];
        /* The above two strlen perform len(a) + len(b) operations where either
         * a or b are fixed (our password) length, and the difference is only
         * relative to the length of the user provided string, so no information
         * leak is possible in the following two lines of code. */
        unsigned int alen = strlen(a);
        unsigned int blen = strlen(b);
        unsigned int j;
        int diff = 0;
Here, it seems quite important that the values of 'alen' and 'blen' do not change during the execution of the function, since it's iterating over them. The 'diff' variable on the other hand is intended to change as a function of all the characters in both strings, that's the whole purpose of the function.

So, I think the middle two lines should be:

    const size_t alen = strlen(a);
    const size_t blen = strlen(b);
That "locks" the values in, so you know that for the rest of the function at least these two values stay the same. Since changing either length mid-function would represent an invalid state, I think this is close to the OP's point.

Also please note that I have massive amounts of respect for Redis and Antirez, I'm not trying to say that the code is bad or anything, it was simply the first file in the first high-profile open source project that came to mind. Obviously this code works and has probably been more tested than most things I've written, again I'm NOT trying to somehow paint that program(mer) in a bad light.

Btw, changing the type (to me) to size_t is also an obvious, free, improvement since it frees the reader from having to worry about why the type was unsigned int to begin with. Also 'int' can be less wide than 'size_t', which again is probably not a problem in practice since the CONFIG_AUTHPASS_MAX_LEN is probably always going to be even less, but still. It's pointless complexity that triggers anxiety in people like me. :)

[1]: https://github.com/redis/redis/blob/unstable/src/acl.c

Re: Applying “make invalid states unrepresentable”

#64

Earlier quoted context omitted.

That's the point. There's no ordering of the items. So the representation can never have an error like [yesterday, tomorrow, today]. It's just a set of (yesterday, tomorrow, today).

Doesn't that just push all the responsibility onto every piece of code that uses the datatype? 'Remember to sort the contents of this set every time before using it' sounds like it is asking for trouble.

The fact that it's a set can be hidden, and any queries that depend upon an ordering can be presented as a sorted view or whatnot.

Re: Applying “make invalid states unrepresentable”

#65
So Google's protocol buffers have this feature called "required" fields, which enforce schema in the type system. You should never use it. Never. It's one of those things that sound good until you're a few years into the project. Similar to how you should never be using meaningful IDs as primary keys for objects, always use meaningless fingerprint-like integers. Or how all integers should be signed unless you're dead sure the number is unsigned (like a fingerprint). And how many integers should actually be strings, unless you're dead sure this is a number (externally provided IDs, such as for example customer account IDs, are not numbers). Or how you should be careful to use bytes rather than unicode strings.

Make your schema permissible and your code paranoid, it will pay off later. Build a data linter if necessary, but don't tie the schema.

Re: Applying “make invalid states unrepresentable”

#67
post #43
post #26

In general I agree that it's nice to make invalid states unrepresentable, but I'm not sure if I agree that this counts as a fundamental "invalid state". There is nothing about contracts which require that you can only have one active at the same time, or that that current one must be open ended. From a practical point of view it might be advantageous if you maintain only a single contract with a customer at all times…

Then the code will be changed. Making up business requirements is the number one reason for instant legacy code. Code is not set in concrete, you can add that flexibility later when it is needed, but making everything overly generic to make it easier to "implement new requirements" only leads to code that is hard to change in my experience. Also don't forget that this is only an example.

A few basic invariants about how your code is structured does a lot more than it being excessively "generic". Writing simple components that actually adhere to the single-responsibility principle (and composing them into more complex logic) doesn't just help out the mysterious future. It makes your existing codebase easier to understand and to validate up front.

Re: Applying “make invalid states unrepresentable”

#68
This is very much like database normalization, in that it has the benefit of making invalid data impossible, but the drawback of often making queries into the data much more cumbersome and usually also inefficient.

As with database normalization, it is a good idea to first do it as much as possible, and then denormalize again until it is fast enough.

Re: Applying “make invalid states unrepresentable”

#69
This violates one of my core, learned-the-hard-way, database design constraints: querying of rows should never have to rely on any linear dependence on any other row in the same table. If you ever have to do some sort of inner join of a table on itself to bring in the single "next" row to tell you information about the current row, then you are stuffed. Query complexity explodes. Performance takes a nose dive.

The end date of a contract is a property of that contract, not any other.

Forgetting all other contracts for a moment, what do you need to know about one contract? There should be a straight forward way to query that contract on its own, with a query that represents a tree through tables in the database. It should not become a graph, with the potential for cycles that graphs allow.

And I get the business requirements could need no overlaps, but gaps are clearly possible if a customer leaves for a while and then comes back later. Does that person need to then become a new "customer", because you don't allow gaps? And then are your customers' PII only allowed to be registered to a single account? Comcast has been a huge pain in the ass in years past because of moving, gaps, and email address reuse.

Re: Applying “make invalid states unrepresentable”

#70
post #48
post #42

This is part of approach to programming that is more popular in functional world. You take requirements and make system exactly right to fit these requirements perfectly and don't bother with any other concerns. When you design this close to the requirements you get better, faster and more elegant code that's easier to understand - but when requirements change you have to do much more work to adapt. Suddenly a state…

This kind of approach (type driven development) was already popular in Algol derived languages, hence why the cowboy coder would call us on Ada, Modula-2, Object Pascal side of the fence, programming with straitjacket.

I don't think it maps 1-1 with strong vs weak typing.

C (very loosely typed language) code is usually pretty close to the requirements and very strongly typed functional languages (like Haskell) are often "make DSL and write the specification in it, then run it".

Meanwhile object oriented languages often have pretty strong typesystems and cultures of using them extensively, but they also encourage designing with margin for changes (and thus using lots of layers of abstractions instead of just implementing the specification as elegantly as possible).

Post reply on HN