안녕하세요, 정재원이라고 합니다.

C++ 관련 글을 올리고 계신 흥배님과 마찬가지로 게임 개발자입니다.

그간 여러분들이 올려주시는 좋은 글들을 읽고만 있다가...; 무언가 올리지 않으면 짤리겠다는 위기감에 소재거리를 뒤져보았습니다;;

흥배님과 가능한한 겹치지 않는 주제와 방식으로 무엇이 적당할까 생각하던 중... 본 동영상을 발견하고는, 그래 여기에 자막을 달아보자! 생각하게 되었습니다. 작년 PDC에서 발표된 Parallel Pattern Library에 대한 동영상입니다.

왠지 간단할듯 여겨졌으나... 처음 해보는 자막 작업 쉽지 않더군요 ㅠㅠ 도구를 찾고 익히는데 시간이 더 들었습니다.

한시간이 넘는 것을 한번에 번역해 올리려면, 날새겠다는 생각이 들더군요; 유투브 업로드가 10분 미만의 동영상으로 제한된다는 것에 안도감(?)을 느꼈습니다...

이를 변명삼아 일단 10분 분량을 올립니다. 포스트 수를 늘리기 위한 수작 아니냐 라는 비난의 소리가 들립니다만... 맞습니다 ㅠㅠ 툴에 좀 더 익숙해지면 자주라도 올리겠습니다.

부족한 번역과 분량에 대해 건설적 충고 부탁드리고요, 기타 제 포스트에 제안 사항 있으시면 댓글 달아주십시오.

 

지난 번 글에서 Visual Studio Team System 2010의 향상된 Unit Test 기능에 대해서 살펴 본 바가 있었습니다. 오늘 소개드릴 PEX는 Microsoft Research 그룹에서 개발한 Automated Whitebox Testing Framework 입니다. 꽤나 긴 정의입니다만, 간단히 말하자면 자동으로 코드를 분석해서 WhiteBox Unit Test를 만들어주는 Visual Studio Extension입니다.

Blackbox Test – Blackbox Test는 우리가 코드 내부에 대한 지식이 없다는 것을 전제로 합니다. Blackbox Test에서는 코드의 노출된 API를 소비하는 Consumer의 입장에서 Function을 테스트하게 됩니다. 비교적 초기에 작성되는 Unit Test들은 이 Blackbox Test가 됩니다. 아직 Code가 성숙되지 않은 상태에서 API의 Requirements, Specification 등만을 가지고 테스트를 작성하게 되기 때문입니다. 하지만 코드 개발이 점점 진행됨에 따라서 Unit Test는 Whitebox Test로 이행되게 됩니다.

Whitebox Test – Whitebox Test는 코드 내부를 알고 있다는 것을 전제로 하는 테스트입니다. 우리는 코드를 알기 때문에 모든 가능한 성공/실패 시나리오를 모두 알 수가 있고, 특정 데이터나 입력 조건이 어떤 Flow를 따르는지에 대해서 충분하게 알 수 있습니다. 이런 가능한 모든 시나리오에 대해서 Unit Test를 작성하고 수행하게 되면 그것을 Whitebox Test라고 말할 수가 있습니다.

* 이는 Blackbox Test, Whitebox Test에 대한 일반적인 정의가 아니라, Unit Test관점에서 바라본 정의입니다. 일반적인 정의에 대해서는 Wikipedia의
Whitebox Testing, Blackbox Testing 항목들을 참고하시기 바랍니다.

Unit Test를 수행할 때에 대개 Code Coverage Test를 같이 수행하는 이유가 바로 우리가 작성한 Unit Test가 어느 정도 수준의 Whitebox Testing에 도달했는지를 알 수가 있기 때문입니다. 당연히 우리가 작성한 모든 코드에 대해서 완벽한 Whitebox Testing을 Unit Test를 통해서 수행했다면 Code Coverage Test의 결과는 100%가 되어야 합니다. 하지만, 실제로 Unit Test건 Manual Test Case건 Code Coverage Test결과가 100%에 도달하기는 참으로 힘듭니다. 일반적으로 80%정도를 목표로 하지만, 그 이하가 되는 경우가 대부분입니다. 그것은 복잡성의 문제이기도 하고, 비용 대비 효과의 문제이기도 합니다. 코드의 모든 경로를 통과하는 Unit Test를 작성하려면 엄청난 시간이 소요될 테니까요.

이 점이 PEX라는 제품이 나오게 된 배경이라고 할 수 있습니다. PEX는 Visual Studio의 코드를 분석해서, Input과 output 조합을 찾아서 자동으로 테스트 코드를 만들어 줍니다. 자동으로 생성된 테스트 코드이지만, 높은 Coverage를 보여 줍니다. PEX를 사용하면, Unit Test 작성에 들이는 수고를 상당히 줄일 수 있을 것 같습니다.

 

PEX 홈페이지는 http://research.microsoft.com/en-us/projects/pex/default.aspx 이며, 현재 Visual Studio Team System 2008과 2010에서 사용할 수 있다고 되어 있습니다. 즉, 현재 VSTS 2008을 쓰고 계시는 개발자 분이라면 다운로드 받아서 써 보실 수 있다는 의미입니다. 현재 정식 버전은 아니고 Pre-release 버전입니다. 그리고 Visual Studio 2008 Professional 버전을 위한 비 상업용 Academic 버전도 다운로드 받을 수 있습니다. 다운로드 링크는 http://research.microsoft.com/en-us/projects/pex/downloads.aspx 입니다.

이번 글은 개요이니까, PEX Tutorial에 나오는 간단한 샘플만 잠시 보도록 하겠습니다.

   1:  [PexMethod]
   2:  public void ParameterizedTest(int i)
   3:  {
   4:       if (i == 123)
   5:           throw new ArgumentException("i");
   6:  }

Integer 타입의 매개 변수를 받는 간단한 메소드입니다. 매개 변수가 123일 때에는 ArgumentException을 일으키는 딱 두 줄의 구현만 있을 뿐입니다. PexMethod Attribute는 이 메소드가 PEX Test method라는 것을 의미합니다. 이 메소드에 대고 마우스 오른쪽 클릭을 하게 되면 컨텍스트 메뉴에 다음과 같이 PEX 관련 메뉴들이 보이게 됩니다.

여기서 Run Pex Explorations를 선택하게 되면, PEX가 자동으로 코드를 분석해서 가능한 모든 매개 변수를 대입해 본 다음 그 결과를 바탕으로 아래처럼 결과를 보여주게 됩니다.

이 부분 – Run Pex Exploration 실행 - 에서 저는 예상치 못했던 에러를 만났습니다.

[critical] unexpected failure during exploration
System.IO.FileLoadException: Could not load file or assembly 'Microsoft.Z3, Version=2.0.30325.1, Culture=neutral, PublicKeyToken=9c8d792caae602a2' or one of its dependencies.


위와 같은 에러였는데, 이 에러는 Visual C++을 설치하지 않았을 때에 발생하는 것으로 보입니다. 아직 PEX 설치 파일이 어떤 조건에서도 완벽하게 모든 필요한 파일들을 설치하지 못하는 것 같습니다. 이 에러는 Visual C++ 2008 SP1 Redistributable Package를 다운로드해서 설치하는 것으로 해결할 수 있습니다. 이 정보는 http://social.msdn.microsoft.com/Forums/en-US/pex/thread/5862d522-0c2e-481c-b537-864e7427a7e5 에서 얻었습니다. 혹시 Visual C++ 가 설치되지 않은 분들은 참고하시기 바랍니다.

예상대로 123이라는 매개변수가 입력되었을 때에 ArgumentException이 발생한 것을 볼 수가 있습니다. 그리고 여기서 또 다시 마우스 오른쪽 컨텍스트 메뉴를 통해서 Go To를 선택하게 되면 실제 자동으로 생성된 테스트 코드를 아래 그림처럼 볼 수가 있습니다.

   1:  [TestMethod]
   2:  [PexGeneratedBy(typeof(HelloWorldTest))]
   3:  public void ParameterizedTest01()
   4:  {
   5:      this.ParameterizedTest(0);
   6:  }
   7:   
   8:  [TestMethod]
   9:  [PexGeneratedBy(typeof(HelloWorldTest))]
  10:  [PexRaisedException(typeof(ArgumentException))]
  11:  public void ParameterizedTest02()
  12:  {
  13:       this.ParameterizedTest(123);
  14:  }

간단한 샘플이긴 하지만, PEX의 강력한 자동 테스트 코드 생성 기능을 볼 수 있으셨을 거라고 생각합니다. 다음 포스팅에서는 PEX에 대해서 더 깊이 들어가 보겠습니다. 아 물론, Code Analysis에 대한 글들도 계속 올릴 예정입니다. 읽어주셔서 감사합니다.

Welcome to F#(7) - 클리프 행어.

F# 2009. 4. 26. 18:22 Posted by 알 수 없는 사용자

-거인의 어깨에 위태롭게 매달려서 안간힘-_-.

여전히 함수형언어를, F#을 이해하기 위해서 몸부림 치고 있습니다. 제 짧은 생각인지 모르겠으나, F#을 처음배우는 프로그래밍언어로 생각하시는 분은 없을거라고 생각합니다. 호기심이든 다른 목적이 있어서든 기존에 주력으로 쓰는 명령형 언어가 있고 추가로 공부하려고 하시는 분이 거의 대부분이 아닐까 합니다.(게다가 F#은 C#이나 VB.NET을 하시던 분이 거의 대부분이 아닐까 싶네요) 저 역시 C#을 주력으로 쓰면서 F#을 공부하고 있는거니까요.

그래서 단순히 문법적 차이를 말씀드리기 보다는 함수형언어에 깔려있는 사상&접근법&관점의 차이에 대해서 말씀드리고자 하는데, 부족한 지식으로 글이 들쭉날쭉하고 앞내용과 뒷내용이 연결이 안되는, 한글이 생긴이래 진급을 못하고 있는 불상사도 자주 보게되는거 같습니다. 

오늘도 함수형언어를 이해하기 위한 노력의 일환으로 한 논문의 내용을 바탕으로 이야기 하도록 하겠습니다. Anders hejlsberg가 언어설계에 대한 이야기를 하면서 자신은 다른 거인의 어깨위에 서있다고 말을 했었는데, 저는 거인의 어깨에 선게 아니라 위태롭게 매달려 있다는 느낌이 드는군요.-_-;  아놔, 오늘도 서론이 길군요-_-;;

 

-무슨 논문이냐.

제가 한번 
"Why Functional Programming Matters?"라는 논문에 대해서 언급한적이 있었습니다. 오늘 포스트의 바탕이 될 이 논문에서 모듈화에 대한 이야기를 하면서 다음과 같은 이야기를 합니다. 

However, there is a very important point that is often missed. When writing a modular program to solve a problem, one first divides the problem into subproblems, then solves the sub-problems and combines the solutions. The ways in which one can divide up the original problem depend directly on the ways in which one can glue solutions together. Therefore, to increase ones ability to modularise a problem conceptually, one must provide new kinds of glue in the programming language. Complicated scope rules and provision for separate compilation only help with clerical details; they offer no new conceptual tools for decomposing problems. 

하지만, 그런 이야기에서 중요한 점이 무시되곤 한다. 어떤 문제를 해결하기 위해서 모듈화된 프로그램을 작성할때, 프로그래머는 우선 문제를 작은 문제로 분리해내야 하고, 각각의 작은문제를 해결해서 하나의 솔루션으로 조합해야 한다. 문제를 어떻게 분리할지는 전적으로 해결된 문제들을 어떻게 하나의 솔루션으로 조합할지에 달려있다. 그러므로 프로그래머가 조금더 능숙하게 문제를 모듈화해서 해결하게 해주려면, 프로그래밍언어에서 문제를 붙이기 위한 새로운 접착제를 제공해줘야 한다.  복잡한 스코프룰이나 분리컴파일을 좀 더 편리하게 할 수 있게 도와주는 것들은 그저 사무적인일(문제를 해결하는일 자체보다는 코드작성을 도와주는 부차적인일)정도를 도와줄 뿐이다. 즉, 문제를 잘개 쪼개기 위해선 아무 도움도 안되는 것이다.


그러면서 저자는 함수형언어에서 문제를 붙이기 위해 쓰이는 두가지 접착제를 소개합니다. 그 중 하나가 지난포스트에서도 언급했었던 higher-order function입니다. 기억나시나요? 함수를 인자로 받고 함수를 리턴가능한 함수가 higher-order function이었죠? 이 논문은 Miranda라는 언어를 사용하면서 Haskell을 쓰지 못해서 죄송하다고 언급하고 있습니다. 하지만, 조금 된 논문이라 F#으로 못해서 죄송하다는 말은 없군요. 크하하하-_-;;

그럼 저자가 higher-order function의 예로 언급한 reduce를 F#으로 구현해보면서 설명드리도록 하겠습니다. reduce는 MapReduce때문에 유명해지기도 했고, fold라는 이름으로도 불리는데요, 아마도 fold나 reduce나 둘다 뭔가를 하나씩 줄여나가는 이미지를 연상하면서 붙인 이름이 아닌가 싶습니다.


우선, 원문을 보실분들을 위해서 Miranda에 대해서 아주초큼 설명을 드리자면, Miranda는 ML에서 ML은 Lisp에서 출발한 언어라고 합니다. Lisp에서는 리스트를 Pair의 조합으로 표현하는데요, 여기서도 그런거 같습니다. Pair의 조합이란 cons(construct의 준말이라는 군요)라는 함수를 통해서 만들어지는데요(Lisp이나 Scheme하신분들은 많이 보셨을듯~) 인자를 두개 받아서 그 두 인자를 하나의 쌍으로 묶어서 리턴하는 함수입니다. 즉 Lisp에서 (1 2 3 4)라는 리스트는,

 

 (cons 1

(cons 2

(cons 3

(cons 4 nil)))) 


nil은 Lisp에서 빈값을 뜻합니다, (1 2)는 (cons 1 2), (1 2 3)은 (cons 1 (cons 2 3))의 결과 인거죠. F#이 OCaml에서, OCaml은 ML에서 출발했으니, F#에서도 리스트는 비슷하게 구현되지 않았을까 싶습니다.(List.hd, List.tl같은 함수를 보면 그런거 같습니다.) 그리고 두개의 값이 하나의 쌍으로 이루어 져있기 때문에, 값에서 head부분과 tail부분으로 나눠서 생각할 수 있습니다. (1 2 3)의 head는 1이고 tail은 (2 3)입니다. (4 5 6 7)의 head는 4이고 tail은 (5 6 7)인거죠

 

-그래 그래 얼른 reduce가 뭔지나 좀 보자.

일단, 숫자의 리스트를 모두더하는 sum을 생각해보도록 하죠. 빈값에 대한 sum은 0이겠죠? 그래서 아래와 같이 정의합니다. 

sum [] = 0 

그리고 리스트의 합은 head의 값과 나머지 tail의 합으로 정의할 수 있고, 그 tail의 합은 다시 head의 값과 나머지인 tail의 합으로 정의할 수 있습니다. 즉, 

sum list = List.hd(list) + sum List.tl(list) 

이렇게 정의할 수 있습니다. List.hd는 head의 약자로 List에서 head를 리턴하고, tl은 tail의 약자로 List에서 head를 제외한 나머지 부분을 리턴합니다. 이런 과정을 어떤 함수가 들어와도 가능하게 일반화 하려면, 위에서 빨간색(?)으로 음영표시한 두부분이 중요해 집니다. 즉, '리스트의 마지막에 도달했을때 어떤 값을 리턴할 것인가'와 '리스트의 head와 tail에 대해 어떤 함수를 수행할 것인가'하는 부분입니다. 예를 들면, 리스트에 대해서 곱셈을 수행하는데, 빈리스트에 대해서 0을 리턴하게 한다면 리스트의 곱셈값은 0이 되겠죠-_-;; 그래서 이럴땐 빈리스트의 값을 곱셈에 영향을 안주는 1로 설정해야 합니다. 물론, 위의 경우에서는 합에 영향을 주지 않는 0으로 선택한 것이구요. 

그럼, 각 리스트의 요소에 대해 수행할 함수를 아래와 같이 정의합니다. 

let add x y = x + y 

그리고 다음과 같이 reduce함수를 작성해줍니다. 

 let rec reduce f x list =
    if list = [] then x

    else f (List.hd list) ((reduce f x) (List.tl list)) 

즉, reduce는 자기자신의 정의속에 자신을 호출하는 부분이 있는 제귀함수이고, 인자로는 f, x, list를 받으며 list가 빈리스트라면 연산에 영향을 주지않는 값으로 x를 리턴하고, 빈 리스트가 아니라면 head와 나머지리스트에 함수를 f를 적용한 값을 가지고 f를 수행합니다. 위의 메서드를 좀 더 읽기 쉽게하기 위해서 타입을 명시해주면 아래과 같습니다.

let rec reduce (f : 'a -> 'b) (x : 'c) (list : 'd list) =
    if list = [] then x

    else f (List.hd list) ((reduce f x) (List.tl list))

즉, f는 함수이고, x는 제네릭한 값하나, list는 제네릭한 리스트인거죠. 일단, 수행결과를 보고 무슨 소리인지 더 풀어보도록 하죠. 위 두 함수의 정의를 평가해주고나서, reduce add 0 [1;2;3;4] 이렇게 실행을 하면 아래와 같은 결과가 나옵니다. 

val add : int -> int -> int

>

val reduce : ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b 

> reduce add 0 [1;2;3;4];;
val it : int = 10 

리스트의 값을 모두 더해서 10이라는 값이 나왔군요. 이 값이 어떻게 나왔는지 reduce가 정의를 확장하고 줄여나가는 모습을 자세히 적어보면 아래와 같습니다.

 

즉,
add x y = x + y이고
(reduce add 0) [1;2;3;4] 라면..
= add 1 ((reduce add 0) [2;3;4])
= add 1 (add 2 ((reduce add 0) [3;4]))
= add 1 (add 2 (add 3 ((reduce add 0) [4])))
= add 1 (add 2 (add 3 (add 4 ((reduce add 0) []))))
= add 1 (add 2 (add 3 (add 4 0)))
= add 1 (add 2 (add 3 4))
= add 1 (add 2 7)
= add 1 9
= 10

음영으로 강조한 부분이 reduce가 확장되는걸 보기 쉽게 만든 부분입니다. 자신의 정의를 이용해서 빈리스트가 나올때까지 값을 확장하고, 하나씩 줄여가면서 값을 게산해 가는거죠. 사실 add는 이미 F#에 구현되어 있습니다. 아래와 같이 쓸 수도 있습니다. 

reduce (+) 0 [1;2;3;4] 

그리고 람다를 이용해서 아래와 같이 써도 똑같은 결과를 낼 수 있습니다. 

reduce (fun x y -> x + y) 0 [1;2;3;4] 

그리고 reduce를 이용해서 아래와 같은 연산들도 가능합니다. 

reduce (&&) true [true; true; true; false; true] //전부다 true인지 검사하는 함수
let append a b = reduce (fun x y -> List.Cons(x, y)) b a //두 리스트를 붙이는 함수 

그리고 아래는 append의 실행결과입니다.

> append [1;2] [3;4];;

val it : int list = [1; 2; 3; 4] 

List.Cons는 위에서 말씀드렸던 cons의 F#의 List버전입니다. 위 메서드를 확장하고 줄여가나는 모습을 자세히 그려보면 아래와 같습니다.

List.Cons(1, reduce (fun x y -> List.Cons(x, y)) [3;4] [2])

= List.Cons(1, (List.Cons (2, reduce (fun x y -> List.Cons(x, y)) [3;4] [])))

= List.Cons(1, (List.Cons (2, [3;4])))

= List.Cons(1, [2;3;4])

= [1;2;3;4]

 

즉, 리스트에 대해서 특정함수의 수행을 누적시켜서 결과를 얻는(Aggregate) 과정을 쪼개고 그 쪼갠 부분을 붙이는 방법으로 higher-order function을 사용해서 만든 reduce로 이렇게 다양한 연산이 가능한 것입니다. 물론 논문의 저자는 여기서 더 나아가고 있습니다만, 제 실력부족으로 더 이상 나가지 못하는 점 양해바랍니다.-_-;;; 아, 그리고 추가로 말씀드리자면 reduce는 F#에 이미 구현되어 있습니다. reduce가 fold라고 불리기도 한다고 위에서 말씀드렸었죠? 아래와 같이 하면 reduce add 0 [1;2;3;4]와 같은 결과를 낼 수 있습니다. 

List.fold_right (+) [1;2;3;4] 0

List.fold_left (+) 0 [1;2;3;4] 

둘의 차이점은 아래와 같습니다. 

(출처 : http://en.wikipedia.org/wiki/Fold_(higher-order_function))


아래그림은 fold_left이고, 윗그림이 fold_right입니다. 그리고
각각 그림의 왼쪽은 리스트이고, 오른쪽은 어떻게 펼치느냐에 대한 도식입니다. 즉, 왼쪽으로 펼쳐서 접느냐, 오른쪽으로 펼쳐서 접느냐에 따라 틀린거지요. 위에서 구현된 reduce는 fold_right라고 보시면 됩니다.

 

- 마치면서

사실, 진정한 함수형언어의 강점과 F#의 강점은 아직 맛도 못봤습니다. 이거 빨리 제가 맛을 보고 잘 전해드려야 할텐데;; 개인적인 사정이 점점 안좋아지는군요-_-. Anders hejlsberg의 인터뷰에서 발췌한 글을 끝으로 남기면서 글을 마치겠습니다.(왜 Anders hejlsberg이야기가 많이 나오냐면, 전  Anders hejlsberg빠돌이거든요-_-;) 이 글을 읽다보니, 왜 함수형언어의 힘을 빌린 LINQ를 추가해서 데이터를 다루는 부분을 추상화 했는지 확 다가오는 군요. 그리고 앤더스가 선언형 언어와 함수형언어의 장점을 점점 더 중요하게 생각하고 있다는 생각이 듭니다.(물론, 원문을 읽어보시면 더 자세한 내용을 보실수 있겠져~.) 그리고 언제나 그렇듯이 제 맘대로 번역-_-.

 

I think one of the things that are true of most programming languages today is that they force you to overspecify the solution to your problem. You're down there writing nested for loops and if statements and whatever, and really all you wanted to do was a join between two pieces of data. But there's nothing that allows you to say that. You have to get down and dirty and do hash tables and dictionaries, blah, blah, blah.

난 요즘 대부분의 프로그래밍언어가 프로그래머에게 문제를 해결하기 위한 솔루션을 너무 필요이상으로 서술하게 만든다고 생각합니다. 물론 사실이구요. 프로그래머는 그저 두 데이터집합을 조인하고 싶을 뿐인데, for루프랑 if문을 여러개 중첩시키고 어쩌고 저쩌고 해야만 하는거죠. 즉, 그냥 조인하게 해달라고만 말할 수 있는게 없다는 겁니다. 진흙탕으로 내려가서 해시테이블이랑 딕셔너리랑 기타등등, 뭐 그런것들이랑 열심히 뒹굴어야 하는거죠.



-참고자료

1. Expert F#, Don Syme, Adam Granicz, Antonio Cisternino, APRESS
2. Programming Language Pragmatics 2nd Edition, Michael L. Scott, Morgan Kaufmann Publishers
3. Structure and Interpretation of Computer Programs, 2nd Edition, Harold Abelson, Gerald Jay Sussman, Julie Sussman, The MIT Press
4. 구글을 지탱하는 기술, 니시다 케이스케, 멘토르
5. http://en.wikipedia.org/wiki/Fold_(higher-order_function)
6. http://broadcast.oreilly.com/2009/04/an-interview-with-anders-hejls-1.html
7. http://en.wikipedia.org/wiki/Miranda_(programming_language
8. http://www.math.chalmers.se/~rjmh/Papers/whyfp.pdf

WPF Features Preview (3) - Styling the DataGrid

WPF 2009. 4. 23. 03:55 Posted by 알 수 없는 사용자
WPF Features Preview (1) - DataGrid
WPF Features Preview (2) - DatePicker 

맨 처음 초기 어플리케이션을 보면 스타일이 적용되어 멋지게 보입니다. DataGrid를 적용하여 수정된 버전은 좀 더 기능적이긴 하지만 밋밋하고 보기엔 좀...
이번에는 DataGrid에 스타일을 주어서 컬러풀하고 비주얼적으로 멋지게 만들어 보도록 하겠습니다.

프로젝트를 열고 MainWindow.xaml 파일을 열어서 DockPanel 부분을 보면 뭔가 스타일이 적용된것을 볼 수 있습니다. 이 중에서 몇가지 brushGridViewColumnHeaderListViewItem 스타일을 DataGrid 스타일에 적용해 보도록 하겠습니다.

DataGrid의 스타일 프로퍼티
1. CellStyle - 각각의 cell에 사용되는 스타일 (DataGridCell)
2. RowStyle - row에 사용되는 스타일 (DataGridRow)
3. ColumnHeaderStyle - header bar에 사용되는 스타일 (DataGridColumnHeader)

사용자가 grid와 상호작용하는 것에 대한 프로퍼티
1. SelectionMode - 단일선택과 확장된 선택에 대한 모드
2. SelectionUnit - 무엇이 선택되는가에 대한 설정 (FullRow vs Cell vs CellOrRowHeader)
3. GridLinesVisibility - cell 주변의 라인에 대한 속성
4. VerticalGridLinesBrush - 수직 라인에 대한 색상
5. HorizontalGridLinesBrush - 수평 라인에 대한 색상

먼저 TargetType으로 스타일이 적용될 타입에 대해 설정해줍니다.
TargetType 값에 GridViewColumnHeaderdg:DataGridColumnHeader로 바꿔줍니다.
그리고 ListViewItemdg:DataGridRow로 바꿔줍니다.
변경된 xaml 코드는 아래와 같습니다.

   <Style x:Key="dgHeaderStyle" TargetType="dg:DataGridColumnHeader">

      <Setter Property="Background" Value="{StaticResource dgHeaderBrush}" />

      <Setter Property="Foreground" Value="White" />

      <Setter Property="BorderBrush" Value="{StaticResource dgHeaderBorderBrush}" />

   </Style>

   <Style x:Key="dgRowStyle" TargetType="dg:DataGridRow">

      <Setter Property="SnapsToDevicePixels" Value="True" />

      <Setter Property="Background" Value="White" />

      <Style.Triggers>

         <Trigger Property="ItemsControl.AlternationIndex" Value="1">

            <Setter Property="Background" Value="#FFD0D0E0" />

         </Trigger>

         <Trigger Property="IsSelected" Value="True">

            <Setter Property="Background" Value="LightGoldenrodYellow" />

         </Trigger>

      </Style.Triggers>

   </Style>

DataGrid가 정의된 곳에는 ColumnHeaderStyleRowStyle 속성을 설정해줍니다.

        <dg:DataGrid x:Name="dg" ItemsSource="{Binding}" Margin="10"

                  AutoGenerateColumns="False"

                  Background="#80909090" AlternationCount="2"

                  ColumnHeaderStyle="{StaticResource dgHeaderStyle}"

                  RowStyle="{StaticResource dgRowStyle}">

이제 어플리케이션을 실행해보면 스타일이 적용되어 이전과 다른 모습을 볼 수 있습니다.

이제 몇가지 grid에 대한 속성을 주도록 하겠습니다.
1. Extended 선택 모드 적용
2. SelecionUnitFullRow로 설정
3. GridLinesVisibilityAll로 설정
4. VerticalGridLinesBrushDarkGray로 설정

        <dg:DataGrid x:Name="dg" ItemsSource="{Binding}" Margin="10"

                  AutoGenerateColumns="False"

                  Background="#80909090" AlternationCount="2"

                  ColumnHeaderStyle="{StaticResource dgHeaderStyle}"

                  RowStyle="{StaticResource dgRowStyle}"

                  SelectionMode="Extended"

                  SelectionUnit="FullRow"

                  GridLinesVisibility="All"

                  VerticalGridLinesBrush="DarkGray">

또 몇가지 설정을 해서 header 스타일을 주도록 해보겠습니다.
1. BorderTickness1로 주어서 border를 볼 수 있도록
2. pixel snapping을 활성화 (SnapsToDevicePixels="True")
3. 컨텐트 horizontal 정렬 (HorizontalContentAlignment="Center")
4. MinWidth="0" MinHeight="30" 으로 설정
5. 기본 커서 모양을 Hand로 설정

  <Style x:Key="dgHeaderStyle" TargetType="dg:DataGridColumnHeader">

     <Setter Property="Background" Value="{StaticResource dgHeaderBrush}" />

     <Setter Property="Foreground" Value="White" />

     <Setter Property="BorderBrush" Value="{StaticResource dgHeaderBorderBrush}" />

     <Setter Property="BorderThickness" Value="1" />

     <Setter Property="SnapsToDevicePixels" Value="True" />

     <Setter Property="HorizontalContentAlignment" Value="Center" />

     <Setter Property="MinWidth" Value="0" />

     <Setter Property="MinHeight" Value="30" />

     <Setter Property="Cursor" Value="Hand" />

  </Style>

다시 컴파일해서 실행해보면 좀 더 직관적으로 보기 쉽게 스타일 적용이 된 것을 볼 수 있습니다.


다음으로는 Cell 자체에 대한 스타일 적용을 해보겠습니다. Cell의 컨텐트가 가운데로 오고 필요할 때 포커스가 적용되면 좀 더 편리할 것 같은데 이것을 해보도록 하겠습니다.



새로운 스타일 속성을 DockPanel.Resources에 추가하도록 합니다.
1. TargetTypedg:DataGridCell로 설정
2. 중요한 속성은 기본 DataGridCell을 따르기 위해 BasedOn 프로퍼티 설정
   "{StaticResource{x:Type dg:DataGridCell}}"

3. SnapsToDevicePixels"True"로 설정
4. VerticalAlignment"Center"로 설정
5. Trigger 컬렉션 추가 - 셀이 선택되었을 때 background/foreground 색상 변경
6. <Style.Triggers> 부분에 IsSelected 프로퍼티를 True로 설정하고
    a. BackgroundTransparent
    b. BorderBrushTransparent
    c. ForegroundBlack으로
7. 두번째 trigger를 추가하고 IsKeyboardFocusWithin="True" 프로퍼티에
    a. Background"{StaticResource whiteBackBrush}" 리소스
    b. BorderBrush"{DynamicResource{x:Static dg:DataGrid.FocusBorderBrushKey}}" 리소스
    c. ForegroundBlack

    <Style x:Key="dgCellStyle" TargetType="dg:DataGridCell"

           BasedOn="{StaticResource {x:Type dg:DataGridCell}}">

       <Setter Property="SnapsToDevicePixels" Value="True" />

       <Setter Property="VerticalAlignment" Value="Center" />

       <Style.Triggers>

          <Trigger Property="IsSelected" Value="True">

             <Setter Property="Background" Value="Transparent" />

             <Setter Property="BorderBrush" Value="Transparent" />

             <Setter Property="Foreground" Value="Black" />

          </Trigger>

          <Trigger Property="IsKeyboardFocusWithin" Value="True">

             <Setter Property="Background" Value="{StaticResource whiteBackBrush}" />

             <Setter Property="BorderBrush"

                  Value="{DynamicResource {x:Static dg:DataGrid.FocusBorderBrushKey}}" />

             <Setter Property="Foreground" Value="Black" />

          </Trigger>

       </Style.Triggers>

    </Style>

그리고 DataGrid 속성에 CellStyle 프로퍼티값을 줍니다.

        <dg:DataGrid x:Name="dg" ItemsSource="{Binding}" Margin="10"

                  AutoGenerateColumns="False"

                  Background="#80909090" AlternationCount="2"

                  ColumnHeaderStyle="{StaticResource dgHeaderStyle}"

                  RowStyle="{StaticResource dgRowStyle}"

                  CellStyle="{StaticResource dgCellStyle}"

                  SelectionMode="Extended"

                  SelectionUnit="FullRow"

                  GridLinesVisibility="All"

                  VerticalGridLinesBrush="DarkGray">

이제 Cell을 클릭해보면 색이 적용되어 하이라이트 되는것을 볼 수 있습니다.

마지막으로 RowDetail 속성을 설정하도록 하겠는데 위에서 했던 방법과 비슷하니 자세한 설명은 생략하겠습니다. xaml 코드를 보면 어렵지 않게 이해 할 수 있을 것입니다.

    <dg:DataGrid x:Name="dg" ItemsSource="{Binding}" Margin="10" ...

        VerticalGridLinesBrush="DarkGray"

        RowDetailsVisibilityMode="VisibleWhenSelected">

 

            <dg:DataGrid.RowDetailsTemplate>

                <DataTemplate>

                    <StackPanel Orientation="Horizontal" Margin="20,0,0,0">

                        <TextBlock />

                        <TextBox />

                    </StackPanel>

                </DataTemplate>

            </dg:DataGrid.RowDetailsTemplate>

TextBlock 속성

       <StackPanel Orientation="Horizontal" Margin="20,0,0,0">

          <TextBlock Text="Category:" VerticalAlignment="Center" FontWeight="Bold" />

          <TextBox />

TextBox 생성

      <dg:DataGrid.RowDetailsTemplate>

         <DataTemplate>

            <StackPanel Orientation="Horizontal" Margin="20,0,0,0">

               <TextBlock Text="Category:" VerticalAlignment="Center" FontWeight="Bold" />

               <TextBox Text="{Binding Category}" Margin="10,5" MinWidth="100">

                  <TextBox.Style>

                     <Style TargetType="TextBox">

                        <Setter Property="BorderBrush" Value="{x:Null}" />

                        <Setter Property="Background" Value="{x:Null}" />

                        <Style.Triggers>

                           <Trigger Property="IsFocused" Value="True">

                              <Setter Property="BorderBrush"

                                      Value="{x:Static SystemColors.WindowFrameBrush}" />

                              <Setter Property="Background"

                                      Value="{x:Static SystemColors.WindowBrush}" />

                           </Trigger>

                           <Trigger Property="IsMouseOver" Value="True">

                              <Setter Property="BorderBrush"

                                      Value="{x:Static SystemColors.WindowFrameBrush}" />

                              <Setter Property="Background"

                                      Value="{x:Static SystemColors.WindowBrush}" />

                           </Trigger>

                        </Style.Triggers>

                     </Style>

                  </TextBox.Style>

               </TextBox>

            </StackPanel>

         </DataTemplate>

            </dg:DataGrid.RowDetailsTemplate>

이제 모든 스타일 적용이 끝났습니다. 실행을 해보면 위에서 적용한 스타일이 적용되어 처음의 DataGrid보다 훨씬 보기도 좋고 스타일이 좋아진것을 알 수 있습니다.



지금까지 DataGrid에 대해 알아보았는데 다음에는 역시 새롭게 추가된 Ribbon 컨트롤에 대해 알아보겠습니다.
WPF에서도 기존의 메뉴와 툴바를 벗어나서 새로운 Ribbon 스타일을 적용할 수 있는데 이것 역시 어렵지 않게 할 수 있으니 기대해주세요.

'WPF' 카테고리의 다른 글

WPF 리본 컨트롤 RTW 출시  (4) 2010.08.05
WPF Features Preview (2) – DatePicker  (1) 2009.04.22
WPF Features Preview (1) – DataGrid  (1) 2009.04.17
WPF 4의 향상된 기능과 Windows 7 지원  (0) 2009.04.09

WPF Features Preview (2) – DatePicker

WPF 2009. 4. 22. 16:50 Posted by 알 수 없는 사용자

WPF Features Preview (1) - DataGrid

저번 시간에는 WPFToolkit을 이용해서 ListView를 DataGrid로 바꿔보았습니다. 훨씬 간편하고 쉽게 데이터를 보여줄 수 있다는것을 알았는데요 이번에는 WPFToolkit에 포함된
(WPF 4.0에는 기본으로 포함) 다른 컨트롤들을 이용해서 좀 더 DataGrid를 강화해 보겠습니다.

XAML 파일을 열고 DataGrid 아래에 보면 DataGridTextColumn 부분에 날짜가 표현되는 것을 볼 수 있습니다. 이 부분을 헤더는 그대로 두고 MinWidth 속성에 100으로 고정값을 주도록 하겠습니다.
그리고 template column에서 CellEditingTemplate을 생성합니다.

이제부터 중요한 작업을 하겠는데 기존에 그냥 날짜가 표시되는것을 DatePicker 컨트롤을 이용해서 표현해보겠습니다.
DataTemplate을 생성 한 후 WPFToolkitDatePicker 컨트롤을 추가해줍니다. DataGrid와 같은 네임스페이스에 있으니 따로 추가적인 작업은 필요하지 않습니다.
SelectedDate 속성에서 Date 프로퍼티를 바인딩해주고 SelectedDateFormat 프로퍼티에서 Short 값을 주어 포맷을 정합니다.
CellTemplate 프로퍼티에서는 TextBlock을 이용해서 Date 프로퍼티를 바인딩해주고 StringFormat에 d 값을 주어서 DateTime 포맷으로 해줍니다.
이렇게 해서 수정된 XAML은 아래와 같습니다.

  <!--<dg:DataGridTextColumn Header="Date" Binding="{Binding Date, StringFormat=d}" />-->

  <dg:DataGridTemplateColumn Header="Date" MinWidth="100">

     <dg:DataGridTemplateColumn.CellEditingTemplate>

        <DataTemplate>

           <dg:DatePicker SelectedDate="{Binding Date}" SelectedDateFormat="Short" />

        </DataTemplate>

     </dg:DataGridTemplateColumn.CellEditingTemplate>

     <dg:DataGridTemplateColumn.CellTemplate>

        <DataTemplate>

           <TextBlock Text="{Binding Date, StringFormat=d}" />

        </DataTemplate>

     </dg:DataGridTemplateColumn.CellTemplate>

  </dg:DataGridTemplateColumn>

이제 어플리케이션을 실행해보면 새로운 DatePicker 컨트롤이 나오는것을 볼 수 있고 달력을 클릭해서 값을 수정할 수 있는 모드가 나옵니다.

간단하게 새로운 컨트롤을 이용해서 좀 더 직관적인 데이터 표현을 할 수 있는것을 보았습니다. 컨트롤 사용법과 데이터 바인딩 방법만 익히면 다른 부분에도 쉽게 적용할 수 있을것입니다.
다음에는 DataGridStyle 속성을 이용하여 기본적인 DataGrid를 좀 더 보기 좋게 만드는 방법을 알아보도록 하겠습니다.


[MEF] 9. Recomposition

Managed Extensibility Framework 2009. 4. 19. 22:47 Posted by POWERUMC

Recomposition

 

이전 포스트의 MEF 의 특징 중에 MEF 의 플러그인 모델(Plugin Model) 은 교체가 용이하다고 하였습니다. Composable Part 는 구성 요소로써 고유의 기능을 구현합니다. 그리고 MEF 는 각각의 Composable Part 를 조립하여 다양한 컴포넌트 또는 애플리케이션을 완성합니다.

 

어떠한 경우에는 특정한 구성 구성요소를 사용하다가 그것이 필요 없어질 경우 구성 요소를 언로드(Unload) 하거나 다른 구성요소로 교체할 필요가 있습니다.

 

MEF 의 이러한 유연함의 예를 들어보죠.

 

예를 들어, 어플리케이션의 로그(Log) 기능을 생각해볼 수 있습니다. 만약 어플리케이션이 동작하는 환경이 인터넷에 연결되지 않는 환경이라면 사용자의 컴퓨터 로컬에 로그를 기록하다가, 인터넷에 연결될 경우 외부 데이터베이스로 로그를 기록하는 시나리오를 가정할 수 있습니다. 그리고 이러한 기능 또는 요구 사항이 언제 변경될지도 모르는 일입니다.

 

일단 위의 시나리오를 구현하기 여러 가지 고려해야 할 사항이 있고, 기능 또는 요구 사항이 변경될 경우도 고려해야 합니다. 결국, MEF 는 이러한 변화에 굉장히 유연하게 대처할 수 있습니다.

 

MEF 에서 ImportAttribute AllowRecomposition 프로퍼티를 통해 구성 요소를 런타임(Runtime) 시 동적(Dynamic) 으로 교체할 수 있도록 합니다.

 

아래는 위의 예로 든 시나리오를 구현하기 위해 작성한 간단한 소스 코드 입니다.

 

ILogger 인터페이스

public interface ILogger

{

        void Write();

}

 

ILogger 인터페이스는 Write 메서드를 통해 로그를 기록할 수 있는 예입니다.

 

 

TextLogger 클래스

[Export(typeof(ILogger))]

public class TextLogger : ILogger

{

        public void Write()

        {

               Console.WriteLine("Logged TextLogger");

        }

}

 

 

DatabaseLogger 클래스

[Export(typeof(ILogger))]

public class DatabaseLogger : ILogger

{

        public void Write()

        {

               Console.WriteLine("Logged DatabaseLogger");

        }

}

 

 

LoggerContext 클래스

[Export]

public class LoggerContext

{

        [Import(AllowRecomposition=true)]

        public ILogger Context { get; set; }

 

        [ImportingConstructor]

        public LoggerContext(ILogger sender)

        {

        }

}

 

여기에서 ImportAttribute AllowRecomposition 프로퍼티의 값을 true 로 지정해 주었습니다. AllowRecomposition 프로퍼티가 true 일 경우 Imported 된 구성 요소를 동적(Dynamic)하게 교체할 수 있도록 합니다.

 

 

Main 어플리케이션

class Program

{

        static void Main(string[] args)

        {

               Program p = new Program();

               p.Run();

        }

 

        void Run()

        {

               var catalog    = new AggregateCatalog(new TypeCatalog(typeof(LoggerContext)));

 

               var container = new CompositionContainer(catalog);

                var batch = new CompositionBatch();

               var defaultPart = batch.AddPart(new TextLogger());

               container.Compose(batch);

 

               var obj = container.GetExportedObject<LoggerContext>();

 

               obj.Context.Write();

              

               batch = new CompositionBatch();

               batch.RemovePart(defaultPart);

               batch.AddPart(new DatabaseLogger());

               container.Compose(batch);

 

               obj.Context.Write();

        }

}

 

이 코드는 처음에 TextLogger 를 사용하다가, 필요 없어진 TextLogger 를 제거하고 DatabaseLogger 로 교체하는 코드입니다.

 

아래는 위의 소스 코드를 실행한 결과입니다.

 

[그림1] 소스 코드 실행 결과

 

 

Wow! Recomposition

 

사실 이러한 것이 기존에는 기능의 정의 또는 요구 사항에 따라 직접 구현할 수 있었지만, MEF 의 특징인 플러그인 모델(Plugin Model) 은 이러한 고민에 대해 좋은 방법을 제공해 줍니다. 개발자는 자신이 구현해야 할 기능에 더 충실하고, 비즈니스 로직에 대한 고민만을 하면 됩니다. 그리고 정책에 따라 그것을 집행하는 결정권을 가진 자는 구현된 구성 요소를 조립하고 교체하여 기존의 정적인(Static) 어플리케이션에게 동적인(Dynamic) 유연함을 제공합니다.

 

기존에 정적인 어플리케이션은 변화에 따라 유지 보수를 위해 지속적인 많은 리소스가 필요하였지만, 플러그인 모델(Plugin Model) 의 동적인 어플리케이션은 그러한 변화에 능동적으로 대처할 수 있는 큰 기쁨을 줄 수 있을 것입니다.

'Managed Extensibility Framework' 카테고리의 다른 글

MEF Preview 6 공개  (0) 2009.07.20
[MEF] 10. Querying the CompositionContainer  (0) 2009.05.18
[MEF] 8. Strongly Typed Metadata  (0) 2009.04.16
[MEF] 7. Exports and Metadata  (0) 2009.04.16
[MEF] 6. Lazy Exports  (0) 2009.04.13

지난 번 포스팅에서 말씀 드렸던 것처럼 Visual Studio Team System 2010에서 이루어진 Code Analysis 기능 개선에 대해서 더 깊게 다뤄 보겠습니다. 그 첫 번째로 새롭게 추가된 Rule Sets 개념에 대해서 알아보도록 하겠습니다.


Visual Studio 2005, 2008에서의 코드 분석 설정

Visual Studio의 코드 분석 시스템에는 약 200개 정도의 규칙이 있습니다. 하지만, 사실 이 규칙들을 모두 준수를 해야 하는 것은 아닙니다. 모든 규칙이 나름의 이유와 정당성을 갖고 있다고 해도, 프로젝트의 성격에 따라, 모듈의 성격에 따라서 꼭 지켜야 할 규칙, 그렇지 않은 규칙, 심지어는 어쩔 수 없이 위반해야 하는 규칙도 있을 수 있습니다.

그래서 코드 리뷰를 할 때, 대부분의 경우 모든 규칙을 다 검사하지는 않습니다. 각각 케이스 바이 케이스로 규칙을 적절하게 조절하게 됩니다.

Visual Studio Team System 2005와 2008 버전에서는, 이런 설정을 Visual Studio Project Configuration에서 각 프로젝트 별로 할 수가 있습니다.

이 규칙 설정은 프로젝트 파일(.vbproj 혹은 .csproj)에 CodeAnalysisRules라는 Property 이름으로 아래와 같이 저장되게 됩니다. 내용을 보면 아시겠지만, “-“ 기호와 규칙 ID를 붙여 놓은 모양으로 되어있습니다. 즉, 거기에 열거된 규칙 ID를 Analysis Rules에서 빼는 형태로 저장이 되는 것입니다. 아래 샘플은 그래서 이 프로젝트는 코드 리뷰를 할 때에 Design Rule의 CA1000번 규칙을 검사하지 않는다라는 의미가 됩니다.

<CodeAnalysisRules>-Microsoft.Design#CA1000</CodeAnalysisRules>

이런 형태로 Visual Studio 프로젝트마다 다른 규칙을 적용할 수 있다는 것은 대단히 유연한 디자인이긴 하지만, 많은 수의 Visual Studio Project가 존재한다면, 관리가 참으로 어렵습니다. 그래서 예전에 Visual Studio Team System 2005 를 사용했던 프로젝트에서 전체 Visual Studio 프로젝트의 분석 규칙들을 통일하기 위해서, 개별 개발자들에게 위임하지 않고 제가 직접 모든 프로젝트를 텍스트 에디터로 열어서 저 부분만 Copy & Paste로 넣어서 Check-In하는 엄청난 단순 반복 작업을 한 적도 있습니다.

 

Visual Studio Team System 2010의 Rule Sets Feature

그래서 새롭게 출시될 Visual Studio Team System 2010에서는 Rule Sets라는 개념이 도입되었습니다. 이 Rule Sets 기능은 이름에서도 알 수 있듯이 Rule들을 미리 정해진 Set으로 관리할 수 있는 기능입니다. 이제 Visual Studio 프로젝트 별로 Rule을 하나 하나 빼는 수고를 할 필요가 없이 Visual Studio 프로젝트에 Rule Set을 지정하는 것으로 Code analysis를 커스터마이징할 수 있게 된 것입니다. 아래 그림에서 볼 수 있듯이, 하나 이상의 Rule Set을 선택하는 것도 가능합니다. (가장 아래의 Choose Multiple Rule sets를 선택하면 됩니다.)


Visual Studio Solution 전체에 Rule Set을 적용하는 것도 가능합니다. Solution Property에서 아래와 같이 설정할 수 있습니다.

 

위 그림에서 보시는 것처럼 마이크로소프트는 기본적으로 7개의 기본 Rule Set을 제공합니다. 이 기본 Rule Set의 목록은 다음과 같습니다.

  • Microsoft All Rules – 전체 Rule이 모두 포함된 Rule Set입니다.
  • Microsoft Basic Correctness Rules – 로직 에러와 자주 저지르는 실수를 예방하는 규칙들로 이루어진 Rule Set입니다.
  • Microsoft Basic Design Guideline Rules – Framework API 작성 상의 Best Practice에 집중된 Rule Set입니다.
  • Microsoft Extended Correctness Rules – Basic Correctness Rules를 확장하여, COM Interop이나 Mobile Application에도 사용 가능하도록 만들어진 Rule Set입니다.
  • Microsoft Extended Design Guideline Rules – Basic Design Guideline Rules를 확장해서, 사용성이나 유지 보수성도 체크할 수 있도록 만들어진 Rule Set입니다.
  • Microsoft Globalization Rules – Application의 Globalization에 있어서 문제가 있는지 검증하는데 집중된 Rule Set입니다.
  • Microsoft Minimum Recommended Rules - MS가 제안하는 최소한의 Rule Set입니다. 50개 정도의 규칙으로 이루어져 있습니다. 다른 대부분의 Rule Set들이 이 Rule Set을 포함하고 있습니다.
  • Microsoft Security Rules – 이름 그대로 보안에 집중된 Rule Set으로, 모든 Security 관련 규칙들이 모두 포함되어 있습니다.

 

물론 이 외에도 사용자가 직접 Rule Set을 편집할 수 있습니다. Rule Set은 .ruleset이라는 확장자를 가지는 XML 파일 포맷으로 되어 있습니다. 기본적으로 Visual Studio 2010에서 아래와 같은 UI를 통해서 편집할 수 있도록 되어 있습니다.


 

아래는 Rule Set 파일을 직접 Notepad로 열어본 것입니다.

<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Microsoft Basic Correctness Rules" Description="These rules focus on logic errors and common mistakes made in the usage of framework APIs. Include this rule set to expand on the list of warnings reported by the minimum recommended rules." ToolsVersion="10.0">
  <Localization ResourceAssembly="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.dll" ResourceBaseName="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.Localized">
    <Name Resource="BasicCorrectnessRules_Name" />
    <Description Resource="BasicCorrectnessRules_Description" />
  </Localization>
  <Include Path="minimumrecommendedrules.ruleset" Action="Default" />
  <Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
    <Rule Id="CA1008" Action="Warn" />
    <Rule Id="CA1013" Action="Warn" />
    <Rule Id="CA1303" Action="Warn" />
    <Rule Id="CA1308" Action="Warn" />
    <Rule Id="CA1806" Action="Warn" />
    <Rule Id="CA1816" Action="Warn" />
    <Rule Id="CA1819" Action="Warn" />
    <Rule Id="CA1820" Action="Warn" />
    <Rule Id="CA1903" Action="Warn" />
    <Rule Id="CA2004" Action="Warn" />
    <Rule Id="CA2006" Action="Warn" />
  </Rules>
</RuleSet>

 

Team Foundation Server Check-In Policy

이 Rule Set 기능은 Team Foundation Server의 Check-In Policy에도 쓰이게 됩니다.

 

맺으며..

이번 Rule Set Feature를 통해서 확실히 이전 버전보다는 Code Analysis를 사용하는 것이 쉬워지고 편리해진 것 같습니다. 하지만, Code Analysis에서 중요한 것은 편의성보다는, Rule의 문제라고 생각됩니다. 그런 면에서 아직 FxCop이 처음 나왔을 때의 200개 정도의 Rule에서 많은 추가가 없다는 것은 굉장히 아쉽습니다. 특히 직접적인 경쟁 제품이라고 볼 수 있는 Compuware의 DevPartner Code Review가 처음 버전부터 600개 이상의 Rule을 가지고 있다는 점에 비교해 본다면 더욱 그렇습니다.

이어지는 포스팅에서는 새롭게 Code Analysis에 추가된 Phoenix 엔진과 그에 따라 다시 복귀한 Data Flow 관련 규칙들에 대해서 한 번 알아보겠습니다.

Visual Studio Team System 2005 에서 코드 분석(Static Code Analysis) 기능이 처음 소개된 바가 있습니다. 이 코드 분석은 Microsoft에서 제안하는 닷넷 프레임워크에서의 디자인 가이드라인과 성능이나 보안, 신뢰성 등의 요소에 대한 Best Practice 등으로 이루어진 200개 이상의 Rule을 기반으로 Code를 검사하고 결함을 발견해 줍니다.

 

원래 이 Code Analysis는 FxCop이란 이름의 독립된 툴로써 세상에 먼저 선을 보인 바 있습니다.

이 FxCop이 Visual Studio 2005 Team System에서 통합되면서 현재의 코드 분석 기능이 나오게 된 것입니다. 실제로 비주얼 스튜디오에서 코드 분석 기능을 담당하는 실행 파일의 이름은 아직도 FxCopCmd.exe입니다.

앞으로 나오게 될 Visual Studio Team System 2010에서는 현재까지 알려진 바로는 다음 세 가지 부분에서 코드 분석의 기능 향상이 이루어 졌습니다. (출처: http://blogs.msdn.com/fxcop/archive/2008/10/30/new-code-analysis-features-in-visual-studio-2010-september-08-ctp.aspx)

  1. Rule Sets – 드디어, 규칙을 프로젝트 별로 하나 하나 빼던 수고스러움을 덜 수 있게 된 것 같습니다. DevPartner Code Review와 같은 상용 툴에서는 진작에 제공되던 기능이었지만, 이제 드디어 Visual Studio 2010에서는 Rule들을 Set으로 만들고 그 Rule Set 기반으로 코드 리뷰를 할 수 있게 되었습니다.
  2. Check-In Policy – 당연한 말이겠지만, 바로 위에서 소개한 Rule Sets 기능이 Team Foundation Server의 Check-In 정책에도 적용할 수 있게 됩니다.
  3. 8개의 새로운 Data-Flow 규칙MSDN Code Gallery의 VS2005와 VS2008 코드 분석 비교 문서를 보시면 아시겠지만, VS2008에서 7개의 규칙이 빠진 바 있습니다. Data-Flow Analysis Engine의 각종 버그와 낮은 성능으로 말미암은 결과라고 하는데, 이번 Visual Studio Team System 2010에서는 Phoenix라는 새로운 분석 엔진의 탑재와 함께 다시 7개의 규칙들이 복귀했고, 새롭게 추가된 하나를 합쳐서 총 8개의 새로운 규칙이 추가되었습니다. 그리고 이번에 추가된 Phoenix 분석 엔진은 당연히 기존 엔진이 가지고 있었던 버그나 성능 문제를 해결한 새롭고 강력한 엔진입니다.

그러면 다음 포스팅을 통해서 이 세 가지 개선점들에 대해서 더 자세히 다뤄보도록 하겠습니다.

WPF Features Preview (1) – DataGrid

WPF 2009. 4. 17. 20:32 Posted by 알 수 없는 사용자

WPF 4에서는 새로운 컨트롤들이 포함되게 되는데 대표적으로 DataGrid, Ribbon, VIsual State Manager 컨트롤이 있습니다.
WPF Features Privew를 통해서 새로운 컨트롤들을 미리 보는 시간을 가져보겠습니다.

Exercise 1 - Using the new WPF DataGrid

먼저 기존에 있는 Checkbook 어플리케이션을 살펴보면 ListView를 이용해서 DataGrid 형태로 사용하는 것을 볼 수 있습니다.
이번 실습에서는 몇가지 단계를 따르게 되는데 다음과 같습니다.

1. DataGrid 어셈블리 추가 (WPF Toolkit)
2. ListView와 GridView를 DataGrid로 교체
3. 스타일과 템플릿 변형

먼저 WPF 어플리케이션을 실행해보면 몇가지 데이터가 바인딩된 어플리케이션 형태를 볼 수 있습니다.

Task 1 - Examine the existing application

CheckbookManager 어플리케이션을 Visual Studio를 이용해서 열어서 살펴보면 어떤 형태로 동작하는지 쉽게 알 수 있습니다.
이제 소스코드를 살펴보면 메인 윈도우인 MainWindow.xaml에 보면 ListView가 정의된것을 볼 수 있습니다. 스타일과 컬럼 효과를 이용해서 형태를 정의하고 있는데 column definition은 템플릿을 필요로 하게 됩니다. 왜냐하면 기본적인 ListView 컬럼은 읽기 전용 속성이기 때문입니다.
이것을 TextBox를 이용해서 DataGrid처럼 표현하고 있는것을 볼 수 있습니다.
Data 폴더에 보면 자료구조가 정의되어 있는데 여기서 살펴볼것은 아니니 그냥 넘어가도록 하겠습니다.

Task 2 - Using the DataGrid

이번 단계에서는 ListView를 WPF DataGrid로 교체해보도록 하겠습니다.
먼저 DataGrid를 포함하고 있는 WPFToolkit 어셈블리를 참조합니다.


참조 추가를 선택하고 WPFToolkit.dll 파일을 선택해주면 됩니다.

그리고 나서 MainWindow.xaml 상단에 보면 네임스페이스가 정의된 부분이 있는데 여기에 WPF Toolkit을 추가해줍니다.


접두어를 붙여주고 Microsoft.Windows.Controls 네임스페이스를 선택해주면 WPFToolkit이 등록됩니다.

스크롤을 좀 더 내려서 XAML을 살펴보면 ListView가 있는데 ListView를 dg:DataGrid로 바꿔서 DataGrid 컨트롤을 사용할 수 있게 해줍니다.
물론 바꾸고 나면 많은 에러가 나게 되는데 이것은 차차 고쳐 나갈것입니다.
그리고 ItemContainerStyle 속성을 지워줍니다. 이것은 나중에 다시 해줄텐데 우선 지우도록 하겠습니다.

그리고 TextBox 스타일 리소스 역시 지워줍니다. 우리는 새로운 데이타 컬럼 타입을 사용하게 되니 더 이상필요하지 않습니다.
ListView의 ContextMenu 속성 역시 dg:DataGrid로 바꿔줍니다.
이렇게 해주면 다음과 같은 XAML 형태를 가지게 됩니다.

<!-- DataGrid fills remainder of space -->

<dg:DataGrid x:Name="dg" ItemsSource="{Binding}" Margin="10"

             Background="#80909090" AlternationCount="2">

   <dg:DataGrid.ContextMenu >

      <ContextMenu >

         <MenuItem Header="Copy Selected Transactions"

                   Command="{x:Static ApplicationCommands.Copy}" />

      </ContextMenu>

   </dg:DataGrid.ContextMenu>

   <!-- <ListView.View> ...

        </ListView.View> -->

</dg:DataGrid>

이제 어플리케이션을 컴파일하고 다시 실행시켜 보면 DataGrid가 자동적으로 컬럼을 생성해서 데이터를 보여주는것을 볼 수 있습니다.

다시 소스코드로 돌아와서 컬럼 타입을 정의해주도록 하겠습니다.
DataGrid 안에 <dg:DataGrid.Columns>를 추가 해 줍니다.
그리고 <dg:DataGridTextColumn> 요소를 위의 컬럼 컬렉션 안에 추가 해 줍니다.

Header 프로퍼티는 Header 텍스트값의 속성이고 Width는 컬럼의 폭을 정할 수 있는데 고정값을 줄 수도 있고 컬럼의 헤더나 셀 사이즈에 맞게 해줄 수도 있습니다.
마지막으로 컬럼에 데이터 바인딩을 해줘야 하는데 Binding 속성을 이용해서 ListView에서 해줬던것 처럼 같은 방식으로 해주면 됩니다.

<dg:DataGrid x:Name="dg" ItemsSource="{Binding}" Margin="10"

                  Background="#80909090" AlternationCount="2">

 

            <dg:DataGrid.Columns>

                <dg:DataGridTextColumn Header="No." Width="SizeToCells"  

                                       Binding="{Binding CheckNumber}" />

            </dg:DataGrid.Columns>

여기까지 하고 실행을 해보면 새로 정의해준 컬럼이 추가되긴 했지만 기존 컬럼이 그대로 남아있는것을 볼 수 있습니다.
DataGrid 속성중에 AutoGenerateColumns라는 속성이 기본적으로 True값을 가지게 되어 이렇게 되는데 이 속성을 False로 바꿔주면 정의된 컬럼만 나타나게 됩니다.

<dg:DataGrid x:Name="dg" ItemsSource="{Binding}" Margin="10"

                  AutoGenerateColumns="False"

                  Background="#80909090" AlternationCount="2">

그리고 나머지 컬럼들에 대해서도 속성을 정의해주면 됩니다.

DataGridCheckBoxColumn
DataGridComboBoxColumn
DataGridHyperlinkColumn
DataGridTextColumn
DataGridTemplateColumn

이름을 보면 알 수 있듯이 DataGrid 안에서 일반 컨트롤과 같은 역할을 하게 됩니다.
이러한 컬럼 스타일을 이용해서 원하는 데이터에 맞게 설정을 해주면 초기의 ListVIew에서 보여줬던 형태를 DataGrid로 만들 수 있습니다.

여기까지 마치고 실행해보면 정의해준 컬럼 스타일에 맞게 데이터가 바인딩되어 보이게 됩니다.
지금까지 WPF Toolkit을 이용해서 DataGrid를 추가하고 컬럼 속성 스타일을 주어서 데이타 바인딩하는 방법까지 살펴보았습니다.
다음에는 여기에 추가해서 Calendar와 DatePicker 컨트롤을 이용해서 데이터 필드를 구성하는것에 대해 보도록 하겠습니다.

참고자료
WPF Toolkit : http://www.codeplex.com/wpf

Strongly Typed Metadata
 
지난 포스트의 [MEF] 7. Exports and Metadata를 통해 Export 의 Contract 에 Metadata 를 제공하는 방법을 알아보았습니다.
 
MetadataAttribute 을 선언하여 Export 의 Metadata 를 제공하는 방법입니다.
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Email")]
[ExportMetadata("Logging", true)]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
Export 에 Metadata 를 제공해 주어서 무척 고맙지만, 위의 방법처럼 MetadataAttribute 를 선언하는 방법을 사용하기에 그다지 내키지 않는 구석이 있습니다.
 
아무래도 아래와 같은 이유 때문이겠죠?
 
l 메타데이터의 타입 불안정
l 빌드 시 오류를 해결이 어려움
l 사용상 모든 키값을 외우기 어렵고, 오타 발생 위험
 
위의 이유 때문에 Metadata 를 사용하기 위해 강력한 타입을 원하게 될 것입니다. 그리고 강력한 타입의 Metadata 를 사용하길 권장 드립니다.
 
 
Declaring Strongly Typed Metadata
 
Attribute 을 상속받아 Export 의 Metadata 를 Strongly Typed 으로 확장시키는 방법입니다.
 
아래의 Stringly Typed Metadata 를 위해 Attribute 클래스를 만드는 소스 코드 입니다.
 
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MessageSenderTypeAttribute : Attribute
{
        public MessageSenderTransport Transport { get; set; }
        public bool IsSecure { get; set; }
 
        public MessageSenderTypeAttribute() { }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport)
               : this(transport, false)
        {
        }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport, bool isSecure)
        {
               this.Transport = transport;
               this.IsSecure = IsSecure;
        }
}
 
public enum MessageSenderTransport
{
        Email,
        Phone,
        Sms
}
 
Strongly Typed Metadata 를 위한 Attribute 클래스를 완성하였으면, 이것을 그대로 ExportAttribute 과 함께 추가적으로 선언하면 됩니다.
 
그리고 ExportAttribute 을 선언한 코드에 위의 Attribute 특성을 부여합니다. 아래는 그 소스 코드의 일부입니다.
 
[Export(typeof(IMessageSender))]
[MessageSenderType(MessageSenderTransport.Email)]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
[Export(typeof(IMessageSender))]
[MessageSenderType(MessageSenderTransport.Phone, true)]
public class PhoneMessageSneder : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import PhoneMessageSneder");
        }
}
 
[Export(typeof(IMessageSender))]
[MessageSenderType(Transport=MessageSenderTransport.Sms, IsSecure=true)]
public class SmsMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import SmsMessageSender");
        }
}
 
그렇다면 아래와 같이 Metadata 를 Strongly Typed 으로 질의(Query) 할 수 있게 됩니다.
 
아래의 소스 코드는 지난 포스트의 소스 전체 소스 코드를 참고하십시오.
 
foreach (var export in program.Sender)
{
        if ((MessageSenderTransport)export.Metadata["Transport"] == MessageSenderTransport.Sms)
        {
               export.GetExportedObject().Say();
 
               if ((bool)export.Metadata["IsSecure"] == true)
               {
                       Console.WriteLine("Security message");
               }
        }
}
 
위의 소스 코드 실행 결과는 원하던 결과대로 다음과 같습니다.
 

[그림1] Strongly Typed 질의 결과
 
 
하지만 아직도 문제는 남아 있는 것 같아 보이네요. Strongly Typed 으로 Export Metadata 를 선언하였지만 여전히 질의(Query) 과정은 똑같은 문제점을 가지고 있습니다.
 
l 메타데이터의 질의(Query) 과정의 타입 불안정
l 빌드 시 오류를 해결이 어려움
l 사용상 모든 키값을 외우기 어렵고, 오타 발생 위험
 
 
More Strongly Typed Metadata Query
 
Metadata 를 질의(Query) 하기 위해 MEF 는 보다 강력하게 Import 하는 방법을 제공해 줍니다. 확장된 MetadataAttribute 에 인터페이스(Interface) 를 구현하도록 하여 Import 시에 인터페이스(Interface) 를 통한 Metadata 를 질의(Query) 하는 방법입니다.
 
우선 Attribute 에 사용되는 프로퍼티를 인터페이스로 선언합니다.
 
public interface IMessageSenderTypeAttribute
{
        bool IsSecure { get; }
        MessageSenderTransport Transport { get; }
}
 
단, 여기에서 반드시 Getter 만 선언하셔야 합니다. Setter 를 선언하시면 MEF 는 Setter 프로퍼티로 인해 유효하지 않는 Attribute 으로 인식하여 예외를 발생하게 됩니다.
 
아래의 소스 코드는 Metadata Attribute 클래스에 위의 인터페이스를 구현한 코드 입니다.
 
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MessageSenderTypeAttribute : Attribute, IMessageSenderTypeAttribute
{
        public MessageSenderTransport Transport { get; set; }
        public bool IsSecure { get; set; }
 
        public MessageSenderTypeAttribute() { }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport)
               : this(transport, false)
        {
        }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport, bool isSecure)
        {
               this.Transport = transport;
               this.IsSecure = IsSecure;
        }
}
 
 
이제 Export 의 MetadataView 로 질의할 수 있도록 MetadataView 의 인터페이스를 알려주도록 해야 합니다.
 
[Import(typeof(IMessageSender))]
ExportCollection<IMessageSender, IMessageSenderTypeAttribute> Sender { get; set; }
 
아래의 소스 코드는 MetadataView 를 통해 Strongly Typed 으로 Export Metadata 를 질의(Query) 하는 소스 코드 입니다.
 
[Import(typeof(IMessageSender))]
ExportCollection<IMessageSender, IMessageSenderTypeAttribute> Sender { get; set; }
                                             
static void Main(string[] args)
{
        Program program = new Program();
        program.Run();
 
        foreach (var export in program.Sender)
        {
               if (export.MetadataView.Transport == MessageSenderTransport.Email)
               {
                       export.GetExportedObject().Say();
               }
        }
}
 


'Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 10. Querying the CompositionContainer  (0) 2009.05.18
[MEF] 9. Recomposition  (1) 2009.04.19
[MEF] 7. Exports and Metadata  (0) 2009.04.16
[MEF] 6. Lazy Exports  (0) 2009.04.13
[MEF] 5. Catalog 사용  (0) 2009.04.09

[MEF] 7. Exports and Metadata

Managed Extensibility Framework 2009. 4. 16. 01:11 Posted by POWERUMC
Exports and Metadata
 
Export 에 Metadata 를 등록하고 제어하는 방법입니다. 지난 포스트에서 알 수 있듯이 Export 는 구성 요소간에 Contact 를 제공하여 이들을 구성(Composition) 할 수 있는 플러그인 모델(Plugin Model) 을 제공해 줍니다.
 
하지만 Contract 가 제공되어 구성 요소를 확장하고 구성하는 것은 매우 용이하다는 것을 알게 되었으나 Contract 로 인해 파생된 다양한 구성 요소를 어떻게 제어하느냐의 고민을 하게 됩니다. 즉, 다양한 구성 요소 가운데 내가 필요로 하는 구성 요소를 골라낼 수 있도록 Metadata 를 제공하여 구성 요소를 질의(Query)할 수 있도록 하는 것입니다.
 

예를 들어 아래와 같은 Export 구성 요소 중 어떻게 EmailMessageSender 로 Say() 를 호출할 것인가가 문제인 것이죠.

[Export(typeof(IMessageSender))]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
[Export(typeof(IMessageSender))]
public class PhoneMessageSneder : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import PhoneMessageSneder");
        }
}
 
[Export(typeof(IMessageSender))]
public class SmsMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import SmsMessageSender");
        }
}
 
이런 경우, 원초적인 방법으로 리플랙션(Reflection) 을 이용하여 Type 검사를 통해 EmailMessageSender 를 골라내서 사용하면 되지만, 직접적으로 Type 을 검사하기 위해서는 Tightly Coupling 이 발생하여 결국 유연한 플러그인 모델을 구현하기 위해 아무런 도움이 되지 않습니다.
 
이러한 방법을 해소하기 위해 또 다른 우회 방법은 리플랙션을 통해 Modules Name 으로 비교하는 방법이지만, 이것 또한 플러그인 모델에서 구성 요소가 교체 되었을 경우를 생각하면 전혀 대응할 수 없는 방법입니다.
 
MEF 에서는 이런 문제를 해소하기 위해 Contract 에 Metadata 를 제공하며 정적/동적인 방법을 제공해 줍니다.
 
 
Attaching Metadata to an Export
 
Export 에 Metadata 를 제공해 주기 위해서 MEF 에서는 ExportMetadataAttribute 특성을 제공해 줍니다.
 
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Field,
                    AllowMultiple = true, Inherited = false)]
public ExportMetadataAttribute(string name, object value)
 
왜 ExportMetadata 클래스는 sealed 로 선언이 되었나요?
 
일반적으로 sealed 로 클래스를 봉인할 경우 리플랙션의 성능이 향상됩니다.
 
 
ExportMetadata 는 키와 값(Key/Value) 을 지정하여 Contract 에 Metadata 를 제공해 줄 수 있습니다. 그리고 하나의 Export 에 여러 개의 Metadata 를 제공할 수 있도록 AllowMultiple 을 지원합니다.
 
Metadata 는 여러 가지의 정보를 포함할 수 있습니다. Export 가 제공하는 기능의 특성을 기술할 수 있으며, 예를 들어, 권한, 로깅, 구성 요소 분류 방법 등이 될 수 있을 것입니다.
 
아래의 소스 코드는 Metadata 를 지정하는 예를 보여줍니다.
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Email")]
[ExportMetadata("Logging", true)]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Phone")]
public class PhoneMessageSneder : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import PhoneMessageSneder");
        }
}
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Sms")]
public class SmsMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import SmsMessageSender");
        }
}
 
 
 
Constraining Imports statically
 
MEF Preview 5 에서는 ImportRequiredMetadataAttribute 클래스가 제거되었습니다.
 
MEF Preview 4 에서는 선언적인 방법으로 ImportRequiredMetadataAttribute 를 통해 Metadata 를 질의할 수 있었으나, MEF Preview 5 에서는 ImportRequiredMetadataAttribute 클래스가 제거되었습니다.
 
아마도 추측으로는 ImportRequiredMetadataAttribute 를 선언 시에 여러 개의 구성 요소가 검색될 경우 Exception 이 발생하는데, Exception 을 최소화 하고자 제거가 된 것 같습니다.
 
혹시 Statically 한 방법으로 ImportRequiredMetadataAttribute 에 대응되는 클래스를 아시면 저에게 알려주세요.
 
 
Constraining Imports dynamically
 
이 방법은 Export 의 ExportMetadata 를 런타임 시에 질의(Query) 하는 방법입니다.
 
Import 시 ExportCollection<T> 을 사용하여 Export 를 수동적으로 질의(Query) 하는 방법입니다. 이 방법은 지난 포스트의 Lazy Load 를 이용한 방법으로 단지 Metadata 만 질의(Query) 뿐이고, 객체의 생성에 대한 판단은 필요 시에만 GetExportedobject() 메서드를 이용하여 생성할 수 있습니다.
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
 
namespace MetadataSample
{
        class Program
        {
               [Import(typeof(IMessageSender))]
               ExportCollection<IMessageSender> Sender { get; set; }
 
               static void Main(string[] args)
               {
                       Program program = new Program();
                       program.Run();
 
                       foreach (var export in program.Sender)
                       {
                              if ((string)export.Metadata["SenderType"] == "Email")
                                      export.GetExportedObject().Say();
 
                              if (export.Metadata.ContainsKey("Logging") &&
                                      (bool)export.Metadata["Logging"] == true)
                                      Console.WriteLine("Logged success");
                       }
               }
 
               void Run()
               {
                       var catalog = new AggregateCatalog(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
 
                       var container = new CompositionContainer(catalog);
                       var batch = new CompositionBatch();
                       batch.AddPart(this);
                       container.Compose(batch);
               }
 
               public interface IMessageSender
               {
                       void Say();
               }
 
               [Export(typeof(IMessageSender))]
               [ExportMetadata("SenderType", "Email")]
               [ExportMetadata("Logging", true)]
               public class EmailMessageSender : IMessageSender
               {
                       public void Say()
                       {
                              Console.WriteLine("Import EmailMessageSender");
                       }
               }
 
               [Export(typeof(IMessageSender))]
               [ExportMetadata("SenderType", "Phone")]
               public class PhoneMessageSneder : IMessageSender
               {
                       public void Say()
                       {
                              Console.WriteLine("Import PhoneMessageSneder");
                       }
               }
 
               [Export(typeof(IMessageSender))]
               [ExportMetadata("SenderType", "Sms")]
               public class SmsMessageSender : IMessageSender
               {
                       public void Say()
                       {
                              Console.WriteLine("Import SmsMessageSender");
                       }
               }
        }
}
 
 
실행 결과는 예상할 수 있듯이 아래와 같이 런타임 시에 결과를 보여줍니다.


'Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 9. Recomposition  (1) 2009.04.19
[MEF] 8. Strongly Typed Metadata  (0) 2009.04.16
[MEF] 6. Lazy Exports  (0) 2009.04.13
[MEF] 5. Catalog 사용  (0) 2009.04.09
[MEF] 4. Import 선언  (0) 2009.04.07

안녕하세요. 이번에 Visual Studio Team System 2010 공식 블로그에 새롭게 참여하게 된 LazyDeveloper.Net의 kkongchi라고 합니다. Better Code 시리즈를 통해서 Code Analysis, Unit Test 등에 대한 포스팅을 해보도록 하겠습니다. 부족한 부분 많이 지적해 주시길 바랍니다.

 

TDD?

eXtreme Programming의 창시자 중 하나인 Kent Beck의 eXtreme Programming explained라는 책을 보면 Test를 작성하는 방법에 대해서 이렇게 기술하고 있습니다.

  • If the interface for a method is at all unclear, you write a test before you write the method. (메서드의 인터페이스가 클리어하지 않다면, 메서드를 작성하기 전에 테스트를 먼저 작성해라)

  • If the interface is clear, but you imagine that the implementation will be the least bit complicated, you write a test before you write the method. (메서드의 인터페이스가 클리어하더라도 당신이 생각하기에 구현이 조금 복잡할 것 같다면, 메서드를 작성하기 전에 먼저 테스트를 작성해라)

  • 이런 eXtreme Programming의 Test-Before-Writing 전략을 개발 프로세스에 전면적으로 도입하는 것을 Test Driven Development, 즉 TDD라고 합니다. TDD에 관한 위키피디아 페이지에서 소개하는 TDD의 개발 사이클은 다음과 같습니다. “Red, Green, Refactor”라고 표현하기도 합니다.

    다들 아시다시피, Visual Studio에서는 2005 버전에서부터 Team System의 일부로써 Testing Framework을 제공하고 지원해왔습니다. 그리고 드디어 이번 Visual Studio Team System 2010에서는 완벽하게 TDD의 개념이 Visual Studio Team System안으로 녹아 들어가게 된 것 같습니다. 바로 새롭게 추가된 기능 “Generate” 기능을 통해서, Test를 먼저 작성한 후에 그 Test로부터 코드를 자동으로 Generate해주는 기능이 추가된 것입니다.

     

    TDD Development in VSTS 2010 by “Generate”

    지난 11월에 나온 Visual Studio 2010 and .NET Framework 4.0 Training Kit의 Lab을 통해서 이 기능에 대해서 좀 더 자세히 알아보도록 하겠습니다.

     

    당연히 먼저 테스트를 작성하는 것부터 시작합니다. 하지만, 아직 만들어지지 않은 클래스이기 때문에 아래 그림처럼 빨간색 물결 라인으로 경고가 뜹니다. 여기서 마우스 오른쪽 버튼을 눌러보면, 새로운 “Generate” 기능을 볼 수가 있습니다.

    Generate class를 선택하면 같은 프로젝트에 Class가 추가됩니다. 하지만 Generate other..를 선택하면 아래와 같은 팝업 윈도우가 나옵니다. 클래스를 만들 수 있는 Wizard 개념이라고 보시면 되겠습니다.

    이 Wizard를 통해서 클래스의 Access 한정자, Type, 그리고 파일을 만들 프로젝트까지 설정을 할 수가 있습니다. 이 과정을 통해서 우리는 완벽하게 Test로부터 시작해서 뼈대 코드를 만들어 낼 수가 있습니다. 아래 그림처럼 말이죠..

    이제 이 자동으로 만들어진 뼈대 코드에 구현을 추가하게 되면 여러분들은 다음과 같이 Test를 통과했다는 기분 좋은 화면을 보실 수 있으실 것입니다.


    위에서 보신 Demo Code의 시연은 http://channel9.msdn.com/shows/10-4/10-4-Episode-5-Code-Focused-in-Visual-Studio-2010/#Page=4 에서 Video로도 감상하실 수 있고, http://www.microsoft.com/downloads/details.aspx?FamilyID=752CB725-969B-4732-A383-ED5740F02E93&displaylang=en 에서 Lab Document와 소스 코드도 얻으실 수 있습니다.
     

    지금까지 보신 것처럼 앞으로 출시될 VSTS 2010에서는 IDE 자체에서 완벽한 TDD 지원 기능이 통합되었습니다. TDD가 만능의 도구는 아닙니다. 하지만, 적어도 개발자가 자신의 코드에 대한 이해도가 통상적인 개발 방법보다는 훨씬 크고 깊을 것이라 기대합니다. 어설픈 문서보다는 잘 만들어진 테스트 코드들이 오히려 실제 구현 코드를 이해하는 데 더 도움이 되는 경우도 많습니다. Visual Studio Team System 2010은 효율적인 Test Driven Development를 가능하게 해주는 최고의 도구가 될 것 같습니다.

    부족한 글 읽어주셔서 감사하고, 많은 의견 부탁 드립니다.

    [MEF] 6. Lazy Exports

    Managed Extensibility Framework 2009. 4. 13. 00:38 Posted by POWERUMC
    일반적인 Exports
     
    Composable Part 를 구성하는 동안 특정 Part 에 대한 요청으로 객체 내부의 객체가 필요할 때가 있습니다. 만약 객체가 이런 연관 관계가 있을 경우 객체 내부에 ImportAttribute 을 선언하여 외부 Part 를 Import 할 수 있습니다. 이런 경우는 객체 내부에 Export 간의 Related 관계를 갖게 됨으로써 자동적으로 객체를 초기화할 수 있게 됩니다. ([MEF] 3. Export 선언 참조)
     
    일반적인 Export 를 통해 객체 내부에 Import 를 선언하는 방법입니다.
     
    class Program
    {
            static void Main(string[] args)
            {
                   Program p = new Program();
                   p.Run();
            }
     
            void Run()
            {
                   var catalog = new AggregateCatalog(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
     
                   var container = new CompositionContainer(catalog);
                   var batch = new CompositionBatch();
                   batch.AddPart(this);
     
                   container.Compose(batch);
     
                   var obj = container.GetExportedObject<EmailMessageSender>();
                   obj.Sender.Say();
            }
    }
     
    [Export]
    public class MessageSender
    {
            public void Say()
            {
                   Console.WriteLine("Say Method");
            }
    }
     
    [Export]
    public class EmailMessageSender
    {
            [Import]
            public MessageSender Sender { get; set; }
    }
     
     
    Lazy Exports
     
    하지만 비용이 비싼 객체를 생성할 경우 위의 [일반적인 Exports] 는 매우 비효율적일 수 있습니다. 객체 내부의 Import Object 를 사용하지 않을 가능성이 크거나, 객체의 생성 비용이 클 경우는 Lazy Export 를 고려해야 합니다. 그리고 내부 객체가 동기적(Synchronicity)이 아닌 비동기성(Asynchronism) 성격의 객체일 경우에도 반드시 필요한 기법입니다.
     
    이런 경우 내부 객체의 생성을 선택적, 수동적으로 제어하여, 게으른 초기화(Lazy Initialization) 를 할 필요가 있습니다.
     
    아래의 굵은 표시의 코드가 Lazy Export 로 제어하는 코드입니다.
     
    class Program
    {
            static void Main(string[] args)
            {
                   Program p = new Program();
                   p.Run();
            }
     
            void Run()
            {
                   var catalog = new AggregateCatalog(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
     
                   var container = new CompositionContainer(catalog);
                   var batch = new CompositionBatch();
                   batch.AddPart(this);
     
                   container.Compose(batch);
     
                   var obj = container.GetExportedObject<EmailMessageSender>();
                   obj.Sender.Say();
     
                   var lazyObj = container.GetExportedObject<EmailMessageSenderByLazyExport>();
                   lazyObj.Sender.GetExportedObject().Say();
            }
    }
     
    [Export]
    public class MessageSender
    {
            public void Say()
            {
                   Console.WriteLine("Say Method");
            }
    }
     
    [Export]
    public class EmailMessageSender
    {
            [Import]
            public MessageSender Sender { get; set; }
    }
     
    [Export]
    public class EmailMessageSenderByLazyExport
    {
            [Import]
            public Export<MessageSender> Sender { get; set; }
    }
     
    ImportAttribute 의 프로퍼티를 Export<T> 타입으로 변경하면 내부 객체를 GetExportedObject() 메서드를 이용하여 지연하여 객체를 생성할 수 있습니다.

    'Managed Extensibility Framework' 카테고리의 다른 글

    [MEF] 8. Strongly Typed Metadata  (0) 2009.04.16
    [MEF] 7. Exports and Metadata  (0) 2009.04.16
    [MEF] 5. Catalog 사용  (0) 2009.04.09
    [MEF] 4. Import 선언  (0) 2009.04.07
    [MEF] 3. Export 선언  (0) 2009.03.29

    [Blueprints] S+S Blueprints

    VIsual Studio Extensibility 2009. 4. 12. 23:13 Posted by POWERUMC
    새로운 트랜드 Blueprints
     
    Blueprints 는 새로운 개념의 Software Factory 입니다. 언제나 우리는 반복되는 작업과 그것을 수행하기 위한 새로운 기술, 그리고 좋은 패키지를 만들기 위한 디자인, 그리고 버그를 수정하기 위한 작업을 팩토리(Factory) 라는 개념의 트랜드된 기술입니다. 팩토리(Factory) 는 Web Service, Rich Internet Application(RIA), Service Facade 를 쉽게 제공할 수 있으며 그것은 특정 프로세스, 리소스, 가이드라인, 패턴, 템플릿, 라이브러리 등이 포함됩니다.
     
    Blueprints 의 팩토리는 소프트웨어 생산 공정(Software product line-SPL) 의 형태입니다. 일관된 공통적인 프로세스를 통해 패턴을 정하여 또는 공통적인 프로세스를 정의함으로써 재사용성을 높이고 체계적인 생산 공정을 정의할 수 있습니다.
     
    Software Factory 는 특정 도메인 언어(Domain-Specific Language-DSLs) 를 통해 모델 기반 개발(Model-driven Development-MDD) 를 중요시하는 마이크로소프트 플랫폼을 위한 SPLs 입니다. MDD 는 Application, Services, Layers, Component 등의 복잡한 요소를 대상으로 하는데, Software Factory 는 이러한 복잡한 요소들을 단순화할 수 있습니다.
     
    [그림1] Software Factory 가 생성되고 적용되는 그림
     
     
    Blueprints 가 있기 까지
     
    Blueprints 는 Guidance Automation Toolkit(GAT) 가 그 기반에 존재합니다. GAT 는 Microsoft 의 Pattern & Practice 팀에서 Guidance Automation(GA) 를 쉽게 적용하기 위한 툴입니다. GAT 는 Guidance Automation Extension(GAX) 를 기반으로 Web Service, Web Client, Smart Client 등 Enterprise Library 와 통합시켰고 Guidance Cunsumer 를 통해 커스터마이징이 가능한 도구입니다.
     
    하지만 GAT 는 Guidance Automation(GA) 는 한계가 있었고, 이런 GAX/GAT 를 기반으로 RSS-based Factory 와 런타임 확장 및 업데이트가 가능한 새로운 서비스를 제공하게 되었습니다. 바로 이것이 S+S Blueprints 입니다.
     
    S+S 는 Software plus Services 라는 의미로 Web 2.0 과 SOA 양측 모두 적용할 수 있습니다. Web 2.0 은 나은 응답성을 요구하고 SOA 는 보안과 안정성을 요구하는데, 기업은 이런 SOA 에 Web 2.0 의 응답성과 유연함의 장점을 원합니다. 이러한 Server Software base 의 IT System 은 SaaS Base 의 Web 2.0 을 지원하기 위해 Loosely connected message-passing system 의 서비스를 추가할 것입니다. 기업은 이러한 데이터 보안, 가용성, 응답성과 유연함을 원할 것입니다.
     
    그리고 DSL Tools 입니다. DSL Tools 은 Visual Studio 2005 이상, Team Architect 버전에 포함된 Domain-Specific Language 를 쉽게 구현하기 위한 도구입니다. DSL 은 저렴한 비용으로 UML Modeling 을 할 수 있으며, 기업의 이해관계가 다른 (예를 들어 기업의 경영진과 실무진??) 사람과 통용할 수 있는 좋은 도구가 됩니다.
     
    [그림2] 오늘날 Factory 기술의 발전
     
     
    Blueprints 다운로드 받기
     
    Blueprints 는 Codeplex 에서 다운로드 할 수 있습니다. 현재 Blueprints 는 November CTP Refresh 2.1.2 버전입니다.
     
    Blueprints Download
     
     
    Blueprints 는 Visual Studio Extensibility(VSX) 로 제작되어 Visual Studio 에 Blueprints Manager(BPM) 를 통해 실행할 수 있습니다.
     
    [그림3] Blueprints Manager(BPM) 실행 화면
     
     
    오늘은 Blueprints 를 소개하는 단원에서 끝내며, 추후에 Blueprints 의 Factory 를 만들고 Publish, 그리고 프로세스의 Work flow 등 보다 근접한 S+S Blueprints 를 소개해 드리도록 하겠습니다.
     
    참고 문헌
     
     

    [MEF] 3. Export 선언

    Managed Extensibility Framework 2009. 3. 29. 21:03 Posted by POWERUMC
    Exports 선언
     
    MEF 는 Export 를 통해 외부로 구성요소를 노출할 수 있습니다. Export 는 System.ComponentModel.Composition.ExportAttribute 특성을 통해 선언합니다. 이 특성은 클래스 뿐만 아니라 프로퍼티와 메서드에도 선언을 할 수 있습니다.
     
     
    구성요소 Export 하기
     
    ExportAttribute 특성을 사용하여 아래와 같이 구성요소를 외부로 노출하게 됩니다. ExportAttribute 은 몇 가지의 시그너처(Signature) 를 제공하는데 매개변수를 생략하게 될 경우 MEF 은 클래스의 타입으로 Contract 를 매핑하게 됩니다.
     
    [Export]
    class MessageSender
    {
     
    }
     
     
    프로퍼티 Export
     
    프로퍼티를 Export 하는 방법입니다. 프로퍼티를 Export 할 수 있게 되어 여러 가지 면에서 유리할 수 있습니다.
     
    Core CLR 이 제공하는 타입(Type) 뿐만 아니라 외부의 다양한 타입(Type) 을 사용할 수 있습니다. 프로퍼티에 Export 를 선언할 수 있음으로써 Export 를 구조적으로 분리하여 단순화 할 수 있습니다. 그러므로 같은 구성요소 내에서 Export 간의 Related 관계를 가질 수 있습니다.
     
    아래의 코드와 같이 Timeout 프로퍼티는 Contract 를 맺게 됩니다.
     
    public class Configuration
    {
       [Export("Timeout")]
       public int Timeout
       {
           get { return int.Parse(ConfigurationManager.AppSettings["Timeout"]); }
       }
    }
     
    [Export]
    public class UsesTimeout
    {
       [Import("Timeout")]
       public int Timeout { get; set; }
    }
     
     
    메서드 Export
     
    구성요소의 메서드를 Export 할 수 있습니다. 메서드의 Export 는 기본적으로 대리자(Delegate) 를 통해 호출하게 됩니다. 메서드를 Export 하게되면 보다 더 세세한 제어를 가능하게 하고, 심플하게 방법으로 Code Generating 이 가능합니다.
     
    public class MessageSender
    {
       [Export(typeof(Action<string>))]
       public void Say(string message)
       {
           Console.WriteLine(message);
       }
    }
     
    [Export]
    public class MessageProcess
    {
       [Import(typeof(Action<string>))]
       public Action<string> MessageSender { get; set; }
     
       public void Send()
       {
           MessageSender("Call send process in MessageProcess");
       }
    }
     
    그리고 ExportAttribute 은 타입(Type) 대신 문자열을 사용하여 Contract 를 사용할 수 있습니다.
     
    public class MessageSender
    {
       [Export("MessageSender")]
       public void Say(string message)
       {
           Console.WriteLine(message);
       }
    }
     
    [Export]
    public class MessageProcess
    {
       [Import("MessageSender")]
       public Action<string> MessageSender { get; set; }
     
       public void Send()
       {
           MessageSender("Call send process in MessageProcess");
       }
    }
     
     
    아래의 소스 코드는 이번 예제에서 사용된 전체 소스 코드입니다.
    namespace ExportSample
    {
       class Program
       {
           [Import]
           MessageProcess MessageProcess { get; set; }
     
           [STAThread]
           static void Main(string[] args)
           {
                 Program p = new Program();
                 p.Run();
     
                 p.MessageProcess.Send();
           }
     
           private void Run()
           {
                 var catalog = new AggregateCatalog();
                 catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
     
                 var container = new CompositionContainer(catalog);
                 var batch = new CompositionBatch();
                 batch.AddPart(this);
                 container.Compose(batch);
           }
       }
     
       public class MessageSender
       {
           [Export("MessageSender")]
           public void Say(string message)
           {
               Console.WriteLine(message);
           }
       }
     
       [Export]
       public class MessageProcess
       {
           [Import("MessageSender")]
           public Action<string> MessageSender { get; set; }
     
           public void Send()
           {
                 MessageSender("Call send process in MessageProcess");
           }
       }
    }
     
     
    Export 요약
     
    이렇게 ExportAttribute 을 사용하여 Contract 를 제공하는 것은 굉장히 중요한 의미를 가지게 됩니다. 플러그인 모델(Plugin Model) 에서 Export 는 구성요소를 외부로 노출하는, 즉 Contract 의 방법을 제공해 주게 됩니다.
     
    http://blog.powerumc.kr/upload/Image/NET/NET-Framework/MEF1/capture1.jpg
     
    Contract 맺음으로써 개발자는 Contract Base 로 단지 Contract 만 제공받으면 됩니다. 이러한 Contract 는 제한된 상호작용을 극복하여 대부분의 커플링(Coupling)을 해소할 수 있으며, 플로그인 모델(Plugin Model) 에서 보다 쉽게 구성요소를 캡슐화 할 수 있습니다.

    'Managed Extensibility Framework' 카테고리의 다른 글

    [MEF] 6. Lazy Exports  (0) 2009.04.13
    [MEF] 5. Catalog 사용  (0) 2009.04.09
    [MEF] 4. Import 선언  (0) 2009.04.07
    [MEF] 2. Parts 와 Contracts 선언  (0) 2009.03.22
    [MEF] 1. Managed Extensibility Framework 이란?  (2) 2009.03.16
    Kirill Osenkov 은 새로운 Visual Studio 2010 의 Visual Studio 2010 의 언어와 IDE 를 다루는 약 30분 정도 동영상을 촬영하여 공개하였습니다. 이 동영상은 매우 기초적인 내용만을 다루며, 기능에 대한 상세한 부분은 다루지 않는다고 합니다.
     
    동영상에서 보는 Visual Studio 2010 은 WPF Shell 을 적용한 IDE 로 진행합니다. 그리고 이미 Visual Studio 2010 CTP 를 사용해본 분이라면 눈치 채셨겠지만, 동영상의 Visual Studio 2010 은 최근의 Internal Build 버전이라고 합니다. 그렇기 때문에, Visual Studio 2010 CTP 의 불안정한 WPF Shell 의 모습과 비교할 때 더욱 안정적이고 신선한 모습입니다.

    이제 Visual Studio 2010 의 베타 버전이 임박한 듯 합니다.
     
    Visual Studio 2010 Screencast: C# 4.0 Language + IDE + WPF Shell + Editor
     
     

     

    Task Parallel Library
     
    Parallel Extension 은 PLINQ 와 더불어 확장 가능한 Task Parallel Library 를 제공합니다. Task Parallel Library 는 PLINQ 를 이용하지 않고 개별적이고 수동적인 병렬 처리 작업을 위해 사용할 수 있습니다.
     
    Task Parallel Library 는 크게 세 가지 방법으로 병렬 처리를 위한 Library 를 제공합니다.
     
    Loops
     
     
     

     

    [그림1] Parallel.For 를 이용한 병렬 처리
     
     


    [그림2] Parallel.Foreach 를 이용한 병렬 처리
     
    Task Parallel Extension 으로 병렬 처리를 쉽게 처리할 수 있으며, 병렬 처리로 인자값을 넘기거나 하는 작업을 쉽게 할 수 있습니다.
     
    Statements
     

     


    [그림3] Parallel.Invoke 를 이용한 병렬 처리
     
     
    Task
     
    특히 Parallel Extension Library 에서 Task 는 수동적으로 병렬 처리를 하기 위해 다양한 기능을 지원합니다. 정교하게 스레드(Thread) 를 처리했던 것에 비하면 심플하고도 직관적으로 병렬 작업을 처리할 수 있습니다.
     
    Task 는 보다 정교하게 병렬 처리 작업을 할 수 있습니다.
    l 대기
    l 취소
    l 연장
    l 상하(부모/자식) 간의 관계
    l 디버그 지원
     
    아래는 ThreadPool.QueueUserWorkItem 처럼 바로 작업을 시작하도록 합니다.
     

    Task.StartNew(…);

     
    아래는 Task 에 대해 대기 및 취소 작업을 진행하도록 합니다.
     

    Task t1 = Task.StartNew(…);
    t1.Wait();
    t1.Cancel();
    Task t2 = t1.ContinueWith(…);

     
    아래는 작업에 대해 지속적인 병렬 처리를 가능하도록 합니다.
     

    var p = Task.StartNew(() => {
        var c = Task.StartNew(…);
    }

     
    아래는 특정 작업의 결과를 받아 올 수 있습니다.
     

    var p =
     Future.StartNew(() => C());
    int result = p.Value;
     

     
     
    Coordination Data Structures
     
    병렬 처리 작업은 PLINQ 와 TPL(Task Parallel Library) 를 지원하기 위해 기존의 데이터 컬렉션 등이 등장하였습니다. 내부적으로 동기화를 지원하지 않았던 문제들을 지원하게 되었고, 특히 오늘날 멀티 코어(Multi Core) 프로세스를 위해 많은 동기적인 문제를 고민해야 했습니다. .NET Framework 4.0 은 이러한 공통적인 문제들을 해결하기 할 수 있습니다.
     
    l Thread-safe collections
           ConcurrentStack<T>
           ConcurrentQueue<T>
           ConcurrentDictionary<TKey,TValue>
          
    l Work exchange
           BlockingCollection<T>
           IProducerConsumerCollection<T>
    l Phased Operation
           CountdownEvent
           Barrier
    l Locks
           ManualResetEventSlim
           SemaphoreSlim
           SpinLock
           SpinWait
    l Initialization
           LazyInit<T>
           WriteOnce<T>
    최근 대부분의 사용자들의 컴퓨터의 사양이 코어2 로 업그레이드 되고 있습니다. CPU 제품에 따라 코어에 대한 아키텍처가 다르지만, 기본적으로 이들 제품은 하나의 컴퓨터에 CPU 가 두 개인 제품들입니다. 인간과 비교하자면 뇌가 두 개인 사람인데 그다지 상상해서 떠올리고 싶지 않네요^^.
     
    컴퓨터는 CPU 두 개를 보다 효율적으로 이용하기 위해 바로 Parallelism Processing(병렬 처리)를 하게 됩니다. 하나의 CPU 의 성능을 향상시키는 방법이 아닌, 두 개의 CPU 에게 작업을 할당함으로써 데이터의 처리 성능을 극대화 시키게 됩니다. 우리에게 익숙한 운영체제인 윈도우(Windows) 의 멀티 쓰레딩(Multi Threading) 을 생각하면 병렬 처리(Parallelism Processing) 는 그렇게 어려운 개념은 아닙니다.
     
    [그림1] 어쨌든 뇌가 두 개 (여기에서 참조)
     
    원래 오픈 소스 프로젝트로 Parallel Extension 프로젝트를 CodePlex 에서 본 기억이 있는데, 지금은 링크의 주소를 찾을 수 가 없네요. 구글을 통해 “Parallel Extension” 을 검색하시면, .NET 에서의 Parallel Programming 의 흔적을 찾아볼 수 있습니다.
     
    우선 아래의 Person 클래스를 작성하여 테스트에 사용할 것입니다.
     
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
     
     
    General~
     
    코어(Core) 하나로 작업할 경우, 개발자는 아무것도 염려 하지 않아도 됩니다. 그 동안 우리가 배웠던 대로 코드를 작성하기만 하면 됩니다. 병렬 처리에 대한 고민을 하지 않고 개발한 코드라면 모두 이 범주에 속하겠네요. 이러한 방법은 가장 보편적으로 작성할 수 있습니다.
     
    private static void GeneralSort(List<Person> people)
    {
           List<Person> resultPeople = new List<Person>();
           foreach (Person person in people)
           {
                 if (person.Age >= 50)
                        resultPeople.Add(person);
           }
     
           resultPeople.Sort((p1, p2) => p1.Age.CompareTo(p2.Age));
     
           foreach (var item in resultPeople) { }
    }
     
    List<Person> 개체를 파라메터로 넘겨주고, Person 중에 Age 가 50이 넘는 개체를 정렬하는 코드입니다.
    바로 이 코드를 병렬 처리를 하고자 합니다. 이 코드를 병렬 처리를 하고자 한다면 코드의 양은 훨씬 늘어나고, 복잡한 처리를 해야 합니다.
     
     
    Manual Parallelism
     
    일반적으로 데이터의 처리를 병렬 처리로 전환하기 위해서는 쓰레드(Thread) 를 사용합니다. 쓰레드(Thread) 가 생성이 되면 커널 또는 물리적인 프로세서에 의해 의해 유휴 상태 또는 처리가 가능한 코어(Core) 로 작업이 할당되어 다중 작업(Multi Process) 을 가능하게 됩니다.
     
    이러한 방법의 병렬 처리는 프로세서(Processor) 개수만큼 쓰레드(Thread) 를 생성하여 비동기 작업을 합니다.
     
    private static void ThreadSort(List<Person> people)
    {
           var resultPeople = new List<Person>();
           int partitionsCount = Environment.ProcessorCount;
           int remainingCount = partitionsCount;
           var enumerator = (IEnumerator<Person>)people.GetEnumerator();
           try
           {
                 using (var done = new ManualResetEvent(false))
                 {
                        for (int i = 0; i < partitionsCount; i++)
                        {
                               ThreadPool.QueueUserWorkItem(delegate
                               {
                                     var partialResults = new List<Person>();
                                     while (true)
                                     {
                                            Person baby;
                                            lock (enumerator)
                                            {
                                                  if (!enumerator.MoveNext()) break;
                                                  baby = enumerator.Current;
                                            }
                                            if (baby.Age >= 50)
                                            {
                                                  partialResults.Add(baby);
                                            }
                                     }
                                     lock (resultPeople) resultPeople.AddRange(partialResults);
                                     if (Interlocked.Decrement(ref remainingCount) == 0) done.Set();
                               });
                        }
                        done.WaitOne();
                        resultPeople.Sort((p1, p2) => p1.Age.CompareTo(p2.Age));
                 }
           }
           finally
           {
                 if (enumerator is IDisposable) ((IDisposable)enumerator).Dispose();
           }
     
           foreach (var item in resultPeople) { }
    }
     
    중요한 부분은 추출된 데이터의 정렬(Sort) 작업입니다. 이 작업을 하기 위해서는 모든 쓰레드(Thread) 의 작업이 끝나야 합니다. 만약 모든 쓰레드(Thread) 가 종료되지 않은 시점에서 정렬 작업을 하게 되면, 과연 정렬된 데이터를 신뢰할 수 있을까요?? ( 왜 그런지는 여러분의 상상에 맡기도록 합니다. )
     
    정렬 작업을 하기 전 ManualResetEvent 의 WaitOne() 메서드를 호출하여 모든 쓰레드(Thread) 의 WaitHandle 이 작업이 신호를 받을 때까지(동기화 작업) 기다려야 합니다. 예를 들어, 두 개의 쓰레드(Thread) 가 생성 되고 첫 번째 쓰레드는 이미 작업을 종료하였지만, 두 번째 쓰레드는 아직 작업이 완료되지 않았다면, 작업을 마친 모든 쓰레드(Thread) 는 가장 늦게 처리가 완료되는 쓰레드를 기다려야 정렬 작업을 진행할 수 있습니다.
     
    마지막으로, 위의 코드의 병렬 처리 작업은 성능에 한계가 있습니다. 프로세서(Processor) 개수만큼 쓰레드(Thread) 를 생성하여 작업을 분배하는 방식이기 때문에, 병렬 처리 작업의 성능은 곧 프로세서(Processor) 개수가 될테니까요!
     
     
    Parallel Extension
     
    C# 4.0 은 병렬 처리를 하기 위해 코드의 양을 획기적으로 줄일 수 있습니다.
     
    private static void ParallelSort(List<Person> people)
    {
           var resultPerson = from person in people.AsParallel()
                                        where person.Age >= 50
                                        orderby person.Age ascending
                                        select person;
     
           foreach (var item in resultPeople) { }
    }
     
    LINQ 식을 사용하여 데이터 처리와 정렬 작업을 간단하게 할 수 있습니다. 감격이네요^^ 바로, .NET Framework 4.0 의 Parallel Extension 을 사용하여 LINQ 처럼 사용하는 것을 PLINQ 라고 합니다.
     
    Q : foreach (var item in resultPeople) { } 코드를넣었나요?
     
    A: 동일한 테스트를 하기 위함입니다. LINQ 식은 내부 구조의 특성상 “쿼리식”에 불과합니다.
    보다 자세한 내용은 필자의 블로그를 참고하세요.
     
    Parallel Extension 은 Manual Parallelism 보다 더 복잡하고 좋은 성능을 낼 수 있는 알고리즘으로 구현이 되어 있습니다. 그렇기 때문에 아무리 많은 코어를 가진 컴퓨터에서 동일한 테스트를 한다고 하여도 결코 Manual Parallelism 은 Parallel Extension 의 병렬 처리 성능을 기대할 수 없습니다.
     
    이제 살며시 그 내부 구조도 궁금해 집니다. (다음에 계속…)

    Visual Studio 2010 의 특징

    Visual Studio 2010 2009. 2. 8. 23:55 Posted by POWERUMC

     

    통합 개발 도구인 Visual Studio IDE 는 보다 사용하기 쉽고, 다양한 플랫폼을 쉽게 개발할 수 있으며, 더 많은 고급 기능이 포함되어 있습니다. 처음 Visual Studio IDE 를 접하는 개발자에게도 쉽게 사용할 수 있는 접근성과 비주얼이 보다 강화되었고, 이제는 IT 조직에서 개발자 뿐만이 아닌, 관리자, 아키텍쳐, 데이터베이스 개발자 들이 모두 사용할 수 있는 편한 툴이 되었습니다.

     

    New IDE Improvements

    • Visual Studio 환경
      • WPF 로 개발된 에디터
      • 멀티 모니터 지원
    • 네비게이터
      • 빠른 검색
      • 하이라이트 레퍼런스(Highlight Reference) 기능
    • 프로젝트 시스템
      • 다양한 버전의 소스 코드 사용성
      • 멀티 타게팅(Multi Targeting)

    새로운 Visual Studio IDE 와 다수의 패키지(Package) 가 WPF 로 개발이 되었습니다. 현재 CTP 버전에서도 레지스트리를 설정하여 WPF Based Shell 로 동작시킬 수 있습니다. HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0\General\EnableWPFShell 의 DWord 값을 1로 설정하면 Visual Studio 를 WPF Based Shell 로 시작하도록 설정할 수 있습니다. 하지만, 그 동작이 아직은 불안정하며 WPF Based Shell 의 사용을 권장하지 않습니다.

    코드를 개발하기 위해 자주 사용하는 에디터도 WPF 로 개발이 되었고, 코드에 하이라이트 레퍼런스(Hightlight Reference) 와 같은 비주얼 요소를 다수 적용하였습니다. 단순히 코드의 컬러로 코드의 시각적인 효과를 주는 이상의 다이나믹한 시각 효과가 다수 추가가 되었습니다. 그리고, Visual Studio 의 시작 페이지도 WPF 기반으로 변경이 되었으며, 아래의 필자의 Umc Blog 에서 참고하세요.

    참고
    VSTS 2010 – 새로워진 UI

    VSTS 2010 – Visualize Code RelationShip ( 코드 관계 시각화 )

    또한 멀티 모니터를 지원하여 더 넓고 크게 IDE 를 활용할 수 있습니다. Visual Studio 2010 CTP 버전에서는 Virtual PC 이미지로 제공되기 때문에 멀티 모니터 지원을 확인할 수 없었지만, CTP 이후 버전에서는 멀티 모니터 기능을 확인할 수 있을 것 같습니다.

     

     

    Code Focused Development

    • 먼저 사용하고, 나중에 선언 (Consume First, Declare Later)
    • 코드 통찰력(Code insight)
      • Call Hierarchy 기능
      • Inline call tree 기능
    • 레이어
      • 코드 서식
      • 문맥의 정보 제공
    • Document Map Margin 기능

    개발자가 코드를 개발하기 위해 좀 더 높은 레벨의 작업이 가능하고, 코드를 이해하기 쉽도록 다양한 기능을 제공합니다. 그 중, 먼저 사용하고, 나중에 선언 (Consume First, Declare Later) 기능은 특정 기능을 구현하기 위해 흐름을 깨지 않고, 지속적으로 기능을 구현할 수 있도록 도와줍니다. 아직까지는 작성중인 프로젝트 외부에 코드의 선언을 추가할 수 없기 때문에 TDD(Test-Driven-Development) 로 사용하기에 부족함이 있지만, 앞으로 더욱 개선되어질 것으로 보입니다.

    참고
    VSTS 2010 – 똑똑해진 에디터

    그리고 메서드 및 클래스의 호출을 관계를 쉽게 이해할 수 있도록 Call Hierarchy 를 제공하여, 이러한 관계를 트리 형태로 보여줍니다. 복잡한 구조의 스텍 정보를 순차적으로 접근할 수 있고, 복잡한 인터페이스 프로그래밍 시에 호출 연관 관계를 구조적으로 표현해 주어 선언과 구현부를 쉽게 검색할 수 있습니다. 또한, 코드 구조 전체를 비주얼하게 파악할 수 있는 Document Map Margin 기능도 유용합니다.

     

    Web Development

    • Javascript tooling 강화
    • HTML 스니펫
    • 새로운 MVC 와 Dynamic Data Tooling
    • 웹 개발의 통합

    이제 더 이상 Visual Studio 에서의 웹 개발 플랫폼은 ASP.NET 이 아닙니다. ASP.NET 뿐만 아니라 다양한 웹 개발 플랫폼을 통합하게 되었습니다. PHP/RoR 그리고 웹 환경에서의 엔터프라이즈 RIA 를 개발하기 표준적인 개발 환경을 제공해 줍니다.

    그리고 ASP.NET MVC 를 개발하기 위해 많은 자동화 기능을 제공합니다. MVC 의 어플케이션 초기 구조를 만들기 위한 마법사가 제공되며, Controller, Action, View 등을 코드 에디터에서 쉽게 추가 하고, MVC 프로젝트의 테스트 프로젝트도 자동으로 생성해 줍니다.

    이제는 HTML 도 코드 스니펫(Code Snippet)을 제공합니다. CSS 리팩토링을 지원하게 되며, 외부 스타일시트(CSS) 를 내 프로젝트에 쉽게 추가할 수 있습니다.

     

    Office Development

    • 차기 오피스 버전을 위한 Tooling
    • 오피스 배포의 ClickOnce

    차기 오피스 버전을 개발하기 위해 Tooling 을 제공합니다. 그리고 이러한 추가 기능을 배포하기 위해 ClickOnce 의 기능도 개선이 됩니다. 다양한 추가 기능(Addin) 솔루션을 생성하고, 유지, 배포하기 쉬워집니다.

     

    Sharepoint Development

    • Sharepoint Tooling 과 공통 사용자 정의
      • 개발 –> 디버그 –> 배포 지원

    앞으로 Sharepoint 의 개발이 용이하도록 Tooling 을 제공합니다. Sharepoint 기능을 개발하기 하고 배포하기 위해 복잡한 과정을 거쳐야 했습니다. Visual Studio 는 이러한 기능을 개발하기 용이하고 쉽게 디버깅하고 배포할 수 있도록 지원합니다.

     

    Debugger

    • 다양한 플랫폼 지원
      • 64 Bit Mixed-Mode 디버깅
      • Managed 와 Mixed-Mode 의 Minidump 디버깅
    • 브레이크 포인트 개선
      • 그룹핑(Grouping)과 레이블(Labeling) 지원
      • 내보내기/가져오기 지원
    • Historical Debugger
      • 디버그 내용을 기록, 재생

    Visual Studio 2010 에서 64 비트 플랫폼을 개발할 수 있게 됨으로써, 64 Bit 어플케이션의 디버깅을 지원합니다. 디버깅을 위해 브레이크 포인터를 관리할 수 있는 기능이 강화됩니다. 브레이크 포인트에 레이블을 표시할 수 있으며, 그룹핑을 통해 관련 있는 브레이크 포인트를 쉽게 관리할 수 있고, 관리되는 브레이크 포인트를 내보내고 가져올 수 있습니다.

    그리고, 막강한 Historical Debugger 기능이 추가되어, 디버깅 이력을 쉽게 조사할 수 있습니다. 이러한 디버깅 이력을 기록하고 재생하여 반복적인 작업을 최소화 할 수 있고, 시나리오 별로 브레이크 포인트를 관리하는 등 다양한 용도로 이용할 수 있습니다.

     

    Team System: Business Alignment

    • 프로젝트 관리
      • 프로젝트 서버
      • 클라이언트 통합
      • 경량의 프로젝트 계획 도구
    • 요구 사항 추적
    • 레포트
    • 개발 대시보드
    • 프로세스 사용자 지정
      • 다양한 예제 제공

    프로젝트를 관리하기 위해 프로젝트 서버(Project Server) + 클라이언트 통합 + 경량의 프로젝트 계획 도구를 통해 다양한 팀 프로젝트를 관리할 수 있습니다. 그리고 다른 사람들의 중요한 정보를 검색하기 위해 대시보드도 추가됩니다.

    더불어 마이크로소프트와 커뮤니티를 통해 다양한 예제가 포함됩니다. 자신의 팀 조직에 맞는 커스텀 프로세스를 적용하기만 하면 됩니다.

    Visual Studio Team System 2010 (CTP10) - 작업 항목 링크

    Team Foundation Server 2009. 1. 23. 16:41 Posted by 알 수 없는 사용자

    Visual Studio Team System 2010 (CTP10) - 작업 항목 링크

    지난 3년여 동안 VSTS에 대한 많은 프리젠테이션과 세미나, 교육을 하면서 가장 많이 들었던 질문 중 하나가 "작업 항목들을 hierarchy 형태로 표현할 수 있나요?"였습니다.. 그러면, 저의 대답은 항상 같았습니다. "아니요. 하지만, Rosario (VSTS 2010 코드 명)에서는 된다고 합니다." 그러면, 질문한 사람의 얼굴에는 '그것도 안 돼?'하는 실망의 빛이 역력했습니다. 사실, 저도 왜 hierarchy 표현이 안되는지 궁금하긴 했습니다.
     그래서, Visual Studio Team System 2010의 CTP가 나왔을 때 가장 먼저 확인해 본 것이 작업 항목들을 hierarchy 구조로 표현해 보는 것이었습니다. 결론적으로 말하면, '된다'입니다.

    그러면, 어떤 식으로 작업 항목의 hierarchy 구조를 표현하고, 또 어떤 식으로 조회하는지 살펴보겠습니다.

     (참고로 이 글은 Visual Studio Team System 2010 CTP10을 기준으로 작성되었습니다.)


    [작업 항목 링크 추가]

    Team Foundation Server 2010 CTP10 (이하 TFS CTP10)에는 작업 항목을 연결하는 링크 유형이 추가되었습니다. 기존의 TFS 2005/2008에는 링크 유형은 한 개 (Related - Related)였습니다.

    • 작업 항목 링크 유형
      • Parent - Child: 작업 항목을 트리 (tree) 형태로 구성할 때 사용. (MS Excel에서 표현 가능).
      • Predecessor - Successor: 작업 항목의 선행/후행 관계를 표현할 때 사용. (MS Excel, MS Project에서 표현 가능)
      • Related - Related: 위의 두 경우 아닌 단순한 relationship을 표현할 때 사용. 

    작업 항목 링크은 TFS 2005/2008에서처럼 링크 탭에서 추가합니다. 그러나, 링크 유형 별로 컨트롤이 분리되어 있다면 추가할 링크 유형에 맞는 컨트롤에서 링크를 추가해야 합니다 (그림 1 참조).
     

    [그림 1]

    링크 탭에서 Add 버튼을 클릭하면 링크 추가 창이 나타납니다. TFS CTP10에 추가된 작업 항목 링크 유형은 [그림 2]에서 보는 바와 같이 Link Type 항목에 나타납니다. 링크 유형을 선택하면 그 유형의 이해를 돕기위한 그림이 아래쪽에 표현됩니다.

     


    [그림 2]

     링크로 추가할 작업 항목을 선택하고 comment를 입력하는 방법은 TFS 2005/2008과  동일합니다.

     [그림 3]은 작업 항목에 Parent - Child 링크를 추가한 예입니다.


    [그림 3]

    작업 항목의 링크를 추가하는 방법은 링크 창을 사용하는 것 외에도 마이스로 drag&drop한다거나 Outdent, Indent 버튼을 클릭하는 것도 있습니다.

    이 기능은 작업 항목 쿼리 유형 중 Tree of Work Items 만 가능합니다 (작업 항목 쿼리 유형은 아래에 설명되어 있습니다).

    쿼리 결과에서 작업 항목 (A) 하나를 클릭한 후, 다른 작업 항목 (B) 쪽으로 drag&drop하면 A와 B 작업 항목 사이에 Parent - Child 링크가 추가됩니다 (그림 4 참조)

    [그림 4]

    또한, 쿼리 결과에서 작업 항목을 선택한 후, Outdent 버튼을 클릭하면 작업 항목의 레벨이 올라가고 아래에 있는 작업 항목들은 그 작업 항목의 Child로 추가됩니다. 만약, 그 작업 항목이 다른 작업 항목의 Child였다면 Parent였던 작업 항목과 동등한 레벨이 됩니다. Indent 버튼을 클릭하면 Outdent와 반대로 레벨이 내려갑니다.
    Outdent, Indent는 MS Project의 Outdent, Indent와 유사합니다.

     
    [그림 5]

    [작업 항목 링크 컨트롤]

    작업 항목 링크 유형이 추가되면서, 각 유형 별로 컨트롤을 분리할 수 있게 되었습니다. [그림 1]에서는 Parent - Child 링크 유형이 다른 링크 유형과 분리되어 별도의 컨트롤로 정의되었습다. 이처럼 Predecessor - Successor 링크 유형도 별도의 컨트롤로 분리될 수 있습니다.

    아래 Task.xml의 Layout은 [그림 1]의 Implementation 탭을 정의한 것입니다. 

                         <Tab Label="Implementation">
                            <Control Type="LinksControl" Name="Hierarchy" Label="Parents and &amp;Child Tasks:" LabelPosition="Top">
                                <LinksControlOptions>
                                    <WorkItemLinkFilters FilterType="include">
                                        <Filter LinkType="System.LinkTypes.Hierarchy" />
                                    </WorkItemLinkFilters>
                                    <ExternalLinkFilters FilterType="excludeAll"/>
                                    <LinkColumns>
                                        <LinkColumn RefName="System.ID" />
                                        <LinkColumn RefName="System.WorkItemType" />
                                        <LinkColumn RefName="System.Title" />
                                        <LinkColumn RefName="System.AssignedTo" />
                                        <LinkColumn RefName="System.State" />
                                        <LinkColumn LinkAttribute="System.Links.Comment" />
                                    </LinkColumns>
                                </LinksControlOptions>
                            </Control>
                        </Tab>
    [Task.xml]

    작업 항목 링크 유형 별 Reference Name은 다음과 같습니다.

    • Parent - Child: System.LinkTypes.Hierarchy
    • Predecessor - Successor: System.LinkTypes.Dependency
    • Related - Related: System.LinkTypes.Related

    작업 항목 링크 컨트롤을 분리하지 않고 TFS 2005/2008처럼 하나의 컨트롤로 정의를 하려면 예전처럼 정의하면 됩니다.

               <Tab Label="Links">
                <Control Type="LinksControl" LabelPosition="Top" />
              </Tab>

     [그림 6]은 Parent -Child 유형의 링크 컨트롤의 예입니다.


    [그림 6]

    [작업 항목 쿼리]

    작업 항목의 Parent - Child 또는 Predecessor - Successor 관계는 작업 항목 쿼리를 통해 아래와 같이 조회할 수 있습니다 (그림 7, 그림 8 참조). 작업 항목이 hierarchy 구조로 조회되는 것을 확인할 수 있습니다.

    [그림 7]

     

    [그림 8] 

    작업 항목을 hierarchy 구조로 조회할 수 있도록 작업 항목 쿼리 유형이 두 개 추가되었습니다.

    • Flat List of Work Items: 기존의 쿼리 유형
    • Work Items and Direct Links: 작업 항목과 연결된 작업 항목도 같이 조회. 단, 직접 연결된 작업 항목만 조회 (그림 8 참조)
    • Tree of Work Items: 작업 항목과 연결된 작업 항목도 같이 조회. 작업 항목의 hierarchy 구조를 tree 구조로 표현한다 (그림 7 참조)

    작업 항목 쿼리 유형은 쿼리를 작성할 때 선택합니다. 선택한 작업 항목 쿼리 유형에 따라 쿼리를 작성하는 UI가 달라집니다.

    • Flat List of Work Items

    이 유형은 기존의 TFS 2005/2008에서 쿼리를 작성하는 것과 동일합니다.

    [그림 9]

    • Work Items and Direct Links

    이 유형은 작업 항목과 연결된 작업 항목을 조회할 수 있는 서브 쿼리를 작성할 수 있습니다. 서브 쿼리에서는 링크 유형을 선택하여 해당 링크 유형만 조회가 가능합니다.

    [그림 10]

    • Tree of Work Items

    이 유형은 쿼리를 작성하는 것은 TFS 2005/2008과 같지만 쿼리를 실행했을 때 결과는 Tree 구조로 표현되는 것이 다릅니다(쿼리 결과는 그림 7 참조).

    [그림 11]

     
    이상으로 TFS CTP10의 작업 항목 링크 유형과 작업 항목 쿼리 유형에 대해 살펴 보았습니다.

    작업 항목을 hierarchy 구조로 표현할 수 있게 되므로써 얻을 수 있는 장점은 여러가지가 있습니다.

    일단, 작업 항목의 선후 관계나 상하 관계를 직관적으로 알 수 있는 장점이 있습니다. TFS 2005/2008에서는 이런 관계를 명시하기 위해서는 단지 comment에 두 작업 항목의 관계를 입력하는 정도였습니다. 하지만, 그 방법은 comment를 일일이 읽어야 한다는 불편함이 있습니다.

    그리고, MS Project와 연계에 있어서 자연스러워졌다는 점입니다. MS Project에서 작업을 tree 구조로 표현한 것과 작업의 선후 관계를 표현한 것이 그대로 TFS에서도 표현이 가능해졌습니다. 따라서, 두 도구의 작업 sync.가 더욱 쉬워졌습니다.

    예전에는 작업 항목을 hierarchy 형태로 보려면 보고서를 작성해야 했습니다. 이제 보고서를 작성해야 하는 번거로움이 줄어들었습니다.

    앞으로도 블로그를 통해 TFS CTP10에서 새로워진 기능을 중심으로 어떻게 사용하는지, 그리고 그 기능을 통해 어떤 점이 좋아졌는지를 살펴 보겠습니다.

     webmars.

     http://cafe.naver.com/teamsystem