<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Ed Spencer &#187; code</title>
	<atom:link href="http://edspencer.net/tag/code/feed" rel="self" type="application/rss+xml" />
	<link>http://edspencer.net</link>
	<description>A Sencha Architect</description>
	<lastBuildDate>Sat, 11 Feb 2012 09:20:06 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Writing Better JavaScript &#8211; split up long methods</title>
		<link>http://edspencer.net/2009/10/writing-better-javascript-split-up-long-methods.html</link>
		<comments>http://edspencer.net/2009/10/writing-better-javascript-split-up-long-methods.html#comments</comments>
		<pubDate>Tue, 06 Oct 2009 14:51:51 +0000</pubDate>
		<dc:creator>Ed Spencer</dc:creator>
				<category><![CDATA[Examples]]></category>
		<category><![CDATA[extjs]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[patterns]]></category>

		<guid isPermaLink="false">http://edspencer.net/?p=249</guid>
		<description><![CDATA[<p>For the second time this week I&#8217;m going to pick on the usually delightful <a href="http://extjs.com">Ext JS</a> library. Last time we discussed <a href="http://edspencer.net/2009/10/javascript-module-pattern-overused-dangerous-and-bloody-annoying.html">the overzealous use of the Module pattern</a>; this time it&#8217;s the turn of bloated methods.</p>
<p>As before, I&#8217;m not really picking on Ext at all &#8211; this happens all over the place. But again, this is the library closest to my heart and the one I know the best.</p>
<h2>The Problem</h2>
<p>We&#8217;re going to take a look at <a href="http://www.extjs.com/deploy/dev/docs/source/XmlReader.html#method-Ext.data.XmlReader-readRecords">Ext.data.XmlReader&#8217;s readRecords method</a>.  Before we get started though, I&#8217;ll repeat that this is intended as an example of an approach, not a whine at Ext in particular.</p>
<pre class="brush: jscript;">
/**
 * Create a data block containing Ext.data.Records from an XML document.
 * @param {Object} doc A parsed XML document.
 * @return {Object} records A data block which is used by an {@link Ext.data.Store} as
 * a cache of Ext.data.Records.
 */
readRecords: function(doc) {
  /**
   * After any data loads/reads, the raw XML Document is available for further custom processing.
   * @type XMLDocument
   */
  this.xmlData = doc;
  var root = doc.documentElement || doc;
  var q = Ext.DomQuery;
  var recordType = this.recordType, fields = recordType.prototype.fields;
  var sid = this.meta.idPath || this.meta.id;
  var totalRecords = 0, success = true;
  if(this.meta.totalRecords){
    totalRecords = q.selectNumber(this.meta.totalRecords, root, 0);
  }

  if(this.meta.success){
    var sv = q.selectValue(this.meta.success, root, true);
    success = sv !== false &amp;&amp; sv !== 'false';
  }
  var records = [];
  var ns = q.select(this.meta.record, root);
  for(var i = 0, len = ns.length; i &lt; len; i++) {
    var n = ns[i];
    var values = {};
    var id = sid ? q.selectValue(sid, n) : undefined;
    for(var j = 0, jlen = fields.length; j &lt; jlen; j++){
      var f = fields.items[j];
      var v = q.selectValue(Ext.value(f.mapping, f.name, true), n, f.defaultValue);
      v = f.convert(v, n);
      values[f.name] = v;
    }
    var record = new recordType(values, id);
    record.node = n;
    records[records.length] = record;
  }

  return {
    success : success,
    records : records,
    totalRecords : totalRecords || records.length
  };
}
</pre>
<p>Anyone care to tell me what this actually does? Personally, I have absolutely no idea. I recently found myself needing to implement an XmlReader subclass with a twist which required understanding how this works, and let&#8217;s just say it wasn&#8217;t easy!</p>
<p>So what is it that makes the above so terrifyingly hard to understand? Well, in no particular order:</p>
<ul>
<li>It&#8217;s <strong>too long</strong> &#8211; you&#8217;d need to be a genius to easily understand what&#8217;s going on here</li>
<li>The variable names <strong>don&#8217;t make much sense</strong> &#8211; some of the oddest include &#8216;q&#8217;, &#8216;ns&#8217;, &#8216;v&#8217;, &#8216;f&#8217; and &#8217;sv&#8217;</li>
<li>There&#8217;s <strong>minimal commenting</strong> &#8211; we&#8217;re given a single-line clue at the very top as to what these 40-odd lines do</li>
</ul>
<h2>A Solution</h2>
<p>Let&#8217;s see how the reworked code below addresses each of the concerns above:</p>
<ul>
<li>Although we end up with more lines of code here, no single method is more than around 10 LOC</li>
<li>No single letter variable names &#8211; you no longer have to decode what &#8217;sv&#8217; means</li>
<li>Constructive commenting allows rapid comprehension by skimming the text</li>
</ul>
<p>One additional and enormous benefit here comes directly from splitting logic into discrete methods.  Previously if you&#8217;d wanted to implement your own logic to determine success, get the total number of records or even build a record from an XML node you&#8217;d be stuck.  There was no way to selectively override that logic without redefining that entire monster method.</p>
<p>With our new approach this becomes trivial:</p>
<pre class="brush: jscript;">
Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, {
  readRecords: function(doc) {
    this.xmlData = doc;

    //get local references to frequently used variables
    var root    = doc.documentElement || doc,
        records = [],
        nodes   = Ext.DomQuery.select(this.meta.record, root);

    //build an Ext.data.Record instance for each node
    Ext.each(nodes, function(node) {
      records.push(this.buildRecordForNode(node));
    }, this);

    return {
      records     : records,
      success     : this.wasSuccessful(root),
      totalRecords: this.getTotalRecords(root) || records.length
    };
  },

  /**
   * Returns a new Ext.data.Record instance using data from a given XML node
   * @param {Element} node The XML node to extract Record values from
   * @return {Ext.data.Record} The record instance
   */
  buildRecordForNode: function(node) {
    var domQuery = Ext.DomQuery,
        idPath   = this.meta.idPath || this.meta.id,
        id       = idPath ? domQuery.selectValue(idPath, node) : undefined;

    var record  = new this.recordType({}, id);
    record.node = node;

    //iterate over each field in our record, find it in the XML node and convert it
    record.fields.each(function(field) {
      var mapping  = Ext.value(field.mapping, field.name, true),
          rawValue = domQuery.selectValue(mapping, node, field.defaultValue),
          value    = field.convert(rawValue, node);

      record.set(field.name, value);
    });

    return record;
  },

  /**
   * Returns the total number of records indicated by the server response
   * @param {XMLDocument} root The XML response root node
   * @return {Number} total records
   */
  getTotalRecords: function(root) {
    var metaTotal = this.meta.totalRecords;

    return metaTotal == undefined
                      ? 0
                      : Ext.DomQuery.selectNumber(metaTotal, root, 0);
  },

  /**
   * Returns true if the response document includes the expected success property
   * @param {XMLDocument} root The XML document root node
   * @return {Boolean} True if the XML response was successful
   */
  wasSuccessful: function(root) {
    var metaSuccess  = this.meta.success;

    //return true for any response except 'false'
    if (metaSuccess == undefined) {
      return true;
    } else {
      var successValue = Ext.DomQuery.selectValue(metaSuccess, root, true);
      return successValue !== false &amp;&amp; successValue !== 'false';
    }
  }
});
</pre>
<p>(For brevity I have omitted the existing readRecords comment blocks from the above)</p>
<p>I suggest that you structure your code in this way at least 99% of the time.  The one exception is if high performance is an issue.  If you are in a situation where every millisecond counts (you probably aren&#8217;t), then taking the former route becomes more acceptable (though there&#8217;s still no excuse for not adding a few comments explaining what the code actually does).</p>
<p>My refactored code almost certainly runs slower than the original as it doesn&#8217;t take as much advantage of cached local variables as the monolithic version does.  For library-level code this can make sense if the performance gain is significant, but for the everyday code you and I write it is rarely a good idea.</p>
<p>I&#8217;ll be watching.</p>
]]></description>
			<content:encoded><![CDATA[<p>For the second time this week I&#8217;m going to pick on the usually delightful <a href="http://extjs.com">Ext JS</a> library. Last time we discussed <a href="http://edspencer.net/2009/10/javascript-module-pattern-overused-dangerous-and-bloody-annoying.html">the overzealous use of the Module pattern</a>; this time it&#8217;s the turn of bloated methods.</p>
<p>As before, I&#8217;m not really picking on Ext at all &#8211; this happens all over the place. But again, this is the library closest to my heart and the one I know the best.</p>
<h2>The Problem</h2>
<p>We&#8217;re going to take a look at <a href="http://www.extjs.com/deploy/dev/docs/source/XmlReader.html#method-Ext.data.XmlReader-readRecords">Ext.data.XmlReader&#8217;s readRecords method</a>.  Before we get started though, I&#8217;ll repeat that this is intended as an example of an approach, not a whine at Ext in particular.</p>
<pre class="brush: jscript;">
/**
 * Create a data block containing Ext.data.Records from an XML document.
 * @param {Object} doc A parsed XML document.
 * @return {Object} records A data block which is used by an {@link Ext.data.Store} as
 * a cache of Ext.data.Records.
 */
readRecords: function(doc) {
  /**
   * After any data loads/reads, the raw XML Document is available for further custom processing.
   * @type XMLDocument
   */
  this.xmlData = doc;
  var root = doc.documentElement || doc;
  var q = Ext.DomQuery;
  var recordType = this.recordType, fields = recordType.prototype.fields;
  var sid = this.meta.idPath || this.meta.id;
  var totalRecords = 0, success = true;
  if(this.meta.totalRecords){
    totalRecords = q.selectNumber(this.meta.totalRecords, root, 0);
  }

  if(this.meta.success){
    var sv = q.selectValue(this.meta.success, root, true);
    success = sv !== false &amp;&amp; sv !== 'false';
  }
  var records = [];
  var ns = q.select(this.meta.record, root);
  for(var i = 0, len = ns.length; i &lt; len; i++) {
    var n = ns[i];
    var values = {};
    var id = sid ? q.selectValue(sid, n) : undefined;
    for(var j = 0, jlen = fields.length; j &lt; jlen; j++){
      var f = fields.items[j];
      var v = q.selectValue(Ext.value(f.mapping, f.name, true), n, f.defaultValue);
      v = f.convert(v, n);
      values[f.name] = v;
    }
    var record = new recordType(values, id);
    record.node = n;
    records[records.length] = record;
  }

  return {
    success : success,
    records : records,
    totalRecords : totalRecords || records.length
  };
}
</pre>
<p>Anyone care to tell me what this actually does? Personally, I have absolutely no idea. I recently found myself needing to implement an XmlReader subclass with a twist which required understanding how this works, and let&#8217;s just say it wasn&#8217;t easy!</p>
<p>So what is it that makes the above so terrifyingly hard to understand? Well, in no particular order:</p>
<ul>
<li>It&#8217;s <strong>too long</strong> &#8211; you&#8217;d need to be a genius to easily understand what&#8217;s going on here</li>
<li>The variable names <strong>don&#8217;t make much sense</strong> &#8211; some of the oddest include &#8216;q&#8217;, &#8216;ns&#8217;, &#8216;v&#8217;, &#8216;f&#8217; and &#8217;sv&#8217;</li>
<li>There&#8217;s <strong>minimal commenting</strong> &#8211; we&#8217;re given a single-line clue at the very top as to what these 40-odd lines do</li>
</ul>
<h2>A Solution</h2>
<p>Let&#8217;s see how the reworked code below addresses each of the concerns above:</p>
<ul>
<li>Although we end up with more lines of code here, no single method is more than around 10 LOC</li>
<li>No single letter variable names &#8211; you no longer have to decode what &#8217;sv&#8217; means</li>
<li>Constructive commenting allows rapid comprehension by skimming the text</li>
</ul>
<p>One additional and enormous benefit here comes directly from splitting logic into discrete methods.  Previously if you&#8217;d wanted to implement your own logic to determine success, get the total number of records or even build a record from an XML node you&#8217;d be stuck.  There was no way to selectively override that logic without redefining that entire monster method.</p>
<p>With our new approach this becomes trivial:</p>
<pre class="brush: jscript;">
Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, {
  readRecords: function(doc) {
    this.xmlData = doc;

    //get local references to frequently used variables
    var root    = doc.documentElement || doc,
        records = [],
        nodes   = Ext.DomQuery.select(this.meta.record, root);

    //build an Ext.data.Record instance for each node
    Ext.each(nodes, function(node) {
      records.push(this.buildRecordForNode(node));
    }, this);

    return {
      records     : records,
      success     : this.wasSuccessful(root),
      totalRecords: this.getTotalRecords(root) || records.length
    };
  },

  /**
   * Returns a new Ext.data.Record instance using data from a given XML node
   * @param {Element} node The XML node to extract Record values from
   * @return {Ext.data.Record} The record instance
   */
  buildRecordForNode: function(node) {
    var domQuery = Ext.DomQuery,
        idPath   = this.meta.idPath || this.meta.id,
        id       = idPath ? domQuery.selectValue(idPath, node) : undefined;

    var record  = new this.recordType({}, id);
    record.node = node;

    //iterate over each field in our record, find it in the XML node and convert it
    record.fields.each(function(field) {
      var mapping  = Ext.value(field.mapping, field.name, true),
          rawValue = domQuery.selectValue(mapping, node, field.defaultValue),
          value    = field.convert(rawValue, node);

      record.set(field.name, value);
    });

    return record;
  },

  /**
   * Returns the total number of records indicated by the server response
   * @param {XMLDocument} root The XML response root node
   * @return {Number} total records
   */
  getTotalRecords: function(root) {
    var metaTotal = this.meta.totalRecords;

    return metaTotal == undefined
                      ? 0
                      : Ext.DomQuery.selectNumber(metaTotal, root, 0);
  },

  /**
   * Returns true if the response document includes the expected success property
   * @param {XMLDocument} root The XML document root node
   * @return {Boolean} True if the XML response was successful
   */
  wasSuccessful: function(root) {
    var metaSuccess  = this.meta.success;

    //return true for any response except 'false'
    if (metaSuccess == undefined) {
      return true;
    } else {
      var successValue = Ext.DomQuery.selectValue(metaSuccess, root, true);
      return successValue !== false &amp;&amp; successValue !== 'false';
    }
  }
});
</pre>
<p>(For brevity I have omitted the existing readRecords comment blocks from the above)</p>
<p>I suggest that you structure your code in this way at least 99% of the time.  The one exception is if high performance is an issue.  If you are in a situation where every millisecond counts (you probably aren&#8217;t), then taking the former route becomes more acceptable (though there&#8217;s still no excuse for not adding a few comments explaining what the code actually does).</p>
<p>My refactored code almost certainly runs slower than the original as it doesn&#8217;t take as much advantage of cached local variables as the monolithic version does.  For library-level code this can make sense if the performance gain is significant, but for the everyday code you and I write it is rarely a good idea.</p>
<p>I&#8217;ll be watching.</p>
]]></content:encoded>
			<wfw:commentRss>http://edspencer.net/2009/10/writing-better-javascript-split-up-long-methods.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

