오류를 범하기 쉬운 Flex 코딩 바로잡기
컴퓨팅 기술은 나날이 빠르게 발전하여 우리의 컴퓨터 환경을 바꾸고 있다. 불과 몇 년 전만 해도 텍스트 위주의 화면구성을 사용자에게 보여주었던 것에 불과한 것이 이제는 이미지와 텍스트 그리고 동영상을 수반한 UCC라는 개념이 등장했다. 이에 사용자들은 좀 더 사용자 위주의 UI를 원하게 되었고, 그에 따라 사용자들을 만족시키기 위해 새로운 개념 RIA(Rich Internet Application)이 탄생하게 되었다.
RIA 기반의 프로그램은 이전의 순차적 웹 페이지 방식이 아닌 이벤트 방식을 수반하고 있으며, 웹 개발자 또한 이에 발맞추어 새로운 방식으로 구조를 설계하고 개발할 필요가 있게 되었다. 그리고 Flex 또한 애플리케이션과 같은 이벤트 방식을 취하고 있다.
Flex는 애플리케이션과는 또 다르게 Flex만의 UI 특징이 있으며 이를 제대로 숙지하지 않은 상태에서 개발해나간다면 상당한 어려움에 빠질 수 있다. 이 문서는 Flex 개발시 고려해야 할 점을 이야기하며, 실무 개발에 도움이 됐으면 한다.
Effect가 지니고 있는 점
Effect는 Flex에서 아주 빈번하게 사용되지만 때때로 사용자의 예외적인 반응에 의해
제대로 작동을 하지 않아 사용자나 개발자 둘 다 당혹하게 만드는 경우가 있다. 그 문제는 다음의 Listing 1.1 에서 확인할 수 있다.
Left 버튼을 누르고 target으로 이동하는 사이에 Right 버튼을 클릭하면 당장 오른쪽으로 이동하지도 않을뿐더러 중간중간 멈칫하기도 한다. 따라서 Right 버튼을 누른 사용자는 자신이 원하는 방향으로 이동하지 않았기 때문에 이것을 버그라고 생각하게 된다.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Move id="btnMover" duration="2000" />
<mx:Button id="target" label="www.adobeflex.co.kr" moveEffect="{btnMover}"/>
<mx:Button label="Left" click="target.x = 10" />
<mx:Button label="Right" click="target.x = 500" />
</mx:Application>
Listing 1.1 - Effect 오류 가능성이 있는 Flex 소스
Move의 부모 클래스에 해당하는 Effect의 play() 메쏘드를 살펴보면 그 어디에도
이팩트가 작동 중이었을 경우 다시 실행하는 문제에 대해 처리하지 않고 있다. 이 문제는 isPlaying 속성을 사용하여 다음과 같은 방법으로 해결해 보았다.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Move id="btnMover" duration="2000"/>
<mx:Button id="target" label="www.adobeflex.co.kr" moveEffect="{btnMover}"/>
<mx:Button label="Left" click="leftClick()" />
<mx:Button label="Right" click="rightClick()" />
<mx:Script>
<![CDATA[
public function leftClick():void
{
if(btnMover.isPlaying) btnMover.stop();
target.x = 10;
btnMover.play();
}
public function rightClick():void
{
if(btnMover.isPlaying) btnMover.stop();
target.x = 500;
btnMover.play();
}
]]>
</mx:Script>
</mx:Application>
Listing 1.2 - Effect 오류 가능성 제거
함수 rightClick() 안쪽을 살펴 보면 isPlaying 속성의 반환 값을 체크한 뒤에 stop()
메쏘드를 호출하는 방법을 사용하였다.
이제 rightClick()는 안전하게 중지된 시점부터 다시 새로운 지점으로의 자연스런 이동이 가능하게 구현되었다. 다만 stop() 메쏘드는 SDK 3부터 지원하기 때문에 이전의 버전의 SDK를 사용한 애플리케이션은 이 방법을 사용할 수가 없다.
잘못된 addChild의 사용법
addChild, removeChild는 컨테이너에 컨트롤들을 등록하는 가장 쉬운 방법이다. 하지만 addChild를 사용할 때 반드시 지켜야 할 점이 있는데, 그것은 사용자 반응과 연동시켜서는 안 된다는 것이다.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical" initialize="initApp()">
<mx:Button label="addChild" click="addChildClick()" />
<mx:Button label="removeChild" click="removeChildClick()" />
<mx:Script>
<![CDATA[
import mx.controls.Label;
private var _child:Label = new Label();
public function initApp():void
{
_child.text = "www.adobeflex.co.kr";
}
public function addChildClick():void
{
addChild(_child);
}
public function removeChildClick():void
{
removeChild(_child);
}
]]>
</mx:Script>
</mx:Application>
Listing 2.1 - 컨트롤과 addChild를 연동시킨 예
Listing 2.1에서 ‘addChild’버튼을 누르면 ‘www.adobeflex.co.kr’ 이라는 문구가 뜨고
‘removeChild’ 를 누르면 화면에서 사라집니다. 우리는 아주 간단한 동작만으로 치명적인 에러 메시지를 만들 수 있다. Remove 버튼을 두 번 눌러보면 콘솔 창에 다음과 같은 문구가 뜰 것이다.
ArgumentError: Error #2025: 제공된 DisplayObject는 호출자의 자식이어야 합니다.
여기에서 무엇이 잘못된 것일까? removeChild 안쪽에는 반드시 타겟이 해당 메쏘드의 자식으로 등록된 DisplayObject만이 해지할 수 있도록 만들어져 있다. 여기에 엉뚱한 다른 컨테이너의 자식을 넣게 되면 위와 같은 메시지가 발생하게 되는 것이다.
try~catch로 예외 처리를 하게 되면 이 오류메시지를 피할 수 있다. 하지만 이렇게 등록과 해제를 하게 되는 과정에서 외부 함수가 접근하려 했을 때 _child 의 접근에 신뢰를 가지기가 힘들게 된다. 여기서 말하고자 하는 점은 메쏘드 addChild가 아닌 사용자의 반응에 직접적으로 addChild를 호출한 점이 문제인 것이다.
좀 더 복잡한 경우 이 Label의 showEffect 또는 hideEffect가 등록되어 있다면 이 이팩트의 실행여부를 판단해주어야 하는 점도 있다. 이런 점은 visible 속성을 사용하는 것으로 해결할 수 있다.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical" initialize="initApp()">
<mx:Button label="addChild" click="addChildClick()" />
<mx:Button label="removeChild" click="removeChildClick()" />
<mx:Script>
<![CDATA[
import mx.controls.Label;
private var btn:Label = new Label();
public function initApp():void
{
btn.text = "www.adobeflex.co.kr";
addChild(btn);
}
public function addChildClick():void
{
btn.visible = true;
}
public function removeChildClick():void
{
btn.visible = false;
}
]]>
</mx:Script>
</mx:Application>
Listing 2.2 - visible 속성의 사용
addChild를 사용자 반응과 관계 없는 initApp() 메쏘드에 삽입함으로써 유저의 버튼 동작에 신경 쓰지 않아도 되고, 전체적인 버튼 접근에 관한 잠재 가능성을 배제할 수 있게 되었다.
컴포넌트 밖 외부데이터와의 처리
Flex의 데이터 바인딩은 개발자의 복잡한 소스를 아주 쉽고 간단하게 만들어준다. 그 외에도 외부데이터의 응답시간을 신경 쓸 필요가 없다는 장점이 있다. 이는 itemRenderer 같은 데이터와 해당 컴포넌트의 시점을 가늠할 수 없을 때에도 사용된다.
Listing 3.1 은 바인딩 되지 않은 형태로 itemRenderer를 통해 컨트롤에 삽입해 봤다.
<mx:itemRenderer>
<mx:Component>
<mx:Box>
<mx:Script>
<![CDATA[
override public function set data(value:Object):void
{
output.text = value.toString();
super.data = value;
}
]]>
</mx:Script>
<mx:Label id="output" />
</mx:Box>
</mx:Component>
</mx:itemRenderer>
Listing 3.1 - 외부데이터를 인라인 컴포넌트 컨트롤에 적용
Listing 3.1은 몇몇의 경우에는 문제를 일으키지 않을 경우가 있다. 하지만 웹 상에서 데이터를 읽어오는 경우 또는 컴포넌트가 생성되지 않았을 시점에 데이터가 들어오는 경우 null 객체 참조 에러가 발생하게 된다.
TypeError: Error #1009: null 객체 참조의 속성이나 메쏘드에 액세스할 수 없다.
그 외에도 null 이 데이터에 들어왔을 경우도 생각해야 한다. 데이터 바인딩은 이러한 경우에 많은 도움이 된다. 컴포넌트의 생성 시점을 생각할 필요가 없기 때문이다.
<mx:itemRenderer>
<mx:Component>
<mx:Box>
<mx:Script>
<![CDATA[
[Bindable] private var outputStr:String;
override public function set data(value:Object):void
{
outputStr = value.toString();
super.data = value;
}
]]>
</mx:Script>
<mx:Label text="{outputStr}" />
</mx:Box>
</mx:Component>
</mx:itemRenderer>
Listing 3.2 - 바인딩을 이용한 외부데이터 처리
위와 같이 외부 데이터와 컨트롤을 바인딩으로 묶어줌으로써 이 문제는 해결된다. 이처럼 외부데이터가 컨트롤에 직접 접근하는 것은 피해야 할 방법 중 하나이다.
코드와 뷰의 완벽한 분리
현재MXML 기반의 Flex는 화면을 다른 파일로 관리하지 않는 한 코드와 뷰를 분리하기란 쉽지가 않다. 따라서 이를 혼용 사용하다 보면 때때로 서로 섞이게 되어 난잡한 코드를 작성하게 될 경우가 많다.
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" width="400" height="300"
initialize="initPanel()">
<mx:Script>
<![CDATA[
import mx.controls.Button;
private var closeBtn:Button = new Button();
private function initPanel():void
{
closeBtn.width = 100;
closeBtn.height = 20;
closeBtn.label = "close";
}
override protected function createChildren():void
{
super.createChildren();
titleBar.addChild(closeBtn);
}
]]>
</mx:Script>
</mx:Panel>
Listing 4.1- 코드에 의존한 Panel 컴포넌트
Listing 4.1은 Panel titleBar에 버튼을 추가하는 내용을 코드에 너무 의존한 형태인데 Flex에 익숙하지 않은 프로그래머들이 자주 사용하게 되는 패턴이라고도 볼 수 있다. Flex 프로젝트의 특성상 디자인은 자주 바뀌게 되는데 이렇게 디자인 요소를 코드상에 표현하게 되면 유지보수가 어렵고 개발에 많은 시간이 소요된다. 이것을 태그 형태로 바꾸어 보도록 하자.
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="400" height="300">
<mx:Script>
<![CDATA[
override protected function createChildren():void
{
super.createChildren();
titleBar.addChild(closeBtn);
}
]]>
</mx:Script>
<mx:Button id="closeBtn" width="100" height="20" label="close"/>
</mx:Panel>
Listing 4.2 - 디자인을 태그로 표현한 Panel 컴포넌트
보기에도 간편할 뿐 아니라 디자인 모드에서도 수정이 가능한 형태로 바뀌었다. 이처럼 태그는 코딩의 속도를 높여줄 뿐만 아니라 유지보수 및 소스분석에도 많은 장점들을 가지고 있다. 그렇다고 무작정 태그가 좋은 건 아니다. 태그의 표현에도 한계가 있기 때문이다. 여기서 태그와 코드가 가져가야 하는 부분을 나누어 생각해 보았다.
1. 비주얼 컴포넌트(Visual Component)는 모두 태그 기반으로 가야 한다.
2. 넌 비주얼 컴포넌트 중 이벤트 기반의 컴포넌트는 태그로 가야 한다.
예) HTTPService , Timer , Effect , TargetTrace …
3. 넌 비주얼 컴포넌트 중 Formatter, Validator는 태그로 가야 한다.
이상으로 Flex 개발시 참고하면 좋은 사항을 살펴봤다. 현재 Flex의 요구가 점점 많아지고 있는 추세이며 새로운 Flex 개발자들이 늘어나고 있기에, 본 글이 개발시 조금이나마 도움이 됐으면 하나 바람이다. Flex의 특징을 이해하고 MXML 자체의 특성을 잘 받아들인다면 훨씬 훌륭한 코드를 작성하게 될 것이다.
* 출처 : http://www.adobeflex.co.kr/iwt/board/board.php?tn=pds_tech&id=163&mode=view