Skip to content

Instantly share code, notes, and snippets.

@sebmck
Created May 3, 2024 23:11
Show Gist options
  • Save sebmck/43171635b27a5cb49d0b237121c61b14 to your computer and use it in GitHub Desktop.
Save sebmck/43171635b27a5cb49d0b237121c61b14 to your computer and use it in GitHub Desktop.

Sebastianisms

List of habits/opinions I have when writing JavaScript/TypeScript. Some of these are nits, some are just generally good practice. Regardless, they are all things I feel strongly about.

What I don’t care about

  • Double or single quotes.
  • Tabs vs spaces.

Opinions

  • Avoid null , never willingly pick it, prefer undefined. Only use it when interfacing with other APIs. (RegExp.prototype.match for example.) Developers seem to love supporting both. If something does expect null, use strict equality to check it.
// GOOD
if (maybeFrames === undefined && stack !== undefined) {
  frames = parseStackString(stack);
}

// BAD
if (maybeFrames == null && stack != null) {
  frames = parseStackString(stack);
}
  • Always put undefined first in a union:
// GOOD
let value: undefined | string;

// BAD
let value: string | undefined;
  • Prefer private properties and methods.
// GOOD
class Foo {
  #bar() {}
}

// BAD
class Foo {
  private bar() {}
}
  • Always declare the accessibility of every class method.
// BAD
class Person {
  getAge(): number {}
  
  generateName(): string {}
}

// GOOD
class Person {
  public getAge(): number {}
  
  protected generateName(): string {}
}
  • Always explicitly type public methods.
// BAD
class Foo {
  bar() {}
}

// GOOD
class Foo {
  public bar() {}
}
  • Very rarely use ternaries.
// GOOD: Mostly use them for functions with only a return statement. eg:
function normalizeString(value: unknown): undefined | string {
  return typeof value === 'string' ? value : undefined;
}

// BAD: Generated by ChatGPT, very nice.
const result = condition1 ? (
  condition2 ? (
    condition3 ? (
      condition4 ? 'Result 1' : 'Result 2'
    ) : (
      condition5 ? (
        condition6 ? 'Result 3' : (
          condition7 ? 'Result 4' : 'Result 5'
        )
      ) : 'Result 6'
    )
  ) : (
    condition8 ? 'Result 7' : (
      condition9 ? (
        condition10 ? 'Result 8' : (
          condition11 ? 'Result 9' : 'Result 10'
        )
      ) : 'Result 11'
    )
  )
) : 'Result 12';
  • Avoid map, forEach, filter, and every. I have found that you end up chaining them, and it’s almost always more maintainable, clearer, and more performant to write a manual loop.
// BAD
arr.filter((elem) => typeof elem === 'string).map((elem) => elem.trim());

// GOOD
for (const elem of arr) {
  if (typeof elem === 'string') {
    stack.push(elem.trim());
  }
}
  • forEach is disgusting. This was in the previous item but it deserves a dedicated section because I hate it so much.
// BAD: Stop reinventing `continue` and `break` with `return`.
arr.forEach((elem) => {
  if (elem === undefined) {
    return;
  }
  
  dispatch(elem);
});

// GOOD
for (const elem of arr) {
  if (elem === undefined) {
    continue;
  }
  
  dispatch(elem);
}
  • Always use explicit comparisons and never rely on boolean coercion. It’s much safer and nicer to be explicit about your data types and what you’re refining away.
// BAD
if (!user) {}
if (user.enabled) {}
if (calback) {}

// GOOD
if (user !== undefined) {}
if (user.enabled === true) {}
if (calback !== undefined) {}
  • Avoid defaults in destructuring. I find it difficult to mentally parse.
// BAD: Not visually clear.
const {foo = false} = opts;

// GOOD
const foo = opts.foo ?? false;
  • Prefer namespace imports. Only use named imports if their names are already namespaced so it’s clear what it’s referring to.
// BAD: Confusing, pollutes module scope.
import { spawn } from 'node:child_process';

// GOOD: Any competent bundler will inline and optimize away the namespace.
import * as cp from 'node:child_process';
cp.spawn;
  • Don't use objects as maps
// BAD
const obj = {};
obj[key] = value;

// GOOD
const obj = new Map();
obj.set(key, value);
  • Function declarations over const arrow expressions. Not only does it look nicer with less syntactic noise, the hoisting makes things a lot easier. You are able to move functions around in your code without it breaking.
// BAD
const doTheThing = (name: string) => {};

// GOOD
function doTheThing() {}
  • Avoid nested destructuring. I don’t generally use it at all but nested destructuring is disgusting.
// BAD: wtf?
const {
   props: { dialogue, editor, events, mapName, onAction, pan, paused },
   state: { animationConfig, animations, behavior, lastActionResponse, map },
 } = this;
 
// Better
const { dialogue, editor, events, mapName, onAction, pan, paused } = this.props;
const { animationConfig, animations, behavior, lastActionResponse, map } = this.state;
  • Don't use .filter(Boolean). I do not understand the infatuation with this pattern. Not only is boolean coercion bad, I never have an array with holes like that in the first place. Maybe it’s because I avoid map where you’re forced to return a value.
// BAD
arr.filter(Boolean);

// GOOD
// Just don't force yourself into a scenario where it's useful.
  • Bail out with small cheap checks. Sometimes a small check can avoid doing a ton of work.
// GOOD
function process(queue, rawOpts) {
  if (queue.length === 0) {
    return;
  }

  const opts = normalizeOptions(rawOpts);
  for (const item of queue) {
    processItem(item, opts);
  }
}
  • Avoid destructuring in function parameters when you have a lot of properties. Put it in a const instead in function body.
// BAD
async function writeStaticAssets({
  files,
  params,
  config,
  outDir,
  address,
  projectRoot,
}: {
  files: FileTarget[];
  params: URLParams;
  config: ConfigT;
  outDir: string;
  address: string;
  projectRoot: string;
}) {}

// GOOD
async function writeStaticAssets(opts: {
  files: FileTarget[];
  params: URLParams;
  config: ConfigT;
  outDir: string;
  address: string;
  projectRoot: string;
}) {
  const { files, params, config, outDir, address, projectRoot } = opts;
}
  • Try to avoid optional chaining except in very specific cases. I find it difficult to know where I need to place the ? and what combination of random symbols I need to optionally call something. It also encourages sloppy typing and initialization.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment