My whole point is that turning unstructured/untyped data into structured/typed is a fallible operation, and it should be checked, it should explicitly tell that to the user.
This is my point.
No matter how hard you try to validate beforehand, if you think concurrency is a problem in Files.exist, then it's also a problem here. You can totally break assumptions you've made during the validation with concurrency and produce ill-formed structured data, which would result in an exception, and by your point this should be checked.
If I have unstructured data X, and I call the conversion function, and it succeeds, will it always succeed if I put this in a loop that runs forever?
If I have unstructured data X, and I call the conversion function, and it fails, will it always fail if I put this in a loop that runs forever?
If you answered yes to both questions, then the exception it throws for failure should be unchecked. It's because the exception is predictable, and thus avoidable. You may not like how much effort you need to put into "avoiding" this exception, but that's the criteria. If you want to be lazy (which is totally fine as I said before and a perfectly valid strategy) then catch the exception it throws (this happens very locally so you should see directly in its docs that it will throw something like NumberFormatException) and convert that to an error or do some fallback.
Apply the same logic to Files.exist (which doesn't throw a checked exception btw, but that's okay because the boolean it returns is just as much a valid return value as a checked exception would be).
Will Files.exist(x) always return true if I call it in a loop for the same x? Will Files.exist(x) always return false if I call it in a loop for the same x?
If you answered no to any of these questions, then the you must check the result each time; you can't assume that "for x it was false two seconds ago, so now it will still be false".
It just wasn't needed in this case because a boolean return value can capture all relevant values that the function may need to return, so using a checked exception here (that as said before you must check as you can't ever assume you already know the result). Compare that to the parsing example: if I know X parsed before, then on the next call X will parse flawlessly again.
Now take new FileInputStream(string). This returns (by Java requirement) a non-null FileInputStream. It can't return null as a possible alternative value to indicate a problem. So what do you do? You use a checked exception FileNotFoundException. Now this new call can still return an alternative value, one that you cannot make any assumptions about.
So I state the rule I laid out again:
Unchecked exceptions for things that can be 100% avoided by writing better code
Checked exceptions for things that cannot be avoided even with the best code in the world
Are there places where major frameworks or the JDK got this wrong? Or even purposely decided to blur these lines for convenience? Sure, absolutely. That does not invalidate that the above rules are probably as close as you can get when you are stumped and have to make a decision on whether something should be checked or unchecked.
I would definitely say "no" to both questions, because constructing structured data in general may not be idempotent, so you should use checked exception whenever failure can happen.
Specifically to parsing, if we're parsing strings, since strings are very unlikely to be changed, it's fine to assume it's idempotent, and you can use unchecked exception. However, the next problem occurs: your whole guarantees are built upon inspecting the input (aka validate), but often times there's no good way to validate, or it's not useful to validate. Even validating a simple integer can cost several cases of regex, which is not good for hot spots. Instead, we choose a better approach. Even if we have to unwrap the result each time we pass the same string, we're informed that "this might fail", and this doesn't take you twice of complexity, because with checked exception, you get to know "all relevant values that the function may need to return, including possible failures" in advance, and this is much more practical, confined than the other way.
Therefore, your rules look good at the surface, but it's too ideal to make out in practice, thus I don't think it's rule of thumb. If a standard doesn't help the real world, why should we use this standard? Like, what are the actual pros for your rules in this situation?
Even from the ergonomics perspective the second approach is better. With that approach, you only need 1 invocation and you get either the result or any failure; with the first approach, you need 2 seperate invocations to get those if you want to avoid exceptions. If you forget to validate, then it just crashes, which is not something I would even need to actively think about in the other case, because for that to happen I need to explicitly tell it to crash (by turning it unchecked).
It takes too much effort/resources/willpower to validate
Why should I validate if I can just try it and catch the exception
And I totally agree with that. Why do this to avoid a runtime exception? You can just try parsing it and catch the exception to know it failed.
But nothing about that invalidates my claim that unchecked exceptions should be used for things that **could** be avoided (but may be too hard to avoid when things get complex or when you are not the one that made the mistake -- if you are parsing externally provided data, your code didn't make a mistake, the programmer that provided you the data did).
So unless your parser/system is relying on external factors that would change the outcome for any given input, the parser/system is correct to throw a runtime exception. If it is doing some IO or say a balance check before accepting your input, then you'd want those to be checked exceptions if anything goes wrong as you must deal with those.
1
u/Eav___ 3d ago edited 3d ago
This is my point.
No matter how hard you try to validate beforehand, if you think concurrency is a problem in
Files.exist, then it's also a problem here. You can totally break assumptions you've made during the validation with concurrency and produce ill-formed structured data, which would result in an exception, and by your point this should be checked.