注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

神魔破杜梓的叨叨堂

Programming every day!

 
 
 

日志

 
 
 
 

AS3中应受指责的地方  

2008-08-14 17:30:56|  分类: My Tech |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
 原文来自InsideRIA

Adobe has made some impressive progress transitioning Flash from plug-in to platform over the past couple of years. The foundation is now in place: there's a proper programming tool (Flex Builder), a desktop runtime (AIR), and a mature language (ActionScript 3.0). Adobe is even working on its own line of killer apps—a prerequisite for any platform. Photoshop Express, Buzzword, and AMP are already live, with surely more to come. Meanwhile, Flash Player's evolution continues at a dizzying pace. Astro, a.k.a. Flash Player 10, is set to introduce basic 3D, high-performance graphics effects, dynamic sound generation, and a new text engine that supports right-to-left and vertical text layout. (And there was much rejoicing.)

But despite all the talk of GPU blitting, pixel shading, and ligatures, a non-negligible percentage of the Flash community is rightfully asking: is Adobe still committed to the simple, agile authoring practices on which Flash was founded? It's a rational enough concern. After all, Flash built its success on "ease of use." Some 11 years ago, the tagline on the Flash 2 box read: "The Easiest Way to Create Fast Web Multimedia." Originally, Flash was purpose-built for people who wanted to make things move without years of animation training, or who wanted to create interactivity and programmatic behavior without a degree in computer science. A decade of loyalty later, those same people—call them the "everyday Flashers"—are now wondering how, or even if, they fit into Adobe's new platform strategy.

Although the general concern over Flash's ease of use is natural, much of it is based on fear, not facts. Despite doomsday predictions that Adobe is abandoning its core Flash user base my impression is that Adobe thinks very highly of the "everyday Flashers." In fact, if the new tweening model and inverse kinematics support in Flash are any indication, Adobe's Flash team seems thoroughly committed to improving content-creation workflows. Approachable authoring experiences, however, take time to develop and refine in the context of new features. In fact, for some kinds of content, the most approachable authoring experience might one day have nothing to do with the Flash authoring tool. As the Flash platform evolves, it seems logical to expect a variety of content-specific authoring tools to emerge, from both Adobe and third parties. Adobe is already developing at least one such tool for the Flash platform, codenamed Thermo.

But for now, huge numbers of Flashers still have to get their work done in the Flash authoring tool as it stands today, taking the good with the bad. More than a year has passed since Flash CS3 was released to widely positive reviews, but many Flash users are still frustrated by some of the workflow changes introduced by ActionScript 3.0. The truly problematic changes are relatively few, but together they have a deep effect on the typical Flash user's daily job.

In the spirit of working toward solutions, and of giving a formal voice to the collective grumbling of everyday Flashers, this article provides a guide to The Charges Against ActionScript 3.0. The list of accusations follows; mostly it applies to the Flash authoring tool, not to Flex Builder.

  1. The removal of on()/onClipEvent() from Flash CS3 makes creating simple interactivity hard.
  2. Getting rid of loaded .swf files is hard.
  3. Casting DisplayObject.parent makes controlling parent movie clips hard.
  4. The removal of getURL() makes linking hard.
  5. The removal of loadMovie() makes loading .swf files and images hard.
  6. ActionScript 3.0's additional errors make coding cumbersome.
  7. Referring to library symbols dynamically is unintuitive.
  8. Adding custom functionality to manually created text fields, to all movie clips, or to all buttons is cumbersome.
  9. The removal of duplicateMovieClip() makes cloning a MovieClip instance (really) hard.

Note that the title of this article, "The Charges Against ActionScript 3.0," is something of a misnomer. In many cases, ActionScript 3.0 as a language is accused of crimes it has never committed. Many of the problems in the preceding list are best solved not at the language level, but at the tool level. For example, the ActionScript 3.0 navigateToURL() function is clearly an improvement over its predecessor, getURL(), but navigateToURL() is also more difficult for nonprogrammers to use. The solution is not to revert to the inferior getURL(), but rather to provide an easier workflow in Flash for adding hypertext links to buttons. A new "link" tool could provide a Flash 4-style menu interface for creating links while completely obscuring the underlying ActionScript 3.0 code. Or maybe the time has come for someone to make an entirely new application for building simple Flash websites and animations. Such a tool could even be written in ActionScript, and deployed as an AIR app. But I digress...

The following sections describe the preceding charges, their possible solutions, and suggested courses of action for both Adobe and the Flash community. If you're affected by these issues, I'd like to encourage you to take the actions suggested. Also feel free to add to this discussion by leaving a comment below. With any luck, collectively we might be able to affect Adobe's future decision making.

For the record, I should point out that despite the issues discussed in this article in my opinion ActionScript 3.0 is vastly superior to ActionScript 2.0 and ActionScript 1.0. Growing pains aside, ActionScript 3.0 is the language of choice for any Flash-platform project. Now, on to the charges.

Charge #1: The Removal of on()/onClipEvent() from Flash CS3 Makes Creating Simple Interactivity Hard

The Accused: Flash CS3
The Verdict: GUILTY

Before the release of Flash CS3, you could add interactivity to movie clips and buttons using the on()/onClipEvent() visual-coding system. For example, imagine an animation whose final frame has a Replay button. When the button is clicked, its parent timeline starts playing at frame 1. In Flash 8, here are the steps you'd follow to implement the Replay button's functionality:

  1. Select the button.
  2. Enter the following code into the Actions panel:
    on (release) {
    this._parent.gotoAndPlay(1);
    }

In Flash CS3, in an ActionScript 3.0 .fla file, here are the equivalent steps:

  1. Assign the button an instance name, "replayBtn".
  2. Select the frame containing the button.
  3. In the Actions panel, define a function to listen to the button's MouseEvent.CLICK event:
    function replayBtnClickListener (e) {
    gotoAndPlay(1);
    }
  4. Still in the Actions panel, register replayBtnClickListener for the button's MouseEvent.CLICK event:
    replayBtn.addEventListener(MouseEvent.CLICK, replayBtnClickListener);

The Flash CS3 approach makes development harder for inexperienced programmers in four different ways. First, it requires an understanding of function creation and function references. Second, it uses a more complicated event name for the mouse click ("MouseEvent.CLICK" versus "release"). Third, in the Flash 8 scenario, the button can be moved or copied from one place to another while retaining its code; in Flash CS3, the button and code have to be moved separately. Fourth, the on() approach creates a listener and automatically registers that listener in one phrase of code, whereas the ActionScript 3.0 approach separates the listener creation and registration. To be fair, ActionScript 3.0 actually also supports inline listener creation and registration:

replayBtn.addEventListener(MouseEvent.CLICK, function (e) {
gotoAndPlay(1);
});

But ActionScript 3.0's inline syntax is less intuitive, is more prone to error, and leaves the program with no reference to the listener.

The preceding four issues are not trivial. Flash CS3 is sorely lacking a reliable, clean, visual system for assigning interactive behavior to graphical assets on stage. Nevertheless, I don't think that on() and onClipEvent() should be reintroduced. Here's why: first, although they are convenient and easy to understand, excessive use of on()/onClipEvent() can easily lead to a messy, unmaintainable .fla file. Second, on()/onClipEvent() introduces its own syntax for event handling, which complicates the language needlessly. Third, on()/onClipEvent() doesn't even do a good job at providing an "easy-to-use" tool for creating interactivity. To use on()/onClipEvent(), the Flash user still needs to learn the on() syntax itself, and has to remember (or look up) the available events ("press", "release", etc.).

But if resurrecting on()/onClipEvent() isn't the right thing to do, what is?

What Should Adobe Do?

Adobe should reintroduce a visual system for producing interactivity—but should modernize it. Get it right this time. Make a system that's even more helpful for nonprogrammers than on()/onClipEvent(). Make it write real ActionScript 3.0 behind the scenes, and let users move objects and behavior together (or choose not to, at their discretion). Here's just one idea: if a user clicks on a button, give her a list of supported events in a pull-down menu. Let the user choose the event and provide the "response" by selecting from a group of actions (go to URL, stop timeline, etc.). At compile time, change the response code to real ActionScript 3.0 code in the button's container class. And let the user see that code at author time via a central code viewer for all buttons in the .fla file, with the option to navigate to individual buttons.

Regardless of the actual interface, the Flash authoring tool needs its visual-coding system back. I firmly reject the notion that because ActionScript is becoming more powerful we should all "grow up" and learn to use MouseEvent.CLICK and event listeners. Such nonsense is programming egotism. What we should all do is use the right tool for the job. Simple content should be easy to build.

Does Adobe agree? At the grass-roots level, definitely. The Flash authoring team is very much in touch with their users, and the on()/onClipEvent() issue is well known internally. In February 2008, I met with Richard Galvan (the Flash authoring tool product manager), and he assured me that the topic of visual coding is considered significant. Unfortunately, "doing things right" takes time. Reviving on()/onClipEvent() in its old, insufficient form would be comparatively easy. But conceiving of, implementing, and testing a new and improved ActionScript 3.0 approach isn't trivial. Mr. Galvan couldn't commit to any time frame for an improved on()/onClipEvent() feature, but we both agreed that Adobe ought to build one. Hopefully, he'll be able to help convert that intention into action within the next version or two of Flash.

What Should We Do?

As a community, we'll need some patience while Flash evolves. Flash Player might be at version 9, but as a whole, the "Flash platform" is realistically only at version 1. During the Flash Player 9 time frame, Adobe released ActionScript 3.0, Flex 2.0, and AIR 1.0. That's a lot of new technology. Things will improve over time, particularly if the community voices its needs strongly. If you want your buttons back, post a comment at the end of this article. Adobe has a history of listening to sincere petitions for improvements in Flash (see the MovieClipLoader, Failure to Unload, and Make Some Noise campaigns).

In the meantime, I'm looking forward to Thermo, which will have a visual coding system. Mr. Galvan pointed out that Flash will naturally be in a good position to learn from Thermo's successes and failures, so it's worth giving feedback on that product even if you don't intend to use it regularly. And remember to keep an eye on labs.adobe.com, where Adobe previews its new tools.

Charge #2: Getting Rid of Loaded .swf Files Is Hard

The Accused: ActionScript 3.0
The Verdict: GUILTY

Prior to ActionScript 3.0, when a parent .swf file loaded a child .swf file, it could subsequently remove that child by calling removeMovieClip(). Calling removeMovieClip() stopped the child's events and play heads, which typically allowed it to be purged from memory (i.e., garbage-collected). In Flash Player 9, there is no removeMovieClip() equivalent. Instead, to remove an .swf file from memory in Flash Player 9, the programmer must manually deactivate and dereference it, ensuring that:

  • The parent .swf file has no references to the child .swf.
  • Flash Player has no references to the child .swf.

A parent .swf file can, of course, remove its own references to the child, but it cannot guarantee that 1) the child has not created a reference to itself in the parent (normally by registering a listener), and that 2) Flash Player itself has no references to the child .swf. Flash Player will hold a reference to the child if the child has registered for input or system events, or has active processes running, such as a sound playing or a file downloading. Hence, to remove a child .swf file, the programmer must follow these steps:

  1. Manually deactivate the child's active processes.
  2. Manually remove the child's system and input listeners.
  3. Manually dereference the child.

Although nice in theory, the manual approach poses several practical problems:

  • Manual deactivation of the child (i.e., removing its event listeners, stopping sounds, stopping timelines and timers, closing network operations, etc.) can be a lot of work, adding complexity and development time to a project.
  • Manual deactivation of the child is error-prone because the child might have dozens or hundreds of active input-event listeners, network connections, animations, and timers to track and disable. If the programmer forgets to unregister just one Event.ENTER_FRAME listener, the child .swf will never be purged from memory.
  • Adobe currently does not publish an authoritative list of things to deactivate before a child .swf can be purged, so developers are often left guessing when their content fails to be removed from memory.
  • Parent .swf files that load child .swf files from third-party sources cannot guarantee that the third-party child will deactivate itself properly. Hence, building applications and sites that aggregate third-party content is hazardous in Flash Player 9.

What Should Adobe Do?

In the short term, Adobe needs to:

  • Publish a list of everything that needs deactivation in a loaded .swf file before that .swf can be purged from memory. Until such a list is published, I'll maintain one on my blog—but my list is, by definition, insufficient because it is neither official nor exhaustive.
  • Update the ActionScript Language Reference, adding a warning to every item in the API that requires deactivation when used in a loaded .swf. For example, under Sound.play(), Adobe should add this warning: "An .swf file that starts a sound cannot become eligible for garbage collection until that sound has stopped playing."
  • Institute an internal documentation procedure guaranteeing that the preceding two publications are kept up-to-date as new features are added to each Flash runtime.

In the long term, Adobe needs to invest the engineering resources required to develop an API for deactivating a child .swf, and perhaps also for brute-force purging of a child .swf from memory. Such an API will be hard to get right and hard to maintain, but I think it's required if Flash is to retain its status as an agile, approachable platform. Flash's current success is partly based on its low barrier of entry for content creators. Presumably, then, adding helpful tools that keep the platform approachable is worth the effort.

Fortunately, thanks in part to the efforts of Grant Skinner, who recently championed the need for better unloading, Adobe is already actively exploring the issue of display-object deactivation and garbage collection. Flash Player 10 beta already includes a fix for a serious garbage collection bug reported by Mr. Skinner (.swf files with timeline code can't be garbage-collected). Flash Player 10 beta also provides a direct API for requesting garbage collection, in both the debug and release versions. Further, according to Flash Player engineer Werner Sharp, Adobe is investigating whether it's feasible to add two new deactivation features in future Flash runtimes:

  • A general deactivation method for halting a child .swf file's active processes. This feature has partly already come to fruition; see unloadAndStop() in Flash Player 10's release notes and the additional information provided by Mr. Skinner.
  • An API for loading an .swf file into a "content sandbox" (a term originally proposed by Mr. Skinner). When loaded into a content sandbox, the child .swf would be prevented from accessing both its parent and the stage, thus preventing the child from creating references that would prevent it from being garbage-collected.

Together, the preceding two experimental features would drastically reduce the effort required to manually purge a loaded child from memory.

In addition to the changes already being explored, I'd like to see Adobe bring back the ability to completely remove all content from Flash Player before loading a new .swf file. That is, ActionScript 3.0 needs an equivalent for this ActionScript 2.0 code:

loadMovieNum("newContent.swf", 0);

For more thoughts on deactivating and purging loaded content from Flash Player, see the following resources:

What Should We Do?

Until an .swf-deactivation API becomes part of Flash Player, developers creating loaded content should:

  • Learn to know and love the .swf deactivation list. Every child .swf file should supply a custom-made deactivate() method that must be called before the parent dereferences that child.
  • Consider loading .swf files from a separate subdomain so that their parent and stage access is limited (thanks to Mr. Skinner for recommending this technique).
  • Use Flex Builder 3's profiler tool to track every application's object usage, memory consumption, and performance.

Charge #3: Casting DisplayObject.parent Makes Controlling Parent Movie Clips Hard

The Accused: Flash CS3
The Verdict: GUILTY

Since movie clips were introduced in Flash 3, child movie clips have been able to control their parent timelines. For example, the original "actions" required to play a parent timeline looked like this:

Begin Tell Target ("../")
Play
End Tell Target

In Flash 5, those actions were replaced by object-oriented code that looked like this:

this._parent.play();

In Flash CS3, the underscore was removed from "_parent", so the code now looks like this:

this.parent.play();

However, if you try that code in Flash CS3, you'll get this error:

1061: Call to a possibly undefined method gotoAndStop through a reference
with static type flash.display:DisplayObjectContainer.

Why? Because the datatype of the "parent" variable is DisplayObjectContainer, not MovieClip. To make the code work, this.parent must be cast to the MovieClip datatype, as follows:

MovieClip(this.parent).play();

The preceding error message and its resolution are perfectly logical to computer scientists—but completely confounding to most animators. Ironically, it is the animators, not the programmers, who typically need to control timelines. Flash 3 let nonprogrammers control parent timelines with ease. Flash CS3 broke that long-standing tradition. Something has to be done. Animators want their easy timeline control back.

What Should Adobe Do?

At the very least, Adobe should enhance the Flash compiler so that it inserts an automatically generated cast wherever feasible, allowing error-free access to a parent's variables and functions. Here's how the automatic insertion could work:

  • First, check whether all author-time instances of a given symbol have the same type of parent; if so, cast to that parent's class wherever necessary

  • For symbols whose instances have incompatible types of parents, wherever irresolvable references to parent functions or variables are made, generate a nice, nonprogrammer-readable error.
  • If the programmer uses a manual cast, honor that cast instead of attempting to insert one automatically.

In the future, Adobe should also consider adding GUI tools for assigning common behaviors to both parent and child movie clips. The current Script Assist feature is too cluttered to be useful for simple interactions.

Of course, it's easy to ask Adobe to "add GUI tools for creating simple interactions," but it's hard to actually design and implement them. Making the right GUI tools available to the right audience in the right contexts is something of a software-industry Holy Grail. And it's not as though the Flash team hasn't clearly tried to add nonprogrammer tools before (see normal mode, designer view, slides, and behaviors). But I think there's still room to explore.

What Should We Do?

Submit the following Flash feature request via Adobe's wish form:

    Product Name: Flash
    Product Version: CS4
    Request: Easy Parent Timeline Control

On a movie clip timeline, please allow error-free access to the parent's variables and functions by adding an automatic cast wherever feasible.

You might also want to leave a comment on Richard Galvan's blog.

As a brute-force workaround to the parent casting issue, developers can consider turning off strict error-checking mode by unchecking Strict Mode, under File→Publish Settings→Flash→ActionScript 3.0 Settings→Errors. Note, however, that disabling strict mode also suppresses all compile-time type checking, and is not recommended for projects with nontrivial code.

Charge #4: The Removal of getURL() Makes Linking Hard

The Accused: ActionScript 3.0, Flash CS3
The Verdict: ActionScript 3.0, NOT GUILTY; Flash CS3, GUILTY

In ActionScript 1.0 and 2.0, the code required to make a link to http://www.oreilly.com looked like this:

getURL("http://www.oreilly.com");
In ActionScript 3.0, the equivalent code looks like this:
navigateToURL(new URLRequest("http://www.oreilly.com"));

The ActionScript 3.0 code is a little more verbose, but it also provides more features and has a cleaner API for sending variables to the destination URL. Nevertheless, some developers miss the convenience of getURL(). Fortunately for them, it's easy to re-create getURL() with a little custom code. Example 1 shows how. It contains an ActionScript 3.0 getURL()-equivalent function for loading a URL into a browser. Note, however, that unlike the real getURL(), Example 1's version does not support sending a movie clip's timeline variables to a server-side application. If you need to send variables to a server in ActionScript 3.0, you should use navigateToURL()'s much cleaner variable-sending API.

Example 1: An ActionScript 3.0 getURL()-Equivalent Function

package {
import flash.net.*;

public function getURL (url:String,
window:String = "_self"):void {
var u:URLRequest = new URLRequest(url);
navigateToURL(u, window);
}
}

To use the preceding getURL() replacement, copy the code into a file named "getURL.as" and then place that file in the root of your program's classpath. If you're not familiar with the classpath, just place getURL.as in the same directory as your .fla file (Flash CS3) or in your main source directory (Flex Builder). Once the file is in place, you can use getURL() like this:

getURL("http://www.oreilly.com");
getURL("http://www.oreilly.com", "_blank");

What Should Adobe Do?

From a language perspective, Adobe has done the right thing already. ActionScript 3.0's navigateToURL() function is cleaner than the legacy getURL(), and it provides more features. However, from a tool perspective, there's work to be done. For the benefit of newer programmers and nonprogrammers, future versions of Flash authoring should provide a simplified GUI for creating hypertext links in Flash content.

What Should We Do?

Programmers: learn to use navigateToURL(), particularly when sending variables. Some keystroke-frugal coders will want to use a simple pass-through function such as the one shown in Example 1.

Nonprogrammers: wait for Adobe to introduce new workflows for creating simple interactivity. Tell Adobe you want easy-to-use linking tools by submitting the following Flash feature request via Adobe's wish form:

    Product Name: Flash
    Product Version: CS4
    Request: Easy Links

Please make an easy-to-use tool for creating hypertext links. The current navigateToURL() function is too hard to use.

Charge #5: The Removal of loadMovie() Makes Loading .swf Files and Images Hard

The Accused: ActionScript 3.0
The Verdict: GUILTY (but there's a workaround)

In ActionScript 1.0 and 2.0, the code required to load a file named "animation.swf" into a movie clip looked like this:

theClip.loadMovie("animation.swf");

In ActionScript 3.0, there is no direct way to load an .swf file into an existing movie clip. Instead, the file must be loaded by a Loader object, and then added to a parent movie clip (or any other DisplayObjectContainer object). The code looks like this:

var l:Loader = new Loader();
l.load(new URLRequest("animation.swf"));
theParent.addChild(l);

The ActionScript 3.0 code is more consistent with the rest of the ActionScript 3.0 API, and it offers more control over the load operation, security, and loading events. Nevertheless, many developers miss the convenience of loadMovie(). Fortunately for them, it's possible to create a loadMovie() equivalent with a little custom code. Example 2 defines a custom function, loadChildAsset(), that offers most of the convenience of ActionScript 1.0/2.0 loadMovie() function. Note, however, that loadChildAsset() is not exactly the same as the original loadMovie(); instead of replacing the target, it makes the loaded asset's Loader object a new child of the specified parent. The loadChildAsset() function also returns a Loader object through which the load operation can be halted. Finally, loadChildAsset() can be passed listener functions for handling load events such as Event.COMPLETE and ProgressEvent.PROGRESS. (Note that as with any load operation, developers should not access the loaded asset until Event.INIT has occurred.)

Example 2: The loadChildAsset() Function

package {
import flash.display.*;
import flash.events.*;
import flash.net.*;

public function loadChildAsset (parent:DisplayObjectContainer,
url:String,
completeListener:Function = null,
initListener:Function = null,
progressListener:Function = null,
ioErrorListener:Function = null,
method:String=null
):Loader {
// Create the Loader and URLRequest objects
var l:Loader = new Loader();
var u:URLRequest = new URLRequest(url);
u.method = method ? method : URLRequestMethod.GET;

// Add the Loader object to the parent
parent.addChild(l);

// Output a warning for any error messages
l.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR,
function (e:IOErrorEvent):void {
trace("Loading error: " + e);
});

// Register any supplied event listeners
if (completeListener != null) {
l.contentLoaderInfo.addEventListener(Event.COMPLETE,
completeListener);
}

if (initListener != null) {
l.contentLoaderInfo.addEventListener(Event.INIT, initListener);
}

if (progressListener != null) {
l.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS,
progressListener);
}

if (ioErrorListener != null) {
l.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR,
ioErrorListener);
}

// Start the load operation
l.load(u);

// Pass the Loader object reference to the caller so the caller
// can stop the load operation if desired
return l;
}
}

To use the preceding loadChildAsset() function, copy the code into a file named "loadChildAsset.as" and then place that file in the root of your program's classpath. Here's how you can use loadChildAsset() in a timeline script to load a file named "animation.swf" as a child of the current movie clip:

// Load a movie as a child of the current movie clip
loadChildAsset(this, "animation.swf");

Here's how you can use loadChildAsset() in a class file with listener functions for responding to load events:

package {
import flash.display.*;
import flash.events.*;
import flash.net.*;

public class SomeApplication extends Sprite {
public function SomeApplication () {
loadChildAsset(this,
"child.swf",
childCompleteListener,
childInitListener,
childProgressListener,
childIoErrorListener);
}

public function childCompleteListener (e:Event):void {
trace("The file finished loading.");
}

public function childInitListener (e:Event):void {
trace("The file has been initialized.");
}

public function childProgressListener (e:ProgressEvent):void {
trace("Part of the file loaded.");
}

public function childIoErrorListener (e:IOErrorEvent):void {
trace("The file could not be loaded.");
}
}
}

What Should Adobe Do?

In creating ActionScript 3.0, Adobe purged the language of its many inconsistencies and limitations, and established a cleaner, more feature-rich API. But in some specific cases, Adobe also purged the language of popular, easy-to-use tools such as loadMovie(). Over time, Adobe should carefully and selectively strive to reintroduce equivalents for those tools.

What Should We Do?

For now, developers looking for convenient .swf-loading tools will have to create and share custom functions, such as Example 2's loadChildAsset(). The best ideas from the community should eventually be integrated into the core Flash runtime APIs.

Charge #6: ActionScript 3.0's Additional Errors Make Coding Cumbersome

The Accused: ActionScript 3.0
The Verdict: NOT GUILTY

ActionScript 1.0 and 2.0 were notorious for their lack of runtime errors and runtime datatype checking. Trivial datatype errors went unreported at runtime, forcing programmers to manually diagnose even routine problems such as misspelled variable names and missing function arguments. ActionScript 2.0 thankfully introduced datatype checking at compile time, but continued to let datatype errors go unreported for dynamically typed variables, parameters, and return values (all of which are commonly found in small scripts and rapid prototypes).

For example, consider the following erroneous ActionScript 2.0 code. It mistakenly uses the variable name "tex" instead of "text" when attempting to display some text on-screen.

someClip.createTextField("t",0, 0, 0, 100, 100);
someClip.t.tex = "Hello world"; // Oops! Should be "text" not "tex"

When the code runs, ActionScript 2.0 does not generate an error. Instead, ActionScript 2.0 creates a new variable named "tex" on the TextField object, and assigns it the value "Hello world". As a result, the programmer is effectively stranded. No text has appeared on-screen, and there's no error explaining what went wrong. The program might have a serious logical failure or a simple typographical mistake; the programmer has no way of knowing for sure without painstakingly examining the code line by line. ActionScript 3.0, by contrast, is very vocal about errors. It dutifully informs the programmer when it cannot execute some code. For example, consider the ActionScript 3.0 equivalent of the preceding erroneous code:

var t = new TextField();
t.tex = "Hello world";

In ActionScript 3.0, the TextField class is not dynamic, so when the code runs, ActionScript 3.0 generates the following error:

ReferenceError: Error #1056: Cannot create property tex on flash.text.TextField.
at Test()[c:\data\someProject\src\Main.as:10]

The error not only describes what went wrong ("Cannot create property tex"), but also indicates exactly where the program failed (line 10 of Main.as). As a result, the programmer can fix the error in a matter of seconds. ActionScript 3.0 reports all datatype errors, both at compile time and at runtime, so programmers can focus on logic rather than wasting time on typographical mistakes.

What Should Adobe Do?

Adobe has already done the right thing by increasing the number of errors reported by ActionScript. Now the company needs to focus on making the errors more readable for average programmers. Many ActionScript errors are too generic, or too filled with ECMAScript-specification jargon. Furthermore, the short error explanations provided in the documentation are insufficient. For example, consider the following error message from the preceding section:

Cannot create property tex on flash.text.TextField.

The message is technically correct, but it focuses on the runtime's point of failure rather than the programmer's probable intention. The error effectively says to the programmer, "You're trying to create a dynamic instance variable on a TextField object, but that's not possible because the TextField class isn't dynamic." The focus of the error message implies that the appropriate solution is to make the TextField class dynamic, thus allowing "tex" to be legally created. Of course, the programmer isn't trying to create a dynamic instance variable. The programmer simply supplied the wrong variable name for "text". The runtime should be smarter in this situation. It should account for the mistaken-variable-name case, as shown in the following potential rewrite of the error message:

Cannot assign 'Hello world' to variable 'tex' on an object of type
flash.text.TextField. Variable 'tex' does not exist and cannot be dynamically
created because TextField is sealed. See http://www.adobe.com/go/aserror1056.

The hypothetical linked page in the preceding error message would describe the problem in more detail, and give examples of typical causes and fixes for the error. Here are a few more examples of errors that are too arcane:

  • 1061 Call to a possibly undefined method %s through a reference with static type %s.
  • 1118 Implicit coercion of a value with static type %s to a possibly unrelated type %s.
  • 1119 Access of possibly undefined property %s through a reference with static type %s.
  • 1151 A conflict exists with definition %s in namespace %s.

What Should We Do?

As developers, we should all learn to love errors. Error messages are a programmer's best friend. They don't waste time, they save it. They're not trying to be frustrating; they're trying to be helpful.

Programmers who feel intimidated or frustrated by ActionScript 3.0's new errors should consider the following analogy: suppose you're getting off a train and the person next to you notices that you left your hat behind. Would you want that person to tell you that you forgot your hat and show you where to retrieve it? Or would you prefer to start searching for your hat only once you realized it went missing? ActionScript 3.0 would tell you about your hat; ActionScript 2.0 wouldn't. (Of course, until Adobe improves its error messages, ActionScript 3.0 might appear to be screaming at you in ancient Latin, but at least you know something's wrong.)

Error messages are a natural, routine, healthy part of programming. They help you fix the obvious errors in your code so that you have more time to focus on building features and diagnosing logical problems. The more error messages a language gives you, the more time you have to get your real work done. Remember, also, that ActionScript error messages appear in the debugger versions of Flash runtimes only. The general public won't see them.

For help deciphering ActionScript 3.0's error messages, see Adobe's compiler error reference.

Charge #7: Referring to Library Symbols Dynamically Is Unintuitive

The Accused: ActionScript 3.0
The Verdict: GUILTY

Imagine you're a Flash user who has created an .fla file with 10 movie clip symbols, named "Animation0" through "Animation9". You want to display an instance of all 10 symbols at runtime. In ActionScript 1.0 and 2.0, the code you would use to create the instances would look like this (note that, for brevity, the code does not use type annotations):

for (var i = 0; i < 10; i++) {
parentClip.attachMovie("Animation" + i, "instance" + i, i);
}

The equivalent ActionScript 3.0 code looks like this (again, type annotations have been omitted for brevity):

var Symbol; 
for (var i = 0; i < 10; i++) {
Symbol = getDefinitionByName("Animation" + i);
parentClip.addChild(new Symbol());
}

The ActionScript 3.0 code has several benefits over the ActionScript 1.0/2.0 version. The first is API consistency: the code for dynamically referencing a symbol is exactly the same as the code for dynamically referring to a class. Second, no instance name is required; child movie clips do not need instance names because they are normally accessed by reference. Third, no depth is required; ActionScript 3.0's depth management system automatically creates depths for objects on the display list. Nevertheless, for typical Flash users the ActionScript 3.0 version of the code is less intuitive than the ActionScript 2.0 version. The ActionScript 2.0 version uses the terminology of the Flash authoring tool, which is familiar to Flash users. It says, "Add a new 'Animation0' movie instance to parentClip." The ActionScript 3.0 code uses the terminology of formal object-oriented programming, which is unfamiliar to many Flash users. It says, "Give me a reference to the class named ‘Animation0,’ create a new object from that reference, and make that object a child of parentClip." For many Flash users, when it comes to dynamically referencing a symbol ActionScript 3.0 no longer speaks their language.

Fortunately, as shown in Example 3, it's possible to create an attachMovie() equivalent with a little custom code. Example 3 defines a custom function, addChildFromLibrary(), which adds an instance of the specified symbol to the specified parent, at the specified depth (optional).

Example 3: An ActionScript 3.0 attachMovie() Equivalent

package {
import flash.display.DisplayObjectContainer;
import flash.display.DisplayObject;
import flash.utils.getDefinitionByName;

public function addChildFromLibrary (parent:DisplayObjectContainer,
symbolName:String,
depth:int = -1):DisplayObject {
var Symbol = getDefinitionByName(symbolName);

if (depth < 0) {
return parent.addChild(new Symbol());
} else {
return parent.addChildAt(new Symbol(), depth);
}
}
}

To use the preceding addChildFromLibrary() function, copy the code into a file named "addChildFromLibrary.as" and then place that file in the root of your program's classpath. Here's how you can use addChildFromLibrary() to replace attachMovie() in the earlier "10 animation symbols" example:

for (var i = 0; i < 10; i++) {
addChildFromLibrary(parentClip, "Animation" + i);
}

Like attachMovie(), addChildFromLibrary() uses terminology from the Flash authoring tool, and should therefore be more intuitive for Flash users.

What Should Adobe Do?

Fundamentally, Adobe made the right choice introducing symbols-as-classes in Flash CS3, and then providing a consistent way to dynamically reference both a regular class and a class linked to a symbol. However, getDefinitionByName() is not particularly approachable for less-experienced programmers. The addChildFromLibrary() function suggested in the preceding section shows at least one way to make dynamic symbol instantiation easier to understand. The question is whether Adobe should add such a function to the built-in MovieClip class. For now, I think not. Blindly adding helper functions to MovieClip could easily lead to the kind of clutter found in ActionScript 1.0 and 2.0, where MovieClip had an inconsistent variety of instantiation methods: createTextField(), createEmptyMovieClip(), attachMovie(), and duplicateMovieClip(). The MovieClip class (or, more precisely, the DisplayObjectContainer class) should not become a factory for creating all possible types of Flash content.

That said, Adobe should monitor ActionScript ease of use closely in Flash authoring and address the key Flash-centric issues carefully as the language matures. For example, to make dynamic symbol instantiation easier to understand, perhaps the Flash API should include a factory method, makeInstanceFromSymbol(), as a simple wrapper for getDefinitionByName(). The following code shows what makeInstanceFromSymbol() might look like:

// Signature
flash.utils.makeInstanceFromSymbol(symbolName:String):DisplayObject

// Implementation
public function makeInstanceFromSymbol(symbolName:String):DisplayObject {
return new getDefinitionByName(symbolName);
}

// Example Usage:
parentClip.addChild(makeInstanceFromSymbol("Animation" + i));

The makeInstanceFromSymbol() method would give "everyday Flashers" a more familiar syntax for dynamic symbol instantiation, without introducing inconsistency into the existing display API.

Whatever Adobe eventually does to make getDefinitionByName() more approachable, it should remain frugal when considering any addition to ActionScript's APIs. Adding too many "convenience functions" leads to language bloat, which ironically could make ActionScript less intuitive, not more intuitive. By providing a strong, consistent set of low-level tools, Adobe can patiently watch the successes and failures of community code libraries, and incorporate only those features that a) truly address the needs of most Flash users, and b) cannot be solved easily or well by third parties.

What Should We Do?

Unfortunately, until Adobe adds a Flash-centric function for dynamic symbol instantiation, Flash users will have to use the existing getDefinitionByName() function, or use a custom wrapper, such as the addChildFromLibrary() or makeInstanceFromSymbol() function listed in the preceding sections.

Charge #8: Adding Custom Functionality to Manually Created Text Fields, to All Movie Clips, or to All Buttons Is Cumbersome

The Accused: Flash CS3
The Verdict: GUILTY

Flash CS3 does not provide a way to specify the class for text fields created manually in an .fla file. Manually created text fields, therefore, cannot be augmented using traditional object-oriented programming techniques.

For example, suppose you want to add a convenience method, selectAll(), to all manually created text fields in an .fla file. Ideally, you would add selectAll() to a custom class—say, EnhancedTextField—as follows:

package {
import flash.text.TextField;

public class EnhancedTextField extends TextField {
public function selectAll ():void {
setSelection(0, length);
}
}
}

Then, in the Flash authoring tool, you would specify that all text fields should be instances of your custom EnhancedTextField class. However, Flash CS3 provides no way to specify the class for manually created text fields. Your text fields must be instances of the TextField class, and cannot benefit from your selectAll() method. In a similar vein, in Flash CS3, although an individual Library symbol's class can be specified via the Linkage Properties dialog, there is no way to specify the class for an entire category of symbol.

For example, suppose you want to add a new method, flipHorizontal(), to all movie clip instances in an .fla file. Ideally, you would add flipHorizontal() to a custom class—say, FlippableMovieClip—as follows:

package {
import flash.display.MovieClip;

public class FlippableMovieClip extends MovieClip {
public function flipHorizontal ():void {
scaleX = -scaleX;
}
}
}

Then you'd specify FlippableMovieClip as the default linked class for all movie clip symbols. But Flash CS3 provides no means of specifying the default class for all movie clip symbols.

Likewise, Flash CS3 provides no way to specify a set of enhancements for all types of graphical assets (i.e., for all movie clips, all buttons, and all text fields). For example, suppose you want to add the preceding flipHorizontal() method not just to all movie clip instances, but also to all button and text field instances in an .fla file. Effectively, you want to augment the functionality of all DisplayObject instances. In theory, one way to achieve your goal is to follow these steps:

  • Create a custom helper class—say, Flipper—for flipping display objects.
  • Compose a Flipper instance into separate MovieClip, Button, and TextField subclasses.
  • Specify those subclasses as the default classes for movie clips, buttons, and text fields in your .fla file.

Here's what the custom helper class from step 1 might look like:

package {
import flash.display.DisplayObject;

public class Flipper {
private var d:DisplayObject;

public function Flipper (displayObject:DisplayObject) {
d = displayObject;
}

public function flipHorizontal ():void {
d.scaleX = -d.scaleX;
}
}
}

And here's what the MovieClip subclass from step 2 might look like:

package {
import flash.display.MovieClip;

public class FlippableMovieClip extends MovieClip {
private var flipper:Flipper;

public function FlippableMovieClip () {
super();
flipper = new Flipper(this);
}

public function flipHorizontal ():void {
flipper.flipHorizontal();
}
}
}

You cannot actually follow the preceding theoretical procedure, however, because Flash CS3 provides no means of specifying the default class for movie clips, buttons, and text fields, so there is no way to perform step 3.

But even if Flash CS3 did provide a means of specifying the default class for movie clips, buttons, and text fields, a typical "everyday Flasher" would not have the programming experience required to write the proposed Flipper and FlippableMovieClip classes. The Flash authoring tool needs an easier way to add custom functionality to all graphical assets in an .fla file.

What Should Adobe Do?

Adobe should add the following features to the Flash authoring tool:

  • A means of specifying the class for individual manually created text fields
  • A means of specifying the default class for all manually created text fields
  • A means of specifying the default class for one symbol type (i.e., all movie clip symbols)
  • A means of centrally augmenting all displayable assets (i.e., all movie clips, buttons, and text fields)

Here are some thoughts on how to implement the preceding features:

  • For specifying the class of an individual text field, Adobe could add a new Properties panel field.
  • For specifying the default class for all text fields, or for symbol types in an .fla file, Adobe could add new fields in the ActionScript Publish Settings dialog.

For augmenting all graphical assets, Adobe could provide a central panel for specifying functions and variables to be applied to all graphical assets. The compiler could then automatically generate the appropriate ActionScript 3.0 code from the contents of that panel.

None of the preceding ideas is particularly elegant, but they provide temporary, easy-to-implement solutions and are a first step toward better linkage between graphical assets and ActionScript code.

What Should We Do?

Each code linkage issue presented in the preceding two sections has its own workaround, as follows.

Specify the class of an individual text field

Because there is no direct way to specify a text field's class, developers wishing to control a manually created text field must pass that text field to a custom helper function or to a method of a custom helper class. For example:

selectAll(someTextField)

Specify the default class for all manually created text fields

To control all text fields in an .fla file, developers can turn to an old friend, TextField.prototype, which can forcibly inject a new method into the TextField class at runtime. For example, the following code injects a custom method, flipHorizontal(), into the TextField class:

TextField.prototype.flipHorizontal = function ():void {
this.scaleX = -this.scaleX;
};

However, because the flipHorizontal() method is unknown at compile time, invoking it causes a strict-mode compiler error. To avoid the error, developers can disable strict-mode compilation, or invoke the method dynamically, as in:

someTextField["flipHorizontal"]();

But before using TextField.prototype, all developers should heed this warning: adding a method to any class via prototype is discouraged because it changes the meaning of the class at runtime in a way that the class's users cannot predict. Furthermore, prototype-method invocations cannot be error-checked at compile time. For example, the following code, which contains a typo, generates no compile-time error:

someTextField.["fliporizontal"]();

Before the error can be detected, the program must actually reach the preceding line of code at runtime.

Specify the default class for an entire symbol type

To control all movie clips or all buttons in an .fla file, developers are advised not to use MovieClip.prototype or SimpleButton.prototype. Instead, developers should create a MovieClip or SimpleButton subclass, and specify that subclass (or one of its descendants) as the linked class for all desired symbols. Laborious, perhaps, but it guarantees compile-time type checking and it promotes clean code.

Charge #9: The Removal of duplicateMovieClip() Makes Cloning a MovieClip Instance (Really) Hard

The Accused: ActionScript 3.0
The Verdict: GUILTY

Both ActionScript 1.0 and 2.0 included a handy tool for making an exact copy of a movie clip instance: duplicateMovieClip(). The duplicateMovieClip() function was used for everything from creating mouse trailers to generating dynamic patterns and implementing "rubber stamp"-style drawing tools. Many early Flash designers and artists achieved widespread recognition for creatively exploiting duplicateMovieClip(). Among them, Robert Hodgin was my favorite. His duplicateMovieClip()-based creatures in Flight 404 v4 are still mesmerizing.

Sadly, duplicateMovieClip() was removed from ActionScript 3.0, and there's no replacement.

What Should Adobe Do?

Adobe should add a new method, DisplayObject.clone(), to replace duplicateMovieClip(). DisplayObject.clone() should apply to all display objects, not just movie clips.

What Should We Do?

Custom versions of DisplayObject.clone()—particularly those that handle deep copies and runtime-generated vector graphics—are very hard to make. Developers should therefore vote for the addition of a native DisplayObject.clone() method in the Flash Player bug and issue management system. For simple duplication needs, developers can use Trevor McCauley's duplicateMovieClip() replacement function, duplicateDisplayObject(). Note, however, that Mr. McCauley's function does not do a deep copy, does not handle runtime-generated graphics, and does not allow arguments to be passed to the duplicated object's constructor method.

The Prosecution Rests

Thus ends the list of Charges Against ActionScript 3.0.

I hope this article has helped to crystallize the Flash community's most common criticisms of ActionScript 3.0. If you'd like to add your own issues and strategies to the discussion, please leave a comment below. Please also report any bugs or factual errors you notice in this article. Happy coding!

  评论这张
 
阅读(647)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017