요즘 구직을 위해 코딩테스트를 보고 있습니다.
프로그래머스 문제를 풀다가 재귀함수를 사용할 때 사용할 변수의 위치가 제각각이라는 것을 알게 되었습니다.
아래의 문제를 예시로 들겠습니다.
https://programmers.co.kr/learn/courses/30/lessons/43165
아래 함수는 숫자 배열(numbers)와 목표 숫자(target)을 인자로 받고, 주어진 숫자 배열을 가지고 적절히 연산해서 목표 숫자를 만드는 문제입니다.
class Solution {
public int solution(int[] numbers, int target) {
// 여기를 작성
}
}
이 때 문제를 재귀 함수를 사용해서 풀이하자면 아래와 같이 파라미터로 1. 주어진 배열과 목표 숫자, 2. 내부에서 재귀함수가 동작하기 위한 단계 변수 가 필요합니다. 왜냐하면 재귀함수에서 solution함수의 지역 변수를 참조할 수 없기 때문이죠.
class Solution {
public int recursive(int[] numbers, int target, int sum, int i) {
if(i == numbers.length) return (sum==target) ? 1 : 0;
return recursive(numbers, target, sum - numbers[i], i+1) +
recursive(numbers, target, sum + numbers[i], i+1);
}
public int solution(int[] numbers, int target) {
return recursive(numbers, target, 0, 0);
}
}
위와 같이 작성하면, 재귀 함수를 호출할 때마다 1번씩 스택에 numbers를 참조하기 위한 변수와, 목표 숫자 target 변수를 생성해야 합니다. 즉, 메모리 공간과 연산이 낭비됩니다.
class Solution {
private static int[] staticNumbers;
private static int staticTarget;
public int recursive(int sum, int i) {
if(i == staticNumbers.length) {
return (sum==staticTarget) ? 1 : 0;
}
return recursive(sum - staticNumbers[i], i+1) +
recursive(sum + staticNumbers[i], i+1);
}
public int solution(int[] numbers, int target) {
staticNumbers = numbers;
staticTarget = target;
return recursive(0, 0);
}
}
하지만 위와 같이, 전역 변수를 사용한다면, 이와 같은 불필요한 낭비를 줄일 수 있습니다.
실제로 비교했을 때, 실행 시간이 2배 정도 향상되었습니다.
그럼 이렇게 하는 게 바람직할까요?
결론
정답은 그렇지 않습니다. 최적화가 필요하다면 적절한 자료구조를 활용해서 시간복잡도를 줄이거나, 반복문을 사용하거나, TCO(꼬리 호출 최적화)를 사용하거나 다른 기술을 이용해야지, 전역 변수를 사용하는 것은 옳지 않습니다.
왜냐하면, 전역변수를 사용한 프로그램을 여러 사람이 작업하게 되면, 변수를 잘못 관리해서 오류가 발생할 확률이 높아지고 디버그가 어려워집니다.
옛날에는 메모리나 CPU 같은 물리적인 장치가 지금만큼 좋지 않아서, 숏코딩(변수를 하나라도 줄이고, 코드를 한 줄이라도 줄이자)이라는게 유행했었습니다. 하지만 지금은 효율을 극한의 극한까지 가독성을 버리고까지 추구하는 시대는 아닙니다. 그래서 공간이나 로직을 조금 더 쓰더라도 명확하게 의도를 잘 보여주는 코드가 좋다는 평가가 대세인듯 합니다.
저는 BE 개발에 있어, performance가 가장 중요하다고 생각했습니다. 하지만, stackoverflow 등의 사이트에서 관련된 이슈들을 찾아보니, Focusing on what is "faster" is bad code anyhow. 빠른 것에만 집중하는 코드는 나쁜 코드라고 합니다.
감사합니다.
'Language > Java' 카테고리의 다른 글
Effective Java - 생성자 대신 정적 팩토리 메서드를 사용하라! (0) | 2022.01.02 |
---|---|
StringBuilder를 초기화 하는 방법 3가지 성능 비교 (0) | 2021.10.07 |
JAVA - 프로젝트 패키지이름 명명(Naming) 관례 (0) | 2021.02.04 |
Java - 스택 트레이스(Stack Trace) 읽기 (3) | 2020.10.09 |
Java - 메인 메서드가 public static void인 이유 (0) | 2020.09.28 |