Saturday, February 25, 2012

Checked Dart Views

‹prev | My Chain | next›

Yesterday, I began exploring type checking in Dart by running my test suite in checked mode (starting Dartium with --enable_type_checks). It did not have quite the effect that I expected and I cannot say that it improved my test suite. So today, am going to have a type-checked look at some of my code that is not yet tested. Perhaps type checking will prove of more use there.

So I load up my application in test mode and... it still works:


When I try to delete one of those test comic books, however, I get a big old stack trace:
Exception: 'http://localhost:3000/scripts/HipsterModel.dart': Failed type check: line 27 pos 31: type 'Comics' is not assignable to type 'bool' of 'boolean expression'.
Stack Trace:  0. Function: 'HipsterModel.get:urlRoot' url: 'http://localhost:3000/scripts/HipsterModel.dart' line:27 col:31
 1. Function: 'HipsterModel.get:url' url: 'http://localhost:3000/scripts/HipsterModel.dart' line:25 col:18
 2. Function: 'HipsterSync._default_sync@2c7ab367' url: 'http://localhost:3000/scripts/HipsterSync.dart' line:50 col:31
 3. Function: 'HipsterSync.call' url: 'http://localhost:3000/scripts/HipsterSync.dart' line:24 col:27
 4. Function: 'HipsterModel.delete' url: 'http://localhost:3000/scripts/HipsterModel.dart' line:45 col:21
 5. Function: 'Comics.function' url: 'http://localhost:3000/scripts/Views.Comics.dart' line:49 col:48
 6. Function: 'HipsterView.function' url: 'http://localhost:3000/scripts/HipsterView.dart' line:35 col:15
 7. Function: 'EventListenerListImplementation.function' url: 'dart:htmlimpl' line:23128 col:35
Although the large stack trace is not indicative of a huge bug, there is a bug here. Specifically, I am using a ternary in a very non-Dart way:
class HipsterModel implements Hashable {
  // ...
  get urlRoot() => collection ?
    collection.url : "";
}
Dart has a funny view of truthiness—everything but the boolean true is false. So even if my collection is non-null, the above will always return an empty string. So, yay! Type checking found a bug. I address it by explicitly comparing to null:
class HipsterModel implements Hashable {
  // ...
  get urlRoot() => (collection == null) ?
    "" : collection.url;
}
And I can again delete comics from the application:


When I try to open the add form, I get another intimidating type check stack trace:
Exception: String expected
Stack Trace:  0. Function: 'ElementImplementation._querySelector@1c3f8015' url: '/mnt/data/b/build/slave/dartium-lucid64-inc/build/src/out/Release/obj/gen/webkit/bindings/dart/generated/dart/ElementImplementation.dart' line:144 col:3
 1. Function: 'ElementImplementation.querySelector' url: '/mnt/data/b/build/slave/dartium-lucid64-inc/build/src/out/Release/obj/gen/webkit/bindings/dart/generated/dart/ElementImplementation.dart' line:139 col:26
 2. Function: 'ElementWrappingImplementation.query' url: 'dart:htmlimpl' line:22910 col:51
 3. Function: 'HipsterView.HipsterView.' url: 'http://localhost:3000/scripts/HipsterView.dart' line:14 col:57
 4. Function: 'AddComicForm.AddComicForm.' url: 'http://localhost:3000/scripts/Views.AddComicForm.dart' line:8 col:5
 5. Function: 'AddComic._toggle_form@19ec418e' url: 'http://localhost:3000/scripts/Views.AddComic.dart' line:19 col:19
 6. Function: 'EventListenerListImplementation.function' url: 'dart:htmlimpl' line:23128 col:35
This stack trace leaves my code on line 14 of the HipsterView base class, which is the first line of the constructor:
class HipsterView {
  // ...
  HipsterView([el, this.model, this.collection]) {
    this.el = (el is Element) ? el : document.query(el);
    this.post_initialize();
  }
  // ...
}
This turns out to be a bit more subtle. When an instance of AddComicForm is created, I am not assigning the optional el parameter in the constructor. Thus el is null, which is not an Element, resulting in a document.query() of null. Since document.query() expects a String, I am now getting a type checking error.

In this particular case, it is not a bug because AddComicForm overrides post_initialize to create this.el. Thus, any other view operations will succeed because this.el is defined. But that is only true for this particular class. If someone else created a view with no el, there would be errors. What I really need is something like Backbone.js's ensureElement. I will worry about that another day. For now, I make the this.el assigment conditional on el being supplied and I assert that this.el has been defined after post_initialize:
class HipsterView {
  // ...
  HipsterView([el, this.model, this.collection]) {
    if (el != null) {
      this.el = (el is Element) ? el : document.query(el);
    }

    this.post_initialize();

    // TODO define an ensureElement to create a default, anonymous element
    assert(this.el != null);
  }
  // ...
}
I also leave myself a little TODO note to make this easier to use for others. If I comment out the post_initialize() method in my sub-class, I now get an assertion error:
Exception: 'http://localhost:3000/scripts/HipsterView.dart': Failed assertion: line 17 pos 12: 'this.el != null' is not true.
Stack Trace:  0. Function: 'AssertionError._throwNew@127eafe4' url: 'bootstrap' line:552 col:3
 1. Function: 'HipsterView.HipsterView.' url: 'http://localhost:3000/scripts/HipsterView.dart' line:17 col:12
 2. Function: 'AddComicForm.AddComicForm.' url: 'http://localhost:3000/scripts/Views.AddComicForm.dart' line:8 col:5
 3. Function: 'AddComic._toggle_form@19ec418e' url: 'http://localhost:3000/scripts/Views.AddComic.dart' line:19 col:19
 4. Function: 'EventListenerListImplementation.function' url: 'dart:htmlimpl' line:23128 col:35
With the post_initialize() method in place, I am again able to bring up the add-new-comic form:


Everything else seems to work. I can again add, delete and view my comics collection—even in type checking mode.

The type checking option seemed more of an annoyance with testing than a help, but it definitely seems to help when actually coding. Without it, I had allowed at least one bug to get into my Hipster MVC framework along some questionable choices. It seems that coding in type checking mode is good advice—even if most users will run in regular mode.


Day #307

No comments:

Post a Comment