Code Tips #1: Hide Dependency to a Data Structure

Many times we encounter situations where our code depends on a complicated data structure or a data structure that has implicit semantics. For example, let’s say we create a chart and the data that we have to use is a response from a server (we can’t change the structure of the response). The response from the server looks like this:

[
 ["2018-12-02", 1000], 
 ["2018-11-02", 900], 
 ["2018-10-02", 200], 
 ["2018-09-02", 300]
]

This structure has an implicit meaning, it is an array of arrays, each of which has two values, the first represents a date and the second one a value. The naive approach is to use this structure directly in our code, like:


class Chart {
  constructor(data){
    this.data = data;
  }

  createDomains(){
    x.domain(d3.extent(this.data, d => d[0])); // date
    y.domain(d3.extent(this.data, d => d[1])); // value
  }

  createLine(){
    d3.line()
      .x(d => d[0]) // date 
      .y(d => d[1]); // value
  }

  formatDate(){
    this.data.forEach(d => {
      d[0] = parseTime(d[0]); // date
    });
  }
}

The problem with this approach is that, if the server decides to change the order of the values, or introduces a new one in the first or second place of the array, our code we break, let’s say the structure changes to look like:

[
 ["Dinos", "2018-12-02", 1000], 
 ["Nikos", "2018-11-02", 900], 
 ["Petros", "2018-10-02", 200], 
 ["Giannis", "2018-09-02",  300]
]

Now our class is broken, every single method of it is broken. We have to update every method to fix the problem. A better approach would be to not depend on the structure of the response.

class Chart {
  constructor(data){
    this.data = data.map(dataPoint => ({
      date: dataPoint[0],
      value: dataPoint[1],
    }));
  }

  createDomains(){
    x.domain(d3.extent(this.data, d => d.date));
    y.domain(d3.extent(this.data, d => d.value));
  }

  createLine(){
    d3.line()
      .x(d => d.date)
      .y(d => d.value)
      .values(this.data) 
  }

  formatDate(){
    this.data.forEach(d => {
      d.date = parseTime(d.date);
    });
  }
}

If the server decides to change the passed data structure, we will have to update our code only in a single place, the constructor, leaving every other method untouched.

Leave a Reply

Your email address will not be published. Required fields are marked *