[Tips] 반복되는 값을 상수로 선언하자.
심심해서 적어보는 시리즈.
트러블 슈팅 끝에 얻은 코딩 팁들을 앞으로 Tips 시리즈로 소개해드리려고 한다.
이 글은 레퍼런스 부담 없이, 간략하게만 쓸 생각이다.
요약
- 함수 호출 횟수를 줄이면 성능이 향상된다.
- 반복되는 값은 상수로 선언하는 것이 좋다.
반복적인 함수 호출은 비용이다
오늘의 첫번째는, 함수 호출에 대한 이야기이다.
우리가 코딩을 하다보면 종종 잊는 사실이 있는데,
함수를 호출하는 것은 생각보다 꽤 큰 비용이 드는 작업이라는 사실이 그것이다.
물론 알고리즘 문제풀이나 빅데이터 가공과 같은 극단적인 상황이 아니라면 충분히 감수할 수 있지만,
그래도 확실하게 티가 나는 부분들이 존재한다.
함수의 반복 호출
for(int i = 0; i < getSize(); i++) {
// 연산...
}
위의 코드를 보자. 그냥 보기에는 명료한 코드 같다.
하지만 만약 위 반복문이 10,000번 실행되면 어떨까?
- 당연히 getSize() 역시 10,000번 호출된다.
- 만약 getSize()가 반복문 진행동안 쭉 같은 값을 반환한다면, 동일한 연산을 10,000번 수행하게 된다.
개선: 상수로 값 정의하기
위를 아래처럼 바꾸어 보자.
실행 시간이 확 줄어든다. 특히 해당 함수에서 연산을 수행한다면, 시간이 압도적으로 줄어들 것이다.
const int size = getSize();
for(int i = 0; i < size; i++) {
// 연산...
}
물론 이것이 모든 경우에 적용되는 룰은 아니다.
- 위 경우는 for문의 사이즈 조건이고, 반환값이 연산 동안 바뀔 일이 없으므로 상수로 값을 빼낼 수 있었다.
- 아래처럼 조건에 따라 다른 값을 반환하는 함수라면, 값을 상수로 선언할 수도 없고 연산 밖으로 빼낼 수도 없다.
while(isBig(value)) {
value++;
}
또한 컴파일러에 따라 해당 작업을 최적화해주기도 하므로, 해당 룰에 너무 집착할 필요는 없다.
다만 이와 같은 함수의 반환값을 외부로 빼는 행위는 또다른 장점을 가진다.
함수 호출을 줄여 가독성 향상
아래의 코드를 한 번 보자.
if(value < getBigNumber()){
printf("big!");
} else if(value == getBigNumber()){
printf("same!");
} else {
printf("small!");
}
getBigNumber라는 함수가 반복적으로 호출되고 있다.
그 탓에 괄호가 많아져 조건문의 가독성도 떨어지고, 코드를 한 눈에 알아보기 어려워진다.
이를 아래처럼 고쳐보면 어떨까?
int bigNumber = getBigNumber();
if(value < bigNumber){
printf("big!");
} else if(value == bigNumber){
printf("same!");
} else {
printf("small!");
}
전반적으로 괄호 수가 적어져 조건을 보기 편해졌다.
또한 이전에 함수로 호출한 값을 상수로 저장했으므로, 해당 값을 재활용할 수 있다.
이에 따라 함수 호출 횟수도 줄어 코드 효율도 높아진다.
반복적으로 사용되는 값을 상수로 선언하기
함수 반환값을 상수로 선언하기
반복적으로 호출되는 함수값을 상수로 저장하는 것은 또 하나의 부수적인 장점을 가지는데,
해당 상수의 이름을 명확하게 지정하면 코드를 더 빨리 이해할 수 있다는 것이다.
아래 코드를 보자.
if(color == Color.valueOf("WHITE")) {
// 연산...
}
사전 지식 없이 위 코드를 보면, valueOf라는 함수가 어떤 동작을 취하는지 한 번 고민을 해야 한다.
const Color COLOR_WHITE = Color.valueOf("WHITE");
(...)
if (color == COLOR_WHITE) {
// 연산...
}
그러나 위처럼 표현하게 되면, 선언부를 확인하지 못하고 조건문만 보더라도 의미를 더 명확히 이해할 수 있다.
이 방식은 부수적인 장점을 또 가지게 된다.
협업 과정에서 로직을 잘못 이해해 벌어지는 실수를 막을 수 있다.
- 예를 들어, 새로운 동료가 해당 조건문을 고치는 과정에서 함수에 인자를 잘못 넣었다고 생각해보자!
- 선언부에 미리 값을 정의해두면 이러한 실수를 줄일 수 있을 것이다.
리팩토링이 쉬워진다.
- 만약 Color.valueOf("WHITE") 라는 코드가 파일 내에 10개 존재한다고 해보자. 이때 로직이 변경되면 어떨까?
- 모든 코드를 Color.valueOf("PINK") 로 변경해야 한다면, 10번 작업하는 것과 1번 작업하는 것의 차이는 크다.
- 만약 Color라는 라이브러리가 삭제되어 코드를 NewColor.getWhite()로 교체한다고 생각해보자.
- 이 경우 원래 코드는 수정 범위도 넓어지고 동료들에게 전파할 변경사항도 많아진다.
- 그러나 선언부만 고쳐도 된다면, 아래의 로직은 변경될 필요도 없고 선언부를 모르는 사람도 안심하고 작업할 수 있다.
자주 사용되는 값 상수로 선언하기
함수 호출값 뿐 아니라 반복되는 값 역시 상수로 저장해두는 것이 좋다.
int SIZE_LIMIT = 1048576;
if(size >= SIZE_LIMIT) {
// 연산...
}
리터럴 값이나 함수 호출, 단순 연산 등을 반복하는 것은 좋지 않기 때문이다.
- 수정이 어렵다
- 코드의 의미를 파악하기 어렵다.
부가설명
본 글에서는 자주 사용되는 함수 반환값이나 리터럴 등을 상수로 저장하라고 이야기했다.
이때 변수가 아닌 상수라고 표현한 이유는 다음과 같다.
변하지 않는 값을 상수로 선언해 코드상에 명시해야 한다.
- 그렇지 않으면, 해당 값을 조작하는 실수를 수행할 수 있다.
만약 주기적으로 바뀌는 값이라면, 필요할 때마다 함수를 호출하거나 직접 연산하는 게 나을 수도 있다.
- 억지로 값들을 모두 변수나 상수로 선언하면, 오히려 코드가 지저분해 질 수 있다.
평소에 코드를 짜면서 하는 생각을 간단히 정리해 보았다.
개인적인 의견이므로 잘못된 내용이 포함되었을 수 있다.
부족하고 그만큼 열려있고자 하기에, 편한 의견과 지적 부탁드립니다.