For-if 반 패턴


9

이 블로그 게시물 에서 For -if 안티 패턴에 대해 읽고 있었는데 왜 안티 패턴인지 이해하지 못했습니다.

foreach (string filename in Directory.GetFiles("."))
{
    if (filename.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))
    {
        return new StreamReader(filename);
    }
}

질문 1:

return new StreamReader(filename);내부 때문 for loop입니까? 또는 for이 경우 루프가 필요하지 않다는 사실 ?

블로그 작성자가 덜 미친 버전을 지적했듯이 다음과 같습니다.

if (File.Exists("desktop.ini"))
{
    return new StreamReader("desktop.ini");
} 

파일을 만들기 전에 파일을 삭제하면가 표시되므로 경쟁 조건 StreamReader이 발생합니다 File­Not­Found­Exception.

질문 2 :

두 번째 예제를 수정하려면 if 문을 사용하지 않고 다시 작성하고 대신 StreamReadertry-catch 블록으로 둘러싸고 블록 File­Not­Found­Exception을 처리하면 catch그에 따라 블록 에서 처리 합니까?



1
@ amon-고맙지 만 추가 포인트는 없지만 기사의 내용과 수정 방법을 이해하려고합니다.

3
나는 그렇게 많이 생각하지 않을 것입니다. 블로그 포스터는 아무도 쓰지 않는 이디 오틱 코드를 ​​만들고 있으며, 이름을 지정하고이를 안티 패턴이라고 부릅니다. 또 다른 것이 있습니다 : "이것은 무의미한 할당 패턴이라고합니다 : x = x;이 작업은 항상 수행되는 것을 봅니다. 멍청합니다. 블로그에 대해 글을 쓸 것 같습니다."
Martin Maat

4
To fix the second example, would you re-write it without the if statement, and instead surround the StreamReader with a try-catch block, and if it throws a File­Not­Found­Exception you handle it in the catch block accordingly?-네, 제가하고자하는 것입니다. 경쟁 조건을 해결하는 것이 "제어 흐름으로서의 예외"개념보다 더 중요하며, 우아하고 깔끔하게 해결합니다.
Robert Harvey

1
파일 중 어느 것도 주어진 기준을 충족하지 않으면 어떻게됩니까? 반환 null합니까? 당신은 일반적으로 코드를 정리하기 위해 LINQ를 사용할 수있는 코드를 귀하의 예를 다음과 같습니다 return Directory.GetFiles(".").FirstOrDefault(fileName => fileName.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))?.Select(fileName => new StreamReader(filename)); 주목하라 ?.두 LINQ 통화 사이의 운영자입니다. 또한 사람들은 이와 같은 객체 생성이 LINQ를 가장 적절하게 사용하는 것이 아니라고 생각하지만 여기서는 괜찮습니다. 이것은 귀하의 질문에 대한 답변이 아니지만, 그중 일부에 혼란을줍니다.
Panzercrisis

답변:


7

이것은 다음과 같은 형태를 취하는 반 패턴입니다.

loop over a set of values
   if current value meets a condition
       do something with value
   end
end

로 대체 될 수 있습니다

do something with value

이에 대한 전형적인 예는 다음과 같습니다.

for (var i=0; i < 5; i++)
{
    switch (i)
        case 1:
            doSomethingWith(1);
            break;
        case 2:
            doSomethingWith(2);
            break;
        case 3:
            doSomethingWith(4);
            break;
        case 4:
            doSomethingWith(4);
            break;
    }
}

다음이 제대로 작동하면 :

doSomethingWith(1);
doSomethingWith(2);
doSomethingWith(3);
doSomethingWith(4);

당신은 자신 루프를 수행하고 찾을 경우 if또는 switch, 다음 중지하고 당신이 무엇을하고 있는지에 대해 생각합니다. 당신은 일을 지나치게 복잡하게 만들고 전체 루프와 테스트를 간단한 "그냥 해"라인으로 대체 할 수 있습니다. 그러나 때로는 루프를 수행해야합니다 (예 : 둘 이상의 항목이 하나의 조건과 일치 할 수 있음).이 경우 패턴이 좋습니다.

이것이 안티 패턴 인 이유입니다. "루프 앤 테스트"패턴을 취하고 남용합니다.

두 번째 질문에 관해서는 그렇습니다. "try do"패턴은 코드가 테스트중인 항목의 상태를 변경할 수있는 전체 장치의 유일한 스레드가 아닌 모든 상황에서 "test then do"패턴보다 강력합니다.

이 코드의 문제점 :

if (File.Exists("desktop.ini"))
{
    return new StreamReader("desktop.ini");
}

사이의 시간이다 File.Exists하고 StreamReader해당 파일을 열려고 시도, 다른 스레드 나 프로세스가 파일을 삭제할 수 있습니다. 따라서 예외가 발생합니다. 따라서 해당 예외는 다음과 같은 방법으로 방지해야합니다.

try
{
    return new StreamReader("desktop.ini");
}
catch (File­Not­Found­Exception)
{
    return null; // or whatever
}

@Flater가 좋은 지적을합니까? 이것 자체가 반 패턴입니까? 제어 흐름으로 예외를 사용하고 있습니까?

코드가 다음과 같은 것을 읽는다면 :

try
{
    if (!File.Exists("desktop.ini")
    {
        throw new IniFileMissingException();
        return new StreamReader("desktop.ini");
    }
}
catch (IniFileMissingException)
{
    return null;
}

그런 다음 실제로 예외를 영광스러운 goto로 사용하고 실제로 반 패턴이 될 것입니다. 그러나이 경우 우리는 새로운 스트림을 만드는 바람직하지 않은 동작을 해결하기 위해 노력하고 있습니다. 이는 안티 패턴의 예가 아닙니다. 그러나 그것은 그 반 패턴을 해결하는 예제입니다.

물론 우리가 정말로 원하는 것은 스트림을 만드는 더 우아한 방법입니다. 다음과 같은 것 :

return TryCreateStream("desktop.ini", out var stream) ? stream : null;

그리고이 try catch코드를 많이 사용하는 경우이 코드를 유틸리티 메소드로 래핑하는 것이 좋습니다 .


1
@Flater, 나는 그것이 이상적이지 않다는 것에 동의하는 경향이 있습니다 (이의를 제기하는 반 패턴의 예입니다). 코드는 역학이 아닌 의도를 보여 주어야합니다. 나는 스칼라의 같은 것을 선호하는 것 그래서 Try, 예를 return Try(() => new StreamReader("desktop.ini")).OrElse(null);하지만 우리는 어설픈 버전으로 작업을해야하므로 C #은 (제 3 자 라이브러리를 사용하지 않고) 그 구조를 지원하지 않습니다.
David Arno

1
@Flater, 나는 예외가 예외적이어야한다는 것에 전적으로 동의합니다. 그러나 그들은 거의 없습니다. 예를 들어, 존재하지 않는 파일은 예외적 File.Open이지 않지만 파일을 찾을 수 없으면 던집니다. 예외가 아닌 예외가 이미 발생했기 때문에 제어 흐름의 일부로 예외를 포착하는 것이 필수적입니다 (단, 앱이 단순히 중단되도록하지 않는 한).
David Arno

1
@Flater : 두 번째 코드 샘플은 파일을 여는 올바른 방법입니다. 다른 것은 경쟁 조건을 도입합니다. 파일 존재 여부를 확인하더라도 해당 확인과 실제로 열 때마다 해당 파일을 사용할 수 없게되고 Open () 호출이 폭발합니다. 따라서 예외에 대한 준비가되어 있어야합니다. 따라서 검사를 방해하지 않고 시도하고 열 수도 있습니다. 예외는 우리가 물건을 완성하는 데 도움이되는 도구이며 그 사용을 종교적 교리로 간주해서는 안됩니다.
whatsisname

1
@Flater : 파일 작업을 시도 / 캐치하지 않으려면 경쟁 조건이 발생합니다. 작성하려는 파일 시스템은 File.Create를 호출하기 직전에 사용할 수 없게되어 검사가 유효하지 않게됩니다.
whatsisname

1
@Flater " 모든 메소드 호출에 리턴 유형이 필요하다는 것을 효과적으로 주장하고 있습니다. 이것은 예외를 다른 방식으로 효과적으로 재발 명하려고합니다 ." 옳은. 그 재창조는 내 것이 아니다. 몇 년 동안 기능적 언어로 사용되었습니다. 그들은 일반적으로 우리가 사용하는 것,이 경우 예를 들어, 노동 조합 유형을 사용하여 (당신이 혼동 주장은 다음 한 숨 안티 - 패턴이고있는 다음에 찬성 주장) 전체 "제어 흐름의 문제로 예외를"피 Maybe<Stream>타입 반환하는 nothing파일이 존재하고 않는 경우 스트림하지 않는 경우.
David Arno

1

질문 1 : (이 for 루프는 반 패턴입니까?)

예. 쿼리 가능한 하위 시스템에 저장된 항목을 직접 검색 할 필요가 없기 때문입니다.

요약하면 데이터베이스와 같은 파일 시스템은 쿼리에 응답 할 수 있습니다. 쿼리 가능한 하위 시스템과 상호 작용할 때 하위 시스템 외부에서 일치를 수행하거나 하위 시스템의 기본 쿼리 기능을 사용하기 위해 해당 하위 시스템이 해당 콘텐츠를 우리에게 열거할지 여부를 근본적으로 선택합니다.

파일 시스템의 디렉토리에있는 파일 대신 데이터베이스에서 레코드를 찾는다고 가정 해 봅시다. 차라리보고 싶니

SELECT * FROM SomeTable;

그런 다음 반환 된 커서 위에 루프 (예 : C #)로 ID = 100을 찾거나 쿼리 가능한 하위 시스템이 원하는 것을 정확하게 찾을 수 있도록합니까?

SELECT * FROM SomeTable WHERE ID = 100;

우리 대부분은 서브 시스템이 정확한 관심 쿼리를 수행하도록 올바르게 선택해야한다고 생각해야합니다. 대안은 잠재적으로 서브 시스템과의 수많은 왕복, 비효율적 인 동등성 테스트, 데이터베이스 및 파일 시스템이 제공하는 색인 ​​또는 기타 검색 가속기를 사용하지 않는 것입니다.


질문 2 : 두 번째 예제를 수정하려면 if 문을 사용하지 않고 다시 작성하고 대신 Streamcat을 try-catch 블록으로 둘러싸고 FileNotFoundException이 발생하면 catch 블록에서 적절하게 처리합니까?

그렇습니다. 이는 특정 API의 작동 방식 일뿐입니다. 이것이 라이브러리 함수이기 때문에 실제로 우리의 선택은 아닙니다. if 검사는 호출 전에 추가 값을 제공하지 않습니다. 어쨌든 (1) FileNotFound 이외의 다른 오류가 발생할 수 있고 (2) 경쟁 조건이 있기 때문에 try / catch를 사용해야합니다.


0

질문 1:

return new StreamReader (filename) 때문입니까? for 루프 안에서? 또는이 경우 for 루프가 필요하지 않다는 사실?

스트림 리더는 이와 관련이 없습니다. 안티 패턴은 다음 foreach과의 명확한 의도 충돌로 인해 나타납니다 if.

의 목적은 무엇입니까 foreach?

귀하의 답변은 "특정 코드를 반복적으로 실행하고 싶습니다" 와 같은 것으로 가정합니다.

처리 할 파일 수는 몇 개입니까?

특정 폴더에는 하나의 특정 파일 이름 (확장명 포함) 만있을 수 있기 때문에 코드에서 적용 가능한 파일을 하나만 찾게 됩니다.

이것은 또한 즉시 값을 반환한다는 사실로 확인됩니다. 두 번째 일치 항목은 존재하더라도 실제로 신경 쓰지 않습니다.


이것이 반 패턴이 아닌 상황이 있습니다.

  • 하위 디렉토리 ( Directory.GetFiles(".", SearchOption.AllDirectories))를 볼 경우 동일한 파일 이름 (확장자 포함)을 가진 둘 이상의 파일을 찾을 수 있습니다
  • 당신이 찾는 경우 일부 파일 이름 일치 (예를 들어 이름이로 시작하는 모든 파일 "Test_"또는 모든 "*.zip"파일.

이 두 경우 모두 실제로 여러 개의 일치 항목을 처리해야하므로 즉시 값을 반환하지 않습니다.


질문 2 :

두 번째 예제를 수정하려면 if 문을 사용하지 않고 다시 작성하고 대신 Streamcat을 try-catch 블록으로 둘러싸고 FileNotFoundException이 발생하면 catch 블록에서 적절하게 처리합니까?

예외는 비싸다. 적절한 흐름 논리 대신 사용해서는 안됩니다. 예외적으로, 이름에서 예외적 인 상황을 시사 합니다.

따라서을 제거해서는 안됩니다 if.

SoftwareEngineering.SE에 대한이 답변에 따라 :

일반적으로, 제어 흐름에 예외를 사용하는 것은 상황과 언어에 따른 기침 예외가 두드러지는 반 패턴입니다.

일반적으로 반 패턴 인 이유에 대한 간단한 요약으로 다음과 같습니다.

  • 본질적으로 정교한 GOTO 문은 예외입니다.
  • 따라서 예외로 프로그래밍하면 코드를 읽고 이해하기가 더 어려워집니다
  • 대부분의 언어에는 예외를 사용하지 않고 문제를 해결하도록 설계된 기존 제어 구조가 있습니다.
  • 효율성에 대한 논거는 현대식 컴파일러에게는 무례한 경향이 있으며 제어 흐름에는 예외가 사용되지 않는다는 가정하에 최적화하는 경향이 있습니다.

보다 자세한 정보 는 Ward의 위키 에서 토론을 읽으십시오 .

이것을 시도 / 캐치로 포장해야하는지 여부는 상황에 따라 크게 다릅니다.

  • 경쟁 조건이 발생할 가능성은 얼마나됩니까?
  • 실제로이 상황을 처리 할 수 ​​있습니까? 또는 처리 방법을 모르기 때문에이 문제가 사용자에게 표시되기를 원하십니까?

"항상 사용하는"문제는 없습니다. 내 요점을 증명하려면

별도의 연구에 따르면 안전 헬멧, 안전 고글 및 방탄 조끼를 입었을 때 다칠 가능성이 적습니다.

왜 우리 모두가이 안전 장비를 항상 착용하지 않습니까?

간단한 대답은 착용에 단점이 있기 때문입니다.

  • 돈이 든다
  • 그것은 당신의 움직임을 더 성가신합니다
  • 그것을 착용하는 것은 매우 따뜻할 수 있습니다.

이제 우리는 어딘가에 있습니다 : 찬반 양론이 있습니다. 다시 말해, 전문가가 단점 보다 큰 경우에만이 장비를 착용하는 것이 좋습니다 .

  • 건설 근로자는 업무 중에 부상을 당할 가능성이 훨씬 높습니다. 그들은 안전 헬멧의 혜택을받습니다.
  • 반면, 사무원은 부상을 당할 가능성이 훨씬 낮습니다. 안전 헬멧은 그만한 가치가 없습니다.
  • SWAT 팀원은 회사원에 비해 총에 맞을 가능성이 훨씬 높습니다.

시도 / 캐치로 통화를 래핑해야합니까? 그렇게하는 것의 이점이 그것을 구현하는 비용보다 큰지에 달려 있습니다.

다른 사람들은 그것을 감싸는 데 몇 번의 키 입력 만 필요하다고 주장 할 수 있으므로 분명히해야합니다. 그러나 이것이 전체 논쟁이 아닙니다.

  • 예외가 발생하면 어떻게해야하는지 결정해야합니다.
  • 코드베이스 전체에 걸쳐 다른 파일에 대한 많은 다른 호출이있는 경우 try / catch에서 랩핑하기로 결정하면 일반적으로 이러한 모든 경우 를 랩핑해야 함을 의미합니다 . 이를 구현하는 데 필요한 노력의 양에 큰 영향을 줄 수 있습니다.
  • 그것은 당신이하는 것이 완벽하게 가능 의도적으로수 없습니다 예외를 처리합니다.
    • 응용 프로그램은 특정 시점 에서 예외를 처리해야 하지만 예외가 발생한 직후에 반드시 그런 것은 아닙니다.

따라서 선택은 당신입니다. 그렇게하면 이점이 있습니까? 응용 프로그램을 구현하는 데 드는 비용보다 응용 프로그램이 향상되었다고 생각하십니까?

업데이트 -나는 당신에게도 고려해야 할 의견으로 다른 답변에 쓴 의견에서 :

주변 환경에 많이 달려 있습니다.

  • 스트림 리더를 여는 것이 앞에 오는 if(!File.Exists) File.Create()경우, 스트림 리더를 열 때 파일이없는 것이 실제로는 예외적 입니다.
  • 기존 파일 목록에서 파일 이름을 선택한 경우 갑작스러운 부재가 다시 예외 입니다.
  • 실제로 디렉토리에 대해 테스트하지 않은 문자열로 작업하는 경우; 파일이 없다는 것은 완벽하게 논리적 인 결과이므로 예외아닙니다 .
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.