• Save
Lessons from a Rewrite
Upcoming SlideShare
Loading in...5
×
 

Lessons from a Rewrite

on

  • 4,555 views

 

Statistics

Views

Total Views
4,555
Views on SlideShare
3,439
Embed Views
1,116

Actions

Likes
3
Downloads
0
Comments
0

12 Embeds 1,116

http://blog.derekperez.com 603
http://coderwall.com 313
http://speakerrate.com 142
http://lanyrd.com 26
http://vimeo.com 14
url_unknown 8
http://www.techgig.com 3
http://paper.li 2
https://twitter.com 2
http://inagist.com 1
http://tweetedtimes.com 1
http://www.slideshare.net 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Lessons from a Rewrite Lessons from a Rewrite Presentation Transcript

  • Lessons from a rewrite Rebecca Murphey • TXJS 2011 • Austin, TexasSaturday, June 11, 2011
  • Rewrite projects are ... tempting because they always look easier than they actually are. If you want to survive a software rewrite, don’t throw away your existing software ... Write the code in such a way that it co-exists with your existing software, perhaps as an optional add-on that you can sell. James ShoreSaturday, June 11, 2011
  • Where a new system concept or new technology is used, one has to build a system to throw away, for even the best planning is not so omniscient as to get it right the rst time. Hence plan to throw one away; you will, anyhow. Fred BrooksSaturday, June 11, 2011
  • Until you are able to prove that you have a viable market ... you shouldn’t be sinking a lot of time and money into implementation. Assuming you are [competent], you should be able to ... take on quite a bit of technical debt to build throwaway proof-of-concept versions of your product. Obie FernandezSaturday, June 11, 2011
  • ere’s a subtle reason that programmers always want to throw away the code and start over. e reason is that they think the old code is a mess. ... e reason that they think the old code is a mess is because of a cardinal, fundamental law of programming: It’s harder to read code than to write it. Joel SpolskySaturday, June 11, 2011
  • For the human makers of things, the incompleteness and inconsistencies of our ideas become clear only during implementation. Fred BrooksSaturday, June 11, 2011
  • A content management system for the rapid creation of content-rich mobile applications. A PhoneGap & Dojo system that consumes the output of the CMS to generate the application.Saturday, June 11, 2011
  • Saturday, June 11, 2011
  • Saturday, June 11, 2011
  • Saturday, June 11, 2011
  • Saturday, June 11, 2011
  • If data is required for a route, such as node data or the users favorites, the route requests the data prior to creating the page. Data Page Controller Component Component Page Controller Component Component Component Component When the app is booted from a "cold" state (that URL Change Router Page Factory is, its not fast-app- Page controllers are responsible for Components are responsible for Once components are placed on the switched), it goes receiving data from the Page Factory. Once receiving and rendering data and page, the Page Controller brokers through a bootstrapping The Router detects a The Route asks the page the page controller receives that data, it reacting to user interaction. communication between components. Route determines which components to display in process. This process change in the URL, looks factory to generate a page for a matching route in controller. If the page its postMixInProperties method and sets up Components whose content can change Components announce events either by ensures the application "placements," which define the components during a single page view should expose publishing a topic (for events that may toura.app.Routes, and controller is for a node, the is working with the that will be placed on the page and the data an API using setters (i.e. have app-wide significance) or by calling parses any additional Page Factory figures out most recent data, and parameters out of the URL which template (Audios, that needs to be passed to them. The _setContentAttr) that allows Page a method (such as onClick). Page also sets up various (such as the node ID, Images, etc.) the node actual instantiation and placement of Controllers to update their content. Controllers can subscribe to these application-wide Device Storage asset type, etc.). uses. The page factory components is handled in the postCreate topics or connect to these method calls creates an instance of the method of toura.pageControllers._Page, Alternately, a component can define an (much like connecting to events). This functionality, including page controller and hands which is inherited by all Page Controllers. attributeMap object that specifies how happens in the Page Controllers the Router, UI, Page the component should react when a postCreate method. the created instance back Factory, Data Store, to the Route. property is set. and several others. Once these pieces are in place, the app Remote triggers a URL change to the home node, and the process outlined here begins. Every time the user goes UI to a new page, we check the age of the local data; if it is more than 8 hours old, we see whether we need to do an over-the-air update. Browser Page Container Old Page Controller New Page Controller Component Component Component Component Component Component The Route asks toura.app.UI to place the Page Controller in the UI; in turn, toura.app.UI sets the content attribute of the Page Container. If there is already a page on screen, the Page Container handles the animation between the pages, and calls the destroy method of the old page, which results in the proper teardown of the old page and its components.Saturday, June 11, 2011
  • e secret to building large apps is never build large apps. Break up your applications into small pieces. en, assemble those testable, bite-sized pieces into your big application. Justin MeyerSaturday, June 11, 2011
  • Managing dataSaturday, June 11, 2011
  • } }], "parent": { "_reference": "node-365" }, "page_controller": "Audios1", "id": "node-369", "sharing_text": null, "sharing_url": null, "images": [{ "caption": { "_reference": "text-asset-60" }, "image": { "_reference": "image-174" } }, { "caption": { "_reference": "text-asset-61" }, "image": { "_reference": "image-182"Saturday, June 11, 2011 }
  • backgroundImage : toura.models.BackgroundImage }, constructor : function(data) { this._loadData(data); }, _loadData : function(data) { this._store = new dojo.data.ItemFileReadStore({ data : { identifier : id, items : data }, hierarchical : false }); this.onLoadData(data); }, onLoadData : function(data) { // stub for connection }, get : function(id, type) { if (!id) { throw new Error(toura.app.Data::getModel requires an id);Saturday, June 11, 2011 }
  • toura.app.Tour.search(drink);Saturday, June 11, 2011
  • store.fetch({ query : q, onComplete : addItems });Saturday, June 11, 2011
  • Building the interfaceSaturday, June 11, 2011
  • Saturday, June 11, 2011
  • page controllers set up components and broker communication between them ui components receive and render data; announce and react to user interaction app components manage app data and stateSaturday, June 11, 2011
  • dijit._Widget and dijit._TemplatedSaturday, June 11, 2011
  • Saturday, June 11, 2011
  • toura.app.Tour.get(nodeId);Saturday, June 11, 2011
  • this.placements = [ [ VideoCaption, { node : this.node }, videoCaption ], [ VideoList, { node : this.node }, videoList ], [ VideoPlayer, { node : this.node }, videoPlayer ] ];Saturday, June 11, 2011
  • this.connect(this.videoList, onSelect, function(assetId) { var video = this._videoById(assetId); this.videoCaption.set(content, video.caption || ); this.videoPlayer.play(assetId); });Saturday, June 11, 2011
  • videoPlayer.set(mediaId, mediaId); _setMediaIdAttr : function(mediaId) { var media = this.media = this.mediasCache[mediaId]; if (this.useHtml5Player && !this.player) { this._queuedMedia = media; return; } this._queuedMedia = null; if (this.player) { this.player.src = media.url; } },Saturday, June 11, 2011
  • pageContainer.set(content, newPage);Saturday, June 11, 2011
  • Saturday, June 11, 2011
  • Saturday, June 11, 2011
  • myComponent.set() to change state myComponent.on<Evt>() to announce state changes myComponent.connect() to listen for events & methods myComponent.subscribe() to react to published topics dojo.publish() to announce occurrences of app-wide interestSaturday, June 11, 2011
  • Facilitating developmentSaturday, June 11, 2011
  • /* {{^android}} */ var mediaPath = "www/media/" + toura.pages.currentId + "/"; /* {{/android}} */ /* {{#android}} */ var mediaPath = [Toura.getTouraPath(), toura.pages.currentId].join("/"); /* {{/android}} */ var imagesList = [], dimensionsList = [], namesList = [], thumbsList = []; var pos = -1, count = 0; /* {{#android}} */ var pos = 0, count = 0; /* {{/android}} */Saturday, June 11, 2011
  • toura.app._Config = { // ... device : { os : android, type : phone } }Saturday, June 11, 2011
  • toura.app.Has = function() { var device = toura.app.Config.get(device); return { cssBackgroundContain : function() { return !( device.os === android && device.version === 2-1 ); }, html5Player : function() { return device.os !== android; }, iScrollZoom : function() { return device.os !== android; } }; };Saturday, June 11, 2011
  • //>>excludeStart(production, kwArgs.production); if (toura.features.debugToolbar) { toura.app._Debug(); } //>>excludeEnd(production);Saturday, June 11, 2011
  • /** * This file generates built dojo and toura files for * use in production. */ dependencies = { /* * tell dojo to strip out ie-specific hacks */ webkitMobile : true, /* * this is a production build -- non-production code should be stripped */ production : true, /* * the name of the release and where to put it, * relative to dojo-release-X.X.X-src/util/buildscripts/. */ releaseName : production, /* * strip all calls to console.log automatically */ stripConsole : all, /* * clean the existing release directory, then create a new releaseSaturday, June 11, 2011 */
  • dojo.provide(toura.app.Phonegap.network); toura.app.Phonegap.network = function(pg, device) { var n = navigator; return { isReachable : function(domain) { console.log(toura.app.Phonegap.network::isReachable()); var dfd = new dojo.Deferred(); if (n && n.network && n.network.isReachable) { console.log(using phonegap network); n.network.isReachable( domain || aws.amazon.com, function(r) { dfd.resolve(r !== NetworkStatus.NOT_REACHABLE); }, function() { console.log(failure on network is reachable); dfd.resolve(false); } ); } else { // resolve false if PhoneGap is present; // network is not indeed reachable if (pg) { dfd.resolve(false);Saturday, June 11, 2011 } else {
  • Taming asynchronicitySaturday, June 11, 2011
  • var myAsyncThing = function() { var dfd = new dojo.Deferred(); setTimeout(function() { dfd.resolve(hello); }, 1000); return dfd.promise; }; myAsyncThing().then(function(result){ console.log(result); });Saturday, June 11, 2011
  • myPromise.then(win, fail) react to an immutable promiseSaturday, June 11, 2011
  • dojo.when(fn(), win, fail) react to maybe-asynchronous things, including promisesSaturday, June 11, 2011
  • /** * @private * @returns {Promise} A promise that, if resolved, will be resolved with the * remote data. */ _getRemoteData : function() { var dfd = new dojo.Deferred(); _xhr : function(url, dfd) { return dojo.xhrGet({ toura.app.Phonegap.network.isReachable() url : url, .then( preventCache : true, dojo.hitch(this, function() { handleAs : json, this._xhr(this.remoteDataUrl, dfd); contentType : false, }), load : dfd.resolve, function() { error : dfd.reject dfd.reject(Remote is not reachable); }); } }, ); return dfd.promise; },Saturday, June 11, 2011
  • dojo.when(this._getAuth(), dojo.hitch(this, function(t) { this.token = t; this._postMessage(msg, t).then(dfd.resolve, dfd.reject); }));Saturday, June 11, 2011
  • Handling stateSaturday, June 11, 2011
  • index.html#/node/123/image/456 index.html#/search/bacon index.html#/favorites index.html#/aboutSaturday, June 11, 2011
  • { route : //node/(.*)/, handler : function(params, route) { var splat = params.splat[0].split(/), nodeId = splat[0], pageState = { assetType : splat[1], assetId : splat[2], assetSubId : splat[3] }; return nodeRoute(route, nodeId, pageState); } }, { route : //search/?(.*)/, handler : function(params) { var page = toura.app.UI.getCurrentPage(), term = params.splat && params.splat[0].split(/)[0]; if (!page || !page.type || page.type !== search) {Saturday, June 11, 2011
  • https://github.com/rmurphey/dojo-boilerplateSaturday, June 11, 2011
  • dojo.require(dojo.Stateful); dojo.declare(toura.app.UI, [ dojo.Stateful ], { // ... });Saturday, June 11, 2011
  • this.watch(fontSize, function(k, oldSize, newSize) { var b = this.body; if (oldSize) { dojo.removeClass(b, oldSize); } dojo.addClass(b, newSize); toura.app.DeviceStorage.set(fontSize, newSize); });Saturday, June 11, 2011
  • Writing to rewriteSaturday, June 11, 2011
  • Feature: A User can view a node with the Video1 template and play each video Scenario: Videos1: Setup When I am on the home node And I click "Plain Node" And I click "Video Player" Then I should see a Video Player And I should see an Asset List And I should see a Child Nodes list And I should see "movie_for_demo" in the asset list Scenario: Videos1: Controlling the video player from the asset list When I am on the home node And I click "Plain Node" And I click "Video Player" And I click "movie_for_demo" in the asset list Then "video-29" should be selected in the asset list And I should see "videos-29" in the video player Scenario: Navigating to a specific video When I am on the home node And I go to the URL "#/node/node-372/videos/video-29" Then I should see "videos-29" in the video player And "video-29" should be selected in the asset listSaturday, June 11, 2011
  • expect(c.queryInput).toBeDefined(); expect(c.i18n_placeholderText).toBeTruthy(); }); }); it("should provide an API for setting the search term", function() { c = C(); c.set(searchTerm, term); expect(c.queryInput.value).toBe(term); }); it("should announce searches when the form is submitted with a term", function() { var submitHandler; toura.app.UI.hasTouch = false; c = C(); c.set(searchTerm, term); spyOn(c, search); submitHandler = getEventHandlers(c, submit, c.searchForm)[0]; submitHandler(fakeEventObj); expect(c.search).toHaveBeenCalledWith(term); }); it("should not announce searches if the form is submitted with no term", function() { var submitHandler; toura.app.UI.hasTouch = false; c = C(); c.set(searchTerm, ); spyOn(c, search);Saturday, June 11, 2011 submitHandler = getEventHandlers(c, submit, c.searchForm)[0];
  • It takes con dence to throw work away ... When people rst start drawing, they’re often reluctant to redo parts that aren’t right ... they convince themselves that the drawing is not that bad, really — in fact, maybe they meant it to look that way. Paul GrahamSaturday, June 11, 2011
  • http://pinboard.in/u:rmurphey/t:lessons-from-a-rewrite/ rebeccamurphey.com • blog.rebeccamurphey.com • @rmurpheySaturday, June 11, 2011