Hacker News new | ask | show | jobs
by 015a 1608 days ago
The only point I take slight issue with is Avoiding Namespaces.

I can't speak for frontend code, but on the backend:

It feels at least somewhat obviously true that unique names are good. Even if you aren't operating in a language which has global imports (which is most nowadays), unique-as-possible names can help disambiguate-at-a-glance something like:

    const user: User = await getGoogleSsoUser();
    // 100 lines later...
    console.log(user.microsoftId); // wait why isn't that field available?
    // ok i'll fix this
    const googleUser = await getGoogleSsoUser();
    // obviously that makes sense; but what about the type?
    export function getGoogleSsoUser(): Promise<User> {}
    // wait... should that return a Google API user object? or our own user model?
    // let me scroll 200 lines up, ok its defined there, open that file... 
    // or just:
    export function getGoogleSsoUser(): Promise<GoogleUser> {}
Contrived example of course, but it's a broader pattern I see every day; there's a lot of overloaded terminology in programming.

But this gets hairy really quickly.

    // google will provide these types... but lets assume you're writing your own
    export type GoogleUserV1 = ...;
    export type GoogleAPIV1GetUserResponse = {
      user: GoogleUserV1,
    }
    export type GoogleAPIV1ListUsersResponse = {
      users: GoogleUserV1[],
      count: number,
    }
    // ok lets import them
    import { GoogleUserV1, GoogleAPIV1GetUserResponse, GoogleAPIV1ListUsersResponse } from "my/service/google";
First, the type names get really long, which makes them hard to read at a glance. Second; this cost is replicated anytime someone wants to import something. Third, they oftentimes become a seemingly randomly ordered set of words written like a sentence; why is it not "GoogleV1APIListUsersResponse" or "GoogleAPIListUsersV1Response"?

We can solve the second problem by doing an old-style wildcard import:

    import * as google from "my/service/google";
    const user: google.GoogleUserV1 = await getGoogleSsoUser();
But this almost always ends up stuttering, because the producer package still wants to guarantee unique-as-possible exported names, as asserted above. So we made problem 1 worse, and did nothing for problem 3.

With namespaces:

    export namespace Google {
      export namespace User {
        export type V1 { ... }
        export type GetResponse { ... }
        export type ListResponse { ... }
      }
    }
    // now to import it
    import { Google } from "my/service/google";
    const user: Google.User.V1 = await getGoogleSsoUser();
The symbol, as a whole, isn't shorter. But, it's easier to read (and write!). It also helps disambiguate where in the symbol each component of the type's name should reside, when the producer wants to for example add a new type or function.

The argument against presented by the article boils down to: it creates unnecessary fluff in the emitted javascript. That's a reasonable argument; it does. In practice, it's more nuanced. First: I've never seen it cause an issue. So, premature optimization, YMMV, etc. Second: the fluff is erased for types anyway; so it only becomes an issue for functional code defined like this (all of my examples were in types, but its easy to imagine a Google.User.List function). Third, though not a direct counterargument to the article: it's literally how Google organizes the types we've been talking about [1] (though, how they organize the functional code, I'm not sure).

[1] https://github.com/DefinitelyTyped/DefinitelyTyped/blob/mast...