포스트

(C#) 4. 코드의 흐름 제어 (if-else)

조건에 따라 갈라지는 코드를 처음 써보며 정리한 것. else if가 별도 문법이 아니라는 점, 중괄호를 생략하면 생기는 문제, 매직 넘버를 enum으로 바꿔야 하는 이유까지.

(C#) 4. 코드의 흐름 제어 (if-else)

위에서 아래로만 흐르지 않게

2편에서 조건을 bool 값으로 만들었다. 이제 그 값에 따라 실행할 코드를 고른다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;

namespace if_else
{
    class Program
    {
        static void Main(string[] args)
        {
            int choice = 0; // 0:가위, 1:바위, 2:보, 3:치트키

            if (choice == 0)
            {
                Console.WriteLine("가위입니다.");
            }
            else if (choice == 1)
            {
                Console.WriteLine("바위입니다.");
            }
            else if (choice == 2)
            {
                Console.WriteLine("보입니다.");
            }
            else
            {
                Console.WriteLine("치트키입니다.");
            }
        }
    }
}

else if 는 하나의 문법이 아니다

처음엔 if, else if, else 세 가지 키워드가 있는 줄 알았다. 아니었다. else ifelse 뒤에 if 문 하나가 온 것뿐이다.

들여쓰기를 원래대로 풀면 이렇게 생겼다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (choice == 0)
{
    // ...
}
else
{
    if (choice == 1)
    {
        // ...
    }
    else
    {
        if (choice == 2) { }
        else { }
    }
}

C#에서는 else에 붙는 문장이 하나면 중괄호를 생략할 수 있고, if 문 전체가 문장 하나다. 그래서 한 줄로 이어 붙여 쓸 수 있고, 그 관례가 굳어져 else if처럼 보이게 됐다.

이걸 알고 나니 조건이 위에서부터 순서대로 검사된다는 게 당연해졌다. 앞 조건이 참이면 뒤는 아예 보지 않는다. 그래서 순서가 중요하다.

1
2
3
// 잘못된 순서 - 두 번째 조건에 절대 도달하지 않는다
if (hp > 0) { }
else if (hp > 50) { }

범위를 나눌 때는 좁은 조건을 먼저 둬야 한다.

마지막 else 가 받아내는 범위

원 코드에서 choice가 3이면 “치트키”가 나오는데, 4나 −1이어도 “치트키”가 나온다. else는 앞의 조건이 전부 거짓인 모든 경우를 받는다.

“치트키”라는 특정 값을 처리하려던 거라면 조건을 명시하고 진짜 예외는 따로 두는 게 맞다.

1
2
3
4
if (choice == 3)
    Console.WriteLine("치트키입니다.");
else
    Console.WriteLine("잘못된 입력입니다.");

의도하지 않은 값이 조용히 정상 경로로 흘러가면, 나중에 원인을 찾기 어렵다.

중괄호를 생략하면

문장이 하나면 중괄호를 뺄 수 있다.

1
2
if (isAlive)
    Console.WriteLine("살아있음");

짧아서 좋아 보이는데, 나중에 줄을 하나 더 추가할 때 사고가 난다.

1
2
3
if (isAlive)
    Console.WriteLine("살아있음");
    Console.WriteLine("체력 회복");   // 조건과 무관하게 항상 실행된다

들여쓰기가 맞아 보여서 눈으로는 안 잡힌다. C#은 들여쓰기가 아니라 중괄호로 블록을 정하기 때문이다. 실제로 이런 실수 때문에 유명한 보안 취약점이 난 적도 있다.

그래서 한 줄이어도 중괄호를 붙이는 쪽으로 굳혔다. 예외는 if (x) return;처럼 같은 줄에 끝나는 경우 정도다.

조건에 bool 만 들어간다

2편에서 본 내용인데 여기서 다시 걸린다.

1
2
if (choice)         // 컴파일 에러
if (choice = 0)     // 컴파일 에러

C에서는 if (choice = 0)이 대입 후 0이 되어 조용히 거짓이 됐다. C#은 intbool로 안 바꿔주니 컴파일 단계에서 잡힌다. ===로 잘못 치는 실수가 원천적으로 막히는 셈이다.

값 하나를 고르는 거면 삼항 연산자

if/else로 값만 정하는 경우가 자주 나온다.

1
2
3
4
5
string result;
if (hp > 0)
    result = "생존";
else
    result = "사망";

이건 한 줄로 쓸 수 있다.

1
string result = hp > 0 ? "생존" : "사망";

변수를 선언과 동시에 초기화할 수 있어서, 나중에 대입을 빠뜨리는 실수가 없어진다. readonlyconst로 만들 수도 있다.

중첩하면 급격히 읽기 어려워지므로 두 갈래까지만 쓰는 게 좋았다. 세 갈래 이상이면 switch 쪽이 낫다.

중첩이 깊어지면 먼저 빠져나간다

조건이 겹치면 들여쓰기가 계속 깊어진다.

1
2
3
4
5
6
7
8
9
10
if (player != null)
{
    if (player.IsAlive)
    {
        if (player.Hp < 50)
        {
            Heal(player);
        }
    }
}

아닌 경우를 먼저 걸러내고 빠져나가면 평평해진다.

1
2
3
4
5
if (player == null) return;
if (!player.IsAlive) return;
if (player.Hp >= 50) return;

Heal(player);

읽을 때 “여기까지 왔으면 앞의 조건은 모두 통과한 것”이 보장되니 머릿속에 담을 조건이 줄어든다. 이걸 가드 절이라고 부른다는 건 나중에 알았다.

매직 넘버를 없애기

0, 1, 2, 3이 각각 무슨 뜻인지는 주석에만 적혀 있다. 주석은 코드와 따로 놀 수 있고, 실제로 값을 하나 추가하면 주석 고치는 걸 잊는다.

enum으로 만들면 이름이 코드에 들어온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Choice
{
    Scissors,   // 0
    Rock,       // 1
    Paper,      // 2
    Cheat       // 3
}

Choice choice = Choice.Scissors;

if (choice == Choice.Scissors)
    Console.WriteLine("가위입니다.");
else if (choice == Choice.Rock)
    Console.WriteLine("바위입니다.");

enum은 값을 안 적으면 0부터 순서대로 붙는다. 주석에 적어둔 번호와 정확히 같다.

타입이 생기니 if (choice == 5) 같은 게 컴파일 에러가 되고, 편집기가 Choice.까지 치면 후보를 보여준다. 이 상태에서는 다음 편의 switch가 훨씬 잘 어울린다.

정리하면

  • else if는 별도 문법이 아니라 else 뒤에 온 if 문이다. 그래서 위에서부터 순서대로 검사된다
  • 범위 조건은 좁은 것을 먼저 둔다. 순서가 틀리면 도달하지 못하는 가지가 생긴다
  • 마지막 else는 나머지 전부를 받는다. 특정 값을 처리하려면 조건을 명시한다
  • 중괄호를 생략하면 줄을 추가할 때 조건 밖으로 새어 나간다
  • 값만 고르는 분기는 삼항 연산자, 중첩이 깊어지면 가드 절로 먼저 빠져나간다
  • 의미 있는 정수 상수는 enum으로 바꾼다
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.