. 이전 레코드 편집시

ArrangeModified 함수의 조건문은 항상 정확하지 않은 문제가 있는데 다음과 같은 경우에는 문서가 변경되었는데 변경되지 않은 것으로 오판하게 된다.

첫 번째 그림은 앞 예의 다섯 번째 그림과 같은 상태이다. 이 상태에서 <Ctrl+Z>를 두 번 눌러 의 입력을 취소하였다. 그리고 를 새로 입력하면 세 번째 그림과 같은 상태가 된다. 보다시피 nowur saveur이 같고 nowur action 0이므로 처음 저장할 때의 상태와 동일한 상태가 되었으며 bModified는 다시 FALSE가 될 것이다.

하지만 눈으로 보다시피 문서에 있던 문자열은 가 나 에서 가 바 로 바뀌었으므로 분명히 다른 문서이다. 하지만 ArrangeModified 함수는 조건에 의해 두 상태를 같은 문서로 오판하게 된다. 이 함수는 취소에 의해 원래 상태로 돌아가는 것은 잘 판단하지만 취소한 후 재실행할 때의 조건 점검을 제대로 못하고 있다. 이 문제를 해결하려면 문서의 상태를 좀 더 정확하게 저장하거나 아니면 질적으로 다른 획기적인 방법을 찾아야 한다. 문서를 저장할 때의 레코드 첨자인 saveur만 저장해서는 이 두 상태가 다르다는 것을 판단하기 무척 어렵다.

저장할 때 아래쪽에 있던 레코드의 상태까지 같이 저장해야만 완전한 비교를 할 수 있는데 이는 무척이나 피곤한 일이다. 취소를 여러 번 할 수 있으므로 결국 모든 레코드를 다 저장해야 하는 셈이 되는데 이렇게 할 바에야 차라리 문서 전체를 다 저장하는 편이 더 나을 것 같다. 하지만 몇 일간의 고심끝에 좀 더 간단한 방법을 찾았는데 바로 다음 명제가 해결책을 제시했다.

 

saveur보다 더 아래쪽에 있던 레코드가 조금이라도 변경되면 문서는 다시 변경되지 않은 상태로 돌아갈 수 없다.

 

저장할 때 이미 입력되어 있던 , 중 하나를 취소한 후 다른 문자로 바꿔버리면 이 문서는 다시 저장할 시점으로 돌아갈 수 없다는 뜻이다. 하지만 취소 자체에 의해서 복원 불가 상태가 되지는 않는다. 왜냐하면 취소한 후 다시 재실행할 수도 있기 때문이다. 예를 들어 의 입력을 취소했더라도 다시 재실행하면 되므로 아직까지는 복원 가능한 상태를 유지하고 있다. 복원 불가 상태가 되는 경우는 취소한 후 레코드를 변경할 때, ClearRedo 함수에 의해 레코드가 삭제될 때이다. 이 상태가 되면 문서는 취소/재실행을 어떻게 하더라도 이미 복원 정보를 잃어 버린 상태가 된다.

그렇다면 이 코드는 어디다 작성해야 할까? 플래그 변수를 하나 더 선언하고 saveur 이전의 레코드가 삭제되었을 때 이 플래그에 복원 불가 상태를 저장한다. 그리고 복원 불가 상태일 때 ArrangeModified는 어떤 경우라도 bModified를 다시 FALSE로 바꾸지 않도록 한다. 그러나 더 간단한 방법은 saveur 자체를 무효화시키는 것이다. ClearRedo 함수에 다음 코드를 작성하도록 하자.

 

void CApiEdit::ClearRedo()

{

     int i;

 

     for (i=nowur;;i++) {

        if (i<saveur) {

           saveur=-1;

        }

          if (pUR[i].action==UR_NONE) {

              break;

          }

          if (pUR[i].data != NULL)

              free(pUR[i].data);

          memset(&pUR[i],0,sizeof(UndoRecord));

     }

}

 

삭제되는 레코드가 saveur보다 더 작으면 saveur -1로 만든다. saveur -1이 되면 nowur은 결코 saveur과 같아질 수 없는 상태가 되고 결국 bModified는 다시 FALSE가 될 수 없다. saveur -1을 대입하는 것은 ArrangeModified의 조건문을 Always FALSE 상태로 만드는 것이다. 변경 관리와 관련된 마지막 처리는 NextRecord 함수에 작성한다.

 

void CApiEdit::NextRecord()

{

     ....

 

          nowur=len-1;

        saveur=saveur-i-1;

     }

}

 

이 함수가 상한선 제한에 의해 취소 레코드의 일부를 삭제할 때 saveur도 삭제된 레코드 수만큼 감소되어야 저장된 시점을 제대로 가리키게 된다. saveur이 왜 -i-1만큼 이동하는가 하면 nowur 이 이만큼 이동했기 때문이다. 이때 saveur이 사라진 레코드의 일부였다면 음수가 될 수도 있는데 그래도 상관없다. 음수가 된다는 것은 곧 저장할 때의 상태로는 취소할 수 없다는 뜻이며 취소 레코드의 상한선을 제한하는 의도에 부합된다.

결코 쉽지 않은 내용을 다루었는데 나도 꽤 많은 고민끝에 작성한 코드라 무척 지쳤다. 특히 변경 관리 코드는 수많은 시행착오를 거쳐 만들어 낸 것인데 보여준 코드보다 몇 배 더 많은 코드를 작성했다가 버리고 다시 만들기를 여러 번 반복했었다. 최종 결과코드가 몇 줄 되지 않아 간단해보일지 모르겠지만 저런 압축된 코드를 한 번에 만든 것은 아니다.

문서가 저장될 때의 상태를 저장하고 비교한다는 것은 무척 어려운 일이다. 처음에는 작성중인 레코드의 상태까지 저장하려고 몇 가지 전역변수를 더 사용했는데 작성중인 레코드는 케이스가 너무 많아 완전히 같은 레코드인지 비교하기가 거의 불가능했다. 레코드가 그대로 있는 것이 아니라 취소되었다가도 재실행될 수 있기 때문에 통상의 방법으로는 두 레코드가 같은지 비교할 수가 없었다. 결국 저장 대상이 되는 레코드를 빈 레코드 또는 취소된 레코드로 제한함(SaveModified 함수의 NextRecord 호출)으로써 비교 코드를 간단하게 만들 수 있었다.

saveur 이전의 레코드를 취소했다가 다시 입력하는 경우와 재실행하는 경우를 구분하기 어려워 아주 많은 고민을 했었다. 그때 내가 작성한 코드를 보인다면 정말 어처구니가 없을 것이다. 삽입 기록 때 그린 순서도보다 훨씬 더 큰 순서도를 그리고도 문제는 자꾸 복잡해져만 갔다. 이 문제는 saveur에 음수를 대입하는 기가 막힌 방법을 찾음으로써 해결했는데 다 풀고 나니 그렇게 간단할 수가 없어 허탈하기도 했었다.

앞서 마진영역을 프로그래밍하면서 미국과 소련의 볼펜 개발 일화로 발상의 전환에 대한 얘기를 한 적이 있는데 변경 관리 문제에서 발상의 전환이 정말 필요했던 것 같다. 처음 생각했던 방식으로 계속 밀어 부쳤더라면 훨씬 더 어려워졌을 것이다. 결국은 문제를 해결했겠지만 그 복잡도로 인해 속도는 떨어지고 유지 보수 비용은 증가하게 된다. 좋은 생각이 떠오르지 않을 때는 무리하게 작업을 진행하지 말고 푹 쉬면서 다른 방법에 대해 생각해보는 여유를 갖도록 하자.