문제

자체 관점에서 볼 때 잘못된 내부 상태 또는 불변 위반으로 인해 실패할 수 있는 작업 중에 객체의 내부 상태를 검증하기 위해 어떤 기술을 사용하고 있는지 듣고 싶습니다.

내 주요 초점은 C++에 있습니다. 왜냐하면 C#에서 공식적이고 널리 사용되는 방법은 예외를 발생시키는 것인데 C++에는 단 하나의 예외도 발생하지 않기 때문입니다. 하나의 이를 수행하는 방법입니다(좋아요, 실제로 C#에서도 마찬가지입니다. 저도 알고 있습니다).

참고로 나는 ~ 아니다 함수 매개변수 유효성 검사에 대해 이야기하지만 클래스 불변 무결성 검사에 더 가깝습니다.

예를 들어, 우리가 Printer 반대하다 Queue 인쇄 작업을 비동기식으로 수행합니다.이용자에게 Printer, 비동기 대기열 결과가 다른 시간에 도착하기 때문에 해당 작업은 성공할 수 있습니다.따라서 호출자에게 전달할 관련 오류 코드가 없습니다.

하지만 Printer 객체의 경우 내부 상태가 좋지 않으면(즉, 클래스 불변성이 깨졌을 때) 이 작업이 실패할 수 있습니다. 이는 기본적으로 다음을 의미합니다.버그.이 조건은 반드시 사용자에게 관심이 있는 것은 아닙니다. Printer 물체.

개인적으로 저는 세 가지 내부 상태 검증 스타일을 혼합하는 경향이 있으며 어느 것이 가장 좋은지 결정할 수는 없으며 어느 것이 절대적으로 최악인지 결정할 수는 없습니다.나는 이에 대한 귀하의 견해를 듣고 또한 이 문제에 대한 귀하의 경험과 생각을 공유하고 싶습니다.

내가 사용하는 첫 번째 스타일은 손상된 데이터보다 제어 가능한 방식으로 실패하는 것이 더 좋습니다.

void Printer::Queue(const PrintJob& job)
{
    // Validate the state in both release and debug builds.
    // Never proceed with the queuing in a bad state.
    if(!IsValidState())
    {
        throw InvalidOperationException();
    }

    // Continue with queuing, parameter checking, etc.
    // Internal state is guaranteed to be good.
}

내가 사용하는 두 번째 스타일 - 손상된 데이터보다 제어할 수 없는 충돌이 더 좋습니다.

void Printer::Queue(const PrintJob& job)
{
    // Validate the state in debug builds only.
    // Break into the debugger in debug builds.
    // Always proceed with the queuing, also in a bad state.
    DebugAssert(IsValidState());

    // Continue with queuing, parameter checking, etc.
    // Generally, behavior is now undefined, because of bad internal state.
    // But, specifically, this often means an access violation when
    // a NULL pointer is dereferenced, or something similar, and that crash will
    // generate a dump file that can be used to find the error cause during
    // testing before shipping the product.
}

내가 사용하는 세 번째 스타일은 손상된 데이터보다 조용하고 방어적으로 구제하는 것이 더 좋습니다.

void Printer::Queue(const PrintJob& job)
{
    // Validate the state in both release and debug builds.
    // Break into the debugger in debug builds.
    // Never proceed with the queuing in a bad state.
    // This object will likely never again succeed in queuing anything.
    if(!IsValidState())
    {
        DebugBreak();
        return;
    }

    // Continue with defenestration.
    // Internal state is guaranteed to be good.
}

스타일에 대한 내 의견:

  1. 저는 액세스 위반으로 인해 실제로 충돌이 발생한다면 오류가 숨겨지지 않는 두 번째 스타일을 선호한다고 생각합니다.
  2. 불변성과 관련된 NULL 포인터가 아닌 경우 첫 번째 스타일을 선호하는 경향이 있습니다.
  3. 나는 세 번째 스타일이 많은 버그를 숨길 것이기 때문에 정말 싫어하지만, 프로덕션 코드에서는 그것을 선호하는 사람들을 알고 있습니다. 왜냐하면 그것은 충돌하지 않는 강력한 소프트웨어의 환상을 만들어내기 때문입니다(기능은 작동을 멈추게 됩니다. 부서진 곳에 줄을 서다 Printer 물체).

이 중 어떤 것을 선호하시나요? 아니면 이를 달성할 수 있는 다른 방법이 있나요?

도움이 되었습니까?

해결책

문제는 소프트웨어를 테스트하는 방법과 함께 가장 잘 고려됩니다.

테스트 중에 불변의 불변을 치는 것은 충돌과 마찬가지로 심각도 버그로 제출되는 것이 중요합니다. 개발 중 테스트를위한 빌드는 Dead 및 출력 진단을 막기 위해 만들어 질 수 있습니다.

당신의 스타일과 같은 방어 코드를 추가하는 것이 적절할 수 있습니다. 3 : DebugBreak 테스트 빌드에서 진단을 덤프하지만 개발자에게는 중단점이됩니다. 이로 인해 개발자가 관련없는 코드로 버그로 작동하지 않는 상황이 적습니다.

슬프게도, 나는 종종 개발자들이 불편 함을 느끼는 다른 방식으로 다른 방식으로 수행 한 것을 보았지만 시험 구축은 깨진 불변을 통해 항해합니다. 이상한 행동 버그가 제출되는데, 실제로 단일 버그가 원인입니다.

다른 팁

NVI(비가상 인터페이스)와 함께 template method 무늬.아마도 다음과 같이 할 것입니다. (물론 이것은 내 개인적인 의견일 뿐이며 실제로 논쟁의 여지가 있습니다.)

class Printer {
public:
    // checks invariant, and calls the actual queuing
    void Queue(const PrintJob&);
private:
    virtual void DoQueue(const PringJob&);
};


void Printer::Queue(const PrintJob& job) // not virtual
{
    // Validate the state in both release and debug builds.
    // Never proceed with the queuing in a bad state.
    if(!IsValidState()) {
        throw std::logic_error("Printer not ready");
    }

    // call virtual method DoQueue which does the job
    DoQueue(job);
}

void Printer::DoQueue(const PrintJob& job) // virtual
{
    // Do the actual Queuing. State is guaranteed to be valid.
}

왜냐하면 Queue 가상이 아닌 경우 파생 클래스가 재정의되면 불변성이 계속 확인됩니다. DoQueue 특별한 취급을 위해.


귀하의 선택에 따라:확인하고 싶은 상태에 따라 다르다고 생각합니다.

내부 불변인 경우

불변의 경우 클래스 사용자가이를 위반할 수는 없습니다.수업은 변하지 않는 자체에 관심을 가져야합니다.따라서, 나는 할 것이다 assert(CheckInvariant()); 몇몇 경우.

그것은 단지 메소드의 전제조건일 뿐이다.

클래스 사용자가 보장 해야하는 사전 조건이라면 (프린터가 준비된 후에 만 ​​인쇄 할 경우) 던질 것입니다. std::logic_error 위에 표시된 것처럼.

조건을 확인하고 아무 것도 하지 않는 것이 좋습니다.


클래스 사용자는 메서드를 호출하기 전에 해당 클래스의 사전 조건이 충족되었음을 스스로 주장할 수 있습니다.따라서 일반적으로 클래스가 일부 상태를 담당하고 상태가 유효하지 않은 것으로 확인되면 어설션해야 합니다.클래스가 자신의 책임에 속하지 않지만 위반할 조건을 발견하면 예외를 발생시켜야 합니다.

훌륭하고 매우 관련성있는 질문입니다. IMHO, 모든 애플리케이션 아키텍처는 깨진 불일치를보고하는 전략을 제공해야합니다. 예외를 사용하거나 '오류 레지스트리'객체를 사용하거나 조치 결과를 명시 적으로 확인하기로 결정할 수 있습니다. 어쩌면 다른 전략도있을 수 있습니다. 그건 요점이 아닙니다.

불량한 충돌에 따라 나쁜 생각은 나쁜 생각입니다. 불변의 위반의 원인을 모르면 응용 프로그램이 충돌 할 것이라고 보장 할 수 없습니다. 그렇지 않은 경우 여전히 손상된 데이터가 있습니다.

그만큼 비 초당의 인터페이스 Litb의 솔루션은 불변을 확인하는 깔끔한 방법입니다.

힘든 질문 이거 :)

개인적으로, 나는 당신의 디자인에 의해 돌봐야 할 일을 돌보기 위해 물건을 구현할 때 내가하고있는 일에 너무 많은 것에 대해 예외를 던지는 경향이 있습니다. 보통 이것은 돌아와서 나중에 나를 물었다 ...

"Do-some-logging and then-don-don-anytho-more"에 대한 나의 개인적인 경험은 그것이 당신을 물기 위해 다시 돌아온다는 것입니다. 모든 수업은 잠재적으로 다른 방식으로 수행 할 수 있습니다).

내가 이와 같은 문제를 발견하자마자 내가 할 일은 팀의 나머지 사람들과 대화하고 우리가 일종의 글로벌 오류 처리가 필요하다고 말하는 것입니다. 취급이하는 일은 제품에 따라 다릅니다 (아무것도하지 않고 Air Traffic Controller-System의 미묘한 개발자 파일에 무언가를 기록하고 싶지는 않지만 드라이버를 만드는 경우 잘 작동합니다. 프린터 :)).

IMHO,이 질문은 구현 수준이 아닌 응용 프로그램의 설계 수준에서 해결해야 할 사항이라고 생각합니다. - 슬프게도 마법의 솔루션은 없습니다 :(

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top