Archive

Archive for 3월, 2010

프로그래머들이여, 캐치볼을 해라!

3월 22nd, 2010

요즘 다른 회사의 프로그래머와 협업할 일이 있어서 작업을 같이 진행하고 있는데 결과물을 보면 참 한심스럽다. 그 사람의 프로그래밍 능력도 문제지만 더 큰 문제는 대화 능력인 듯 하다. 요구 사항이 전달되지 않아 결국 제멋대로 넘어온 결과물을 우리 쪽에서 다시 맞추는 작업할 수 밖에 없다.

흔히들 프로그래머들이 소통 능력이 떨어진다고 한다. 기획자나 디자이너는 작업 흐름 상 자신의 작업이 끝나면 다른 사람에게 자신의 작업물을 넘겨주고 자신의 작업물이 최종 결과물에 잘 반영될 수 있도록 뒷 사람에게 소통을 시도한다. 그러나 프로그래머는 최종 결과물을 만드는 사람들이다(물론 그 뒤에 QA가 있기는 하지만 QA는 결과물을 확인할 뿐이지 제품을 만들지는 않는다). 그래서 프로그래머들은 상대적으로 소통의 필요성을 덜 느끼며 결국 다른 작업자들 보다 소통 능력이 떨어진다.

그러나 모든 프로그래머들의 소통 능력이 떨어지는 것은 아니다. 익스트림 프로그래밍(XP)에서는 프로그래머의 소통 능력을 매우 중요하게 생각한다. 고객과 개발자 간의 소통 능력 뿐만 아니라 프로그래머 간의 소통 능력에 대해서도 매우 중요하게 다루고 있다. 우리 회사에서도 짝-프로그래밍(pair programming)을 하기 때문에 프로그래머의 소통 능력을 매우 중요하게 생각한다. 나아가 짝-프로그래밍을 통해서 프로그래머들의 소통 능력이 많이 향상되고 있다.

대부분의 프로젝트라는 것이 여러 사람의 공동 작업이고 공동 작업에서 가장 중요한 것은 소통이다. 프로젝트 팀 구성원들 간에 얼마나 소통이 원활하냐에 따라 프로젝트 진행 및 결과가 결정된다.

소통을 잘 하는 방법은 간단하다. 잘 듣고 잘 말하면 된다. 우선 상대방이 전달하려는 내용을 잘 들어야 한다. 상대방의 생각을 충분히 이해한 다음 정리된 자신의 생각을 상대방에서 말하면 된다. 우리 팀은 이것을 야구의 캐치볼에 비유하곤 한다. 캐치볼은 2명(일반적으로)이 플레이어가 하나의 공을 주고 받는 행동이다. 공이 하나이기 때문에 내가 공을 받아야 상대방에게 공을 던질 수 있다. 대화도 마찬가지이다. 내가 나의 생각을 말하기 위해서는 상대방의 생각을 들어야 한다.

그리고 공을 던질 때엔 상대방의 가슴을 향해서 던진다. 상대방이 다음 동작을 잘 이어갈 수 있도록 하기 위한 배려이다. 대화도 마찬가지이다. 내가 하고 싶은 말을 아무렇게나 하는 것이 아니라 상대방이 잘 알아들을 수 있도록 최대한 배려해서 말해야 한다. 그러기 위해서 말하기 전에 듣는 사람의 입장을 생각해봐야 한다.

끝으로 상대방이 던진 공이 내 가슴을 향해 날아오지 않으면 내가 움직여야 한다. 상대방이 가슴을 향해서 던지려 해도 사람이기 때문에 원하는 곳을 공이 날아오지 못할 때가 많다. 특히 초보자의 경우엔 더욱 그러하다. 그래서 공을 받는 사람이 움직여야 한다. 만약 움직이지 않으면 캐치볼은 중단된다. 대화도 비슷한다. 상대방의 하는 말이 자신이 것이 아닐 때 감정 상하지 않고 상대방의 생각을 이해하려고 노력해야 한다. 그리고 대화를 계속 이어나아가야 한다.

요즘은 프로그래머들이 블로그나 트위터, 미투데이에서 많은 대화를 시도하고 있다. 하지만 아직까지 내가 경험한 프로그래머 중에는 소통 능력이 떨어지는 사람들이 많다. 인간은 혼자 살 수 없듯이 프로젝트도 혼자할 수 없다.

프로그래머들이여, 오늘부터 캐치볼을 해라!

Post to Twitter Post to Delicious

, , ,

iPhone, UITableViewCell에 텍스트 설정하기

3월 16th, 2010

UITableViewCell의 setText 메소드는 아이폰 OS 3.0부터는 비권장(deprecated)하고 있습니다. 따라서 아래의 코드는 3.0 이상인 경우 컴파일은 되지만 경고(warning)이 발생합니다.

1
2
3
UITableViewCell* cell;
// ...
[ cell setText: @"Hello" ];

3.0부터는 textLabel 메소드로부터 반환 받은 UILabel 객체에 텍스트를 설정하도록 변경되었습니다.

1
2
3
4
UITableViewCell* cell;
// ...
UILabel* label = [ cell textLabel ];
[ label setText: @"Hello" ];

이 경우 버전 2.1에서는 컴파일 오류가 발생합니다. 하지만 국내에 출시된 아이폰의 경우 3.1부터 시작하기 때문에 하위 호환 문제는 크게 신경쓰지 않아도 될 듯 합니다.

Post to Twitter Post to Delicious

,

iPhone, XML 처리 예제 – NSXMLParser 이용하기

3월 10th, 2010

몇일 전 iPhone에서 HTTP로 XML 데이터 요청하는 예제를 올렸다가 iPhone SDK에서 NSXMLDocument 클래스를 지원하지 않는 다는 것을 확인하고 NSXMLParser 클래스를 이용해서 XML 데이터를 처리하는 예제를 다시 준비했습니다. 아마도 iPhone에서 NSXMLDocument를 지원하지 않는 건 NSXMLDocument가 메모리를 많이 먹기 때문인 아닐까 싶습니다.

NSXMLParser는 XML 문서를 순차적으로 분석(parse)하면서 이벤트별 처리를 위임(delegate)하는 방식입니다. 따라서 NSXMLDocument를 사용하는 예제보다는 훨씬 더 복잡합니다. 참고로 iPhone에서 사용할 수 있는 XML 관련 라이브러리들이 개발되어 있기 때문에 해당 라이브러리를 익혀서 사용하는 것이 개발 효율은 더 좋을 것 같습니다. 하지만 NSXMLParser를 이용해서 구현하면 메모리도 적게 먹고 나름 성능 향상을 기대할 수 기대할 수 있기 때문에 직접 작성하는 것도 알아두면 좋을 듯 합니다.

예제에 포함된 파일 목록은 아래와 같습니다.

파일 설명
showrooms.xml XML 파일
Dealer.h
Dealer.m
Dealer 모델 클래스
Showroom.h
Showroom.m
Showroom 모델 클래스
ShowroomsXmlParse.h
ShowroomsXmlParse.m
showrooms.xml용 파서 클래스
showrooms_xml_parser.m 실행(main) 소스

우선 showrooms.xml은 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8" ?>
 
<showrooms>
  <showroom>
    <name>강남전시장</name>
    <phone>02-111-1111</phone>
    <dealer>
      <name>김태연</name>
    </dealer>
  </showroom>
  <showroom>
    <name>삼성전시장</name>
    <phone>02-222-2222</phone>
    <dealer>
      <name>구하라</name>
    </dealer>
  </showroom>
</showrooms>

예제에서 showroom 및 dealer 엘리먼트가 각각 name 엘리먼트를 자식으로 가지도록 작성했습니다.

Dealer.h와 Dealer.m은 다음과 같습니다.

1
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h>
 
 
@interface Dealer : NSObject
{
  NSString* _name;
}
  @property( copy ) NSString* name;
@end
1
2
3
4
5
6
#import "Dealer.h"
 
 
@implementation Dealer
  @synthesize name = _name;
@end

Showroom.h와 Showroom.m은 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#import "Dealer.h"
 
 
@interface Showroom : NSObject
{
  NSString* _name;
  NSString* _phone;
  Dealer* _dealer;
}
  @property( copy ) NSString* name;
  @property( copy ) NSString* phone;
  @property( retain ) Dealer* dealer;
@end
1
2
3
4
5
6
7
8
#import "Showroom.h"
 
 
@implementation Showroom
  @synthesize name = _name;
  @synthesize phone = _phone;
  @synthesize dealer = _dealer;
@end

NSXMLParser는 NSXMLDocument의 NSXMLNode처럼 모델이 없기 때문에 분석(parse) 결과를 담을 수 있는 모델이 필요합니다. 이 모델 클래스들은 MVC에도 적용될 수 있기 때문에 모델 클래스를 만들어 두면 조금 더 우아한 프로그래밍할 수 있습니다. 모델 클래스들은 간단하기 때문에 자세한 설명은 생략하도록 하겠습니다.

ShowroomsXmlParser.h는 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "Showroom.h"
 
 
@interface ShowroomsXmlParser : NSObject
{
  NSMutableArray* _showrooms;
  NSMutableArray* _elementStack;
}
  - (void) parser: (NSXMLParser*) parser 
      didStartElement: (NSString*) elementName 
      namespaceURI: (NSString*) namespaceURI 
      qualifiedName: (NSString*) qualifiedName 
      attributes: (NSDictionary*) attributeDict;
  - (void) parser: (NSXMLParser*) parser
      foundCharacters: (NSString*) string;
  - (void) parser: (NSXMLParser*) parser 
      didEndElement: (NSString*) elementName
      namespaceURI: (NSString*) namespaceURI 
      qualifiedName: (NSString*) qName;
  - (NSMutableArray*) parseContentOfUrl: (NSURL*) url;
@end

ShowroomsXmlParser는 NSXMLParser가 발생시키는 event를 처리해서 Showroom 객체 배열을 넘겨주는 클래스입니다. NSXMLParser가 위임(delegate)하는 method들을 처리하기 위해서 NSXMLParserDelegate 프로토콜을 구현해야 합니다. 참고로 프로토콜은 자바에서 interface랑 동일합니다. 멤버 변수 _elementStack은 파서의 트리 탐색 경로 및 정보를 담아두는 스택입니다.

ShowroomsXmlParse.m은 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#import "ShowroomsXmlParser.h"
 
 
NSString* const SHOWROOM = @"showroom";
NSString* const DEALER = @"dealer";
NSString* const NAME = @"name";
NSString* const PHONE = @"phone";
 
 
@implementation ShowroomsXmlParser
  - (void) parser: (NSXMLParser*) parser 
      didStartElement: (NSString*) elementName 
      namespaceURI: (NSString*) namespaceURI 
      qualifiedName: (NSString*) qualifiedName 
      attributes: (NSDictionary*) attributeDict
  {
    if ( TRUE == [ elementName isEqualToString: SHOWROOM ] )
    {
      Showroom* showroom = [ Showroom new ];
      [ _elementStack addObject: showroom ];
    }
    else if ( TRUE == [ elementName isEqualToString: DEALER ] )
    {
      Dealer* dealer = [ Dealer new ];
      Showroom* showroom = (Showroom*)[ _elementStack lastObject ];
      [ showroom setDealer: dealer ];
 
      [ _elementStack addObject: dealer ];
    }
    else if ( TRUE == [ elementName isEqualToString: NAME ] ||
      TRUE == [ elementName isEqualToString: PHONE ] )
    {
      NSString* element = [ NSString stringWithString: elementName ];
      [ _elementStack addObject: element ];
    }
  }
 
  - (void) parser: (NSXMLParser*) parser
      foundCharacters: (NSString*) string
  {
    NSCharacterSet* characterSet = [ NSCharacterSet 
      whitespaceAndNewlineCharacterSet ];
    NSString* trimmedValue = [ string stringByTrimmingCharactersInSet:
      characterSet ];
    if ( 0 == [ trimmedValue length ] )
    {
      return;
    }
 
    id element = [ _elementStack lastObject ];
    if ( TRUE == [ element isKindOfClass: [ NSString class ] ] )
    {
      NSUInteger count = [ _elementStack count ];
      NSUInteger index = count - 2;
      id parentElement = [ _elementStack objectAtIndex: index ];
 
      if ( TRUE == [ element isEqualToString: PHONE ] )
      {
        Showroom* showroom = (Showroom*)parentElement;
        [ showroom setPhone: trimmedValue ];
      }
      else if ( TRUE == [ element isEqualToString: NAME ] )
      {
        if ( TRUE == [ parentElement isMemberOfClass: [ Showroom class ] ] )
        {
          Showroom* showroom = (Showroom*)parentElement;
          [ showroom setName: trimmedValue ];
        }
        else if ( TRUE == [ parentElement isMemberOfClass: [ Dealer class ] ] )
        {
          Dealer* dealer = (Dealer*)parentElement;
          [ dealer setName: trimmedValue ];
        }
      }
    }
  }
 
  - (void) parser: (NSXMLParser*) parser 
      didEndElement: (NSString*) elementName
      namespaceURI: (NSString*) namespaceURI 
      qualifiedName: (NSString*) qName
  {
    if ( TRUE == [ elementName isEqualToString: SHOWROOM ] )
    {
      Showroom* showroom = (Showroom*)[ _elementStack lastObject ];
      [ _showrooms addObject: showroom ];
      [ _elementStack removeLastObject ];
    }
    else if ( TRUE == [ elementName isEqualToString: DEALER ] ||
      TRUE == [ elementName isEqualToString: NAME ] ||
      TRUE == [ elementName isEqualToString: PHONE ] )
    {
      [ _elementStack removeLastObject ];
    }
  }
 
  - (NSMutableArray*) parseContentOfUrl: (NSURL*) url
  {
    NSXMLParser* xmlParser = [ [ NSXMLParser alloc ] 
      initWithContentsOfURL: url ];
    [ xmlParser setDelegate: self ];
 
    _showrooms = [ NSMutableArray arrayWithCapacity: 1024 ];
    _elementStack = [ NSMutableArray arrayWithCapacity: 1024 ];
 
    [ xmlParser parse ];
 
    return _showrooms;
  }
@end

NSXMLParser가 동작하기 위해서는 최소한 아래의 메소드들이 구현되어야 합니다.

didStart는 파서가 엘리먼트의 시작 태그를 만났을 때 호출됩니다. 비단말(nonterminal) 노드인 경우 엘리먼트 해당하는 모델 객체를 생성한 후 스택에 넣습니다. 단말(terminal) 노드인 경우 엘리먼트 이름으로 NSString 객체를 만들 후 스택에 넣습니다.

found는 파서가 엘리먼트를 제외한 스트링을 만날을 때 호출됩니다. 인자로 넘어온 스트링의 앞뒤 공백을 제거합니다. 유효한 스트링(이하 속성값)인 경우 스택에서 마지막으로 추가된 NSString 객체를 제거합니다. 제거된 NSString 객체의 값에 따라 적절한 부모 모델 객체의 속성(property)을 속성값으로 설정합니다. 만약 엘리먼트 이름이 NAME인 경우 중복되기 때문에 부모 모델 객체 클래스에 확인합니다.

didEnd는 파서가 엘리먼트의 종료 태그를 만났을 때 호출됩니다. 스택에서 모델 객체를 제거하고 SHOWROOM인 경우 _showrooms 배열 객체에 해당 객체를 추가합니다.

끝으로 콘솔 화면에서 결과를 확인할 수 있는 showrooms_xml_parser.m 파일은 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#import "ShowroomsXmlParser.h"
 
 
int main( int argc, char* argv[] )
{
  NSAutoreleasePool* pool = [ NSAutoreleasePool new ];
 
  ShowroomsXmlParser* xmlParser = [ ShowroomsXmlParser new ];
  NSURL* url = [ NSURL URLWithString: 
    @"http://www.yourserver.com/showrooms.xml" ];
  NSMutableArray* showrooms = [ xmlParser parseContentOfUrl: url ];
 
  for ( Showroom* showroom in showrooms )
  {
    Dealer* dealer = [ showroom dealer ];
    NSLog( @"%@ %@ (%@)", [ showroom name ], [ showroom phone ], 
      [ dealer name ] );
  }
 
  [ xmlParser release ];
  [ url release ];
 
  [ pool drain ];
  return 0;
}

해당 소스들을 컴파일 하기 위해서는 아래의 명령어를 실행합니다.

1
2
$ gcc -framework Foundation Dealer.m Showroom.m ShowroomsXmlParser.m showrooms_xml_parser.m
-o showrooms_xml_parser

실행 결과 화면은 아래와 같습니다.

1
2
3
$ ./showrooms_xml_parser
강남전시장 02-111-1111 (김태연)
삼성전시장 02-222-2222 (구하라)

프로그래밍에 도움되길 바랍니다~

수정 사항

엘리먼트 값이 없는 경우 에러가 발생하는 문제를 해결하기 위해서 showrooms_xml_parser.m의 found 메소드 안에 _elementStack의 마지막 요소 제거 코드를 didEnd로 옮겼습니다.

Post to Twitter Post to Delicious

, , , ,