개발/C#

.Net Framework Guideline - Member Design -1

huiyu 2021. 8. 30. 17:40

멤버 오버로드

멤버 오버로드(Member Overload)는 매개 변수의 개수나 형식만 다르고 이름은 같은 멤버를 둘 이상 만드는 것을 의미한다. 다음과 같이 WriteLine은 오버로드 되어있다.

public static class Console {
    public void WriteLine();
    public void WriteLine(string value);
    public void WriteLine(bool value);
    ...
}

매개 변수를 가질 수 있는 메서드, 생성자 인덱싱된 속성맞 오버로드할 수 있다. 

 오버로드는 재사용 가능한 라이브러리의 유용성(usability), 생산성(productivity), 가독성(readability)을 개선할 수 있는 기법으로, 매개 변수 개수를 오버로드하여 더 간단한 버전의 생성자 및 메서드를 제공할 수 있다. 매개 변수 형식을 오버로드하면 다른 형식을 전달하는 동일한 멤버 이름을 갖는 메서드를 구현할 수 있다.

- 매개 변수의 이름은 설명이 포함된 매개 변수(descriptive parameter) 이름을 사용한다.
- 오버로드 된 함수에서 매개변수의 이름을 임의로 변경하지 않는다. 오버로드의 매개 변수가 다른 오버로드의 매개변수와 동일한 입력값이라면 배개 변수 이름은 동일하여야 한다.
- 오버로드 된 멤버에서 매개 변수 순서는 일관되어야 한다. 이름이 같은 매개 변수는 모든 오버로드에서 동일한 위치에 표시되어야 한다.
- 가장 긴 오버로드만 virtual로 설정한다(확장성이 필요한 경우). 짧은 오버로드 함수는 긴 오버로드를 통해 호출하면 된다.
- 'ref' 또는 'out'한정자를 사용하여 멤버를 오버로드하지 않는다. 일부 언어에서는 이러한 오버로드에 대한 호출을 확인할 수 없다. 또한 이러한 오버로드는 일반적으로 완전히 다른 의미 체계를 사용하며 별도의 두 메서드를 제외하면 오버로드 되지 않을 수 있다.

- 유사한 형식이지만 의미 체계가 다르고 동일한 위치에 매개 변수가 있는 오버로드는 사용하지 않는다.
- 선택적 인수에 대해서는 null을 전달하도록 허용한다.
- 기본 인수로 멤버를 정의하는 대신 멤버 오버로드를 사용한다.

속성 디자인 (Property design)

속성은 기술적으로 메서드와 유사하지만 사용 시나리오 측면에서는 전혀 다르다. 속성은 스마트 필드로 표시되어야 한다.

- 호출자가 속성의 값을 변경할 수 없어야 하는 경우 'get-only' property로 만든다.
 속성 형식이 변경 가능한 참조 형식이면 'get-only' property여도 속성 값을 변경할 수 있다.
- 설정 전용 속성(properties with the setter) 또는 set-only properties는 getter보다 광범위한 속성을 제공하지 않는다.
 예를 들어 public setter 및 protected getter가 있는 속성은 사용하지 않는다.
 property getter를 제공할 수 없는 경우 대신 메서드로 기능을 구현한다. 메서드 이름은 Set으로 시작하고 속성의 이름을 지정한 항목으로 사용하는 것이 좋다. 예를들어 AppDomain에는 'CachePath'라는 setter property 대신 'SetCachePath'라는 메서드를 제공한다.
- 모든 속성에 적절한 기본값을 제공하여 기본값으로 인해 보안 허점이나 비효율적인 코드가 생성되지 않도록 한다.
- 객체의 상태가 일시적으로 잘못되더라도 속성을 원하는 순서대로 설정할 수 있도록 한다. 일반적으로 동일한 객체에 다른 속성의 값을 지정하면 한 속성의 일부 값이 잘못될 수 있는 지점에서 둘 이상의 속성이 관련될 수 있다. 이러한 경우 잘못된 상태에서 발생한 예외는 서로 관련된 속성이 실제로 객체에서 함께 사용될 떄까지 연기되어야 한다.

- Property setter에서 예외가 throw되는 경우 이전 값을 유지한다.
- Property setter에서 예외를 throw하면 안된다.
속성 getter는 간단한 작업이어야 하며 사전 조건이 없어야 한다. getter에서 예외가 throw될 수 있다면 메서드로 다시 디자인 해야 한다. 인수 유효성 검사의 결과로 예외가 예상되는 인덱서에는 이 규칙이 적용되지 않는다.

인덱싱된 속성 디자인(Indexed Property Design)

인덱싱된 속성은 매개 변수를 포함할 수 있고 배열 인덱싱과 유사한 특수 구문으로 호출할 수 있는 특수 속성이다.
인덱싱된 속성을 일반적으로 인덱서라고 한다. 인덱서는 논리 컬렉션의 항목에 대한 액세스 권한을 제공하는 API에서만 사용해야 한다. 예를 들면 문자열(string)은 문자의 컬렉션이며 System.String의 인덱서는 해당 문자에 액세스하기 위해 추가되어 있다.

- 내부 배열에 저장된 데이터에 대한 액세스 권한을 제공하려면 인덱서를 사용하는 것이 좋다.
- 컬렉션의 항목을 나타내는 형식에서 인덱서를 제공하는 것이 좋다.
- 둘 이상의 매개 변수가 있는 인덱싱된 속성을 사용하면 안된다.
 여러 매개 변수가 필요한 디자인에서는 속성이 실제로 논리 컬렉션에 대한 접근자를 나타내는지 확인해야 한다. 그렇지 않다면 메서드를 사용한다. 메서드 이름은 'Get' 또는 'Set'으로 시작한다.
- System.In32, System.Int64, System.String, System.Object 또는 열거형 이외의 매개 변수 형식을 사용하는 인덱서는 사용하면 안된다. 다른 형식의 매개 변수가 필요한 디자인에서는 API가 실제로 논리 컬렉션에 대한 접근자를 나타내는지 다시 확인해야 한다. 그렇지 않은 경우라면 메서드를 사용하며, 메서드 이름은 'Get' 또는 'Set'으로 시작한다.
- 명백하게 더 좋은 이름(예: System.String의 Chars[] 속성 참조)이 없는 경우 인덱싱된 속성에 'Item'이란 이름을 사용한다. C#에서는 인덱서 이름이 기본적으로 Item으로 지정된다. IndexerNameAttribueㅅ를 사용하여 이 이름을 지정할 수 있다.

- 동일한 기능을 하는 인덱서와 메서드를 둘다 제공하지 않는다.
- 한 형식에서 오버로드된 인덱서 패밀리를 둘 이상 제공하지 않는다.
- 기본값이 아닌 인덱싱된 속성을 사용하지 않는다.

속성 변경 알림 이벤트

경우에 따라 속성 값의 변경을 사용자에게 알리는 이벤트를 제공하면 도움이 된다. 예를 들어 Text 속성 값이 변경된 후 'System.Windows.Forms.Control'에서 'TextChanged'이벤트가 발생한다.

- 상위 수준 API(일반적으로 디자이너 구성 요소)에서 속성 값이 수정되면 변경 알림 이벤트를 발생시키는 것이 좋다.
 객체의 속성이 변경될 때 사용자가 알아야 하는 시나리오가 있는 경우 객체는 속성에 대해 변경 알림 이벤트를 발생시킨다.
 그러나 기본 형식이나 컬렉션과 같은 하위 수준 API에 대해 이러한 이벤트를 발생시키면 오버헤드가 될 가능성이 있다. 예를 들어 새 항목이 목록에 추가되고 'Count' Property가 변경되어도 List<T>에선 Count가 변경된 이벤트를 발생시키지 않는다.

- 속성 값이 외부의 힘에 의해 변경된다면 변경 알림 이벤트를 발생시키는 것이 좋다.
 속성 값이 일부 외부의 힘(객체에서 메서드를 호출하는 것 이외의 방식)을 통해 변경된다면 이벤트 발생은 값이 변경되고 있고, 변경되었음을 개발자에게 표시해야 한다. 좋은 예로 텍스트 상자 컨트롤의 Text 속성이 있다. 이는 사용자가 TextBox에 텍스트를 입력하면 속성값이 자동으로 변경된다. 

 

생성자 디자인(Constructor Design)

형식 생성자(Type Constructor)와 인스턴스 생성자(Instance Constructor) 두가지 생성자가 존재한다.

형식 생성자는 정적(static)이며, 형식을 사용하기 전에 clr에서 실행된다. 인스턴스 생성자는 형식의 인스턴스를 만들 때 실행된다. 형식 생성자는 매개 변수를 사용할 수 없으나 인스턴스 생성자는 매개 변수를 사용할 수 있다. 배개 변수를 사용하지 않는 인스턴스 생성자를 parameterless constructor라고 부른다.

생성자는 형식(type)의 인스턴스를 만드는 가장 일반적인 방법이다. 대부분 개발자는 인스턴스를 만드는 방법을 고려하기 전에 생성자를 검색하고 사용한다.

- 간단한 생성자(simple constructor), 이상적으로는 기본 생성자를 제공하는 것이 좋다.
 간단한 생성자는 매우 적은 수의 매개 변수를 갖고 있으며, 모든 매개 변수는 기본 형식 또는 열거형 이다. 이러한 간단한 생성자는 프레임워크의 유용성을 향상시킨다.

- 원하는 작업의 의미 체계가 새 인스턴스 생성에 직접 매핑되지 않거나 생성자 디자인 지침의 일반적인 경우가 아니라면 생성자 대신 정적 팩터리 메서드를 사용하는 것이 좋다.

- 주요 속성 설정은 생성자의 매개 변수를 이용하여 설정한다. 빈 생성자를 사용한 다음 일부 속성 세트를 사용하는 경우와 여러 인수가 있는 생성자를 사용하는 경우 두가지의 의미는 동일해야 한다.

- 생성자 매개 변수가 단순히 속성을 설정하는 데 사용되는 경우 생성자 매개 변수와 속성에 동일한 이름을 사용한다. 유일하게 구분되는 것은 대/소문자로 구분하는 것이다.

- 생성자에서 최소한의 작업만 수행한다. 생성자는 생성자 매개 변수를 저장하는 것 외에 많은 작업을 수행하면 안된다. 다른 모든 처리 비용이 지연된다.

- 필요한 경우 인스턴스 생성자에서 예외를 throw한다.

- 매개 변수가 없는 public constructor가 필요한 경우 클래스에서 이러한 생성자를 명시적으로 선언한다. 생성자를 명시적으로 선언하지 않으면 대부분의 언어에서 매개 변수가 없는 public constructor를 자동으로 추가한다. 추상 클래스는 protected constructor를 가져온다. 

매개 변수가 있는 생성자를 클래스에 추가하면 컴파일러에서는 매개 변수 없는 생성자를 추가할 수 없다. 따라서 호환성이 손상되는 변경이 실수로 발생되는 경우가 있다.

- 구조체에서 매개 변수가 없는 생성자를 명시적으로 정의하면 안된다. 

- 생성자 내의 객체에서 가상 멤버(virtual member)를 호출하면 안된다. 가상 멤버를 호출하면 가장 많이 파생(상속받은)된 형식의 생성자가 완전히 실행되지 않은 경우에도 파생(상속받은)된 재정의가 호출될 수 있다.

형식 생성자 지침

- 정적 생성자를 프라이빗으로 설정한다.
 클래스 생성자라고도 하는 정적 생성자는 형식을 초기화하는 데 사용된다. clr은 형식의 첫번째 인스턴스가 만들어지거나 해당 형식에서 정적 멤버가 호출되기 전에 정적 생성자를 호출한다. 사용자는 정적 생성자가 호출되는 시기를 제어할 수 없다. 정적 생성자가 프라이빗이 아닌 경우 clr 이외의 코드에서 호출할 수 있다. 따라서 생성자에서 수행된 작업에 따라 예기치 않은 동작이 발생할 수 있다. C# 컴파일러는 강제로 정적 생성자를 프라이빗으로 설정한다.

- 정적 생성자에서 예외를 throw하지 않는다.
  형식 생성자에서 예외가 throw 되는 경우 해당 어플리케이션 도메인에서 형식을 사용할 수 없다.

- 런타임은 정적 생성자를 명시적으로 정의하지 않은 형식에서 최적화를 수행할 수 있으므로 정적 생성자를 명시적으로 사용하는 대신 정적 필드를 인라인으로 초기화하는 것이 좋다.

 

이벤트 디자인 (Event Design)

-  이벤트는 일반적으로 사용하는 콜백(사용자 코드를 호출할 수 있도록 구현) 구분. 다른 콜백 메커니즘으로 대리자를 사용하는 멤버, 가상멤버, 인터페이스 기반 플러그인이 있으나 대부분 개발자가 event를 사용할 때 보다 편안함을 느낀다고 한다.
- 이벤트에는 상태 변경 이전에 발생한 이벤트(사전이벤트) 와 상태 변경 후 발생한 이벤트(사후 이벤트) 두 그룹이 존재
  (ex) 사전 이벤트 - Form.Closing, 사후 이벤트 - Form.Closed
- 이벤트에는 'fire' 또는 'trigger'가 아닌 'raise'라는 용어 사용
- 이벤트 처리기로 사용할 새 대리자(delegate)를 수동으로 만들기보단 System.EventHandler<TEventArgs>를 사용하기

- CONSIDER using a subclass of EventArgs as the event argument, unless you are absolutely sure the event will never need to carry any data to the event handling method, in which case you can use the EventArgs type directly.
 -> data 전달이 확실하게 필요가 없을 경우에만 EventArgs를 사용할 것! EventArgs를 사용하여 API를 제공할 경우 호환성을 손상하지 않고 이벤트와 함께 데이터를 추가할 수 없다. EventArgs를 상속받은 subclass를 사용한다면, 처음엔 비어있을 수 있지만 나중에 속성을 추가할 수 있다!

- 각 이벤트를 발생시키려면 보호된 가상 메서드를 사용하자. (DO use a protected virtual method to raise each event.)
  (구조체, sealed class, static event 제외)
 => On_형태의 이름으로 재정의하는 함수 제공, 여기서 이벤트 호출.

- 이벤트를 발생시키는 protected method에는 하나의 매개 변수를 사용, 매개 변수는 'e'로 이름을 지정하고 이벤트 인수 클래스로 형식화 해야한다. (The parameter should be named e and should be typed as the event argument class.)

- 비정적(non-static) 이벤트를 발생시킬 때 sender를 null로 보내지 말 것.
- 정적(static) 이벤트의 경우엔 sender를 null로 전달.

- 이벤트 발생 시 이벤트 데이터 매개 변수로 null을 전달하지 말 것. 데이터가 없을 경우 EventArgs.Empty를 전달. 개발자는 이 매개 변수로 판단.

- 최종 사용자가 취소할 수 있는 이벤트를 발생시키는 것이 좋다.(사전 이벤트의 경우에만), 이벤트를 취소하게 하려면 CancelEventArgs 또는 서브클래스를 이벤트 인수로 사용(CancelEventArgs)

Custom Event Handler Design

 - 이벤트 처리기엔 void 반환 형식을 사용, 이벤트 처리기는 여러 개체에서 여러 이벤트 처리 메서드를 호출할 수 있다. 여러 메서드에서 여러 반환이 있을 수 있다.
 - 첫번째 매개 변수 형식은 object로 하며, 'sender'라고 한다.
 -  System.EventArgs 또는 이를 상속받아 구현하고 이를 두번째 매개변수로 하고 이름을 e로한다.
 - 이벤트 처리기엔 세 개 이상의 매개변수를 사용하지 않는다.

 

728x90
반응형