3 ms·
In the tables I define everything is not null with sane defaults by default. The places I do allow null are few and far between (e.g. updated_at) and I'm strug
by lugg 8y ago
In the tables I define everything is not null with sane defaults by default.
The places I do allow null are few and far between (e.g. updated_at) and I'm struggling to think of instances I've used them as anything other than absence indicators.
In fact I don't think I ever treat it as anything other than that in code either.
Was the purpose of null ever to mean anything other than I have not been defined/set?
All my objects are statically typed so I never run into the issue of testing is thing.x a thing, it's always a thing, or it's a compile error. It's either set or not set, and thanks to the database convention I only have to worry about certain values having null, most of the time it makes sense anyway. Is updated_at turthy doubles as has been updated tests.
Am I incorrect in this method? With this method I fail to see big extra complication. Will switching to option types help me? I debate they will not. But I'm happy to be convinced. I do avoid nulls. I just haven't seen a problem with them in my own code. (Not true for others)
Specifically, with the caching problem, provided you constrain the cache to reason about null == not set. I see no problem.
Cache.get(K) // null
Cache.set(K,3) // void
Cache.get(K) // 3
Cache.set(K, null) // deletes, void
Cache.get(K) // null
Cache.set(K, false)
Cache.get(K) // false
Only certain values of mine are going to potentially be null from the database, all of which will be contained within serialised objects.
I just never see the issue the author has. The times K do see it are when people get too clever with default values.
I understand it, I just don't see it in practice. Certainly my not frequently enough to make language changes.
Title should just read "Stop abusing null" because the only time I've seen it be an issue is when people are dual encoding meaning.
- lmm 8y ago> In fact I don't think I ever treat it as anything other than that in code either. > Was the purpose of null ever to mean anything other than I have not been defined/set? Different people understand null differently (it might mean "error", "value not in map", "invalid user input", ...) and there's never been a clear consensus. If "null" only ever has one meaning anywhere in your codebase, and any third-party libraries you use only ever use it to mean the same thing, you're probably ok. But as soon as there are multiple meanings you'll have confusion and bugs. > Specifically, with the caching problem, provided you constrain the cache to reason about null == not set. I see no problem. If you only have the one cache, sure. As soon as you have a two-level cache you start to have problems (you can no longer cache absent results, since you're using the same representation for absence from your outer cache). Or as soon as null shows up anywhere else. It's the same problem as stuff that relies on evaluation order, or threadlocals: it's ok most of the time, as long as you're not combining it with something else that does the same thing. The trouble is the times when these noncompositional constructs break down are when you have complex nested code - which is precisely when you most need everything to work the way you'd expect.
- lugg 8y ago> Different people understand null differently (it might mean "error", "value not in map", "invalid user input", ...) Null only has one meaning, null. That's the point. As soon as you start applying more to it than that you get problems. > and there's never been a clear consensus. This is simply not true. Null is null. That is all. Period. End of story. It has never been more than that. If you have libraries, functions or existing code that ignores this, then that's on you, the developer, to reason about. I guess I'm taking your meaning a bit out of context, I do understand years of common practice have resulted in much abuse but I don't think the language designers would have ever denoted double meanig in null values. > If you only have one cache, sure I feel like you're missing my point. If you need to handle more meaning than null == notset/unset/absent then you need to resort to a new data type. Null only has one meaning, null. You can't get two meanings out of one. You, as the consumer of the cache must then decide on how to represent or encode further meaning. Either using the Some<T> pattern or an empty string or something like that. This serialisation can easily be wrapped around a base cache class that just deals with simple storage where null == not set or unset (absent value.) But the underlying pattern shouldn't involve itself with further concerns than it needs to. This opinion is precisely because I've seen this sort of oh I'll just add a has function, oh and then I'll add a sub par serialisation library. Ok now I need a sometimes unserialize. Oh some legacy? Ok now I need to deserialize once to one level and twice to all levels. Oh yea, now I should throw not found. Ok now every single call to cache.get must be wrapped in try catch and we must cast some values to false and others to empty string, oh yea and you have to call has before every get even if you just want to take advantage of a dynamically typed language and test for that falsely value. Cache.get(test) && okdothing(); It's two functions set sets the thing. Get gets it. If it's not there it returns null. That is the whole contract. Why do people try to over complicate the base contract? It's just crazy over engineering. > which is precisely when you most need everything to work the way you'd expect Indeed. And I expect null == null. Not null == not yet set, set but cleared, set but empty, false, error, not found, or anything else for that matter. I can use null to represent that my cache does not have a value for that key because that is the design I chose that
- lmm 8y ago> I guess I'm taking your meaning a bit out of context, I do understand years of common practice have resulted in much abuse but I don't think the language designers would have ever denoted double meanig in null values. The language designers didn't give any single clear meaning to null. They just put it in the language, and so different library authors (entirely understandably) used it for different things, and it's now impossible to standardise on any one universal meaning. > I feel like you're missing my point. If you need to handle more meaning than null == notset/unset/absent then you need to resort to a new data type. Null only has one meaning, null. You can't get two meanings out of one. Indeed, because null is a language-level special case. (Whereas using Option you wouldn't have any problem: Option is just another normal user-defined type in the language, so Option<Option<T>> works no differently from any other Option). > You, as the consumer of the cache must then decide on how to represent or encode further meaning. Either using the Some<T> pattern or an empty string or something like that. So you have a bunch of awkward complexity in precisely the case where you least want extra trouble. You don't know how many places the cache might assume that null values have its particular meaning, and you have no way to know whether you've got them all. The most dangerous pitfalls in programming are things that usually work. > It's two functions set sets the thing. Get gets it. If it's not there it returns null. That is the whole contract. Why do people try to over complicate the base contract? It's just crazy over engineering. There's nothing complicated about using option. Set sets the thing. Get gives you an option that's either some if the thing was set, none if it wasn't. Perfectly normal datatype like you'd write yourself, no special cases anywhere. > I can use null to represent that my cache does not have a value for that key because that is the design I chose that Only if you write all you own code and never use anyone else's libraries. And even then, you have to remember all the things you used it to mean in all the places you used it. There's only one null and there's no way to define a user-defined thing that works like null, so it begs to be abused (I'd argue to use it at all is to abuse it, given that it has no particular meaning defined in the language).