Skip to content
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

Open
victorlin opened this issue Jun 5, 2024 · 1 comment
Open

List Resources: improve null checks on dates #892

victorlin opened this issue Jun 5, 2024 · 1 comment

Comments

@victorlin
Copy link
Member

1

from #874 (comment)

function _snapshotSummary(dates: string[]) {
const d = [...dates].sort()
if (d.length < 1) throw new Error("Missing dates.")
const d1 = new Date(d.at( 0)!).getTime();
const d2 = new Date(d.at(-1)!).getTime();
const days = (d2 - d1)/1000/60/60/24;

@ivan-aksamentov's recommendation:

Why is this bang needed? the preceding line ensures that there is at lease one element in d.

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:

const t0 = d.at(0)?.getTime()
if(isNil(t0)) { // careful to not exclude the legit value `0`
    throw ...
}
// `t0` is guaranteed to be `number` in this branch

I would go further. Finding first and last value is a common enough "algorithm" that I would introduce a utility function:

export function firstLastOrThrow<T>(a: T[]): [T, T] {
    // TODO: use .at() or lodash head() and tail() along with null checks 
}

This would make the code very pretty and safe, with all dirty details hidden:

const [t0, t1] = firstLastOrThrow(dates) // guaranteed to return a pair of numbers or to  throw

2

from #874 (comment)

function _draw(ref, resource: Resource) {
// do nothing if resource has no dates
if (!resource.dates) return
/* Note that _page_ resizes by themselves will not result in this function
rerunning, which isn't great, but for a modal I think it's perfectly
acceptable */
const sortedDateStrings = [...resource.dates].sort();
const flatData = sortedDateStrings.map((version) => ({version, 'date': new Date(version)}));

const x = d3.scaleTime()
// presence of dates on resource has already been checked so this assertion is safe
.domain([flatData[0]!.date, new Date()]) // the domain extends to the present day

@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:

export function at<T>(a: T[], index: number): T {}

If you can find a fallback value, then something like this might work:

flatData[0]?.date ?? new Date()

This cannot fail and requires no hacks.

Continuing with the wrapper (though not directly applicable here sadly):

export function at<T>(a: T[], index: number, fallback?: T): T {}

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.

@ivan-aksamentov
Copy link
Member

ivan-aksamentov commented Jun 5, 2024

To shorten a bit:

  • Try to narrow down the checks to null checks
  • Make wrappers for common operations to avoid scattering the checks everywhere (there might be libs for that)

(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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants