4 ms·
> All this, without touching any tests. I didn’t need to - because the types provided the direction. Which is all great and wonderful - until things change, an
by kstenerud 3y ago
> All this, without touching any tests. I didn’t need to - because the types provided the direction.
Which is all great and wonderful - until things change, and requests can suddenly come in without an ID - meaning that you must assign them an ID in your response. Not so easy to model using only the type system!
And then later as session support is added, there are now two IDs, but one of them must be omitted if the session is not already active in the system. So now you must either make your request aware of the session system so it can check, or you must move the validation out of the request. Your type system is no longer helping you avoid erroneous conditions, and your lack of tests leaves you exposed.
It's all great when the system is small and easy to model...
- turboponyy 3y agoDepends on the type system - sounds trivial in dependently typed languages.
- angarg12 3y agoHere is a sharp take in the types vs tests debate https://www.destroyallsoftware.com/talks/ideology https://www.destroyallsoftware.com/talks/ideology
- drewcoo 3y agoNow do one on attempting to make a simple point via scannable/searchable text essays versus video-only content. I take a hard stance on that debate.
- mrkeen 3y ago> Which is all great and wonderful - until things change, and requests can suddenly come in without an ID But this is where type systems shine! You create a RequestWithoutId and let the boundary accept it (per your scenario). Hit compile and see what fails, in this case, calling the next method into the system will fail, because it expected a different type. Now you can choose to keep plumbing RequestWithoutId through the system - guided only by the type-checker, or you can write a RequestWithoutId->RequestWithId implementation. If your system functions correctly for-all RequestWithId, and you can make a conversion for-all RequestWithoutIds, then your system will function correctly for-all RequestWithoutIds. But can you turn a RequestWithoutId into a RequestWithId? Inspect the type of Id. If it's a UUID, it's most likely safe to generate in-place, and you're gold. If it's a String or an Int or an Any, then it's no longer mechanical and you probably need to start thinking.
- taeric 3y agoWhere it starts to get more cumbersome than it is worth is requests that can come in with any combination of N fields. The generic path defined here would have RequestWithA, RequestWithB, RequestWithC, etc. And, arguably, yes, that is a good idea. Realistically, nobody wants to review all of that. And you will get phantom code of RequestWithZ that isn't actually valid anymore, but nobody remembers it and there is nothing about the type that makes that clear. In fact, you assume that it has a type, it must be needed. I suspect you will push for groups of the fields that are relevant for the request. Such that it is RequestWithGroupA and such, but, isn't that what "Request" is supposed to be already? The group of data sent? So now you start down a taxonomy of Requests and you build an impressive machine that can deal with this. Which is even more amusing, as that is called a program. And often we accept some invalid possibilities for highly theoretical reasons that help keep the programs easy to read for the people using them.
- mrkeen 3y ago> And you will get phantom code of RequestWithZ that isn't actually valid anymore It is the opposite. My IDE will show "no usages of RequestWithZ". I will not get any such help if it was encoded as Request.get("z").
- majormajor 3y agoNah, your IDE will show all the usages of RequestWithZ, but won't tell you anything about how the thing that sent RequestWithZ has been gone for two years. You'd still need instrumentation with that.
- taeric 3y agoAs the sibling post said, you probably have a ton of code that deals with what happens when a RequestWithZ appears. The thing that changed is that that isn't a thing that can actually be sent to you. Now, you are right that you can contrive a situation where you can choke off all parts that trim this down. And then you can use the type system as a tool to help delete dead code. When it works, it is a great tool. Just like the example in the original article. Please don't take my point as a complete teardown of the idea. Just don't oversell it, either.
- williamdclt 3y agoHonestly, you’re giving a really good scenario to show the usefulness of a (good) type system. What you’re describing can be expressed very well in eg typescript, and while I’d write test, I’d be confident writing the code only supported by tests
- majormajor 3y agoYeah, something like the history of Protobuf or Avro and all the discussions about schema evolution in those things will show how these systems break down in the real world. Schema evolution and "supported types of requests" are hard in similar ways. Do that munging at the boundary? Your boundary is gonna eventually be a heap of messy compatibility code to normalize requests. PLUS you would need to build some sort of sidecar to pass forward any necessary information about which sort of boundary request it was, once the business logic somewhere deep in the system starts depending on more than just the post-normalization form. If instead you want to reflect that in the type system through the whole system you need some sort of crazy-complex type hierarchy complete with a bunch of conditional validation rules or lots of permutations of types... or Maybes all over the place that just shift the burden back to the business logic code, to know which ones are REALLY needed when. Cause your business logic has no limits on its complexity, but your type system probably does.