CodeGym /행동 /C# SELF /코드 조직화에 대한 권장사항

코드 조직화에 대한 권장사항

C# SELF
레벨 54 , 레슨 1
사용 가능

1. 이벤트 선언 및 이름 규칙

이벤트는 단순한 델리게이트가 아니다. 애플리케이션의 부분들 사이의 커뮤니케이션을 위한 별도 엔티티이며, 선언은 이해하기 쉬워야 한다.

적절한 델리게이트 타입 사용

99% 경우에는 표준 델리게이트를 사용하자:

  • EventHandler — 데이터가 없는 이벤트에.
  • EventHandler<TEventArgs> — 파라미터를 전달해야 할 때.

표준화는 코드 유지보수와 .NET 라이브러리와의 통합을 쉽게 만든다. EventHandler로 충분하면 굳이 새로운 델리게이트를 만들지 말자.


public event EventHandler SomethingHappened; // 데이터 없음
public event EventHandler<MyEventArgs> DataReceived; // 추가 데이터 있음

특별한 커스터마이징이 필요하면 자체 델리게이트를 선언할 수 있지만, 드문 경우다.

이벤트 이름 짓기

.NET에서는 이벤트를 과거형으로 이름 짓는다: Completed, Clicked, Changed, Received. 이는 이미 발생한 사실을 강조한다.

예:


public event EventHandler DataLoaded;    // 데이터가 로드되었음
public event EventHandler<MessageEventArgs> MessageReceived; // 메시지 수신됨
public event EventHandler Saving;        // 저장 프로세스가 시작됨

때로는 변경 "전" 이벤트를 위해 Changing 형태를 사용하여 개입할 기회를 준다.

2. 발행자 클래스 구성: 가상 메서드 OnEvent

항상 이벤트를 호출하는 protected virtual 메서드를 추가하라: 호출의 중앙화, 상속 시 확장성, 예측 가능한 동작을 제공한다.


public class FileLoader
{
    public event EventHandler<FileLoadedEventArgs> FileLoaded;

    protected virtual void OnFileLoaded(FileLoadedEventArgs e)
    {
        FileLoaded?.Invoke(this, e);
    }

    public void Load(string filename)
    {
        // ... 파일 로드 로직 ...
        OnFileLoaded(new FileLoadedEventArgs(filename));
    }
}

public class FileLoadedEventArgs : EventArgs
{
    public string FileName { get; }
    public FileLoadedEventArgs(string fileName) => FileName = fileName;
}

오직 OnFileLoaded만이 이벤트를 호출하도록 하라 — 이렇게 하면 유지보수와 테스트가 쉬워진다.

3. 구독 및 구독 해지 규칙: 생명주기, IDisposable

구독자의 수명주기가 발행자보다 짧다면, 구독자는 파괴되기 전에 반드시 구독을 해지해야 한다. IDisposable을 구현하고 Dispose()에서 해지하는 것이 편리하다.


public class TemporaryListener : IDisposable
{
    private readonly Publisher _publisher;

    public TemporaryListener(Publisher publisher)
    {
        _publisher = publisher;
        _publisher.DataReceived += HandleData;
    }

    private void HandleData(object sender, EventArgs e)
    {
        // 데이터 처리
    }

    public void Dispose()
    {
        _publisher.DataReceived -= HandleData;
    }
}

// using과 함께 사용:
using (var listener = new TemporaryListener(myPublisher))
{
    // listener는 여기서 이벤트를 수신함
}
// using 블록을 벗어나면 Dispose가 호출되어 구독 해지됨

구독 해지를 잊으면 발행자가 구독자 델리게이트에 대한 참조를 유지한다 — 메모리 누수와 "좀비 객체"가 발생한다.

4. 이벤트의 스레드 안전한 호출

멀티스레드 코드에서는 구독자가 이벤트 호출과 동시에 추가/제거될 수 있다. 이는 레이스와 NullReferenceException을 초래할 수 있다. 스레드 안전한 패턴을 사용하라: 델리게이트를 로컬 변수에 복사하자.


protected virtual void OnSomethingHappened()
{
    EventHandler handler = SomethingHappened;
    handler?.Invoke(this, EventArgs.Empty);
}

C# 6+에서는 다음과 같이 충분하다:


SomethingHappened?.Invoke(this, EventArgs.Empty);

5. EventArgsobject 대신 사용

데이터를 object나 클래스 필드를 통해 전달하지 말라. EventArgs의 상속을 통해 강한 타입을 사용하자.


public class DownloadCompletedEventArgs : EventArgs
{
    public string FileName { get; }
    public long Size { get; }
    public DownloadCompletedEventArgs(string fileName, long size)
    {
        FileName = fileName;
        Size = size;
    }
}

public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;

6. 이벤트와 구독자 문서화

이벤트가 언제 호출되는지, EventArgs 필드의 의미, 구독 해지가 필요한지와 시점을 문서화하라.


/// <summary>
/// 데이터가 성공적으로 로드된 후 발생하는 이벤트입니다.
/// </summary>
public event EventHandler<DataLoadedEventArgs> DataLoaded;

7. 이벤트 아키텍처에 대한 요약 권장사항

책임 분리

발행자는 사실만 알리고, 구독자는 언제 구독하고 해지할지 스스로 결정해야 한다.

이벤트 과다 발생 피하기

필요 없이 같은 이벤트를 초당 수십 번 생성하지 말라 — 과도한 부하를 초래한다.

양방향 통신에 이벤트를 남용하지 말기

이벤트는 "하나가 알리고 여러가 듣는" 패턴에 적합하다. 양방향 통신이 필요하면 인터페이스, 콜백 또는 다른 메커니즘을 고려하라.

클래스에 구독자 리스트를 보관하지 말기

구독자를 명시적으로 참조하지 말라 — 이벤트와 델리게이트가 자동으로 이를 처리한다.

8. 전형적인 안티패턴

비타입 이벤트


public event Action<object> SomethingHappened; // 내부가 무엇인지 불분명

나쁨: 타입 정보가 깨지고 캐스트가 필요하며 유지보수성이 떨어진다.

구독 해지 잊기


public class ShortLivedListener
{
    public ShortLivedListener(Publisher p) =>
        p.DataReceived += DoWork;

    private void DoWork(object sender, EventArgs e) { /* ... */ }
    // Dispose 없음, 구독 해지 없음 => 좀비 객체 발생!
}

SRP 위반

클래스가 동시에 발행자, 구독자, 핸들러 역할을 모두 하면 책임이 혼재된다 — 책임을 분리하라.

9. 인터뷰와 프로젝트에서의 실용적 적용

퍼블리시-서브스크라이브 패턴을 사용하는 많은 프로젝트에서 이벤트를 잘 조직하는 것은 확장성과 유지보수성의 핵심이다. 인터뷰에서 자주 요청받는 것:

  • 올바른 타입화를 가진 이벤트 시스템 구현,
  • 구독자의 생명주기 관리 보여주기,
  • 이벤트의 스레드 안전한 호출 설명하기.

깔끔하고 문서화된, 올바르게 조직된 이벤트 코드는 지원자들 사이에서 즉시 눈에 띈다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION