Friday, May 10, 2013

Over-Thinking Class Design

‹prev | My Chain | next›

For better or worse, the data in existing JavaScript ICE Code Editor installs (like that at http://gamingjs.com/ice) store their data in browser localStorage with a single localStore key: codeeditor. The value pointed to by the codeeditor key is a JSON string that lists all of the projects that have been stored.

The UI lists projects by title:



Titles are also guaranteed to be unique, so it might make sense to index the localStorage for the Dart version of ICE Code Editor by title. Doing so would make all the more sense since Dart has such a nice HashMap-based interface into localStorage.

As nice as it might be to switch the storage strategy, I think switching an entire app from JavaScript to Dart is enough of a change. So for now, I will create a new Store class that works with the existing JavaScript storage. In addition to not rocking the boat too much, this also give me the change to deploy the Dart application side-by-side with the JavaScript version so that I can test it out before making the official switch over. If I deploy to the http://gamingjs.com/ice-beta URL, it will still have access to the same localStorage store since both the /ice and /ice-beta URLs resides on the same server.

Still, it makes sense for Store to implement the HashMap interface just like Dart's localStorage does—that way my code will not need to change much should I ever decide to migrate. At the risk of prematurely optimizing, I will not implement the store with the same HashMap<String, String> signature. Instead, the values returned will be of a self-defined Project class:
library ice;

import 'dart:collection';

class Store implements HashMap<String, Project> {
  // Need code here
}

class Project {
  String name, code;
  bool autoupdate = true;
  String type = 'text/plain';
}
In other words, Store will be responsible for serializing and de-serializing projects.

By implementing the HashMap interface, I give myself a built-in TODO list:
ice-code-editor git:(master) ✗ dart_analyzer lib/store.dart
file:/home/chris/repos/ice-code-editor/lib/store.dart:7:7: Concrete class Store has unimplemented member(s) 
    # From Map:
        Iterable<String> keys
        Iterable<Project> values
        int length
        bool isEmpty
        bool containsValue(Project)
        bool containsKey(String)
        Project [](String)
        void []=(String, Project)
        Project putIfAbsent(String, () -> Project)
        Project remove(String)
        void clear()
        void forEach((String, Project) -> void)
    # From HashMap:
        void addAll(Map<String, Project>)
     6: 
     7: class Store implements HashMap<String, Project> {
              ~~~~~
It makes the most sense to start with the two square bracket operators for lookup and setup.

So I start with a test:
import 'package:unittest/unittest.dart';
import 'package:ice_code_editor/store.dart';
import 'dart:html';

main() {
  // ...
  group("store/retrieve", (){
    test("it can store data", (){
      var it = new Store();
      it['foo'] = {'bar': 42};

      expect(it['foo']['bar'], equals(42));
    });
  });
}
I am not testing the underlying implementation of localStorage here—just the interface. In fact, I may skip testing that the data persists in localStorage. Rather I may get the test passing first, then switch to localStorage while continuing to keep the test passing.

First steps, first: I need to get the test passing:
class Store implements HashMap<String, Object> {
  Store() { }

  HashMap _docs = {};
  void operator []=(String key, Object data) {
    _docs[key] = data;
  }

  Project operator [](String key) => _docs[key];
}
With that, I have my passing test:
PASS: store/retrieve it can store data 
Now, it is time to vary the implementation.

Which I do with my #pairwithme pair!

Update: While pairing, it became apparent that the Project class was premature. We ended up deleting it and returning just HashMaps. We also found it necessary to add a test for localStorage after all. But it works. Another successful pairing -- yay!


Day #747

No comments:

Post a Comment