역공학과 재구조화
역공학, 재구조화, 재공학의 목적을 구별하고, 기존 코드가 실제로 하는 일과 요구가 의도한 일을 나누어 확인합니다. 옛 신청 코드를 지금의 요구와 같은 입력으로 비교해 차이를 찾고, 문서가 부족한 시스템을 안전하게 고쳐 가는 순서를 정합니다.
새 담당자가 설명 문서 없이 오래된 신청 코드만 넘겨받았습니다. 어떤 거절 이유를 먼저 확인하는지, 취소하면 모임이 다시 열리는지 아무 데도 적혀 있지 않습니다. 이럴 때 곧바로 새 구조로 옮기기보다, 지금 코드가 실제로 어떻게 동작하는지와 업무가 원래 무엇을 의도했는지를 먼저 확인해야 합니다. 이 글에서는 기존 시스템을 이해하고 개선하는 세 가지 활동을 구별하고, 옛 코드의 동작을 지금의 요구와 비교해 봅니다.
세 가지 활동의 차이
역공학(reverse engineering)은 코드, 데이터, 실행 결과에서 구조와 의미 같은 더 높은 수준의 설명을 끌어내는 활동입니다. 이해하는 것이 목적이므로 그 자체로 제품을 바꾸지는 않습니다. 재구조화(restructuring)는 같은 수준의 표현을 더 나은 구조로 바꾸는 활동으로, 코드라면 코드를, 설계라면 설계를 다시 짜며, 동작을 지키는지 분명히 해야 합니다. 재공학(reengineering)은 기존 시스템을 조사하고 필요한 만큼 바꾸어 새로운 형태로 다시 구성하는 더 넓은 개선 활동입니다.
CASE(computer-aided software engineering) 도구는 모델링, 코드 생성, 문서화, 검증 같은 개발 활동을 돕는 도구를 묶어 부르던 말입니다. 모델에서 코드를 만들거나 코드에서 구조 그림을 그려 줄 수는 있지만, 업무 정책이 옳은지까지 정해 주지는 않습니다. 도구가 만든 코드와 사람이 고친 코드를 함께 어떻게 관리할지도 따로 정해야 합니다.
실제 동작과 요구의 의도를 나눈다
옛 함수가 정원이 찬 모임에 이미 신청한 아이디로 다시 신청할 때 FULL을 돌려준다면, 그것은 이 코드가 실제로 하는 일입니다. 그 동작이 사용자가 원한 정책인지, 아니면 결함인지는 다른 질문입니다. 유지보수와 기술 부채의 특성화 테스트는 지금의 동작을 기록하는 데 쓸모가 있지만, 기록한 동작을 그대로 올바른 요구로 삼아서는 안 됩니다.
다음 코드는 옛 신청 함수 legacyJoin과 지금의 요구를 담은 core.mjs의 register를 같은 입력으로 비교합니다. reading-lab/examples/ch10-3.mjs로 저장해 실행합니다.
import { createSession, register } from "../core.mjs";
function legacyJoin(list, capacity, id) {
if (list.length >= capacity) return "FULL";
if (list.includes(id)) return "ALREADY_REGISTERED";
list.push(id);
return "REGISTERED";
}
const cases = [
["빈 모임에 min", [], 1, "min"],
["정원이 찬 모임에 min 다시", ["min"], 1, "min"],
["정원이 찬 모임에 hana", ["min"], 1, "hana"],
["자리 있는 모임에 min 다시", ["min"], 2, "min"],
["형식이 틀린 Min", [], 1, "Min"],
];
for (const [label, participants, capacity, id] of cases) {
const legacy = legacyJoin([...participants], capacity, id);
let session = createSession({ id: "legacy", title: "옛 모임", capacity });
for (const member of participants) session = register(session, member).state;
const current = register(session, id).code;
console.log(`${label}: 옛 코드 ${legacy}, 지금 요구 ${current} (${legacy === current ? "같음" : "다름"})`);
}빈 모임에 min: 옛 코드 REGISTERED, 지금 요구 REGISTERED (같음)
정원이 찬 모임에 min 다시: 옛 코드 FULL, 지금 요구 ALREADY_REGISTERED (다름)
정원이 찬 모임에 hana: 옛 코드 FULL, 지금 요구 FULL (같음)
자리 있는 모임에 min 다시: 옛 코드 ALREADY_REGISTERED, 지금 요구 ALREADY_REGISTERED (같음)
형식이 틀린 Min: 옛 코드 REGISTERED, 지금 요구 INVALID_MEMBER (다름)다섯 사례 가운데 두 곳에서 결과가 달랐습니다. 옛 코드는 정원 검사를 중복 검사보다 먼저 해서 정원이 찬 모임의 재신청에 FULL을 돌려주고, 아이디의 형식을 검사하지 않아 Min도 받아들입니다. 또 legacyJoin은 넘겨받은 배열에 push로 직접 아이디를 더하므로, [...participants]로 사본을 넘겨 바로 아래에서 지금 요구 쪽의 모임을 만들 때 쓸 participants가 바뀌지 않게 했습니다. 원본을 그대로 넘기면 첫 사례에서 옛 코드가 더한 min이 participants에 남아, 지금 요구 쪽은 ALREADY_REGISTERED를 받고 첫 줄의 결과도 다름으로 바뀝니다.
이 차이를 발견했다고 해서 함수를 다른 파일로 옮기는 재구조화 도중에 몰래 고치지는 않습니다. 먼저 차이를 기록하고 업무 담당자에게 어느 쪽이 의도한 정책인지 확인합니다. 지금의 요구대로 바꾸기로 했다면, 그것은 구조를 바꾸는 리팩터링이 아니라 정책을 바로잡는 변경이므로, 관련된 테스트와 안내를 함께 바꾸고 그 이유를 기록합니다.
고쳐 가는 순서
문서가 부족한 시스템을 고칠 때는 다음 순서를 따를 수 있습니다.
- 실행 환경, 데이터 형식, 바깥 시스템과의 연결을 식별합니다.
- 정상, 거절, 복원 사례를 실행해 보고 특성화 테스트로 기록합니다.
- 실제 동작과 업무 요구가 다른 곳을 업무 담당자와 확인합니다.
- 상태 모델, 모듈의 나눔, 저장 방식을 문서로 남깁니다.
- 작은 구조 변경을 하고, 동작을 지킨 부분과 의도해서 바로잡은 부분을 나누어 확인합니다.
- 새 기준선과 실행 및 복구 안내를 남깁니다.
다른 언어로 다시 쓴다고 유지보수 비용이 저절로 줄지는 않습니다. 문서에 없던 데이터 형식과 오류 처리, 운영 절차를 잃지 않았는지 확인해야 하기 때문입니다. 시스템의 작은 부분부터 하나씩 새 구현으로 바꾸어 가는 방식이 전체를 한 번에 다시 쓰는 것보다 위험이 적은 경우도 있고, 기존 환경을 더 이상 유지할 수 없어 다시 써야 하는 경우도 있으므로, 어느 쪽이든 확인한 사실에 근거해 결정합니다.
정리
- 역공학은 코드와 데이터에서 더 높은 수준의 설명을 끌어내는 이해의 활동이고, 재구조화는 같은 수준에서 구조를 고치는 활동이며, 재공학은 조사와 변경을 아우르는 넓은 개선 활동입니다.
- CASE 도구는 모델과 코드, 문서를 다루는 일을 돕지만 업무 정책이 옳은지는 정해 주지 않습니다.
- 기존 코드가 실제로 하는 일과 업무가 의도한 일은 다를 수 있으므로, 특성화 테스트로 기록한 동작을 그대로 요구로 삼지 않습니다.
- 재구조화 도중에 발견한 동작의 차이는 몰래 고치지 않고, 확인을 거쳐 별도의 변경으로 다룹니다.
- 다시 쓸지, 조금씩 바꿀지는 문서에 없던 데이터 형식과 오류 처리, 운영 절차를 잃을 위험을 따져 결정합니다.
연습 문제
1. 그림만 자동으로 만든 역공학
도구로 모듈 사이의 의존 관계 그림을 만들고 나서, 팀은 역공학이 끝났으니 새 앱을 만들자고 합니다. 아직 확인해야 할 것 세 가지와, 각각을 무엇으로 확인할지 제시하세요.
해설 보기
첫째, 중복 신청과 만석의 우선순위 같은 업무 정책은 업무 담당자가 겪은 사례와 실제 실행 결과로 확인합니다. 둘째, 저장 자료가 어떤 상태일 때 유효한지는 필드와 형식의 버전을 살피고 복원 시험으로 확인합니다. 셋째, 장애가 났을 때 성공 안내를 잘못 보여 주지 않는지와 어떻게 복구하는지는 오류를 재현해 보고 운영 기록을 살펴 확인합니다. 의존 관계 그림은 누가 누구를 부르는지 알려 줄 뿐 이런 의미까지 담지 않으므로, 확인하지 못한 부분을 기록해 두고 작은 부분부터 개선합니다.
2. 재구조화 도중에 발견한 차이
본문의 비교로 옛 코드가 형식이 틀린 아이디 Min을 받아들인다는 것을 알게 되었습니다. 지금은 함수를 다른 파일로 옮기는 재구조화를 하는 중입니다. 이 차이를 어떻게 다루어야 할까요?
해설 보기
재구조화는 동작을 지키는 변경이므로, 파일을 옮기는 작업 안에서 형식 검사를 몰래 더하지 않습니다. 먼저 이 차이를 기록하고, 업무 담당자에게 대문자가 든 아이디를 거절하는 것이 맞는지 확인합니다. 이미 저장된 명단에 대문자 아이디가 있을 수 있으므로, 거절하기로 했다면 기존 자료를 어떻게 처리할지도 함께 정해야 합니다. 그런 다음 형식 검사를 더하는 일을 별도의 변경으로 만들고, 그 변경에 맞는 테스트와 안내를 함께 바꿉니다.
3. 다시 쓸까, 조금씩 바꿀까
오래된 모임 앱이 더 이상 지원되지 않는 실행 환경에서만 동작하고, 저장 형식은 문서로 남아 있지 않습니다. 전체를 한 번에 다시 쓰자는 의견과 조금씩 바꾸자는 의견이 맞섭니다. 판단하기 전에 확인할 것을 제시하세요.
해설 보기
먼저 지금의 실행 환경을 얼마나 더 유지할 수 있는지, 그 환경의 보안 문제가 얼마나 급한지 확인합니다. 그리고 저장 형식을 실제 자료에서 역공학으로 정리하고, 정상, 거절, 복원의 동작을 특성화 테스트로 기록해 새 구현이 지켜야 할 기준을 만듭니다. 기존 환경에서 부분만 새 구현으로 옮길 수 있다면 조금씩 바꾸는 쪽이 위험이 적고, 환경 자체를 유지할 수 없다면 다시 쓰되 기록한 동작과 저장 형식의 변환 절차를 기준으로 삼습니다.
이 교재의 마지막 확인은 프로그램을 한 번 더 실행하는 것이 아닙니다. 요구를 정한 이유에서 시작해 모델, 모듈, 테스트, 변경 기록, 복구 방법까지를 스스로 설명할 수 있는지 돌아보세요. 다시 살펴볼 때는 기능 요구와 수락 조건, 상태와 데이터 모델, 통합·인수·회귀 테스트, 형상과 변경 기준선을 이어서 읽으면 좋습니다.