조건을 중복 확인하는 것이 나쁜 스타일입니까?


10

종종 내 코드에서 특정 조건을 반복해서 확인하는 위치에 도달합니다.

"a"로 시작하는 줄, "b"로 시작하는 줄 및 다른 줄을 포함하는 텍스트 파일이 있고 실제로 처음 두 종류의 줄만 사용하고 싶다고 가정합니다. 내 코드는 다음과 같습니다 (파이썬을 사용하지만 의사 코드로 읽음).

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a")):
        # do stuff
    elif (line.startsWith("b")):
        # magic
    else:
        # this else is redundant, I already made sure there is no else-case
        # by using clear_lines()
# ...

여기 에서이 조건을 확인하지 않을뿐만 아니라 다른 기능에서도 확인할 수 있습니다.

소음으로 생각합니까 아니면 내 코드에 가치를 더합니까?


5
기본적으로 방어 적으로 코딩하는지 여부에 관한 것입니다. 이 코드가 많이 편집되고 있습니까? 이것이 매우 안정적인 시스템의 일부일 가능성이 있습니까? assert()테스트를 돕기 위해 거기 에 밀어 넣는 데 많은 해를 끼치 지 않지만 그 이상은 지나칠 것입니다. 즉 상황에 따라 다를 수 있습니다.
Latty

귀하의 '다른'사건은 본질적으로 죽은 / 도달 할 수없는 코드입니다. 이를 금지하는 시스템 전체 요구 사항이 없는지 확인하십시오.
NWS

@ NWS : else 케이스를 유지해야한다고 말하고 있습니까? 당신을 완전히 이해하지 못해 죄송합니다.
marktani

2
특히 질문과 관련이 없지만- '어설 션'을 불변으로 만들 것입니다. 행을 문자열로 처리하고 무엇을 말하지 않고 새로운 "라인"클래스 (아마도 A & B의 파생 클래스가 필요)가 필요합니다 그들은 외부에서 나타냅니다. 나는에서이 이상에 자세히 설명 드리겠습니다 코드 검토
MattDavey

당신은 의미 elif (line.startsWith("b"))했습니까? 그건 그렇고, 조건에서 해당 괄호를 안전하게 제거 할 수 있습니다. 파이썬에서는 관용적이지 않습니다.
tokland

답변:


14

이는 매우 일반적인 관행이며이를 처리하는 방법은 고차 필터를 사용하는 것 입니다.

기본적으로 필터링하려는 목록 / 시퀀스와 함께 함수를 필터 메서드에 전달하면 결과 목록 / 시퀀스에는 원하는 요소 만 포함됩니다.

나는 파이썬 구문에 익숙하지 않지만 (위 링크에 표시된 것과 같은 기능을 포함하고 있지만) c # / f #에서는 다음과 같습니다.

씨#:

var linesWithAB = lines.Where(l => l.StartsWith("a") || l.StartsWith("b"));
foreach (var line in linesWithAB)
{
    /* line is guaranteed to ONLY start with a or b */
}

f # (이너 머블이라고 가정하고 그렇지 않으면 List.filter가 사용됨) :

let linesWithAB = lines
    |> Seq.filter (fun l -> l.StartsWith("a") || l.StartsWith("b"))

for line in linesWithAB do
    /* line is guaranteed to ONLY start with a or b */

따라서 명확하고 : 검증되고 테스트 된 코드 / 패턴을 사용하면 나쁜 스타일입니다. 즉, clear_lines ()를 통해 나타나는 방식으로 메모리의 목록을 변경하면 스레드 안전성과 병렬 처리에 대한 희망이 사라집니다.


3
참고로 이에 대한 파이썬 구문은 생성기 표현식 (line for line in lines if line.startswith("a") or line.startswith("b"))입니다.
Latty

1
+1 (필수) 명령 구현 clear_lines은 실제로 나쁜 생각 임을 지적한 +1 . 파이썬에서는 아마도 생성기를 사용하여 전체 파일을 메모리에로드하지 않을 것입니다.
tokland

입력 파일이 사용 가능한 메모리보다 크면 어떻게됩니까?
Blrfl

@Blrfl : 음, generator라는 용어가 c # / f # / python간에 일관된 경우 @tokland와 @Lattyware는 c # / f # 수율 및 / 또는 수율로 변환됩니다! 진술. Seq.filter는 IEnumerable <T> 모음에만 적용 할 수 있지만 lines생성 된 모음 인 경우 두 코드 예제가 모두 작동하기 때문에 f # 예제에서 조금 더 분명 합니다.
Steven Evers

@ mcwise :이 방법으로 작동하는 다른 모든 사용 가능한 기능을 살펴보기 시작하면 서로 연결되고 구성 될 수 있기 때문에 실제로 섹시하고 믿을 수 없을 정도로 표현력이 높아지기 시작합니다. 봐 skip, take, reduce( aggregate, .NET에서) map( select.NET)에, 그리고 거기에 더하지만 그건 정말 고체 시작합니다.
Steven Evers

14

최근에 Motorola S 레코드 형식을 사용하여 펌웨어 프로그래머를 구현해야했습니다 . 시간이 오래 걸리기 때문에 첫 번째 초안은 중복을 무시하고 실제로 응용 프로그램에서 사용해야하는 하위 집합을 기반으로 단순화했습니다. 내 테스트를 쉽게 통과했지만 다른 사람이 시도하자마자 열심히 실패했습니다. 문제가 무엇인지 전혀 알지 못했습니다. 완전히 끝났지 만 결국 실패했습니다.

따라서 문제의 범위를 좁히기 위해 모든 중복 검사를 구현할 수밖에 없었습니다. 그 후 문제를 발견하는 데 약 2 초가 걸렸습니다.

올바른 방법으로 처리하는 데 2 ​​시간이 더 걸렸지 만 문제 해결에 다른 사람들의 시간을 낭비했습니다. 몇 번의 프로세서주기가 문제 해결에 낭비되는 경우는 매우 드 rare니다.

즉, 파일 읽기와 관련하여 전체 파일을 메모리로 읽고 메모리에서 처리하는 대신 파일을 읽고 한 번에 한 줄씩 처리하도록 소프트웨어를 설계하는 것이 유리합니다. 그렇게하면 여전히 매우 큰 파일에서 작동합니다.


"몇 번의 프로세서주기가 문제 해결에 낭비되는 경우는 거의 없습니다." 답변 주셔서 감사합니다, 당신은 좋은 지적이 있습니다.
marktani

5

else경우에 따라 예외를 제기 할 수 있습니다 . 이 방법은 중복되지 않습니다. 예외는 발생하지 않지만 어쨌든 확인되는 것입니다.

clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a)):
        # do stuff
    if (line.startsWith("b")):
        # magic
    else:
        throw BadLineException
# ...

나는 후자가 그것이 덜 명백하기 때문에 나쁜 생각이라고 주장 할 것입니다. 나중에를 추가하기로 결정하면 "c"덜 명확 할 수 있습니다.
Latty

첫 번째 제안은 장점이 있습니다 ... 두 번째 ( "b"라고 가정)는 나쁜 생각입니다
Andrew

@ Lattyware 나는 대답을 향상시켰다. 귀하의 의견에 감사드립니다.
Tulains Córdova

1
@Andrew 나는 대답을 향상시켰다. 귀하의 의견에 감사드립니다.
Tulains Córdova

3

에서 계약에 의해 설계 , 하나의 문서에 설명 된대로 각 함수가 작업을 수행해야합니다 추측. 따라서 각 기능에는 사전 조건 목록, 즉 기능 입력 조건, 사후 조건, 즉 기능 출력 조건이 있습니다.

이 기능은 입력이 사전 조건을 준수하는 경우 출력이 사후 조건에 설명 된대로 클라이언트에 보장해야합니다. 사전 조건 중 하나 이상을 준수하지 않으면 함수가 원하는 것을 수행 할 수 있습니다 (충돌, 결과 반환 등). 따라서 사전 및 사후 조건은 기능의 의미 론적 설명입니다.

계약 덕분에 함수는 클라이언트가 올바르게 사용하는지 확인하고 클라이언트는 함수가 올바르게 작동하는지 확인합니다.

일부 언어는 기본적으로 또는 전용 프레임 워크를 통해 계약을 처리합니다. 다른 사람들에게는 @Lattyware가 말한 것처럼 어설 션 덕분에 사전 및 사후 조건을 확인하는 것이 가장 좋습니다. 그러나 나는이 개념이 (사람의) 사용자 입력에 대한 보호에 더 집중되어 있기 때문에 방어 적 프로그래밍이라고 부르지 않을 것입니다.

계약을 악용하는 경우 호출 된 함수가 완벽하게 작동하고 이중 검사가 필요하지 않거나 호출 된 함수가 작동하지 않고 호출 기능이 원하는대로 작동 할 수 있으므로 중복 검사 조건을 피할 수 있습니다.

더 어려운 부분은 어떤 기능이 무엇을 담당하는지 정의하고 이러한 역할을 엄격하게 문서화하는 것입니다.


1

실제로 시작시 clear_lines ()가 필요하지 않습니다. 라인이 "a"또는 "b"가 아닌 경우 조건부 만 트리거되지 않습니다. 해당 줄을 제거하려면 else를 clear_line ()으로 만드십시오. 서있는 것처럼 문서를 두 번 통과하고 있습니다. 처음에 clear_lines ()를 건너 뛰고 foreach 루프의 일부로 처리하면 처리 시간을 반으로 줄입니다.

그것은 나쁜 스타일 일뿐 만 아니라 계산적으로 나쁩니다.


2
해당 행이 다른 용도로 사용되고있을 수 있으며 "a"/ "b"행을 처리하기 전에 처리해야합니다 . 가능성이 있다고 말하지 않고 ( 명백한 이름은 폐기 될 가능성이 있음) 단지 필요할 가능성이 있다는 것입니다. 나중에 해당 라인 세트가 반복적으로 반복되는 경우 많은 무의미한 반복을 피하기 위해 미리 제거하는 것이 좋습니다.
Latty

0

유효하지 않은 문자열 (예 : 출력 디버그 텍스트)을 찾으면 실제로 무언가를하고 싶다면 절대 괜찮습니다. 알 수없는 이유로 작동이 중지 될 때 몇 줄이 추가되고 몇 개월이 지나면 출력을보고 이유를 찾을 수 있습니다.

그러나 그것을 무시하는 것이 안전하거나 확실하지 않은 문자열을 얻지 못할 경우 여분의 분기가 필요하지 않습니다.

개인적으로 나는 예상치 못한 조건에 대해 최소한 추적 출력을 넣는 데 항상 노력하고 있습니다.


0

... "a"로 시작하는 줄, "b"로 시작하는 줄 및 기타 줄이 포함 된 텍스트 파일이 있고 실제로 처음 두 종류의 줄만 사용하고 싶습니다. 내 코드는 다음과 같습니다 (파이썬을 사용하지만 의사 코드로 읽음).

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if ...

나는 if...then...else건축을 싫어한다 . 나는 전체 문제를 피할 것이다.

process_lines_by_first_character (lines,  
                                  'a' => { |line| ... a code ... },
                                  'b' => { |line| ... b code ... } )
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.