For-if 반 패턴

이 블로그 게시물 에서 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그에 따라 블록 에서 처리 합니까?



답변

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

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 : (이 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를 사용해야합니다.


답변

질문 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()경우, 스트림 리더를 열 때 파일이없는 것이 실제로는 예외적 입니다.
  • 기존 파일 목록에서 파일 이름을 선택한 경우 갑작스러운 부재가 다시 예외 입니다.
  • 실제로 디렉토리에 대해 테스트하지 않은 문자열로 작업하는 경우; 파일이 없다는 것은 완벽하게 논리적 인 결과이므로 예외아닙니다 .