내 메소드에서 빈 콜렉션을 반복해야합니까?


40

메서드의 매개 변수를 반복하는 foreach 루프 내에서 모든 논리를 수행하는 방법이 있습니다.

public IEnumerable<TransformedNode> TransformNodes(IEnumerable<Node> nodes)
{
    foreach(var node in nodes)
    {
        // yadda yadda yadda
        yield return transformedNode;
    }
}

이 경우 빈 컬렉션을 보내면 빈 컬렉션이 생성되지만 이것이 현명하지 않은지 궁금합니다.

내 논리는 누군가 누군가 가이 메소드를 호출하면 데이터를 전달하려고하며 잘못된 환경에서 빈 컬렉션 만 내 메소드에 전달한다는 것입니다.

이 동작을 잡아서 예외를 던져야합니까, 아니면 빈 컬렉션을 반환하는 것이 가장 좋은 방법입니까?


43
"누군가이 메소드를 호출하면 데이터를 전달할 것"이라는 가정이 맞습니까? 어쩌면 그들은 결과를 얻기 위해 손에 든 것을 전달했을 것입니다.
SpaceTrucker

7
BTW : .NET (및 .NET에서 이름을 얻은 SQL)에서는 일반적으로 "select"라고하지만 "transform"방법은 일반적으로 "map"이라고합니다. "Transform"은 C ++에서 호출하는 이름이지만, 인식 할 수있는 일반적인 이름은 아닙니다.
Jörg W Mittag

25
컬렉션이 null비어 있지만 비어 있지 않으면 던질 것 입니다.
코드 InChaos

21
@NickUdell-항상 빈 컬렉션을 코드에 전달합니다. 코드를 사용하기 전에 컬렉션에 아무것도 추가 할 수없는 경우 특수 분기 논리를 작성하는 것보다 코드가 동일하게 작동하는 것이 더 쉽습니다.
theMayer

7
컬렉션이 비어있는 경우 오류를 확인하고 throw하면 호출자가 빈 컬렉션을 확인하여 메서드에 전달하지 않아야합니다. 비어있는 컬렉션이 귀찮 으면 유틸리티 메소드에 도달하기 전에 이미 확인해야합니다. 이제 두 개의 중복 검사가 있습니다.
Lie Ryan

답변:


175

유틸리티 메소드는 빈 컬렉션을 버리지 않아야합니다. 당신의 API 클라이언트는 당신을 미워할 것입니다.

컬렉션은 비어있을 수 있습니다. "비워 둘 필요가없는 컬렉션"은 개념적으로 작업하기가 훨씬 더 어렵습니다.

빈 컬렉션을 변환하면 빈 컬렉션이 분명해집니다. (파라미터 자체를 반환하여 쓰레기를 절약 할 수도 있습니다.)

모듈이 이미 무언가로 채워 졌거나 채워지지 않은 것들의 목록을 유지하는 많은 상황이 있습니다. 호출 할 때마다 공허함을 확인해야하는 transform것은 성가 시며 단순하고 우아한 알고리즘을 추악한 혼란으로 바꿀 수 있습니다.

실용적 방법은 항상 입력에있어 자유롭고 출력에 보수적이어야한다.

이 모든 이유로, 하나님을 위해 빈 수집 물을 올바르게 취급하십시오. 당신이 원하는 것보다 더 나은 것을 알고 있다고 생각 하는 도우미 모듈보다 더 무서운 것은 없습니다 .


14
+1-(가능한 경우 더). 나는 심지어 null빈 컬렉션에 합류하기까지 갈 수도 있습니다 . 암시 적 제한이있는 기능은 고통입니다.
Telastyn

6
"아니면 안됩니다"... 받아들이지 말아야하거나 예외를 던져서는 안됩니까?
벤 Aaronson

16
@Andy- 유틸리티 방법 은 아닙니다 . 비즈니스 방식이나 유사한 구체적인 행동 일 것입니다.
Telastyn

38
빈 컬렉션을 재활용하여 재활용하는 것은 좋은 생각이 아닙니다. 당신은 단지 약간의 메모리를 절약하고 있습니다; 발신자에게 예기치 않은 동작이 발생할 수 있습니다. 약간 고안된 예 : Populate1(input); output1 = TransformNodes(input); Populate2(input); output2 = TransformNodes(input); Populate1이 콜렉션을 비워두고 첫 번째 TransformNodes 호출에 대해 리턴 한 경우 output1과 input은 동일한 콜렉션이되고 콜렉션에 노드를 넣은 경우 Populaten2가 호출되면 두 번째로 끝납니다. output2의 입력 세트.
Dan Neely

6
@Andy 그럼에도 불구하고 인수가 비어 있지 않아야하는 경우-처리 할 데이터가없는 것이 오류 인 경우- 인수를 정렬 할 때 호출자가 이를 확인 하는 책임이 있다고 생각합니다 . 이 방법을 더 우아하게 유지할뿐만 아니라 더 나은 오류 메시지 (더 나은 컨텍스트)를 생성하여 오류없이 빠르게 발생시킬 수 있습니다. 다운 스트림 클래스가 호출자의 불변을 확인하는 것은 이치에 맞지 않습니다.
Andrzej Doyle

26

이에 대한 답을 결정하는 두 가지 중요한 질문이 있습니다.

  1. 빈 컬렉션 ( null 포함 )을 전달하면 함수가 의미 있고 논리적 인 것을 반환 할 수 있습니까 ?
  2. 이 응용 프로그램 / 라이브러리 / 팀의 일반적인 프로그래밍 스타일은 무엇입니까? (구체적으로 FP는 어떻습니까?)

1. 의미있는 수익

본질적으로 의미있는 것을 반환 할 수 있다면 예외를 던지지 마십시오. 발신자가 결과를 처리하도록합니다. 그래서 당신의 기능이라면 ...

  • 컬렉션의 요소 수를 세고 0을 반환합니다. 쉬웠습니다.
  • 특정 기준과 일치하는 요소를 검색하고 빈 컬렉션을 반환합니다. 아무것도 던지지 마십시오. 호출자는 방대한 컬렉션을 보유하고있을 수 있으며 일부는 비어 있습니다. 호출자는 컬렉션에서 일치하는 요소를 원합니다. 예외는 발신자의 삶을 더욱 어렵게 만듭니다.
  • 목록에서 최대 / 최소 / 최 적합 기준을 찾고 있습니다. 죄송합니다. 스타일 질문에 따라 예외를 throw하거나 null을 반환 할 수 있습니다. 나는 null (매우 FP 사람)을 싫어하지만 여기서 더 의미가 있으며 자신의 코드 내에서 예기치 않은 오류에 대한 예외를 예약 할 수 있습니다. 호출자가 null을 확인하지 않으면 상당히 명확한 예외가 발생합니다. 하지만 당신은 그들에게 맡깁니다.
  • 컬렉션의 n 번째 항목 또는 첫 번째 / 마지막 n 개의 항목을 요청합니다. 이것은 예외에 대한 가장 좋은 경우이며 발신자에게 혼란과 어려움을 유발할 가능성이 가장 적은 경우입니다. 좋은 예가 된 정지 할 수 널 (null) 당신과 당신의 팀이 이전 점에 주어진 모든 이유에 대해, 그 검사에 사용하는 경우, 그러나 이것은 던지는 아직 가장 유효한 경우가 DudeYou 알고 YouShouldCheckTheSizeFirst의 예외. 귀하의 스타일이 더 기능적이라면 null 이거나 내 스타일 답변 을 읽으십시오 .

일반적으로 내 FP 편견은 "의미있는 것을 반환합니다"라고 말하며이 경우 null 은 유효한 의미를 가질 수 있습니다.

2. 스타일

일반 코드 (또는 프로젝트 코드 또는 팀 코드)가 기능적 스타일을 선호합니까? 그렇지 않은 경우 예외가 예상되고 처리됩니다. 그렇다면 옵션 유형 반환을 고려하십시오 . 옵션 유형을 사용하면 의미있는 답변 또는 None / Nothing을 반환합니다 . 위의 세 번째 예에서 FP 스타일의 좋은 답변은 없습니다 . 이 함수가 옵션 유형 신호를 의미있는 응답보다 명확하게 호출자에게 반환한다는 사실은 불가능할 수 있으며 호출자가이를 처리 할 준비가되어 있어야합니다. 나는 그것이 발신자에게 더 많은 옵션을 제공한다고 생각합니다 (당신이 말장난을 용서한다면).

모든 멋진 닷넷 아이들이 이런 종류의 일을하는 곳 F #은 그러나 C #은 않습니다 지원 이 스타일을.

tl; dr

자신의 코드 경로에 예상치 못한 오류에 대한 예외를 유지하십시오. 다른 사람의 예측 및 합법적 인 입력은 아닙니다.


"최적 적합"등 : 일부 기준과 일치하는 컬렉션에서 가장 적합한 개체를 찾는 방법이있을 수 있습니다. 이 방법은 비어 있지 않은 컬렉션에 대해 동일한 문제가 있습니다. 기준에 맞는 것이 없으므로 비어있는 선택에 대해 걱정할 필요가 없습니다.
gnasher729

1
아니요, 그렇기 때문에“매우 맞습니다”가 아니라“매치”가 아닙니다. 이 문구는 정확히 일치하는 것이 아니라 가장 가까운 근사치를 의미합니다. 가장 크고 작은 옵션은 단서 였을 것입니다. "최적의 적합"만 요청 된 경우 1 명 이상의 회원이있는 컬렉션은 회원을 반환 할 수 있어야합니다. 멤버가 하나 뿐인 경우 가장 적합합니다.
itsbruce

1
대부분의 경우 "널"을 반환하면 예외가 발생하는 것보다 나쁩니다. 그러나 빈 컬렉션을 반환하는 것은 의미가 있습니다.
Ian

1
단일 품목 (최소 / 최대 / 머리 / 꼬리)을 반품해야하는 경우에는 해당되지 않습니다. null에 관해서는, 내가 말했듯이 나는 그것을 싫어하고 언어가없는 언어를 선호하지만 언어가있는 곳에서는 언어를 다루고 코드를 전달할 수 있어야합니다. 로컬 코딩 규칙에 따라 적절할 수 있습니다.
itsbruce

빈 입력과 관련이없는 예제는 없습니다. 응답이 "아무것도 아닌"상황의 특별한 경우 일뿐 입니다. 빈 컬렉션을 "처리"하는 방법에 대한 OP의 질문에 대답해야합니다.
djechlin

18

언제나 그렇듯이 그것은 다릅니다.

컬렉션이 비어있는 것이 중요 합니까 ?
대부분의 컬렉션 처리 코드는 아마도 "아니오"라고 말할 것입니다. 컬렉션 에는 0을 포함하여 여러 항목 이 포함될 수 있습니다 .

이제 아이템이없는 "유효하지 않은"컬렉션이 있다면, 그것은 새로운 요구 사항이며 그것에 대해 어떻게해야할지 결정해야합니다.

데이터베이스 세계에서 몇 가지 테스트 로직을 차용하십시오 : 0 개 항목, 1 개 항목 및 2 개 항목에 대한 테스트 . 가장 중요한 경우를 충족시킵니다 (나쁜 형태의 내부 또는 직교 결합 조건을 플러시).


컬렉션에 항목이없는 것이 유효하지 않은 경우 이상적으로는 인수가 a java.util.Collection가 아니라 사용자 정의 com.foo.util.NonEmptyCollection클래스가되어 불변성을 일관되게 보존하고 시작하기에 유효하지 않은 상태가되는 것을 막을 수 있습니다.
Andrzej Doyle

3
이 질문은 [c #]로 태그되었습니다
Nick Udell

@NickUdell은 C #에서 객체 지향 프로그래밍 또는 중첩 네임 스페이스 개념을 지원하지 않습니까? 틸.
John Dvorak

2
그렇습니다. Andrzej가 혼란스러워해야했기 때문에 내 의견은 명확했습니다.
Nick Udell

"거의 확실하다"보다 "의존한다"를 강조하기위한 -1. 농담 없음-잘못된 것을 전달하면 여기가 손상됩니다.
djechlin

11

좋은 디자인의 문제로, 입력의 변화를 실용적이거나 가능한 한 많이 받아들이십시오. 예외는한다 (받아 들일 수없는 입력이되게 또는 처리하는 동안 예기치 않은 오류가 발생되는) 경우에 슬로우 수 및 프로그램이 예측 가능한 방식으로 계속할 수 없습니다 결과.

이 경우 빈 컬렉션이 표시되고 코드에서 이미 수집 된 컬렉션을 처리해야합니다. 코드에서 예외가 발생하면 모든 것을 위반하는 것입니다. 이것은 수학에서 0에 0을 곱하는 것과 비슷합니다. 중복 적이지만 작동 방식에 따라 반드시 필요합니다.

이제 null 컬렉션 인수로 넘어갑니다. 이 경우 널 콜렉션은 프로그래밍 실수입니다. 프로그래머가 변수를 지정하는 것을 잊었습니다. 예외적으로 출력을 처리 할 수 ​​없으므로 예상치 못한 동작이 발생할 수 있기 때문에 예외가 발생할 수 있습니다. 이것은 수학에서 0으로 나누는 것과 비슷합니다. 그것은 완전히 의미가 없습니다.


대담한 진술은 전체적으로 매우 나쁜 조언입니다. 나는 이것이 사실이라고 생각하지만, 당신은이 상황에서 무엇이 특별한 지 설명해야합니다. (자동 실패를 조장하기 때문에 일반적으로 나쁜 조언입니다.)
djechlin

@AAA-분명히 우리는 여기서 소프트웨어를 디자인하는 방법에 관한 책을 쓰지 않지만, 내가 말하는 것을 정말로 이해한다면 전혀 나쁜 조언이라고 생각하지 않습니다. 내 요점은 잘못된 입력이 모호하거나 임의의 출력을 생성한다는 것입니다. 예외를 던져야합니다. 출력이 완전히 예측 가능한 경우 (여기에서와 같이) 예외를 발생시키는 것은 임의적입니다. 핵심은 프로그램에서 임의의 결정을 내리는 것입니다. 예측할 수없는 동작이 발생하기 때문입니다.
메이어

그러나 그것은 당신의 첫 번째 문장이고 유일한 강조된 것입니다 ... 아직 아무도 당신이 말하는 것을 이해하지 못합니다. (나는 여기에서 너무 공격적이지 않으려 고 노력하고있다. 여기서 우리가해야 할 것 중 하나는 글을 쓰고 의사 소통하는 법을 배우는
것인데,

10

올바른 솔루션은 기능을 독립적으로보고있을 때보기가 훨씬 어렵습니다. 더 큰 문제의 일부로 기능을 고려하십시오 . 그 예에 대한 한 가지 가능한 해결책은 다음과 같습니다 (스칼라)

input.split("\\D")
.filterNot (_.isEmpty)
.map (_.toInt)
.filter (x => x >= 1000 && x <= 9999)

먼저 문자열을 숫자가 아닌 숫자로 나누고 빈 문자열을 필터링하고 문자열을 정수로 변환 한 다음 4 자리 숫자 만 유지하도록 필터링합니다. 함수가 map (_.toInt)파이프 라인에 있을 수 있습니다 .

파이프 라인의 각 단계는 빈 문자열 또는 빈 컬렉션 만 처리하므로이 코드는 매우 간단합니다. 처음에 빈 문자열을 넣으면 끝에 빈 목록이 나타납니다. null모든 통화 후에 중지하고 확인 하거나 예외를 요구할 필요는 없습니다 .

물론, 그것은 빈 출력 목록이 하나 이상의 의미가 없다고 가정합니다. 빈 입력으로 인한 빈 출력과 변환 자체로 인한 빈 출력을 구별 해야하는 경우 상황이 완전히 변경됩니다.


매우 효과적인 예인 경우 +1 (다른 좋은 답변이 없음).
djechlin

2

이 질문은 실제로 예외에 관한 것입니다. 그런 식으로보고 빈 컬렉션을 구현 세부 사항으로 무시하면 대답은 간단합니다.

1) 메소드가 진행할 수 없을 때 예외를 발생시켜야한다 : 지정된 작업을 수행 할 수 없거나 적절한 값을 반환한다.

2) 방법은 실패에도 불구하고 진행될 수있을 때 예외를 잡아야한다.

따라서 도우미 메서드는 "유용한"것이 아니어야 하며 빈 컬렉션으로 작업을 수행 할 수 없는 경우 가 아니면 예외를 throw 해야합니다. 호출자가 결과를 처리 할 수 ​​있는지 판별하도록하십시오.

빈 컬렉션을 반환하는지 또는 null을 반환하는지 여부는 조금 더 어렵지만 그리 많지는 않습니다. 가능하면 nullable 컬렉션을 피해야합니다. nullable 컬렉션의 목적은 (SQL에서와 같이) 정보가 없음을 표시하는 것입니다. 예를 들어 누군가가 있는지 모르지만 모르는 경우 자식 컬렉션이 null 일 수 있습니다. 그들이하지 않는 것을 알고있다. 그러나 그것이 어떤 이유로 중요하다면, 그것을 추적하는 데 추가 변수가 가치가 있습니다.


1

이 방법의 이름은 TransformNodes입니다. 빈 컬렉션을 입력으로 사용하는 경우 빈 컬렉션을 다시 가져 오는 것은 자연스럽고 직관적이며 수학적으로 적합합니다.

메서드의 이름을 지정 Max하고 최대 요소를 반환하도록 설계된 NoSuchElementException경우 최대 값이 수학적으로 의미가 없으므로 빈 컬렉션 을 던지는 것이 당연합니다 .

메소드가 JoinSqlColumnNamesSQL 쿼리에서 사용하기 위해 요소가 쉼표로 결합 된 문자열을 리턴하도록 이름 지정 되고 설계된 IllegalArgumentException경우 호출자가 사용한 경우 결국 SQL 오류가 발생하므로 빈 콜렉션 을 던지는 것이 좋습니다. 추가 검사없이 직접 SQL 쿼리의 문자열을 반환하고 반환 된 빈 문자열을 확인하는 대신 실제로 빈 컬렉션을 확인해야합니다.


Max아무것도 음의 무한대가 아닙니다.
djechlin

0

뒤로 물러서서 값 배열의 산술 평균을 계산하는 다른 예제를 사용하십시오.

입력 배열이 비어 있거나 호출자 인 경우 호출자의 요청을 합리적으로 이행 할 수 있습니까? 아니요. 옵션은 무엇입니까? 글쎄, 당신은 할 수 있습니다 :

  • 오류를 제시 / 반환 / 투시합니다. 해당 오류 클래스에 대한 코드베이스의 규칙 사용
  • 0과 같은 값이 반환 될 것이라고 문서화
  • 지정된 유효하지 않은 값이 반환 될 것임을 문서화합니다 (예 : NaN)
  • 매직 값이 반환 될 것임을 문서화하십시오 (예 : 유형에 대한 최소 또는 최대 또는 희망적으로 표시하는 값)
  • 결과가 지정되지 않았다고 선언
  • 액션이 정의되지 않았다고 선언
  • 기타

그들이 당신에게 잘못된 입력을 주었고 요청을 완료 할 수 없다면 오류를 낸다고 말합니다. 나는 프로그램의 요구 사항을 이해하기 위해 첫날부터 어려운 오류를 의미합니다. 결국, 함수가 응답 할 위치에 있지 않습니다. 작업 실패 할 수있는 경우 (예 : 파일 복사) API에서 처리 할 수있는 오류를 제공해야합니다.

따라서 라이브러리가 잘못된 요청 및 실패 할 수있는 요청을 처리하는 방법을 정의 할 수 있습니다.

코드가 이러한 클래스의 오류를 처리하는 방식에서 일관성을 유지하는 것이 매우 중요합니다.

다음 범주는 라이브러리가 넌센스 요청을 처리하는 방법을 결정하는 것입니다. 귀하의 것과 유사한 예제로 돌아 가기-파일이 경로에 존재하는지 여부를 결정하는 함수를 사용하십시오 : bool FileExistsAtPath(String). 클라이언트가 빈 문자열을 전달하면이 시나리오를 어떻게 처리합니까? 빈 배열이나 null 배열은 어떻게 전달 void SaveDocuments(Array<Document>)됩니까? 라이브러리 / 코드베이스를 결정하고 일관성을 유지하십시오.. 이러한 경우 오류를 고려하고 클라이언트가 넌센스 요청을 오류로 표시하여 어설 션을 통해 금지합니다. 어떤 사람들은 그 아이디어 / 행동에 강하게 저항 할 것입니다. 이 오류 감지가 매우 유용합니다. 프로그램에서 문제를 찾는 데 매우 유용합니다. 문제를 일으키는 프로그램의 위치가 양호합니다. 프로그램은 훨씬 명확하고 정확하며 (코드베이스의 발전을 고려) 아무 것도하지 않는 함수 내에서 사이클을 태우지 않습니다. 이 방법으로 코드가 더 작고 깨끗해지며 일반적으로 확인이 문제가 발생할 수있는 위치로 푸시됩니다.


6
마지막 예에서, 말도 안되는 것을 결정하는 것은 그 기능의 일이 아닙니다. 따라서 빈 문자열은 false를 반환해야합니다. 실제로, 그것은 File.Exists 가 취하는 정확한 접근법 입니다.
theMayer

@ rmayer06 그것은 그 일이다. 참조 된 함수는 null, 빈 문자열을 허용하고 문자열에서 유효하지 않은 문자를 검사하고 문자열 잘림을 수행하며 파일 시스템 호출을 수행해야하며 환경을 쿼리해야 할 수도 있습니다. 비용이 많이 들며, 정확하게 정확성 (IMO)을 흐리게합니다. 나는 if (!FileExists(path)) { ...create it!!!... }많은 버그를 보았습니다. 커밋 전에 잡히는 버그 중 많은 부분이 정확성의 선이 흐려지지 않았습니다.
저스틴

2
함수의 이름이 File.ThrowExceptionIfPathIsInvalid 인 경우에 동의하지만, 올바른 마음으로 누가 그러한 함수를 호출합니까?
theMayer

평균 0 개의 항목은 이미 함수가 정의 된 방식 때문에 NaN을 반환하거나 0으로 나누기 예외를 발생시킵니다. 존재할 수없는 이름의 파일이 존재하지 않거나 이미 오류를 발생시킵니다. 이러한 경우를 특별히 처리해야 할 강력한 이유는 없습니다.
cHao

파일을 읽거나 쓰기 전에 파일의 존재를 확인하는 전체 개념이 어쨌든 결함이 있다고 주장 할 수도 있습니다. (내재 된 경쟁 조건이 있습니다.) 계속해서 파일을 읽거나 읽기 위해 또는 쓰기 전용 설정을 사용하여 파일을 열어 보는 것이 좋습니다. 파일 이름이 유효하지 않거나 존재하지 않으면 (또는 쓰는 경우) 이미 실패합니다.
cHao

-3

경험상 표준 기능은 가장 광범위한 입력 목록을 수용하고 이에 대한 피드백을 제공 할 수 있어야하며, 프로그래머가 설계자가 계획하지 않은 방식으로 기능을 사용하는 많은 예가 있습니다. 빈 컬렉션뿐만 아니라 광범위한 입력 유형을 수용하고 피드백을 정상적으로 반환 할 수 있어야합니다. 입력에서 수행 된 모든 작업의 ​​오류 개체조차도 ...


디자이너는 컬렉션이 항상 전달되고 해당 컬렉션을 반복적으로 수행한다고 가정하는 것은 절대 잘못입니다. 수신 된 인수가 예상 데이터 유형을 준수하는지 확인해야합니다.
Clement Mark-Aaba

3
"광범위한 입력 유형"은 여기서 관련이 없습니다. 질문 에는 강력한 유형의 언어 인 c # 태그가 지정 됩니다. 즉, 컴파일러는 입력이 모음임을 보증합니다
gnat

친절하게 구글 "거짓의 규칙", 내 대답은 프로그래머로서 당신이 예기치 않은 또는 잘못된 발생을위한 공간을 만들 때, 이들 중 하나가 잘못 함수 인수를 전달할 수 있다는 일반적인 경고였습니다 ...
Clement Mark-Aaba

1
이것은 잘못되었거나 긴장된 것입니다. writePaycheckToEmployee음수를 입력으로 받아들이지 말아야합니다. 그러나 "피드백"이 "다음에 일어날 수있는 일을한다"는 것을 의미한다면, 모든 기능은 다음에 할 일을합니다.
djechlin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.