IT_Programming/C#

[Effective C#] 개발 지침 50

JJun ™ 2007. 1. 19. 17:27

[ C# 언어 요소 ]

 

1. 데이터 멤버 대신에 항상 프로퍼티를 사용하라.

 

   → 프로퍼티는 캡슐화 되어 있는 곳끼리 접근제한자 public 데이터 멤버와 같이 접근하는 것을

       가능하게 해준다. 하지만 프로퍼티 사용을 더욱 권장하는 이유는 실제로 닷넷 프레임워크에서

       데이터 바인딩을 지원하는 클래스들은 일반적인 public 데이터 멤버가 아니라 프로퍼티를 이용하여

       타입의 값을 구현했을 때만 정상적으로 동작하기 때문이다.

       웹컨트롤이나 윈도우 폼 컨트롤과 같은 유저 인터페이스 컨트롤들은 객체의 프로퍼티와 밀접하게

       연관되어 있다. 데이터바이닝 메커니즘은 타입내의 값에 접근하기 위해서 Reflection을 사용하는데,

       이 때 프로퍼티로 구현된 값만을 찾는다.

       그리고 모든 데이터 멤버는 예외없이 private로만 사용하는 것이 좋다.

       이렇게 함으로써 데이터 바인딩 지원이 가능해지며 추후에 메서드의 변경이 발생했을 때

       좀 더 쉽게 코드를 변경 할 수 있다.

     

2. const 보다는 readonly가 좋다.

 

   → 런타임 상수는 readonly 키워드로 선언되고, 컴파일 상수는 const 키워드로 선언된다.

       런타임 상수와 컴파일 상수의 근본적인 차이는 그 값들이 어떤 방식으로 평가되는가에 있다.

       컴파일 상수를 이용하면 컴파일 시에 상수형 변수가 사용되는 위치가 실질적인 값으로 치환된다.

       반면, 런타임 함수는 수행시에 평가된다. readonly 키워드를 이용하여 선언된 상수를 사용하는

       코드를 살펴보면 컴파일타임 상수의 사용 예와는 다르게 실질적인 값으로 대체되지 않고 상수에

       대한 참조자가 위치하게 된다.

       이러한 제약 때문에 컴파일타임 상수는 숫자와 문자열에 한해서만 사용할 수 있다.

       런타임 상수는 생성자가 호출이 완료된 이후에는 값을 변경할 수 없지만,

       생성자 내에서는 값이 할당될 수 있고, 컴파일타임 상수에 비해 비교적 유연하게 사용될 수 있다.

       (: DataTime 구조체형의 readonly 상수는 정의할 수 있지만, const 상수는 정의할 수 없다.)

       특정 타입이 readonly 상수를 가지고 있다고 할 때, readonly 상수는 타입의 인스턴스 별로

       서로 다른 값을 가질 수 있는 반면, const 상수는 기본적으로 항상 static이므로 항상 동일한 값만을

       가질 수 있다. const는 컴파일시에 값으로 평가 되어야 하는 곳에서만 사용되어야 한다.

       (Attribute의 인자나, enum형의 상수 값 선언 등에 주로 쓰일 수 있음)

       하지만 다른 대부분의 경우에 있어서 유연성 확보 할 수 있는 readonly 상수를 쓰는 것이 좋다.

 

3. cast 보다는 is나 as가 좋다.

 

   → 형변환의 가능성 여부는 is 연산자 수행하고, 형변환 수행을 위해서는 as 연산자를 쓴다면

       좀 더 방어적으로 프로그램을 작성할 수 있다.

       C언어에서 사용해왔던 cast 방식 보다 as 연산자를 쓰는 것이 좋은 데 그 이유는 as 연산자가

       소위 말하는 Blinding casting보다 좀 더 안정적이기 때문이다.

       그리고 as와 is 연산자는 사용자가 정의한 형변환 연산자의 존재를 고려하지 않기 때문에

       런타임의 수행성능 효율도 좋다. as와 is 연산자는 런타임에 정확한 타입의 변환이 가능할 때에만

       형변환을 수행한다. 또한 형변환을 수행하기 위해서 새로운 객체를 만들거나 하지 않는다.

       형변환은 가능한 피하는 것이 좋다. 하지만 선택의 여지가 없다면, as나 is 연산자를 쓸 수 있는지

       먼저 검토하라. 강제적인 형변환은 가끔 예기치 못한 결과를 초래할 수 있다.

       as와 is는 항상 일관된 동작을 보여주며 형변환이 정확히 수행될 수 있는지를 확인하고

       정확한 형변환을 수행한다. 따라서 cast연산을 수행하였을 때 발생할 수 있는 의도하지 않은

       동작들을 피할 수 있다.

 

4. #if 대신 Conditional Attribute를 사용하라.

 

   → #if~#endif 블록은 혼돈스럽고 이해하기 어려우며 디버깅을 수행하기도 매우 힘든 코드를 만드는

       부작용이 있다. C#에서는 이러한 부작용을 경감하기 위해 Conditional이라는 Attribute가 포함되어

       있다. 이 Attribute는 특정 메소드가 환경설정 값에 따라 호출될 수 있는지의 여부를 결정한다.

       컴파일러는 컴파일시에 Conditional Attribute의 값을 조사하고, 주어진 값을 검토하여

       적절한 코드를 만들어 낸다.

       베테랑 개발자들은 객체내에서 특정동작을 수행하기 이전 상태와 이후 상태를 조사하기 위해서

       조건 컴파일 구문을 많이 사용한다. 주로 private형태의 메서드를 만들어두고, 그 메서드 내에서

       객체의 상태를 확인할 수 있도록 구성한다.

       Conditional Attribute는 단지 메서드에 대해서만 지정이 가능하다. 추가적인 제한사항으로는

       Conditional Attribute를 지정하는 메서드는 반드시 void형태의 리턴 타입을 가져야 한다는 점과

       메서드 내의 일부 문장에 대해서만 Conditional Attribute를 지정할 수 없다는 점이다.

       메서드 내의 일부 문장에 대해서만 Conditional Attribute를 지정해야 한다며느 해당 문장을 독립된

       메서드로 분리하는 것이 좋다.  이러한 메서드로 분리시키는 과정은 세심한 주의가 필요하긴 하지만

       #if~#endif를 이용하는 방법 보다는 좋다. #if~#endif 블록을 자주 사용하다 보면 중요한 메서드의

       호출이나 변수 할당문이 수행되지 않는 오류를 범할 가능성이 크기 때문이다.

       Conditional Attribute는 #if~#endif를 사용하는 것보다 좀 더 효율적인 IL코드를 생성한다.

       그리고 컴파일러에게 #if~#endif를 사용했을 때 자칫 저지르기 쉬운 오류들을 미연에 방지할 수

       있도록 도와주고, #if~#endif보다 좀 더 깔끔하게 그러한 루틴을 격리시킬 수 있다.

 

5. 항상 ToString()을 작성하라.

 

   → 언젠가는 사용자들이 우리가 개발한 타입에 대한 모든 내용을 확인하려 할 것이고,

       이 때 문자열을 통해 확인할 수 있도록 하는 것이 가장 간단한 방법이다.

       모든 타입에 대해서 ToString()을 overriding하자!!

 

6. value 타입과 reference 타입을 구분하라.

 

   → value 타입은 다형적이지 않기 때문에 우리의 애플리케이션이 직접 다루는 값을 저장하기에

       알맞다. 반면, reference 타입은 다형적일 수 있으며, 애플리케이션의 동작을 정의하는데

       주로 쓰인다. value 형태로 값을 전달하는 것은 때때로 매우 효율적이다.

       하지만 partial copying(부분 복사)이 문제 될 때가 있다. 상속관계에 있는 Parent/Child 클래스가

       있다고 할 때 Parent형 객체에 포함된 부분만 복사가 일어난다. Child형 객체를 전달한 경우,

       Child형 객체의 내용 중 Parent형 객체에 포함된 부분만 복사가 일어난다.

       Child형 객체에서 새롭게 정의한 부분은 모두 소실되기 때문에, 설사 우리가 전달된 객체의

       virtual 메서드를 호출한다 하더라도 Child형 객체에서 overriding한 메서드가 호출되지 않는다.

       C#에서는 stuct나 class 키워드를 이용하면, value타입이든, reference 타입이든 사용할 수 있다.

       value타입은 작고 단순한 타입에 사용되는 것이 좋다. 그리고 복사본이 전달되기 때문에 좀 더

       안정적이고, 스택메모리를 사용하기 때문에 메모리 사용의 효율이 좀 더 증대되고, 애플리케이션의

       로직을 생성하기 위한 기본적인 표준화 기법을 사용할 수 있다. reference타입은 클래스 간 상속이

       필요한 경우에 사용하는 것이 좋다.

 

7. immutable atomic value 타입이 더 좋다.

 

   → 이 타입은 생성된 이후에는 마치 상수와 같이 변경될 수 없는 타입이다.

       immutable 타입은 코드를 좀 더 간단하고 유지하기 쉽게 만든다.

       하지만 모든 타입을 immutable 타입으로 구성하는 것은 좋지 않다.

       만일 그렇게 하면 애플리케이션의 상태를 변경하기 위해서 항상 새로운 상태를 가진 객체를

       만들어야 한다.

       그렇기 때문에 immutable 타입은 단지 atomic value 타입에 대해서만 분리하여 적용하는 것이

       좋다.

 

8. value 타입을 사용할 때 0이라는 값이 의미를 가질 수 있도록 하라.

   

   → 닷넷 시스템은 value 타입의 인스턴스를 항상 '0'으로 초기화한다.

       사용자가 value 타입을 활용함에 있어 이러한 초기화 자체를 명시적으로 막을 수 있는 방법은 없다.

       따라서 가능한 '0'이라는 값을 유효한 값의 범주로 포함시키고, '다른 어떤 유효한 값도 가지지 않음

       의 의미'를 유지할 수 있도록 하자.

       (하지만 예외적으로 enum과 같은 자료형에서는 '0'을 의미있는 값으로 활용하는 것이 좋지 않을 수

        있다. enum과 같은 자료형에서는 의미있는 값으로 '0'을 사용하지 말자.

        enum타입은 System.ValueType을 상속하고 있으며, 각 항목은 항상 '0'으로부터 시작하는 값을

        가지게 되지만 다음과 같이 각 항목이 가지는 값에 수정을 가할 수도 있다.)

 

9. ReferenceEquals(), static Equals(), instance Equals(), operator==의

   상호연관성을 이해하라.

  

   → C#언어는 우리에게 4가지의 비교연산 방법을 제공한다. 하지만 이중 우리가 자체적으로 비교연산  

       을 재정의해야 하는 메서드는 2가지 뿐이다.

       static Object.ReferenceEquals()와 static Object.Equals()는 런타임에 피연산자의 타입을 몰라도

       제대로 비교연산을 수행할 수 있도록 잘 정의되어 있다.

       우리가 value 타입을 정의하는 경우라면 수행성능의 향상을 위해서 인스턴스형 Equals()메서드와

       operator==()를 재정의하자. Reference 타입을 정의하는 경우라면 객체에 대한 참조자를 비교하는

       System.Object의 기본동작을 변경해야 할 경우에 한해서 인스턴스형 Equals()만을 재정의하자.

 

10. GetHashCode()의 함정에 유의하라.

 

   → 해시 테이블이나 Dictionary와 같이 해시 코드를 기반으로 하는 collection에 key로 활용할 타입을

       정의하는 경우를 제외하고는 GetHashCode() 메서드는 재정의 안하는 것이 좋다.

       Reference 타입에 대해서는 비효율적이기는 하지만 동작은 한다.

       하지만 value 타입에 대한 GetHashCode() 메서드의 구현 내용은 제대로 동작조차 하지 않는

       경우가 많다. GethashCode() 메서드를 재정의 하는데 최소 3가지의 규칙을 준수해야 한다.

       1. 만일 두개의 객체가 동일하다면(operator== 에 의해서 정의된 동일 여부) 두 객체는 동일한

           해쉬코드를 생성해야 한다. 하지만 해시 코드를 이용하여 특정 컨테이너 내에서 다수의 객체들을

           찾아내는 데 사용할 수 없다.

       2. 특정객체 a에 대해서 a.GetHashCode()의 반환값은 객체의 인스턴스가 생성된 이후에는 변하지

           않아야 한다. 어떠한 시점에 a객체의 GetHashCode()를 호출하더라도 반환되는 값은 항상

           동일해야 한다. 해시 기반 컨테이너 내에서는 객체를 찾을 때 해시 코드를 이용하여 저장공간을

           검색하기 때문에 이 값이 변경되면 객체가 저장된 올바른 저장 공간을 찾지 못할 수 있다.

       3. 해시 함수는 모든 입력 값에 대해서 integer의 표현범위 내에서 골고루 잘 분산되어야 한다.

          이러한 특성이 해시 기반 컨테이너의 수행성능에 영향을 미친다.

 

 

11. foreach 루프가 더 좋다.

 

   → foreach는 매우 융통성 있는 언어요소이다. 배열의 최소, 최대 범위를 자동으로 결정하여 올바르게

       코드를 만들어 주며 다차원 배열에 대해서도 활용이 가능할 뿐만 아니라, 참조되는 타입이 항상

       올바른 터입이 될 수 있도록 처리해준다. 그리고 무엇보다도 루프에 대한 가장 효율적인 코드를

       만들어낸다. 이는 collection에 대한 순회를 위한 최고의 방법이다.

       또한 foreach를 사용하면 코드의 변화에 강하기 때문에 전체 코드를 뒤지면서 수정해야 하는

       수고를 덜어주고, 이것은 생산성에 도움이 될 것이다.

 

 

 

[ 닷넷 리소스 관리 ]

 

12. 할당문보다는 변수 초기화를 사용하는 편이 더 좋다.

  

    → 클래스들은 종종 하나 이상의 생성자를 갖는다. 생성자 내에 멤버변수에 값을 할당하게 되면

        개발이 차츰 진행됨에 따라 멤버 변수들과 생성자 사이에는 불일치가 생겨날 수 있다.

        이러한 불일치를 해소하기 위해서 생성자 내에서 멤버변수들을 할당하는 대신 변수의 선언시에

        초기화를 사용하는 것이 좋다.

        선언시에 변수의 초기화는 어떤 생성자가 호출될지의 여부와 상관없이 반드시 초기화를

        수행할 수 있는 가장 간단한 방법이다. 이러한 초기화 방법은 어떠한 생성자보다 먼저 수행된다.

        모든 생성자에서 멤버변수에 대한 할당이 동일하게 이루어진다면 선언시에 변수를 초기화하는

        방법을 사용하자.. 이렇게하면 코드를 읽기도 쉽고, 관리하기도 편하다.

 

13. static 클래스 멤버는 static 생성자를 사용하여 초기화하라.

 

    → static 멤버변수를 포함하고 있는 타입의 경우, 인스턴스를 생성하기 이전에 해당 변수를

        초기화하는 것이 좋다는 것은 이미 잘 알려진 사실이다.

        C#에서 static 멤버변수에 대한 초기화를 수행하는 방법에는 선언시 초기화를 사용하는 방법과

        static 생성자를 사용하는 방법이 있다.

        static 생성자는 접근할 클래스의 어떤 메서드,변수,프로퍼티보다 먼저 수행되는 특별한

        생성자이다. 이러한 static 생서자를 이용하여, 싱글턴 패턴을 구현할 수도 있고, 클래스의

        사용 이전에 무엇인가를 수행해야 할 필요가 있을 때 활용할 수도 있다.

        static 변수를 인스턴스형 생성자 내에서 초기화한다거나 초기화만을 위한

        특별한 private 메서드를 이용하거나 혹은 특수한 초기화 구문등은 가능한 사용하지 않는 것이

        좋다. static 생성자를 사용하는 방법과 함께 선언시 초기화 방법을 사용하는 것도 좋은 대안이

        될 수 있다. 만일 static 멤버변수에 대한 단순한 초기화를 수행하려 한다면 선언시 초기화 방법을

        사용하자. 하지만 좀 더 복잡한 구현부가 필요한 초기화를 수행하려 한다면 static 생성자를

        사용하자. 선언시 초기화 하거나 static 생성자를 사용한 방법은 static 멤버변수를 초기화할 수

        있는 가장 훌륭한 방법이다. 코드에 대한 가독성도 좋아지고, 쉽고 올바르게 구현할 수 있다.

        C#언어의 설계자들은 다른 언어에서는 static 멤버변수의 초기화가 어렵다는 사실에 착안하여

        이러한 요소를 추가하였다.

 

14. 연쇄적인 생성자를 이용하라.

 

    → 다수의 생성자 내에서 중복적으로 수행되고 있는 코드를 발견했을 때,

        중복코드들을 새로운 생성자로 분리시켜 호출하는 방식으로 초기화를 수행하도록 하자.

        이렇게 함으로써 코드의 중복성을 피할 수 있을 뿐만 아니라 좀 더 효율적인 코드를 구성할 수 있다

        C# 컴파일러는 생성자에서 또다른 생성자를 호출하는 구조를 특수한 문법의 일부로 인지하고,

        변수에 대한 초기화나 기반 클래스의 생성자 호출 등의 중복코드를 제거한다.

        이런 방법을 통해 객체의 초기화를 위해 필요한 최소한의 코드만이 수행될 수 있도록 최적화를

        수행한다. 뿐만 아니라 우리가 코딩해야 하는 코드의 양도 최소화 할 수 있다.

        간단히 정리하면 간단한 리소스는 선언시 초기화를 사용하고 복잡한 초기화 과정이 필요하면 

        생성자를 사용하자. 그리고 생성자의 연쇄호출을 사용할 수 있도록 하자. 이렇게 함으로써

        중복적으로 초기화가 일어나는 것을 방지할 수 있다.

 

15. 자원해제를 위해서 using과 try/finally를 이용하라.

   

    → Unmanaged 자원들을 사용하는 타입들은 IDisposable interface의 Dispose() 메서드를 호촐하여

        명시적으로 자원을 해제해 주어야 한다. 닷넷 환경에서의 이러한 책임은 타입 자체나 시스템의

        책임이 아니라 타입을 사용하는 사용자의 책임이다. 그러므로 Dispose() 메서드를 구현해둔

        타입을 사용할 때에는 항상 자원의 해제를 위해서 Dispose()가 호출될 수 있도록 주의해야 한다.

        Unmanaged 자원을 사용하는 타입들은 항상 IDisposable interface를 구현하고 있다.

        또한 Dipose()를 호출하는 것을 잊었을 경우를 대비하여 방어적으로 finalizer까지 구현되어 있다.

        하지만 finalizer에 의해서 Unmanaged 자원이 해제되어야 하는 경우에는 메모리 상에 좀 더

        오랫동안 unmanaged 자원이 남아있게 되고, 애플리케이션의 성능에 좋지 않은 영향을 미칠 수

        있다. IDisposable interface를 구현한 타입을 사용할 경우라면 using이나 try~catch/finally 블록

        으로 감싸서 사용하면 된다.

        IDisposable interface를 구현한 객체는 항상 적절한 시점에 Dispose()가 호출될 수 있도록 코드를

        작성하자.

        (Dispose()는 메모리로부터 객체를 제거하지 않는다. 이것은 단지 unmanaged 자원을 해제하는

         것을 도와주는 시기를 제공해 줄 뿐이다. 그렇기 때문에 만일 객체가 다른 부분에서 참조되고 있는

         경우라면, Dispose()를 호출했을 때 문제가 발생할 수 있다. 따라서 프로그램 내의 다른 부분에서

         참조되고 있는 객체의 경우는 절대로 Dispose()를 호출해서는 안된다.)

 

16. Garbage를 최소화하라.

 

    → Garbage Collector는 애플리케이션이 사용하는 메모리를 효율적으로 관리해주는 역할을 수행한다

        하지만 힙에 객체를 생성하고 삭제하는 과정은 여전히 시간을 필요로 한다.

        필요하지 않은 객체를 과도하게 생성하는 것을 피하라. 함수 내에 reference 타입의 지역변수를

        반복적으로 생성하지 않도록 하기 위해서 멤버변수로의 변경을 고려하고, 공통으로 사용하는

        객체에 대해서는 static변수로 선언하자. 마지막으로 immutable 타입을 구성하고 있는 경우라면

        mutable builder 클래스를 제공할 것인지를 고려해 보자.

 

17. boxing과 unboxing을 최소화하라.

 

    → boxing과 unboxing은 항상 수행성능에 좋지 않은 영향을 미친다.

        boxing과 unboxing 동작은 항상 복사 과정을 수반하기 때문에 때때로 이로 인해 기대하지 않은

        오류가 발생하기도 한다. boxing과 unboxing은 피할 수 있다면 가능한 피하는 것이 좋다.

 

18. 표준 Dispose 패턴을 구현하라.

 

    → 표준화된 패턴이란 IDisposable Interface를 구현하여 unmanaged 자원을 해제하고,

        개발자들이 Dispose() 메서드의 명시적인 호출을 잊었을 경우를 대비하기 위해서 finalizer 에도

        적절한 해제 코드를 배치해 두는 것을 말한다.

        finalizer를 구현할 경우에는 Garbage Collector가 객체를 제거하는데 있어서 수행성능에

        나쁜 영향을 주지만, 그럼에도 이러한 방식이 unmanaged 자원을 다루는 가장 유효한 방법이다.

        ( IDisposable .Dispose() 메서드를 구현할 때에는 반드시 다음의 4가지 작업을 수행해야 한다.

          1. unmanaged 자원 해제

          2. managed 자원의 해제 - 참조되지 않은 이벤트 포함

          3. 객체가 dispose 되었음을 표시하는 사태 플래그 값 설정.

              Dispose()가 호출된 이후에 사용자가 객체를 사용할 경우에 대비하여

              모든  public 메서드에서는 상태 플래그값이 설정되어 잇는 경우에 ObjectDisposed 예외를

              발생 시키도록 작성되어야 한다.

          4. finalization 동작이 수행되지 못하도록 함. GC.SuppressFinalize(this)를 호출하면

             finalization 동작이 일어나지 못하도록 할 수 있다. )

        managed 환경에서는 대부분의 경우 finalizer를 구현할 필요가 없다.

        하지만 특정 타입이 unmanaged 자원을 저장하고 있거나 IDisposable을 구현한 객체를 멤버로

        가지고 있는 경우에는 finalizer를 구현하자.

 

 

[ C# 설계사항 구현 ]

 

19. 상속보다는 interface를 정의하고, 구현하는 것이 좋다.

   

    → 기반 클래스들은 상속관계가 있는 타입들 사이에 공통적인 동작을 정의하고 구현하는데 사용된다.

        interface는 상관관계가 없는 타입들간의 공통적인 기능성을 표현하는 데 주로 사용된다.

        각각은 나름대로의 적절한 쓰임새가 있다. 클래스는 타입을 정의하는 데 쓰고,

        interface는 타입들이 어떠한 기능을 제공해야 하는지를 나타내기 위해서 사용된다.

        우리가 이러한 차이점에 대해서 정확히 이해하고 있다면, 설계를 좀 더 잘 할 수 있으며

        변화에 탄력적으로 대응할 수 있다. 타입간의 관련성을 정의할 때는 클래스의 상속관계를

        이용하되, 타입간의 공통적인 동작을 노출할 때에는 interface를 이용하자.

 

20. interface의 구현과 virtual 메서드의 overriding을 구분하라.

 

    → 언뜻보면 interface를 구현하는 것은 virtual 메서드를 overriding하는 것과 유사해 보인다.

        다른 타입에서 선언한 멤버를 구현한다는 측면에서 유사하기 때문에 가끔은 혼돈스럽기도 하다.

        하지만 interface의 구현은 virtual function에 대한 overriding과는 매우 다르게 동작한다.

        interface내에 선언된 멤버는 virtual가 아니다. 따라서 상위 클래스에서 이미 특정 interface를

        구현해 두었다면 하위 클래스는 해당 interface 멤버를 overriding 할 수 없다.

        하위 클래스에서도 동일 interface 멤버에 대해서 새로운 구현부를 제공할 수 있으나 이 경우

        상위 클래스의 구현부를 대체하게 된다. 이것은 virtual 메서드에 대한 overriding과는 전혀 다른

        개념이다.

        (interface를 구현하는 것은 virtual function을 선언하고 구현하는 것에 비해 몇가지 다른

         선택사항이 있다. interface를 더이상 추가적으로 구현하지 못하도록 seal 형태로 구현할 수도

         있고,virtual 형태로 구현하거나 또는 abstract 형태로 선언할 수 있다. 그렇기 때문에 interface를

         이미 구현하고 있는 클래스를 상속 받아 새로운 구현부를 작성하고 싶을 때는 어떻게 구현할 것인

         지 그리고 언제 구현할 것인지를 좀 더 명확히 해야 한다. interface는 virtual 메서드가 아니라

         타입이 구현해야 할 계약이다.)

 

21. delegate를 이용하여 콜백을 표현하라.

 

    → delegate는 런타임시 콜백을 구성하는 최고의 방법이다. 콜백으로 호출되어야 하는 메서드들의

        리스트를 런타임시 구성할 수 있다. 또한 다수의 메서드가 호출되도록 구성할 수 있다.

        닷넷 환경에서 콜백을 구성할 때에는 반드시 delegate를 사용하자.

        (콜백은 서버가 클라이언트에게 비동기적으로 피드백을 주기 위해 많이 사용하는 방법이다.

         여기에는 멀티쓰레딩 기술도 사용되고, 동기적으로 상태를 갱신하기 위한 방법도 제공된다.

         콜백은 C#언어에서 delegate를 이용하여 표현한다. delegate를 선언할 때에는 반환형을

         void 이외의 형으로 지정 할 수 있는데, 이것은 사용자의 호출 중단을 감지할 목적으로만

         사용되어야 한다.)

 

22. 이벤트를 이용하여 외부로 노출할 인터페이스를 정의하라.

 

    → 외부로 노출할 클래스의 인터페이스를 구성하기 위해 이벤트를 정의함으로써 많은 수의

        이벤트 핸들러를 추가할 수 있으며, 컴파일타임에는 어떤 이벤트 핸들러가 추가되는가에 대해서도

        알 필요도 없다. C#에서 이벤트를 사용하게 되면 이벤트 송신자와 수신자 사이의 결합도를 낮추는

        효과를 가져온다. 따라서 이벤트 송신자를 개발할 때에는 수신자의 구성을 전혀 고려하지 않아도

        된다. 이벤트는 다수의 클래스에게 특정정보를 손쉽게 전달할 수 있는 표준화된 방법이기도 하다.

  

23. 클래스 내부 객체에 대한 reference 반환을 피하라.

 

    → 특정타입을 구현할 때 reference 타입 객체를 public 필드로 선언하여 외부에서의 직접 접근을

        허용하면, 사용자는 우리가 구현하는 타입에서 정의한 메서드나 프로퍼티를 이용하여 간접적으로

        내부자료에 접근하려 하지 않고 직접 내부 데이터를 변경하려고 시도할 것이다.

        이 경우 매우 발견하기 어려운 오류를 유발할 가능성이 있다.

        reference 타입을 private으로 선언하고, 프로퍼티나 특정 메서드의 반환값으로만 사용한다

        하더라도 사용자는 반환된 객체내의 public 필드를 무제한으로 사용할 수 있다.

        따라서 private으로 선언된 reference 타입 객체를 노출할 경우에는 interface를 사용하거나

        wrapper class를 생성하여 접근권한을 제한할 필요가 있다.

        만일 사용자가 내부자료에 변겨을 가하는 것을 허용해야 하는 경우라면 내부자료의 변경이

        유효한 변경인지를 검토하기 위해서 Observer pattern을 구현하는 것이 좋다.

 

24. 명령적 프로그래밍보다 선언적 프로그래밍이 더 좋다.

 

    → 선언적 프로그램이란 명령행을 작성하여 프로그램을 작성하는 것이 아니라 프로그램의 동작방식을

        선언을 통해 구현하는 것을 말한다. C# 언어를 포함하여 많은 언어들은 프로그래의 동작 방식을

        정의하기 위해서 메서드를 구현한다. 제한적이긴 하지만 C# 언어에서도 attribute를 이용하여

        선언적 프로그램을 수행할 수 있다. 우리는 클래스,프로퍼티,데이터멤버,메서드와 런타임시 특별한

        동작방식을 추가하기 위해 attribute를 사용할 수 있다. 이러한 선언적 프로그래밍 방식은 구현하기

        쉬울 뿐더러 코드의 가독성을 증대시키고 코드의 유지보수에 도움이 된다.

 

25. serializable 타입이 더 좋다.

 

    → 우리가 만드는 UI 전용 컨트롤, 윈도우 혹은 폼을 가지는 타입을 제외한 모든 타입은 반드시

        serialization 기능을 제공해야 한다. 추가적으로 개발해야 하는 코드가 많지 않기 때문에

        serialization 기능은 반드시 제공하는 것이 좋다. 많은 경우에 Serializable attribute만을

        추가하는 것으로 충분하다.

        닷넷 프레임 워크는 객체의 serialization을 위한 쉽고 표준화된 방법을 제공한다.

        만일 객체가 영속성을 가져야 한다면 이러한 표준화된 방법을 이용하는 것이 좋다.

        만일 우리가 작성하는 타입이 serialization을 지원하지 않는다면, 이 타입을 사용하는 클래스들은

        serialization을 지원하지 않을 수도 있다. 가능한 다른 사용자들이 쉽게 serialization을 구현할 수

        있도록 모든 타입을 serialization이 가능하도록 만들고, 기본적으로 제공되는 기느을 이용하되

        기본기능이 충분하지 않을 경우에는 ISerialization을 구현하자.

 

26. IComparable과 IComparer를 이용하여 순차관계를 구현하라.

 

    → IComparable과 IComparer는 타입가의 순차관계를 정의하는 표준화된 기법이다.

        IComparable은 타입의 일반적인 순차관계를 표현하기 위해서 구현한다.

        만일 IComparable을 구현하는 경우라면, IComparable의 CompareTo()와 일관성을 유지하기

        위해서 비교연산자도 동시에 overload하는 하는 것이 좋다.

        또한 IComparableCompareTo()은 System.Object형 인자를 취하므로, 각 타입에 부합하는

        CompareTo()메서드를  overload하는 것이 좋다. IComparer는 기본적인 수차관계와는 별도로

        다른 형태의 수차관계를 추가로 제공하거나, 순차관계를 제공하지 않는 타입에 대하여 정렬을

        수행할 경우 사용될 수 있다.

 

27. ICloneable의 구현을 피하라.

 

    → ICloneable은 유용한 interface임에는 분명하다. 하지만 반드시 지켜야만 하는 규칙들이 있다.

        value타입에 대해서는 ICloneable interface를 추가해서는 안되며, 상속관계에 있는 타입들에

        대해서는 가장 하위의 클래스에서만 필요에 따라 ICloneable interface를 구현하는 것이 좋다.

        상위 클래스는 하위 클래스가 ICloneable interface를 구현할 때, 상위 클래스의 멤버를 복사할 수

        있도록 protected 복사 생성자를 제공하는 것이 좋다. 이러한 경우를 제외하고는 ICloneable를

        사용하지 말자.

 

28. 형변환 연산자의 구현을 피하라.

 

    → 형변환 연산자는 우리가 작성한 코드에 대체성과 관련된 문제점을 야기할 수 있다.

        사용자는 다른 타입의 객체가 필요한 경우 명시적 또는 묵시적으로 형변환 연산자를 호출할 수

        있고, 이 경우 형변환 연산자를 어떻게 구현하느냐에 따라 임시객체를 반환하거나 객체의 내부값을

        반환할 수 있다. 임시객체가 반환되는 경우 객체에 대한 내부값 수정이 완전히 무효화 될 수

        있으며, 객체의 내부값이 반환되는 경우에는 캡슐화를 해치는 결과를 가져오게 된다.

        이러한 버그들은 컴파일러가 내부적으로 생성하는 코드에 의해서 발생하는 문제이므로

        발견하기 매우 어렵다. 형변환자는 가급적 피하자. 

 

29. 기반 클래스의 변경이 영향을 줄 경우에만 new 한정자를 사용하라.

 

    → new 한정자는 상위 클래스의 변경으로 인한 이름 충돌을 극복할 목적으로만 사용하는 것이

        가장 좋으며, 이러한 경우라도 장기적인 관점에서 이름을 바꾸는 것이 더 적절한지에 대해서

        심사숙고해야 한다. 중요한 것은 new연산자를 남용하지 말라는 것이다.

 

 

 

[ 이진 컴포넌트 작성 ]

 

30. CLS를 준수하는 어셈블리가 더 좋다.

 

    → 닷넷 환경은 언어 중립적이다. 개발자는 제한없이 서로 다른 닷넷 언어를 이용하여 독립적으로

        컴포넌트를 작성할 수 있다. 사실상 이 말은 거의 맞는 말이다.

        하지만 우리가 개발하는 어셈블리가 서로 다른 닷넷 언어를 이용할 때에도 완전히 사용될 수

        있도록 하기 위해서는 Common Language Specification(CLS)을 준수하도록 어셈블리를

        개발해야 한다. (CLS를 준수하는 어셈블리를 개발할 때에는 두가지 규칙을 반드시 따라야 한다.

        첫때는 public이나 protected 멤버의 인자형과 반환형은 반드시 CLS 규격을 준수해야 한다는

        것이다. 두번째 규칙은 CLS를 준수하지 않은 public이나 protected 멤버는 CLS를 준수하는

        별칭을 가져야 한다는 것이다.)

        그러나 그렇다고 컴포넌트 구현 전반에 있어 CLS를 모두 따라야 하는 것은 아니다.

        단지 public과 protected 인터페이스만 정확히 CLS를 준수하면 된다.

        (기반 클래스, public이나 protected 메서드나 프로퍼티나 인덱서의 반환형, 런타임의 이벤트

         인자형, public interface의 선언부와 구현부)

        다수의 언어에서 사용 가능한 컴포넌트를 만드는 것은 추가적인 시간을 들일만한 충분한 가치가

        있다.

 

31. 작고 단순한 메서드가 더 좋다.

 

    → C# 코드가 실제 수행을 위한 machine 코드로 변경되는 과정은 두 단계를 거친다.

        C# 컴파일러는 어셈블리 내에 IL로 구성된 코드를 만들고, JIT 컴파일러는 특정 메서드가

        호출되는 시점에 메서드 단위로 IL코드를 machine 코드로 만든다.

        메서드의 크기가 작다면 JIT 컴파일러에 의해 최적화될 더 많은 기회를 가지게 되며,

        inlining이 수행될 가능성도 많아진다. 추가적으로 단순한 제어흐름을 가진 메서드도 최적화 될

        기회가 그렇지 않은 메서드에 비해 많은 편이다. 메서드 내의 복잡한 분기를 가지지 않는

        메서드들은 지역변수를 레지스터상에 할당하는 최적화가 적용될 가능성도 높다.

        코드를 간결하게 구성해야 함은 물론이고, 어떻게 작성하는 것이 좀 더 효율적인지에 대해서도

        고려하면서 코드를 작성해야 한다.

 

32. 작고 응집도 높은 어셈블리가 더 좋다.

 

    → 작다는 것은 매우 상대적인 개념이다. 실제로 mscorlib.dll은 거의 2MB에 달하는 반면,

        System.Web.Regularexpressions.dll은 56kb 정도밖에 되질 않는다.

        '작고 재사용 가능한 어셈블리를 만드는 것이 좋다'라는 기본적인 설계 규칙이나

        '관련성이 있는 클래스와 interface들을 단일의 어셈블리 내에 포함하는 것이 좋다'는

        기본설계원칙을 준수하도록 하자.

        Mscorlib.dll 파일은 모든 애플리케이션에서 사용되는 공통적인 기능을 담고 있는 반면,

        System.Web.Regularexpressions.dll은 웹 개발 시에 정규표현식을 사용할 필요가 있는 경우에

        한하여 사용된다. 우리는 보통 매우 작고 특화된 기능을 위한 어셈블리를 만들거나, 공통적이면서

        일반적인 기능을 담고 있는 어셈블리 중 하나를 만들게 될 것인데, 어떤 경우라도 적절하게

        작은 크기를 유지하는 것이 좋다. 하지만 인위적인 방법으로 필요이상의 크기를 줄인 어셈블리도

        좋지 않다.

 

33. 타입의 가시성을 제한하라.

 

    → 모든 사람이 모든 것을 볼 필요는 없다. 어떤 방식을 사용하든 외부에서 직접 접근 가능한 public

        클래스를 최소화하는 것이 좋다. 외부로 노출되는 타입이 클래스냐 아니면 interface이냐와

        상관없이 노출된 타입은 계약의 일부이므로 가능한 변경되지 말아야 하며, 그 내용을 계속해서

        일관되게 유지하는 것이 좋다. 너무 많은 것을 노출시켜 두면 추후에 내요을 변경하는 데

        많은 제약이 따르게 된다.

        외부로 노출되는 타입이 적을수록 추후 변경시에 더 많은 선택의 여지를 가질 수 있다.

 

34. 웹 API는 큰 단위로 작성하라.

 

    → 웹서비스를 사용하든 아니면 닷넷 리모팅을 사용하든 상관없이 떨어져 있는 다른 컴퓨터로

        객체를 전달하는 것은 매우 비싼 동작임을 기억해야 한다. 단일의 컴퓨터에서 지역적으로만

        사용되던 API를 동일한 형태의 웹 메서드로 구성하는 것은 적절하지 않다.

        물론 동작은 할지 몰라도 매우 비효율적이다.

        API 내의 각각의 메서드들이 수행하는 동작이 세분화되면 될수록, 애플리케이션은 응답을

        기다리는데 더 많은 시간을 허비하게 된다.

        대신 웹 서비스의 인터페이스를 구성할 때에는 문서기반의 방식을 사용하거나,

        객체의 집합을 이용하여 한번에 많은 정보를 교환하는 방식을 사용하는 것이 좋다.

        즉, 서버로 접속하기 이전에 필요한 정보를 모두 수집한 후에 한번에 서버로 정보를 보내고,

        서버 또한 클라이언트가 필요한 정보를 수집해서 한번에 응답을 주는 것과 같은 방식으로

        작성하는 것이다. 서버는 클라이언트로부터 필요한 모든 정보를 한번에 수신하였기 때문에

        응답을 보낼때도 정보를 한번에 모아서 보낼 수 있다.

        '어떻게 하면 서버와 클라이언트 사이에 송수신 횟수의 증가 없이 데이터의 크기를 최소화

        할 것인가?', '어떻게 하면 고객의 대기시간을 최소화 할 것인가?' 이러한 두가지 목표는

        상반된 면이 있다. 극단적으로 한쪽에 치우치게 시스템을 설계하지 말고, 두가지 설계 목표의

        적절한 타협점을 모색해야 한다.

 

 

 

[ 프레임워크 사용 ]

 

35. 이벤트 핸들러보다 override를 사용하는 편이 낫다.

 

    → 많은 닷넷 클래스들은 시스템으로부터 전달되는 이벤트를 처리하기 위해서 이벤트 핸들러를

        결합하거나 기반 클래스의 virtual 메서드를 overriding하는 두가지 방법을 제공한다.

        왜 동일한 일을 하기 위해서 이와같이 서로 다른 두가지 방법을 제공하는 것일까?

        이는 서로 다른 상황에서 각기 다른 방법이 좀 더 효율적이기 때문이다.

        상위 클래스를 상속한 하위 클래스에서 이벤트를 처리할 경우에는 virtual 메서드를 override하여

        사용하고, 객체 상호간에 연관성이 전혀 없는 경우에는 이벤트 핸들러를 이용하는 것이 좋다.

        메서드 구현부는 특정 이벤트에 대해 결합된 이벤트 핸들러들을 호출하도록 구성되어 있으므로,

        overriding 메서드를 구현한 이후에도 이미 결합된 이벤트 핸들러들이 호출되기를 원한다면,

        반드시 기반 클래스의 메서드를 다시 호출해주어야 한다. 아주 드물게는 기반 클래스의 이러한

        동작을 완전히 대체하고 싶을 경우가 있는데, 이런 경우라면 기반 클래스의 메서드를 호출하지

        않을 수도 있을 것이다. 사실 이벤트 핸들러를 사용하는 경우에는 우리가 작성한 이벤트 핸들러가

        항상 노출되리라 보장 할 수 없다. 앞에서도 말한바와 같이 잘못 구성된 이벤트 핸들러가 예외를

        발생 시킬 수도 있기 때문이다. 하지만 하위 클래스에서 메서드를 overriding 한다면,

        이벤트 발생시에 항상 우리가 작성한 메서드가 호출될 것을 보장할 수 있다.

        이벤트핸들링 메커니즘은 특정 이벤트 발생시에 이벤트 핸들러가 결합되었는지의 여부를 확인해야

        하기 때문에 좀 더 많은 시간을 필요로 한다. 이벤트 핸들러의 결합여부를 확인하는 과정은 결국

        이벤트 핸들러를 담고 있는 collection으로부터 이벤트 핸들러를 찾아내는 과정을 수반하게 되므로

        이는 override된 메서드를 호출하는 것에 비하면 더욱 많은 수행시간을 필요로 하게 된다.      

        다른 관점으로 다시 살펴보면 vitual 메서드를 overriding하는 것은 폼을 유지, 관리하기 위해서

        단일의 메서드만을 수정하는 반면, 이벤트 핸들러 메커니즘을 사용하게 되면 이벤트 핸들러 메서드

        와 이벤트 핸들러를 결합하는 두개의 메서드를 수정해야한다.

        두개의 메서드 모두가 잘못될 가능성이 있으므로 하나의 메서드가 더 단순하고 유지보수가

        편하다고 할 수 있다.

        상속관계의 하위 클래스에서 하나의 이벤트를 처리하기 위해 하나의 메서드로 충분한 경우라면

        overriding을 사용하는 것이 더 적절한 방법이다. 이러한 방법이 좀 더 관리하기 쉽고 효율적이다.

        이벤트 핸들러는 다른 상황에서 쓰여져야 한다. 이벤트 핸들러를 가용하는 것보다 상위 클래스의

        virtual 메서드를 overriding 하는 편이 더 좋다.

 

36. 닷넷 런타임의 진단기능을 활용하라.

 

    → 닷넷 프레임워크에서 제공하는 기능이 우리의 요구사항을 수용하기에 충분한지를 분석해

        보기 전에이러한 기능을 자체적으로 작성하지 말자. 대부분의 경우에 기본적으로 제공되는

        기능만으로도 우리의 목표를 달성할 수 있을 것이며, 이를 통해 운용환경에서 문제점을 분석할 때

        필요한 충분한 기능을 제공할 수 있다.

 

37. 표준 환경설정 메카니즘을 이용하라.

 

    → 닷넷 프레임워크는 사용자의 설정정보, 애플리케이션 설치시의 정보, 하드웨어의 설정정보나

        그 외의 많은 정보들을 저장하는데 사용할 수 있도록  표준화된 특수한 위치들을 가져오는 기능을

        가지고 있다. 이러한 위치들은 사용자가 컴퓨터에 대하여 제한된 권한을 가지고, 애플리케이션을

        수행하더라도 정상적으로 수행이 가능하다.

        설정정보를 정확한 위치에 저장해두면 사용자의 개인적인 설정정보를 쉽게 저장할 수 있으면서도

        보안 설정을 낮춰야 하는 문제를 해결할 수 있다. 이러한 위치정보의 특성을 활용함과 동시에

        닷넷 프레임 워크의 serialization 기능을 활용하면 좀 더 쉽게 개인호된 정보를 저장하고 활용할 수

        있다.

 

38. 데이터 바인딩을 사용하라.

 

    → 닷넷 프레임워크는 데이터 바인딩을 위한 아주 일반적인 기능들만을 기본으로 제공해주고 있다.

        따라서 새롭고 복잡한 자신만의 구성을 위해서는 적절하게 이벤트 핸들러를 구현해 주어야 한다.

        WinForm과 WebForm 대부분의 컨트롤들은 데이터 바인딩에 필요한 충분한 도구들을 제공하고

        있다. 그러므로 사용자 인터페이스 출력에 사용되는 각종 프로퍼티를 데이터 소스에 기반하여

        원하는 형태로 변경하여 출력할 수도 있으며, 반대로 적절한 자료형으로 변경하여 가져올 수도

        있다. 이러한 점은 개발자로 하여금 화면에 어떤 형태로 정보를 출력할지와 그것을 어떻게

        데이터로 표현할 것인가에 대해서만 집중할 수 있도록 한다. 나머지는 WinForm과 Webform의

        데이터 바인딩을 이용함으로써 손쉽게 구현이 가능하다. 이러한 데이터 바인딩 기법 덕에 더이상

        컨트롤과 데이터 소스 객체 사이에 값을 주고 받기 위한 코드를 반복적으로 쓸 필요가 없게 되었다

        대부분의 애플리케이션들은 어떤 방식으로든 데이터를 Business 객체들로부터 획득하고 이렇게

        획득된 데이터들은 컨트롤을 이용하여 사용자들과 상호작용하게 된다.

        닷넷 프레임워크는 우리가 개발하는 애플리케이션이 WinForm이나 WebForm에 상관없이

        데이터와 컨트롤 간의 전달과정을 대신함으로써 좀 더 쉽게 프로그램을 개발할 수 있도록 해준다.

 

39. 닷넷의 유효성 검증 기능을 사용하라.

 

    → 닷넷 프레임워크가 제공하는 유효성 검증 기능은 다양한 애플리케이션에서 반드시 필요한 유효성

        검증 기능을 효과적으로 개발할 수 있는 토대가 된다. 사용자의 입력을 절대로 신뢰해서는 안된다.

        사용자가 입력한 데이터는 언제든 잘못될 수 있고, 어떤 경우에는 애플리케이션을 비정상적으로

        동작시키기 위한 악의적인 시도일 수도 있다. 닷넷 프레임워크가 제공하고 있는 다양한 기능을

        충분히 활용하면 필요한 검증 루틴을 쉽게 개발할 수 있다. 모든 사용자의 입력정보를 검증하되

        닷넷 프레임워크가 제공하고 있는 기능을 십분이용하라.

 

40. 적절한 collection 개체를 사용하라.

 

    → 어떤 collection을 사용하는 것이 가장 좋은가를 결정하기 위해서는 collection에 대하여

        어떤 동작을 주로 수행하는가와 애플리케이션의 목표라 할 수 있는 공간과 속도 문제와도

        매우 밀접하게 연관되어 있다. 하지만 많은 경우에 배열을 이용하는 것이 가장 효과적이다.

        C#언어에 추가된 다차원 배열은 다차원 자료구조를 표현하는데 있어 최적의 구조이며 수행성능도

        빠르다. collection에 새로운 아이템을 추가, 삭제하는 작업이 빈번할 경우라면 닷넷 프레임워크가

        제공하는 다른 collection들을 고려해 보아야 한다. 마지막으로 collection을 포함하는 새로운

        클래스를 작성하는 경우에는 인덱서와 IEnumerable interface를 같이 구현하는 것이 좋다.

 

41. 새로운 구조체보다는 DataSet이 좋다.

 

    → 수행성능이 매우 중요한 부분에 사용되는 collection이거나 매우 가벼운 포맷을 정의하는 경우가

        아니라면 DataSet을 사용하라. 그 중에서도 Typed DataSet을 사용하라. DataSet은 엄청난 시간을

        절약해줄 것이다. 물론 객체 지향적인 관점에서 DataSet의 구조를 살펴본다면 그다지 좋은 구조를

        가지고 있다고 말하기는 어렵다. 특히나 Typed DataSet의 경우는 객체지향설계의 규칙을 더 많이

        어기는 것이 사실이다. 하지만 프로그램 개발의 생산성이 미려한 코드를 직접 설계하는 것보다 더

        중요함을 알아야 한다.

 

42. reflection을 단순화하기 위해서 attribute를 사용하라.

 

    → Reflection을 이용하면 동적으로 코드를 생성하는 것이 가능하다. 설계단계나 구현단계에서

        타입, 메서드, 프로퍼티들에 대해서 사용자 정의 attribute를 고려한다면 런타임시에 오류를

        발생할 가능성을 현저히 낮출 수 있다. 이러한 장점은 결국 최종 사용자에게 만족도가 높은

        애플리케이션을 전달할 가능성을 높인다는 측면에서 매우 중요하다.

 

43. reflection을 과도하게 사용하지 말라.

 

    → reflection은 느린 바인딩을 수행하는 강력한 메커니즘이다. 닷넷 프레임워크는 WinForm기반이나

        WebForm기반의 모든 컨트롤들에 대해서 데이터 바인딩을 지원하기 위해서 reflection을 사용한다.

        하지만 많은 경우에 있어 reflection을 사용하기보다는 클래스 factory, delegate, interface등을

        활용하는 것이 유지보수가 쉬운 시스템을 구성하는데 있어 많은 이점을 가져다 준다.

 

44. 애플리케이션에 특화된 예외 클래스를 완벽하게 작성하라.

 

    → 우리가 개발한 애플리케이션은 분명 예외를 유발할 것이다. 물론 자주 발생하지 않기를

        희망하겠지만 완전히 피할 수는 없다. 예외에 대해서 특별한 처리를 하지 않았다면 우리가 호출한

        메서드내에서 무엇인가 잘못된 경우, 기본적으로 닷넷 프레임워크 예외가 발생할 것이다.

        예외발생시점에 좀 더 자세한 추가 정보를 제공할 수 있다면 우리 자신에게나 또는 우리가 개발한

        라이브러리의 사용자들에게까지도 문제 상황을 정확히 분석하고 오류를 고치는데 많은 도움이 될

        것이다. 예외상황이 발생했을 때 대처법이 각기 다른 경우라면, 각각의 경우에 대비한 독립된

        예외 클래스를 작성하는 것이 좋다. 새로운 예외 클래스를 만들때에는 기반 예외 클래스가

        제공하는 모든 생성자 형식을 제공해야 한다.

        또한 새로운 예외 클래스의 InnerException 프로퍼티에 최초 발생한 예외를 담고 있어야 한다.

 

 

 

[ 기타 ]

 

45. 견고한 예외 보증 기법이 더 좋다.

 

    → 예외는 애플리케이션의 제어흐름에 심각한 변화를 초래한다. 최악의 경우 어떤문제가 발생할지

        예측할 수 없으며, 수행되어야 하는 코드가 수행되지 않을 수도 있다.

        예외발생시에 무엇이 바뀌었고 무엇이 바뀌지 않았는지를 알 수 있는 유일한 방법은

        강력한 예외 보증 기법을 충실히 따르는 것이다.

        Finalizer, Dispose(), delegate target 메서드 등은 아주 특별한 경우로 어떠한 상황에서도 예외를

        발생 시켜서는 안된다. 마지막으로 reference 타입을 복사본으로 대체하는 메커니즘을 사용할 때

        에는 특별히 주의를 기울여야 한다. 이러한 동작은 잠재적인 버그를 내포할 가능성이 있다.

 

46. Interop을 최소화하라.

 

    → Microsoft가 구성한 Interop는 그다지 쉽지도 않으며, 효율적이지도 못하다. 

        Interop가 해결하고 있는 문제는 그저 이전의 코드를 사용할 수 있는 방법이 있다는 수준 정도이다.

        Interop를 사용하는 것은 매우 고통스럽다. Interrop을 사용하기 전에 native 코드로 작성된

        애플리케이션을 재작성하는 것에 대해서 심각하게 고려하기 바란다. 때로는 이러한 방법이

        더 쉽고 더 빠를수도 있다. 만일 이미 개발된 COM객체를 사용하는 경우라면 COM interop를

        사용하자. 기존의 C++로 개발된 코드를 재사용해야 하는 경우라면 /clr 컴파일 옵션과 managed

        C++의 확장기능을 상용하여 기존 코드를 활용하는 라이브러리를 만들어서 C#에서 활용하는 것이

        최상의 방법이다. 가장 시간이 덜 드는 방법을 선택하라. 때로는 '과감하게 던져버리는' 전략이

        최선의 선택일 수 있다는 점을 명심하라.

 

47. 안전한 코드가 더 좋다.

 

    → 닷넷의 보안모델은 프로그램 수행시에 지속적으로 권한정보를 확인한다.

        따라서 수행할 프로그램이 적절한 권한을 가지고 있는지에 대해서 항상 주의해야 하며,

        필요한 최소한의 권한만을 가지도록 하는 것이 좋다. 프로그램이 보호된 자원들을 덜 사용하면

        할수록 보안과 관련되 예외가 발생할 가능성이 적은 것은 너무나 당연하다.

        가능한 보호된 자원들을 덜 사용하고, 다른 대안을 모색해보자.

        특정코드를 수행하기 위해서 반드시 높은 권한이 필요한 경우라면 그러한 코드들은 독립된

        어셈블리로 격리하자.

 

48. 활용할 수 있는 다양한 툴과 리소스에 대해서 알아두라.

 

    → 많은 도구들을 사용하면 할수록 지식은 더욱 더 빠른 속도로 쌓여갈 것이다.

        모든 C#과 닷넷 관련 커뮤니티는 매우 빠른 속도로 발전해 나가고 있다.

        커뮤니티로부터 배우고, 커뮤니티에 기여하자.

 

49. C# 2.0의 주요 특징

 

    → 주로 generics,iterator, anonymous메서드, partial 타입이 추가 되었다.

        (generics는 '인자를 통한 다형성'을 제공한다. 이것은 하나의 소스를 통해서

         유사한 일련의 클래스들을 만들어내는 고급기술이다.

         iterator는 enumerator pattern을 좀 더 적은 코딩으로 구현할 수 있도록 하기 위해서

         추가된 문법사항이다. 내부적으로 살펴보면 컴파일러는 이전의 방법처럼 IEnumerator interface를

         구현한 클래스를 생성하며 필요한 메서드를 적절히 구현한다, 하지만 중요한 것은 이러한 작업을

         우리가 직접 하는 것이 아니라 컴파일러가 내부적으로 해준다는 것이다.

         partial 타입은 여러개의 파일에서 하나의 C# 클래스를 나누어 구현할 수 있도록 해주는 기능을

         말한다.)

 

50. ECMA 표준을 익혀라.

 

    → ECMA 표준문서는 C#언어가 어떻게 동작하는가를 기술한 공식문서이다.

        진정한 C# 프로그래머라면 다른 언어와의 차이점을 이해하는 수준을 넘어서 좀 더 자세히 언어의

        명세를 이해하는 과정이 필요하다. 그래야만 실제 프로그램을 작성할 때 가장 적절한 언어요소를

        적재적소에 사용할 수 있을 것이기 때문이다. 또한 서로 다른 다양한 표기법 사이의 미묘한 차이점

        까지도 이해할 수 있게 될 것이다.