Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ajax In Action (2006).pdf
Скачиваний:
63
Добавлен:
17.08.2013
Размер:
8.36 Mб
Скачать

Order out of chaos

71

 

 

3.1 Order out of chaos

The main tool that we will apply is refactoring, the process of rewriting code to introduce greater clarity rather than to add new functionality. Introducing greater clarity can be a satisfying end in itself, but it also has some compelling advantages that should appeal to the bottom-line, when-the-chips-are-down mentality.

It is typically easier to add new functionality to well-factored code, to modify its existing functionality, and to remove functionality from it. In short, it is understandable. In a poorly factored codebase, it is often the case that everything does what the current requirements specify, but the programming team isn’t fully confident as to why it all works.

Changing requirements, often with short time frames, are a regular part of most professional coding work. Refactoring keeps your code clean and maintainable and allows you to face—and implement—changes in requirements without fear.

We already saw some elementary refactoring at work in our examples in chapter 2, when we moved the JavaScript, HTML, and stylesheets into separate files. However, the JavaScript is starting to get rather long at 120 lines or so and is mixing together low-level functionality (such as making requests to the server) with code that deals specifically with our list object. As we begin to tackle bigger projects, this single JavaScript file (and single stylesheet, for that matter) will suffer. The goal that we’re pursuing—creating small, easily readable, easily changeable chunks of code that address one particular issue—is often called separation of responsibilities.

Refactoring often has a second motive, too, of identifying common solutions and ways of doing things and moving code toward that particular pattern. Again, this can be satisfying in its own right, but it has a very practical effect. Let’s consider this issue next.

3.1.1Patterns: creating a common vocabulary

Code conforming to any well-established pattern stands a good chance of working satisfactorily, simply because it’s been done before. Many of the issues surrounding it have already been thought about and, we hope, addressed. If we’re lucky, someone’s even written a reusable framework exemplifying a particular way of doing things.

This way of doing things is sometimes known as a design pattern. The concept of patterns was coined in the 1970s to describe solutions to architectural and planning problems, but it has been borrowed by software development for the

72CHAPTER 3

Introducing order to Ajax

last ten years or so. Server-side Java has a strong culture of design patterns, and Microsoft has recently been pushing them strongly for the .NET Framework. The term often carries a rather forbidding academic aura and is frequently misused in an effort to sound impressive. At its root, though, a design pattern is simply a description of a repeatable way of solving a particular problem in software design. It’s important to note that design patterns give names to abstract technical solutions, making them easier to talk about and easier to understand.

Design patterns can be important to refactoring because they allow us to succinctly describe our intended goal. To say that we “pull out these bits of code into objects that encapsulate the process of performing a user action, and can then undo everything if we want” is quite a mouthful—and rather a wordy goal to have in mind while rewriting the code. If we can say that we are introducing the Command pattern to our code, we have a goal that is both more precise and easier to talk about.

If you’re a hardened Java server developer, or an architect of any hue, then you’re probably wondering what’s new in what we’ve said. If you’ve come from the trenches of the web design/new media world, you may be thinking that we’re those weird sorts of control freaks who prefer drawing diagrams to writing real code. In either case, you may be wondering what this has to do with Ajax. Our short answer is “quite a lot.” Let’s explore what the working Ajax programmer stands to gain from refactoring.

3.1.2Refactoring and Ajax

We’ve already noted that Ajax applications are likely to use more JavaScript code and that the code will tend to be longer lived.

In a classic web app, the complex code lives on the server, and design patterns are routinely applied to the PHP, Java, or .NET code that runs there. With Ajax, we can look at using the same techniques with the client code.

There is even an argument for suggesting that JavaScript needs this organization more than its rigidly structured counterparts Java and C#. Despite its C-like syntax, JavaScript is a closer cousin to languages such as Ruby, Python, and even Common Lisp than it is to Java or C#. It offers a tremendous amount of flexibility and scope for developing personal styles and idioms. In the hands of a skilled developer, this can be wonderful, but it also provides much less of a safety net for the average programmer. Enterprise languages such as Java and C# are designed to work well with teams of average programmers and rapid turnover of members. JavaScript is not.

Order out of chaos

73

 

 

The danger of creating tangled, unfathomable JavaScript code is relatively high, and as we scale up its use from simple web page tricks to Ajax applications, the reality of this can begin to bite. For this reason, I advocate the use of refactoring in Ajax more strongly than I do in Java or C#, the “safe” languages within whose communities design patterns have bloomed.

3.1.3Keeping a sense of proportion

Before we move on, it’s important to say that refactoring and design patterns are just tools and should be used only where they are actually going to be useful. If overused, they can induce a condition known as paralysis by analysis, in which implementation of an application is forestalled indefinitely by design after redesign, in order to increase the flexibility of the structure or accommodate possible future requirements that may never be realized.

Design patterns expert Erich Gamma summed this up nicely in a recent interview (see Resources at end of chapter) in which he described a call for help from a reader who had managed to implement only 21 of the 23 design patterns described in the seminal Design Patterns book into his application. Just as a developer wouldn’t struggle to make use of integers, strings, and arrays in every piece of code that he writes, a design pattern is useful only in particular situations.

Gamma recommends refactoring as the best way to introduce patterns. Write the code first in the simplest way that works, and then introduce patterns to solve common problems as you encounter them. If you’ve already written a lot of code, or are charged with maintaining someone else’s tangled mess, you may have been experiencing a sinking, left-out-of-the-party feeling until now. Fortunately, it’s possible to apply design patterns retroactively to code of any quality. In the next section, we’ll take some of the rough-and-ready code that we developed in chapter 2 and see what refactoring can do for it.

3.1.4Refactoring in action

This refactoring thing might sound like a good idea, but the more practicalminded among you will want to see it working before you buy in. Let’s take a few moments now to apply a bit of refactoring to the core Ajax functionality that we developed in the previous chapter, in listing 2.11. To recap the structure of that code, we had defined a sendRequest() function that fired off a request to the server. sendRequest() delegated to an initHttpRequest() function to find the appropriate XMLHttpRequest object and assigned a hard-coded callback function, onReadyState(), to process the response. The XMLHttpRequest object was defined as a global variable, allowing the callback function to pick up a reference

74CHAPTER 3

Introducing order to Ajax

to it. The callback handler then interrogated the state of the request object and produced some debug information.

The code in listing 2.11 does what we needed it to but is somewhat difficult to reuse. Typically when we make a request to the server, we want to parse the response and do something quite specific to our application with the results. To plug custom business logic into the current code, we need to modify sections of the onReadyState() function.

The presence of the global variable is also problematic. If we want to make several calls to the server simultaneously, then we must be able to assign different callback handlers to each. If we’re fetching a list of resources to update and another list of resources to discard, it’s important that we know which is which, after all!

In object-oriented (OO) programming, the standard solution to this sort of issue is to encapsulate the required functionality into an object. JavaScript supports OO coding styles well enough for us to do that. We’ll call our object ContentLoader, because it loads content from the server. So what should our object look like? Ideally, we’d be able to create one, passing in a URL to which the request will be sent. We should also be able to pass a reference to a custom callback handler to be executed if the document loads successfully and another to be executed in case of errors. A call to the object might look like this:

var loader=new net.ContentLoader('mydata.xml',parseMyData);

where parseMyData is a callback function to be invoked when the document loads successfully. Listing 3.1 shows the code required to implement the ContentLoader object. There are a few new concepts here, which we’ll discuss next.

Listing 3.1 ContentLoader object

var net=new Object();

b Namespacing object

net.READY_STATE_UNINITIALIZED=0;

 

 

net.READY_STATE_LOADING=1;

 

 

net.READY_STATE_LOADED=2;

 

 

net.READY_STATE_INTERACTIVE=3;

 

 

net.READY_STATE_COMPLETE=4;

 

c Constructor function

net.ContentLoader=function(url,onload,onerror){

this.url=url;

this.req=null;

this.onload=onload;

this.onerror=(onerror) ? onerror : this.defaultError; this.loadXMLDoc(url);

}

net.ContentLoader.prototype={

 

Order out of chaos

 

75

 

 

 

 

loadXMLDoc:function(url){ d Renamed initXMLHttpRequest function

 

if (window.XMLHttpRequest){

 

e Refactored

 

this.req=new XMLHttpRequest();

 

 

} else if (window.ActiveXObject){

 

loadXML

 

 

function

 

this.req=new ActiveXObject("Microsoft.XMLHTTP");

 

 

 

 

 

 

 

 

 

}

if (this.req){ try{

var loader=this; this.req.onreadystatechange=function(){

loader.onReadyState.call(loader);

 

}

 

 

f

Refactored

 

this.req.open('GET',url,true);

 

 

 

 

this.req.send(null);

 

 

 

sendRequest

 

 

 

 

function

}catch (err){

 

 

 

 

 

 

 

 

this.onerror.call(this);

 

 

 

}

 

 

 

 

 

}

 

 

 

 

 

},

 

g Refactored callback

onReadyState:function(){

var

req=this.req;

 

 

 

 

var

ready=req.readyState;

 

 

 

 

if (ready==net.READY_STATE_COMPLETE){ var httpStatus=req.status;

if (httpStatus==200 || httpStatus==0){ this.onload.call(this);

}else{

this.onerror.call(this);

}

}

},

defaultError:function(){ alert("error fetching data!"

+"\n\nreadyState:"+this.req.readyState +"\nstatus: "+this.req.status

+"\nheaders: "+this.req.getAllResponseHeaders());

}

}

The first thing to notice about the code is that we define a single global variable net b and attach all our other references to that. This minimizes the risk of clashes in variable names and keeps all the code related to network requests in a single place.

We provide a single constructor function for our object c. It has three arguments, but only the first two are mandatory. In the case of the error handler, we test for null values and provide a sensible default if necessary. The ability to pass a varying number of arguments to a function might look odd to

76CHAPTER 3

Introducing order to Ajax

OOprogrammers, as might the ability to pass functions as first-class references. These are common features of JavaScript. We discuss these language features in more detail in appendix B.

We have moved large parts of our initXMLHttpRequest() e and sendRequest() functions f from listing 2.11 into the object’s internals. We've also renamed the function to reflect its slightly greater scope here as well. It is now known as loadXMLDoc. d We still use the same techniques to find an XMLHttpRequest object and to initiate a request, but the user of the object doesn’t need to worry about it. The onReadyState callback function g should also look largely familiar from listing 2.11. We have replaced the calls to the debug console with calls to the onload and onerror functions. The syntax might look a little odd, so let’s examine it a bit closer. onload and onerror are Function objects, and Function.call() is a method of that object. The first argument to Function.call() becomes the context of the function, that is, it can be referenced within the called function by the keyword this.

Writing a callback handler to pass into our ContentLoader is quite simple, then. If we need to refer to any of the ContentLoader’s properties, such as the XMLHttpRequest or the url, we can simply use this to do so. For example:

function myCallBack(){ alert(

this.url

+" loaded! Here's the content:\n\n" +this.req.responseText

);

}

Setting up the necessary “plumbing” requires some understanding of JavaScript’s quirks, but once the object is written, the end user doesn’t need to worry about it.

This situation is often a sign of good refactoring. We’ve tucked away the difficult bits of code inside the object while presenting an easy-to-use exterior. The end user is saved from a lot of unnecessary difficulty, and the expert responsible for maintaining the difficult code has isolated it into a single place. Fixes need only be applied once, in order to be rolled out across the codebase.

We’ve covered the basics of refactoring and shown how it can work to our benefit in practice. In the next section, we’ll look at some more common problems in Ajax programming and see how we can use refactoring to address them. Along the way, we will discover some useful tricks that we can reuse in subsequent chapters and that you can apply to your own projects as well.