방프리

24.01.02 Chapter2. API 설계 (Item 26) 본문

C#/More Effective C#

24.01.02 Chapter2. API 설계 (Item 26)

방프리 2024. 1. 2. 23:12

Item 26 : 지역 함수를 사용해서 반복자와 비동기 메서드의 오류를 즉시 보고하라

 

메서드에서 어떤 내용을 수행하다가 발생되는 예외는 즉시 발생되어 처리되어야 한다.
하지만 반복자 메서드와 비동기 메서드는 예외가 생겼을 때 바로 출력되지 않고 정상적으로 처리되는 듯 넘기고
실제 사용하는 부분에서 예외를 발생시킨다. 
이는 반복자 메서드와 비동기 메서드가 코드를 재구성하기 때문이다.
즉, 예외가 발생되더라도 어느 시점에서 발생된 예외인지 추적이 어렵다.

//반복자 메서드에서 예외가 발생하는 순간
public IEnumerable<T> GenerateSample<T>(IEnumerable<T> sequence, int sampleFrequency)
{
    if (sequence == null)
    {
        throw new ArgumentException("Source sequence cannot be null", 
            paramName: nameof(sequence));
    }
    if (sampleFrequency < 1)
    {
        throw new ArgumentException("Sample frequency must be a positive integer",
            paramName: nameof(sampleFrequency));
    }
    
    int index = 0;
    foreach (var item in sequence)
    {
        if (index % sampleFactory == 0)
        {
            yield return item;
        }
    }
}

var samples = processor.GenerateSample(fullSequence, -8);
foreach (var item in samples)	//예외발생
{
}

이럴 땐 함수의 역할을 쪼개는 것이 제일 좋다. 매개변수의 검증과 수행부분을 나누는 것이다. 
하지만 검증부분을 앞으로 분할시켰기 때문에 실제 수행부분은 예외 검증을 하지 않기 때문에 외부로 노출되서는 안된다.
저자는 최소 접근자가 private라고 한다. 하지만 안전하게 하려면 지역함수로 구현하는 것이 좋다.

public IEnumerable<T> GenerateSample<T>(IEnumerable<T> sequence, int sampleFrequency)
{
    if (sequence == null)
    {
        throw new ArgumentException("Source sequence cannot be null", 
            paramName: nameof(sequence));
    }
    if (sampleFrequency < 1)
    {
        throw new ArgumentException("Sample frequency must be a positive integer",
            paramName: nameof(sampleFrequency));
    }
    
    return generateSampleImpl();
    
    IEnumerable<T> generateSampleImpl()
    {
        int index = 0;
        foreach (var item in sequence)
        {
            if (index % sampleFactory == 0)
            {
                yield return item;
            }
        }
    }
}

비동기 메서드도 대기일 때에만 예외 발생을 감지할 수 있기 때문에 반복자메서드와 동일하게 검증과 수행을 나누는 것이
좋다.

public Task<string> LoadMessageFinal(string userName)
{
    if (string.IsNullOrWhiteSpace(userName) == true)
    {
        throw new ArgumentException(
            message: "This must be a valid user",
            paramName: nameof(userName));
    }
    
    return loadMessageImpl();
    
    async Task<string> loadMessageImpl()
    {
        var settings = await context.LoadUser(userName);
        var message = settings.Message ?? "No message";
        return message
    }
}

지역 함수는 람다 표현식과 비슷한 모습을 보이지만 람다 표현식이 더 복잡한 구조를 지니고 있다.
지역함수는 private 메서드로도 구현이 되지만 람다 표현식은 객체를 인스턴스화를 하기 때문이다.

Comments