자바 프로그래밍

코드 리뷰 ABC - if문과 조건식의 코드 클린업 방법

plas 2020. 7. 24. 16:27
제 블로그에서 가장 인기있는 글이 if 문 줄이기인데, 최근 신간 『자바 코딩의 기술』 에 비슷한 내용이 있네요. 새로운 것도 있고 중요한 것 같아서 제 나름대로 다시 한번 정리해 보았습니다. 한국 상황에 맞게 약간 변경도 했습니다. 물론 이 책에는 이외에도 수준높은 내용들이 엄청나게 많습니다. 거기서 초보 개발자에게 의미있을 만한 일반적인 규칙들입니다.

(1) 불필요한 비교 조건식을 지워라

자바 언어에서는 int나 숫자 타입에 대해 반드시 비교를 해야 참거짓으로 바꿀 수 있다. 그런데 가끔 이런 문제 때문에 수식이나 함수의 결과가 진위형일 때도 == true 같이 비교식을 쓴 경우가 있다. 불필요한 진위식의 비교는 지우는 것이 필요하다.

if (largerThanA(x) == true)

일반적으로 진위형을 반환하는 함수를 만드는 것은 좋은 습관이다. 프로그램 안에서 참거짓을 판단해야 할 경우가 많이 생기고 이것을 함수화할 수 있다면 프로그램 구조가 좋아질 수 있다.

또는 복잡한 조건식을 아예 함수로 빼는 방법도 있다. 특히 그 조건의 검사가 자주 사용되는 수식이라면 함수로 만드는 것이 좋은 방법이다. 객체지향 프로그램에서는 객체에게 뭔가를 물어볼 때 참거짓을 답으로 받는 경우도 많다. 예를 들어 학생 객체가 책을 대여할 수 있는 자격 기준을 만족하는가 물어볼 때 다음과 같은 조건식을 이용할 수 있다.

student.hasEnrolled() && !student.isDelayed()

이 학생이 등록했고 연체가 없으면 대여 가능하다 라는 조건을 이렇게 표현할 수 있다. 이들 메소드는 모두 참거짓을 반환하는 함수가 된다.

(2) if 문을 대칭적으로 작성하는 것이 좋다.

if 문은 많은 경우 참 거짓을 대칭으로 가진다. ifelse가 짝이 되는 것이다. 가끔 여러 개의 조건을 ifelse if로 연결하기도 하는데 그것은 좋은 방법이 아니다.

if (user == null) {
	System.out.println(“유저가 없습니다.”);
} else if (user.getAverage() >= classAverage) {
	System.out.println(“열심히 하셨군요.”);
} else if (user.getAverage() < classAverage) {
	System.out.println(“조금 더 힘내세요.”);
}

이렇게 작성된 코드가 있다면 다음과 같이 바꾸면 구조가 훨씬 좋아짐을 알 수 있다. 별개의 조건식을 분리해서 user가 널인 경우는 그냥 리턴하게 한다. 그럼 user가 널이 아닌 경우만 5번 줄로 넘어가게 되고, 다음 조건에 대해 참과 거짓을 대칭되게 구성하므로써 가독성을 높이고 if 문도 간결해 진다.

if (user == null) {
	System.out.println(“유저가 없습니다.”);
	return;
}
if (user.getAverage() >= classAverage) {
	System.out.println(“열심히 하셨군요.”);
} else {
	System.out.println(“조금 더 힘내세요.”);
}

(3) 조건식에서 !(not)은 가능한 한 사용하지 마라

이상하지만 가끔 보게 되는 코드 형태 중 하나가 다음과 같이 !을 안 써도 되는 경우에 사용하는 것이다. 여기서 if 문은 당연히 !을 빼고 return 값을 바꾸어서 Status.ENROLLED;로 하는 것이 맞을 것이다.

1 	Status getState(Student member) {
2         if (!member.isEnrolled()) {
3             return Status.LEAVED;
4         } else {
5             return Status.ENROLLED;
6         }
7 	}

boolean 타입이나 여기서처럼 이것 또는 저것을 판단해야 하는 경우 !을 사용하지 않도록 if문과 else문을 한다. 보통 프로그래머는 자신의 생각의 순서에 따라 일단 코드를 작성하게 된다. 그러다 보면 이런 순서의 조건식이 나오게 되는데, 이것은 코딩을 마친 후 다시 한번 새로운 마음으로 체크해 보아야 하는 지점이다.

(4) 진위식은 바로 리턴해라

함수에서 boolean 타입을 리턴할 때 결과는 참 또는 거짓으로 돌아가게 된다. 이런 함수에서 다음과 같은 구조로 작성하는 것을 흔히 본다.

boolean qualified(int base) {
	boolean result = false;
	if (getSCore() >= base)
		result = true;
	return result;
}

여기서 수정할 수 있는 것은 무엇일까? 이런 경우 우리는 다음과 같이 간단한 return 문으로 바꿀 수 있고 지역변수의 선언은 물론 if 문도 필요하지 않음을 알 수 있다. 참거짓 결과가 하나의 조건식으로 표현되는 경우는 이렇게 if문을 따로 사용하지 않는 return 문이 좋다. 이것도 역시 체크리스트에 넣어두고 코딩이 끝난 후에 다시 한번 점검해 주는 것이 좋다.

boolean qualified(int base) {
	return (getSCore() >= base);
}

(5) 복잡한 진위식은 단순하게 바꿔라

프로그램이 커지면서 가끔 아주 복잡한 조건을 검사해야 되는 경우가 있다. 여기서 검사해야 하는 조건은 상당히 복잡할 수 있는데, 다음과 같은 경우를 생각해 보자.

if (ball.state != Ball.drop &&
	ball.state != Ball.sleep && ... &&
	ball.position.x > ground.LEFTMARGINE &&
	ball.position.x < ground.WIDTH &&
	ball.position.y ...)

이렇게 엄청나게 길어지는 조건식이 있다면 이런 것을 하나의 if 문으로 만드는 것은 절대 좋은 스타일이 아니다. 일단 이 조건식은 몇 개의 관련된 부분 함수로 나누어져야 한다.

if (ball.isActive() && ball.isInside() && ...)

그리고도 너무 길어지고 복잡해진다면 또다른 함수나 변수를 만들어 관련된 조건식을 묶어주는 것이 좋다. 하나의 if 문 조건식에서 너무 많은 &&||가 이어진다면 그것을 그룹으로 묶어서 2-3개의 조건으로 줄일 방법을 찾아봐야 한다. 이 때 나누어진 것도 너무 많으면 또 나누어서 계층적인 구조로 모듈화하게 되고 이렇게 검사식을 잘 나누어 두면 재사용하기에도 좋고 코드의 가독성도 높아진다.

(6) 조건식에서 널포인터 예외를 항상 체크하라

참조 변수를 사용하는 모든 조건식은 반드시 변수가 널인 경우를 체크해야 한다. 자바에서는 널 이외의 댕글링 참조가 일어나는 경우는 없으므로 참조 변수는 널인가만 체크해 주면 된다. 참조의 널 여부를 검사할 때 가장 좋은 방식은 다음과 같이 조건식 앞에 &&로 널 검사를 추가해 주는 것이다.

if (newStudent != null && newStudent.isOK())

이것은 단축 계산에 의해 앞의 조건이 거짓일 때는 && 뒤의 부분이 계산되지 않으므로 isOK는 아예 호출되지 않는다. 그러므로 널인 경우에 isOK를 호출하여 널포인터 오류가 나는 것을 막을 수 있다.

참조를 사용하는 모든 조건식에서 이렇게 널이 아닌지 검사하는 && 조건식을 추가해 주는 것이 좋은 프로그래밍 습관이다.

(7) 항상 {}를 사용하는 습관을 들여라

마지막 규칙으로 if 문과 다른 블록에 항상 {}를 사용하는 습관을 들이는 것이 좋다. 포함되는 문장이 한 줄밖에 없을 때는 {}를 생략해도 되지만 언제 코드가 추가될지 모르므로 자바에서는 항상 괄호로 블록을 묶어주는 것을 추천한다.

사실 이렇게 반드시 {}를 하고 조건식에는 항상 ()를 하는 등 자바는 타이핑이 많아지는 편이다. 특히 파이썬과 비교하면 이런 점이 두드러지는데, 자바는 코딩할 때 간결함보다는 안전성과 이해하기 쉬운 코드 작성에 방점을 두는 것이 특징이다. 이런 점이 파이썬이나 C를 주로 쓰던 사람에게는 상당히 번거롭고 불필요하게 길어진다(verbose)고 느껴질 수 있으나 그런 것이 자바의 스타일이라고 할 수 있다.