-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
List Resources: improve null checks on dates #892
Comments
To shorten a bit:
(Sadly, compilers are not yet smart enough to take array length checks into account. The length is only known at compile time, so in some cases it is plain impossible to decide at build time). |
@ivan-aksamentov sorry for the late reply. I'm revisiting this while doing some TypeScript conversion in Auspice.
I tried I don't see how this avoids scattering checks. There would still be a potential error from that function. Example: function at<T>(a: T[], index: number): T {
const value = a[index];
if (value === undefined) {
throw new Error("Index out of bounds!");
}
return value;
}
let firstDate;
try{
firstDate = at(flatData, 0).date;
} catch (e) {
// TODO: handle this
}
const x = d3.scaleTime()
.domain([firstDate, new Date()])
// ... |
@victorlin I don't remember anything about it. Here is a current brain dump :)
The choice here is that you could either insist on using syntactic sugar bracket notation in an unsafe way and defeat the compiler checks or you could combine it with a check - runtime and compile-time. Then, if checks appear in many places, it makes sense to extract a function. That's how I ended up with my thesis. You are doing the check in the original code anyways, it's just it is disconnected from the array access. Which is dangerous, because there can be any amount of code between the check and array access in the future. Someone can come and refactor them apart into different functions, etc. Not every place needs this, so no, you won't replace every array access with this. In many places you'd be able to find a default, then You would not need to catch the exception. You don't catch it in the original code. What would you do in the There needs to be a distinction:
Also, conceptually this is not about array accesses here. Here you want to find the first and the last date, and it's a well-known algorithm. Wrapped into a function it will look much better. Then the checks become preconditions to the algo, meaning assertions, the first type of errors. There will be much less questions and design decisions to make this way. Constant inline reimplementation of well-known algorithms, especially small ones like this, can be a constant source of headaches. Pragmatically, if all that feels too tedious, then perhaps it makes sense to turn off this particular check in tsconfig. After all, it worked fine in js. This particular case won't make program much safer. So I am def being pedantic here :) |
@ivan-aksamentov thanks! I like the framing around error handling instead of the vague "improve null checks". This is what I came up with in #1073 for each of the examples in the issue description: And as for error handling, it's the first commit in that PR: d6741a5 If you have some time to review, it would be helpful in shaping how we use TypeScript in our codebases 🙏 |
@victorlin I like it! I think function _snapshotSummary(dates: string[]) {
const { first, last } = rangeBounds(dates)
return humanizeDuration(last - first)
} This makes the body self-documented - gives you 2 easily recognizable well-known algo steps, which can be extracted, named and tucked away into the
If replaced with libraries - there's no code to maintain or type-check - this eliminates the need for this part of refactoring adventure. The most type-safe code is no code |
@ivan-aksamentov thanks for reviewing!
+1 for splitting into 2 parts especially with the verbosity of added error handling. I can look into that separately. Each part is only ~5 lines of code that is working fine. I think easier to just maintain these in-house and not add extra dependencies. Even more so when considering a similar change in Auspice which is published as a library, where I think we should avoid unnecessary dependencies to improve the experience of using the package in another project. |
1
from #874 (comment)
nextstrain.org/static-site/src/components/ListResources/Modal.tsx
Lines 134 to 140 in aae874c
@ivan-aksamentov's recommendation:
Compiler is not smart enough to figure this out from this kind of the conditional. Connecting
.length
to.at()
is tough for a type system.What it can easily figure out is null checks. So a cleaner way would be to check and throw after non-fallible array access:
I would go further. Finding first and last value is a common enough "algorithm" that I would introduce a utility function:
This would make the code very pretty and safe, with all dirty details hidden:
2
from #874 (comment)
nextstrain.org/static-site/src/components/ListResources/Modal.tsx
Lines 148 to 156 in aae874c
nextstrain.org/static-site/src/components/ListResources/Modal.tsx
Lines 180 to 182 in aae874c
@ivan-aksamentov's recommendation:
The problem with comments like this:
// presence of dates on resource has already been checked so this assertion is safe
is that 3 months from now a fresh intern comes and inserts new code between the check and the assertion (not knowing that this assertion exists - it's hard to find even when looking for it specifically) and then 💥. Though, to be fair, it did not happen with js previously (due to lack of interns?)
Again, a small wrapper would make it safe and clean:
If you can find a fallback value, then something like this might work:
This cannot fail and requires no hacks.
Continuing with the wrapper (though not directly applicable here sadly):
Another option, although more involved, is to write a custom array type which always returns value or throws and never returns undefined. There might be libraries implementing non-empty arrays.
The text was updated successfully, but these errors were encountered: