고민끝에 정리해보는 if depth 줄이기

Mark64-1·2022년 1월 3일
1

잡담

목록 보기
2/3

처음 회사에 와서 첫 선배한테 많이 혼난 부분들이 있었는데, 그 부분들을 1년차가 된 지금 많이 고쳤다.
개발 동아리도 들어가서 같이 일해보고 하면서 개발 실력이 나름 늘어서 차근차근 기초공부를 하면서 다시 기반을 다지고있는 도중에, 처음 혼났던 부분들중에 아직도 이해할 수 없고 공부하지 못한 영역이 있다.
가장 많이 혼나고 고친 부분은 함수/변수 이름이였고, 그 다음은 프로그램 로직이 간결하고 명확해지는 것이였다.
( 보통 1함수 1기능이 안돼는 부분들이 많았고, 일단 실행해서 원하는대로 동작만 하면 완성!마인드가 고쳐졌다. )

그러나 아직도 햇깔리고 모르겠는 부분은 너는 if를 너무 많이 쓴다는 이야기를 들었던게 생각이 남았는데, 아무리 생각해봐도 대체 어떻게해야 경우의 수를 걸러야하는 if문과 같은 행동을 반복하는 for문이 더 적게 사용될 수 있다는거지? 라는 생각이였는데, 1년정도 막 개발하면서 어느정도 생각이 정립되어 글을 써본다.

항상 아직 멀었다는 생각도 하고 개발 공부할게 산더미라 이게 정답인지 명확한지 모르겠지만 당장 1년차 주니어일때 검색해서 공부하고 그것을 블로그에 써보는것도 의미가 있다고 생각한다.
(이번 글은 거의 여기서 퍼왔다 라는 뜻이다.)

if (select == 1) System.out.println("하복 판매");
else if (select == 2) System.out.println("춘추복 판매");
else if (select == 3) System.out.println("동복 판매");

개발하다보면 비슷한 if문이 나올 일들이 참 많다.
나도 그랬고, 위에서 햇던 이야기도 이것때문에 나왔는데 나는 당시에 If를 줄일수 있다는 이야기를 듣고 switch case문으로 바꿨다(...)

switch(select)
case 1 : System.out.println("하복 판매");
case 2 : System.out.println("춘추복 판매");
case 3 : System.out.println("동복 판매");

그런데 이제서야 그 의미가 어느정도 깨달아졌다. 바로 배열을 잘 이용하는것이다.

String seasons[] = {"하복", "춘추복", "동복"};
System.out.println(seasons[select-1]) + " 판매");

바로 이런식으로 처리하면 if문 없이 바로 처리할 수 있는것이다.
지금이야 sout형식이지만, 변수에 담아지더라도 동일한 처리가 가능하다.

String selectedSeason = seasons[select-1]

좀 더 복잡해진 경우들도 많은데, key value 사용해야하는 dictionary 형태가 대표적이다.

if (keyword.equals("하복") price = (int) (20000 * 1.1);
else if (keyword.equals("춘추복") price = (int) (40000 * 1.1);
else if (keyword.equals("동복") price = (int) (50000 * 1.1);

!!!위와 같이 수식 계산이 중복되는 경우도 다음과 같이 고칠 수 있다.!!!

String seasons[] = {"하복", "춘추복", "동복"};
int prices[] = {20000, 40000, 50000};
int index = findIndexOf(seasons, keyword);
price = (int) (prices[index] * 1.1);

또한 예시중에서 가장 대표적이고, 특히 고등학교 코딩수업시간 같은 경우에 많이 배우는 예시가 또 줄일 수 있는 예시이다.

for (int i = 0; i < 10; i++){
  if (score[i] >= 90) grade = "A";
  else if (score[i] >= 80) grade = "B";
  else if (score[i] >= 70) grede = "C";
  else if (score[i] >= 60) grade = "D";
  else grade = "F"; 
}

해당 코드는 고등학교때 코딩시간이 있다면 한번쯤 해볼만한 그 코드다
그런데, 이 당연하다고 생각된 코딩이 사실 간편해질 길이 있다면?

int interval[] = {90, 80, 70, 60};
char* gradeStr[] = {"A", "B", "C", "D", "F"};
for (int i = 0; i < 10; i++) { 
  grade = "F";
  for (int j = 0; j < 4; j++) {
  	if (score[i] >= interval[j]) { 
    		grade = gradeStr[j]; break; 
            }
  }
}

위 코드는 뭐가 많이 다른걸까?
사실 나도 if문을 줄였다면 줄인거지만 이게 뭐가 줄인거야 라는 생각을 했지만, 이런 코딩에는 2가지 이면이 있다.

  1. 자유자재로 점수와 등급을 늘릴 수 있다.(물론, 10이나 4같은 숫자를 length로 바꿔줘야한다)
  2. if가 switch case가 된것보다 가독성이 줄어든다.

사실 저런 코딩은 if를 줄이고 코드를 자유자재로 사용하기 좋게 만들어주지만, 코드 가독성은 조금 떨어졌을 수 있다. 한눈에 봤을때는 위 코드가 더 눈에 한방에 이해시켜주기 때문이다.
사실 개발에서 아직도 답을 찾지 못하고 해메고있는 질문중에 하나는 가독성 vs 편의성이다.
대충 개발한 코드가 머리에서 나온대로 바로 짜서 가독성이 좋다라는 헛소리는 하면 안돼겠지만, 가독성과 편의성에서 줄타기를 하면서 그 중간을 찾는게 중요할 것 같다. 이제 다음 케이스를 보자.

boolean match(String kwd) { 
  boolean result = false;
  if (name.equals(kwd) || Integer.toString(id).equals(kwd)) 
  	result = true; return result; 
}

해당 코드는 얼핏 아무 문제가 없어보인다.
1개의 메서드에 1개의 역할.
메서드가 실행했을때 같은 값을 집어넣으면 항상 같은 값을 배출하는것까지 문제가 없다.
그러나 해당 코드는 이런 수정이 가능하다.

boolean match(String kwd) { 
	return (name.equals(kwd) || Integer.toString(id).equals(kwd));
}

한줄로 줄어들었다!
애초에 반환할 변수이기도 하지만, 어떤 절차없이 바로 반환해야하는 변수는 선언할 이유가 없기 때문이다.
if문을 줄인다는것. 더 나아가 간결한 코드를 만든다는것은 이런것들을 빠짐없이 체크해줘야한다.

if {
	(seasonOff) System.out.printf("%s (%d벌) %d원 [50% 세일]\n", category, copies, price/2);
} else {
	System.out.printf("%s (%d벌) %d원\n", category, copies, price);
}

출력 뿐만아니라 변수에 뭔가를 담을때도 이런 if문 많이 사용하는 경우가 많다.
특히 회사에서 뭔가 배너하나 추가해달라고 요청같은거 오면 이런식으로 자주 처리를 하고는 한다.
허나 이 케이스도 좀 더 정립이 가능하다.

System.out.printf("%s (%d벌) %d원", category, copies, (seasonOff) ? price/2 : price); 
if (seasonOff) System.out.print(" [50% 세일]");
System.out.println();

변수에도 저런식으로 담는다고 생각하면 편하다.
통일되는 부분들. 일정한 부분을 먼저 담고, 그 이후에 더하는것이다.

int matches(String kwd) { 
	if (kwd != null) { 
          if (names.equals(kwd)) return true; 
          if (dept.equals(kwd)) return true;
    	}
    return false; 
}

와... 이거 정말 많이 보는 케이스다.
현업에서 이 경우가 좀 많이 나오는 일이 잦은데, 자꾸 기획팀에서 이거 추가해달라. 영업팀에서 이거 추가해달라 하기 시작하면 일정 맞추다가 많이 겪는 실수중에 하나다.
해당 코드는 가독성도, 의미도 한눈에 알아보기 힘든 정말 단언할 수 있는 개쓰레기 코드인데, 해당 코드는 이런 형식으로 정리할 수 있다.

int matches(String kwd) { 
	if (kwd == null) { 
	    return false
        }
         if (names.equals(kwd)) return true; 
         if (dept.equals(kwd)) return true;
}

이것도 충분히 쓰레기같은데... 뭐가 다른거지?라고 생각한다면 바로 depth의 문제다.
이번 문제의 주제기도 한데, 자꾸 depth가 깊어지면 하나의 Return에 조건이 늘어나고 생각점이 늘어난다.
또한 메서드의 실행에서 명확한 실행점을 찾기가 힘든 부분도 있다.
핵심은 이거다. 조건이 늘어나면 If가 따라 늘어나는것은 어쩔 수 없는 영역에 존재하므로, depth라도 줄이고 가독성이라도 올리며 코드 단순화를 통해 사용자의 실수를 줄이자는것이다.

while (true) {
	num = scan.nextInt();
    if (num != 0) { 
    	if (num >= 0 && num < 10) { 
	        // 썸띵 원하는 동작
        }
        else { System.out.println("잘못된 입력입니다."); }
    } else break; 
}

걸러내기 조건부도 생각보다 고려가 많이 되는 영역인데, 이것도 핵심 동작이 있는 영역의 depth를 줄여보자.

while (true) {
	num = scan.nextInt();
    if (num == 0) break;
    if (num < 0 || num >= 10) { 
    	System.out.println("잘못된 입력입니다."); continue;
    }
    //섬띵 원하는 동작
    }

놀랍게도 원하는 동작이 맨 아래로 가면서 4depth쯤에 있던 동작들이 2depth로 나오게되면서 동작이 직관적으로 변했다.
코드의 걸러내는 순서와 구조만 변경해서 복잡도를 낮춘것이다!
여기까지 우리는 depth를 줄이면서 뭐가 이득인지를 알아낼 수 있게 되었다.
1. 결국 루프 전체적으로 depth의 깊이가 얕아져 보기가 훨씬 좋다.
2. 결국 루프의 원하는 동작 부분에서 코드가 깊이 중첩되지 않고 나타난다. 그 결과 코드가 훨씬 간결해 보이고 이해하기도 쉬워진다.
3. 루프가 종료되거나 입력을 무시하고 넘어가야 하는 경우에 대해 루프의 앞부분에서 처리하므로서 루프의 메인 부분 코드를 읽을 때 그런 조건을 고려하지 않아도 된다.
이득밖에 없는 개발법이다. depth를 줄이고 필요없는 IF를 줄이는것이 좋은 개발을 위한 첫걸음이 아닐까?

profile
개발자임미다.

0개의 댓글