저희는 요즘 글로벌 우주지상국 네트워크의 첫번째 사이트 릴리즈를 위해 QA (Quality Assurance) 활동이 한창입니다. 저는 프로젝트 매니저겸 지상국 하드웨어 제어를 담당하는 백엔드 개발자이지만 스타트업에는 QA 팀이 따로 없습니다. 그래서 열심히 버그를 찾고 리포트하고 수정하고를 반복하고 있습니다. 오늘은 특별한 버그 하나를 만났는데 트러블 슈팅 과정과 그것을 통해 얻게된 인사이트를 정리해 보면 좋겠다는 생각이 듭니다.
우주지상국은 위성이 지상국 상공을 지나갈 때 Uplink 를 통해 위성에게 명령(지구 촬영 계획, 영상 다운로드 계획 등)을 전달하고, Downlink 를 통해 촬영한 지구 영상 및 우주에서 측정한 원격 데이터를 다운로드 받습니다. 이렇게 지상국과 위성이 교신할 수 있는 하나의 사이클을 1개의 위성 패스(약 15분 이내)라고 합니다. 이 패스란 것은 해가 지평선 너머로부터 떠올라 최대 고도에 오른 후 다시 반대 지평선으로 내려가는 것과 같은 하나의 큰 궤적으로 생각하면 되는데 경우에 따라서는 최대 고각을 지난 후에 실제 교신이 시작되는 Partial 패스가 진행되기도 합니다. 이번 QA 과정 중 Partial 패스 시 Uplink 교신이 자동으로 시작되지 않는 문제가 있어 원인을 분석해 보게 되었습니다.
문제 코드를 살펴보니 위성 패스가 항상 지평선에서 시작해서 반대 지평선으로 끝나는 일반적인 조건만을 가정하고 있었습니다. 위성이 지평선에서 떠오르기 시작할 때 한 번 Uplink 를 시작하고 위성이 지평선으로 넘어가기 직전에 다시 Uplink 를 종료하도록 되어 있었기 때문에 패스가 최대 고각을 지나 지평선으로 내려오는 시점에 시작되는 Patial 패스의 Uplink 는 시작할 수 없는 구조를 가지고 있었습니다.
public void processAutoUplink(double nowElevation) {
if (isBefore(maxElevationTime)) { // 상승 궤도
// AutoOn
if (!triedOn && nowElevation > startElevation) {
triedOn = true;
autoUplinkOn();
}
} else { // 하강 궤도
// AutoOff
if (!triedOff && nowElevation < endElevation) {
triedOff = true;
autoUplinkOff();
}
}
}
그래서 위성과의 패스가 최대 고각을 지나 지평선으로 내려오는 시점에 시작되더라도 Uplink 가 시작될 수 있도록 조건을 하나 추가해 주었습니다.
public void processAutoUplink(double nowElevation) {
if (isBefore(maxElevationTime)) { // 상승 궤도
// AutoOn
if (!triedOn && nowElevation > startElevation) {
triedOn = true;
autoUplinkOn();
}
} else { // 하강 궤도
// AutoOn
if (!triedOn && nowElevation > startElevation) {
triedOn = true;
autoUplinkOn();
}
// AutoOff
if (triedOn && !triedOff && nowElevation < endElevation) {
triedOff = true;
autoUplinkOff();
}
}
}
위 코드를 테스트 해보면 정상적으로 잘 동작합니다. 그런데 생각보다 코드가 복잡하다는 생각이 듭니다. 우리에게 요구되는게 진짜 이렇게 복잡한 건지 물음표가 생겼습니다. 그래서 우리에게 요구되는 명세를 다시 정리해 보니 다음과 같았습니다.
위 명세를 그대로 코드로 옮겨 보니 상승 궤도(Ascending)와 하강 궤도(Descending)를 체크하느 분기문이 필요가 없었습니다. 이 조건은 문제를 해결하기 위해 개발자가 만든 조건이었지 진짜 문제가 요구하는 조건은 아니었습니다. 결과 코드를 비교해 보면 훨씬 단순하고 깔끔한 코드가 되었습니다. 아래가 최종 코드이고 정상적으로 잘 동작합니다.
public void processAutoUplink(double nowElevation) {
// AutoOn
if (!triedOn && nowElevation > startElevation) {
triedOn = true;
autoUplinkOn();
}
// AutoOff
if (triedOn && !triedOff && nowElevation < endElevation) {
triedOff = true;
autoUplinkOff();
}
}
이번 트러블슈팅을 진행하며 문제를 해결하기 위한 코드 수정을 진행하기 전에 진짜 우리에게 요구되는 것이 무엇인지 간결하게 정리하는 것이 얼마나 중요한지 새삼 깨닫게 되었습니다. 무작정 코드를 수정해 나가다보면 잘못갔던 궤적을 그대로 두고 다시 돌아오는 코드를 만들게 될 수 있지만, 요구사항을 명확히 이해하게 되면 곧장 올바른 목표로 나아가는 간결한 새 길을 만들 수 있다는 걸 눈으로 확인하는 시간이었습니다.