Skip to content

Instantly share code, notes, and snippets.

@bdadam
Last active February 23, 2023 21:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bdadam/1129e5601ad34cdda2eaffafa6ab3e04 to your computer and use it in GitHub Desktop.
Save bdadam/1129e5601ad34cdda2eaffafa6ab3e04 to your computer and use it in GitHub Desktop.
Dev Meeting 2022-02-10

Things we are always saying

  • Parts of the code are under-tested
  • Parts of the code are "hard to reason about"
  • Parts of the code are slow

Stateless functions, No side effects, Dependency injection

const state = {
  counter: 123,
};

function increase(num = 1) {
  state.counter += num;
}

function decrease(num = 1) {
  state.counter -= num;
}

describe('Test Counter', () => {
  it('works', () => {
    increase(1);
    expect(state.counter).toEqual(124);
  });

  it('can increment by 87', () => {
    increase(87);
    expect(state.counter).toEqual(210);
  });
});

Now try to change the order of the it functions. The test will break.

What are the issues?

  • How to test increment()? The order of it calls matters.
  • Multiple functions work on the same state

Make it stateless

Decouple the function from the state

function increase(state: State, num = 1) {
  state.counter += num;
}

function decrease(state: State, num = 1) {
  state.counter -= num;
}

describe('Test Counter', () => {
  it('works', () => {
    const state = { counter: 123 };
    increment(state, 1);
    expect(state.counter).toEqual(124);
  });

  it('can increment by 87', () => {
    const state = { counter: 123 };
    increment(state, 87);
    expect(state.counter).toEqual(210);
  });
});

Make it pure (free of side effects)

We make the state immutable.

function increase(state: State, num = 1) {
  return {
    ...state,
    counter: state.counter + num,
  };
}

function decrease(state: State, num = 1) {
  return {
    ...state,
    counter: state.counter + num,
  };
}

describe('Test Counter', () => {
  it('works', () => {
    const state = { counter: 123 };
    const newState = increment(state, 1);
    expect(newState.counter).toEqual(124);
    
    // The old state does not change
    expect(state.counter).toEqual(123);
  });

  it('can increment by 87', () => {
    const state = { counter: 123 };
    const newState = increment(state, 87);
    expect(newState.counter).toEqual(210);
    
    // The old state does not change
    expect(state.counter).toEqual(123);
  });
});

Time complexity

const taxonomyExpensive = {
  makes: [
    { id: 9, name: 'Audi' },
    { id: 13, name: 'BMW' },
    { id: 47, name: 'Mercedes-Benz' },
    // ...
  ]
};

const findMakeExpensive = (taxonomy: Taxonomy, id: number) => {
  const make = taxonomyExpensive.makes.find(m => m.id);
  return make;
};

VS.

const taxonomyCheap = {
  makes: new Map([
    [9, { id: 9, name: 'Audi' }],
    [13, { id: 13, name: 'BMW' }],
    [47, { id: 47, name: 'Mercedes-Benz' }],
    // ...
  ]),
};

const findMakeExpensive = (taxonomy: Taxonomy, id: number) => {
  const make = taxonomy.makes.get(id);
  return make;
};

What is the difference?

  • Number of comparisions? The expensive find needs to compare each make's id with the one we are looking for.
  • The more makes we have the more costly it gets.
  • Lookup in a Map is O(1) => very cheap

Set().has is also O(1)

Why do we care?

  • We don't want to block the event loop => otherwise other operations need to wait
  • Pagespeed
  • Costs

Pre-compute as much as we can

function createSlug(str: string) { return 'lorem-ipsum-dolor'; }

const taxonomy = {
  makes: new Map([
    [9, { id: 9, name: 'Audi' }],
    [13, { id: 13, name: 'BMW' }],
    [47, { id: 47, name: 'Mercedes-Benz' }],
    // ...
  ]),
};

function Component({ make }) {
  return <a href={`/lst/${createSlug(make.name)}`}>{make.name}</a>
}

Every time the component runs we execute createSlug(make.name). But the result of this function never changes (it's a stateless function).

What could be better?

function createSlug(str: string) { return 'lorem-ipsum-dolor'; }

const taxonomy = {
  makes: new Map([
    [9, { id: 9, name: 'Audi', slug: createSlug('Audi'), }],
    [13, { id: 13, name: 'BMW', createSlug('BMW'), }],
    [47, { id: 47, name: 'Mercedes-Benz', createSlug('Mercedes-Benz') }],
    // ...
  ]),
};

function Component({ make }) {
  return <a href={`/lst/${make.slug}`}>{make.name}</a>
}
  • We only create Taxonomy once, but we use it millions of times.
  • With this pattern we only pay the price of createSlug once per make instead of once per rendering.
  • We also pay in advance (during application startup) and not while we need to be as fast as possible to deliver the response to the client.

How to test list page

import resolveUrlToLsApiQueryParams from '@/services/resolveUrlToLsApiQueryParams';

describe('resolveUrlToLsApiQueryParams', () => {
  it('can resolve make, model, bodytype', () => {
    const url = `/lst/auti/a3/bt_cabrio`;
    const mockedTaxonomy = {};
    const mockedGeoLocationService = {};
    
    const queryParamsForLsApi = resolveUrlToLsApiQueryParams(url, mockedTaxonomy, mockedGeoLocationService);
    expect(queryParamsForLsApi).toEqual({
      atype: 'C',
      mmmmv: '9|||',
      body: 2,
    });
  });
  
  it('can resolve make, model, city', () => {
    const url = `/lst/auti/a3/city`;
    const mockedTaxonomy = {};
    const mockedGeoLocationService = {
      async resolve(location) {
        if (location === 'berlin') {
          return {
            zip: 'Berlin',
            lon: '13.41144',
            lat: '52.52343',
          };
        }
      }
    };
    
    const queryParamsForLsApi = resolveUrlToLsApiQueryParams(url, mockedTaxonomy, mockedGeoLocationService);
    expect(queryParamsForLsApi).toEqual({
      mmmmv: '9|||',
      body: 2,
      zip: 'Berlin',
      lon: '13.41144',
      lat: '52.52343',
    });
  });

});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment