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…
Applying “make invalid states unrepresentable”
61–70 of 193 posts
Re: Applying “make invalid states unrepresentable”
#62Re: Applying “make invalid states unrepresentable”
#63For 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. :)
Re: Applying “make invalid states unrepresentable”
#64Earlier 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.
Re: Applying “make invalid states unrepresentable”
#65Make 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”
#66Re: Applying “make invalid states unrepresentable”
#67In 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.
Re: Applying “make invalid states unrepresentable”
#68As 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”
#69The 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”
#70This 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.
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).