-
2022년 8월 12일 TIL - 혼동되는 변수명을 사용하면 생기는 문제Today I Learned 2022. 8. 12. 23:56
이전 주차에 출제되었던 코딩 도장 문제들 중 이번 주 수요일 코딩 도장 문제로 다시 주어졌던 부족한 금액 계산하기 문제를 풀던 중 있었던 이슈를 간단히 정리해본다. (수요일 문제를 금요일에 풀은 이유는... 쉿...)
우선 기존에 문제를 풀었던 방식인 반복문을 통해 놀이기구의 이용 횟수가 누적됨에 따라 증가하는 놀이기구의 이용 금액을 합산하는 방식 대신, 메서드를 재귀 호출하는 방식을 이용해 문제 풀이를 시도했다.
❗️ 여기서 잠깐! 재귀 호출이란?
- 메서드 내에서 메서드 자기 자신을 다시 호출하는 것을 뜻한다.
- 반복문은 재귀 호출 형태로 변환될 수 있다. 마찬가지로 재귀 호출 역시 반복문의 형태로 변환될 수 있다.
- 메서드에는 재귀 호출을 중단하고 값을 반환하기 시작하는 Base Case가 정의되어 있어야 한다. Base Case가 정의되어 있지 않거나 Base Case에 도달할 수 없을 경우 메서드 자기 자신을 무한히 호출하는 과정에서 스택 오버플로우가 발생할 수 있다.
처음 작성했던 소스코드는 다음과 같다.
public class InsufficientAmount { public long solution(int price, int money, int count) { this.price = price; this.money = money; this.count = count; long totalPrice = priceCalculate(price, 1); return totalPrice > 0 ? totalPrice : 0; } public long priceCalculate(long currentPrice, long currentCount) { if (currentCount >= count) { return currentPrice - money; } return priceCalculate(currentPrice + price * (count + 1), count + 1); } }
해당 소스코드는 다음과 같은 풀이과정을 의도했다.
- priceCalculate 메서드는 메서드 내에서 자신에게 주어진 매개변수들인 첫 번째 매개변수 '현재까지 누적된 이용금액'에 '놀이기구 이용금액 * (놀이기구 이용 횟수 + 1)'을 한 금액을 더한 금액을 재귀 호출하는 priceCalculate의 첫 번째 인자로, 두 번째 인자로 '놀이기구 이용 횟수 + 1'을 전달한다. 이 과정을 통해 놀이기구 이용 횟수가 1 증가할 때마다 계속해서 증대되는 누적 이용 금액이 첫 번째 파라미터에 지속적으로 쌓인다.
- 메서드를 재귀 호출하기 전에 두 번째 매개변수인 '현재까지의 놀이기구 이용 횟수'가 '놀이기구 총 이용 횟수'를 초과했는지 검사한다. 초과할 경우, 더 이상 메서드를 재귀 호출하지 않고 '누적 이용 금액 - 보유 금액'의 결과를 반환한다.
- 지금까지 재귀 호출된 모든 메서드는 Base Case로부터 반환되는 결과를 계속해서 반환해 최초 메서드 호출 지점에 결과값이 반환된다.
문제가 잘 해결되었는지 확인하기 위해 테스트 코드를 작성한 후 실행시켰다.
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class InsufficientAmountTest { @Test void solution2() { InsufficientAmount application = new InsufficientAmount(); assertEquals(10, application.solution2(3, 20, 4)); } }
실행한 결과 기대하던 결과값인 10을 반환하는 것이 아닌, 0을 반환하는 것이 확인되었다. 어느 지점에서 문제가 발생했는지 확인하기 위해 소스코드의 특정 시점마다 변수에 어떤 값이 들어있는지 출력문을 통해 확인을 시도했다.
메서드의 시작 지점에서 currentPrice와 currentCount를 출력해 보았고, 출력 결과를 보면서 고민한 결과 문제점이 어디인지를 확인할 수 있었다.
currentPrice: 3, currentCount: 1 currentPrice: 18, currentCount: 5
분명히 currentCount가 1씩 증가해나가야 하는데, 1에서 다음 재귀 호출 때 바로 5로 점프해버리는 것이었다. 그러는 바람에 재귀 호출 종료 조건을 즉시 만족해 재귀가 종료되고, 18 - 20인 -2가 0보다 작았기 때문에 결과값으로 0을 반환하는 사실을 확인할 수 있었다.
왜 저런 문제가 발생했을까? 소스코드를 보던 중 황당한 사실을 발견했다. 재귀 호출 메서드의 인자에서 사용되는 변수 중 count 이름으로 주어진 변수가 '그때까지의 놀이기구 이용 횟수'를 나타내는 변수인 currentCount가 아니라 '놀이기구 총 이용 횟수'인 count였던 것이었다. 이름이 count였기 때문에 조금만 신경을 덜 쓰면 currentCount와 혼동하기 쉬운 변수명이었고, 실제로 변수명을 혼동해 의도하지 않은 변수를 대입해 잘못된 결과를 초래했다는 사실을 확인하고 변수명을 다시 수정했다.
count 변수명을 '놀이기구 총 이용 횟수'의 의미를 담을 수 있을 것이라 판단한 maxCount라는 이름으로 변경하고, count 대신에 currentCount를 대신 대입한 결과 의도한 결과를 정상적으로 출력함을 확인할 수 있었다.
수정한 소스코드는 다음과 같다.
public class InsufficientAmount { public long solution(int price, int money, int maxCount) { this.price = price; this.money = money; this.maxCount = maxCount; long totalPrice = priceCalculate(price, 1); return totalPrice > 0 ? totalPrice : 0; } public long priceCalculate(long currentPrice, long currentCount) { if (currentCount >= maxCount) { return currentPrice - money; } return priceCalculate(currentPrice + price * (currentCount + 1), currentCount + 1); } }
오늘의 이슈로부터 생각해볼 점은 무엇일까? 단순히 약어를 사용하지 않고 풀 네임으로 이뤄진 단어나 단어들의 조합을 사용한다고만 해서 좋은 변수명라고 할 수는 없다는 점일 것이다.
어떤 변수가 사용되는 위치에서 변수가 어떤 의미로 쓰이는지를 고려해 변수의 역할이 무엇인지를 명확하게 정의내리는 것이 의미 있는 변수명을 짓기 위한 첫걸음이 아닐까 생각해본다.
'Today I Learned' 카테고리의 다른 글
2022년 8월 15일 TIL (0) 2022.08.15 2022년 8월 14일 TIL (0) 2022.08.14 2022년 8월 11일 TIL (0) 2022.08.11 2022년 8월 10일 TIL - 우연히 (0) 2022.08.10 2022년 8월 9일 TIL - 코드는 쳐지는데 찜찜하네... (0) 2022.08.09