11 minute read

개요

조건문은 프로그램의 실행 흐름을 제어하는 핵심 구문입니다. Go는 ifswitch 두 가지 조건문을 제공하며, 각각 고유한 특징과 장점이 있습니다.

특징

  • 괄호 생략: 조건식을 괄호 ()로 감싸지 않음
  • 중괄호 필수: 코드 블록은 반드시 중괄호 {}로 감싸야 함
  • if with initializer: 조건문 내에서 변수 초기화 가능
  • 표현식 switch: 값뿐만 아니라 조건식도 사용 가능
  • type switch: 인터페이스의 타입 판별 가능


if 문

기본 형식

단순 if

package main

import "fmt"

func main() {
	age := 20
	
	if age >= 18 {
		fmt.Println("성인입니다.")
	}
}

// 출력: 성인입니다.

if-else

package main

import "fmt"

func main() {
	score := 75
	
	if score >= 60 {
		fmt.Println("합격")
	} else {
		fmt.Println("불합격")
	}
}

// 출력: 합격

if-else if-else

package main

import "fmt"

func main() {
	score := 85
	
	if score >= 90 {
		fmt.Println("A 학점")
	} else if score >= 80 {
		fmt.Println("B 학점")
	} else if score >= 70 {
		fmt.Println("C 학점")
	} else {
		fmt.Println("D 학점")
	}
}

// 출력: B 학점

if with initializer

조건문 내에서 변수를 초기화하고 해당 스코프 내에서만 사용할 수 있습니다.

기본 사용법

package main

import "fmt"

func main() {
	// if 문 안에서 변수 초기화
	if num := 10; num > 5 {
		fmt.Println("num이 5보다 큽니다:", num)
	}
	
	// num은 if 블록 밖에서 사용 불가
	// fmt.Println(num)  // 컴파일 에러!
}

// 출력: num이 5보다 큽니다: 10

에러 처리 패턴 (가장 일반적)

package main

import (
	"errors"
	"fmt"
)

func divide(a, b float64) (float64, error) {
	if b == 0 {
		return 0, errors.New("0으로 나눌 수 없습니다")
	}
	return a / b, nil
}

func main() {
	// 함수 호출과 동시에 에러 체크
	if result, err := divide(10, 2); err != nil {
		fmt.Println("에러:", err)
	} else {
		fmt.Println("결과:", result)
	}
	
	// result, err은 if 블록 밖에서 사용 불가
}

// 출력: 결과: 5

파일 처리 예제

package main

import (
	"fmt"
	"os"
)

func main() {
	// 파일 열기와 에러 체크를 한 번에
	if file, err := os.Open("test.txt"); err != nil {
		fmt.Println("파일 열기 실패:", err)
	} else {
		defer file.Close()
		fmt.Println("파일 열기 성공")
		// file 사용
	}
}

여러 변수 초기화

package main

import "fmt"

func main() {
	if x, y := 10, 20; x < y {
		fmt.Printf("%d가 %d보다 작습니다\n", x, y)
	}
}

// 출력: 10가 20보다 작습니다

복합 조건

논리 연산자

package main

import "fmt"

func main() {
	age := 25
	hasLicense := true
	
	// AND 연산자 (&&)
	if age >= 18 && hasLicense {
		fmt.Println("운전 가능")
	}
	
	// OR 연산자 (||)
	isWeekend := true
	isHoliday := false
	
	if isWeekend || isHoliday {
		fmt.Println("쉬는 날")
	}
	
	// NOT 연산자 (!)
	isRaining := false
	if !isRaining {
		fmt.Println("우산 필요 없음")
	}
}

// 출력:
// 운전 가능
// 쉬는 날
// 우산 필요 없음

조건 결합

package main

import "fmt"

func main() {
	age := 25
	income := 3000
	hasJob := true
	
	if (age >= 20 && age < 30) && (income > 2000 || hasJob) {
		fmt.Println("대출 승인")
	}
}

// 출력: 대출 승인

중첩 if문

package main

import "fmt"

func main() {
	score := 85
	attendance := 90
	
	if score >= 60 {
		if attendance >= 80 {
			fmt.Println("합격")
		} else {
			fmt.Println("출석 부족")
		}
	} else {
		fmt.Println("점수 부족")
	}
}

// 출력: 합격


switch 문

기본 switch

기본 형식

package main

import "fmt"

func main() {
	day := 3
	
	switch day {
	case 1:
		fmt.Println("월요일")
	case 2:
		fmt.Println("화요일")
	case 3:
		fmt.Println("수요일")
	case 4:
		fmt.Println("목요일")
	case 5:
		fmt.Println("금요일")
	case 6, 7:  // 여러 값 매칭
		fmt.Println("주말")
	default:
		fmt.Println("잘못된 요일")
	}
}

// 출력: 수요일

여러 값 매칭

package main

import "fmt"

func main() {
	char := 'a'
	
	switch char {
	case 'a', 'e', 'i', 'o', 'u':
		fmt.Println("모음")
	default:
		fmt.Println("자음")
	}
}

// 출력: 모음

switch with initializer

package main

import (
	"fmt"
	"time"
)

func main() {
	// switch 문 안에서 변수 초기화
	switch hour := time.Now().Hour(); {
	case hour < 12:
		fmt.Println("오전")
	case hour < 18:
		fmt.Println("오후")
	default:
		fmt.Println("저녁")
	}
}

표현식 switch

조건식을 평가하여 true인 case 실행 (if-else if 대체 가능)

기본 사용

package main

import "fmt"

func main() {
	score := 85
	
	// switch 뒤에 조건 변수 없음
	switch {
	case score >= 90:
		fmt.Println("A")
	case score >= 80:
		fmt.Println("B")
	case score >= 70:
		fmt.Println("C")
	case score >= 60:
		fmt.Println("D")
	default:
		fmt.Println("F")
	}
}

// 출력: B

복잡한 조건

package main

import "fmt"

func main() {
	age := 25
	income := 3000
	
	switch {
	case age < 20:
		fmt.Println("미성년자")
	case age >= 20 && age < 30 && income > 2000:
		fmt.Println("청년 고소득자")
	case age >= 30 && age < 60:
		fmt.Println("중년")
	case age >= 60:
		fmt.Println("노년")
	default:
		fmt.Println("기타")
	}
}

// 출력: 청년 고소득자

fallthrough

다음 case를 강제로 실행합니다 (조건 검사 없이).

package main

import "fmt"

func main() {
	num := 1
	
	switch num {
	case 1:
		fmt.Println("Case 1")
		fallthrough  // 다음 case로 계속 진행
	case 2:
		fmt.Println("Case 2")
		fallthrough
	case 3:
		fmt.Println("Case 3")
	case 4:
		fmt.Println("Case 4")
	}
}

// 출력:
// Case 1
// Case 2
// Case 3

fallthrough 주의사항

package main

import "fmt"

func main() {
	score := 95
	
	switch {
	case score >= 90:
		fmt.Println("우수")
		fallthrough
	case score >= 80:  // score 값과 무관하게 실행됨
		fmt.Println("양호")
	case score >= 70:
		fmt.Println("보통")
	}
}

// 출력:
// 우수
// 양호

Type Switch

인터페이스의 실제 타입을 판별합니다.

기본 사용

package main

import "fmt"

func describe(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("정수: %d\n", v)
	case float64:
		fmt.Printf("실수: %.2f\n", v)
	case string:
		fmt.Printf("문자열: %s\n", v)
	case bool:
		fmt.Printf("불린: %t\n", v)
	case nil:
		fmt.Println("nil")
	default:
		fmt.Printf("알 수 없는 타입: %T\n", v)
	}
}

func main() {
	describe(42)
	describe(3.14)
	describe("hello")
	describe(true)
	describe(nil)
	describe([]int{1, 2, 3})
}

// 출력:
// 정수: 42
// 실수: 3.14
// 문자열: hello
// 불린: true
// nil
// 알 수 없는 타입: []int

여러 타입 매칭

package main

import "fmt"

func checkType(i interface{}) {
	switch i.(type) {
	case int, int32, int64:
		fmt.Println("정수 타입")
	case float32, float64:
		fmt.Println("실수 타입")
	case string:
		fmt.Println("문자열 타입")
	default:
		fmt.Println("기타 타입")
	}
}

func main() {
	checkType(42)
	checkType(3.14)
	checkType("hello")
}

// 출력:
// 정수 타입
// 실수 타입
// 문자열 타입

타입별 처리

package main

import "fmt"

func process(data interface{}) {
	switch v := data.(type) {
	case string:
		fmt.Printf("문자열 길이: %d\n", len(v))
	case []int:
		sum := 0
		for _, num := range v {
			sum += num
		}
		fmt.Printf("슬라이스 합계: %d\n", sum)
	case map[string]int:
		fmt.Printf("맵 크기: %d\n", len(v))
	default:
		fmt.Printf("처리할 수 없는 타입: %T\n", v)
	}
}

func main() {
	process("Hello")
	process([]int{1, 2, 3, 4, 5})
	process(map[string]int{"a": 1, "b": 2})
	process(123)
}

// 출력:
// 문자열 길이: 5
// 슬라이스 합계: 15
// 맵 크기: 2
// 처리할 수 없는 타입: int


switch vs if 비교

if 문 사용이 적합한 경우

// 복잡한 조건식
if (age >= 18 && hasLicense) || isSpecialCase {
	// ...
}

// 범위 체크
if score >= 90 && score <= 100 {
	// ...
}

// 부정 조건
if !isValid {
	// ...
}

switch 문 사용이 적합한 경우

// 명확한 값 매칭
switch status {
case "pending", "processing":
	// ...
case "completed":
	// ...
case "failed":
	// ...
}

// 타입 검사
switch v := data.(type) {
case string:
	// ...
case int:
	// ...
}

// 여러 조건 분기 (가독성)
switch {
case score >= 90:
	// ...
case score >= 80:
	// ...
case score >= 70:
	// ...
}


실용 예제

예제 1: HTTP 상태 코드 처리

package main

import "fmt"

func handleHTTPStatus(statusCode int) {
	switch {
	case statusCode >= 200 && statusCode < 300:
		fmt.Println("성공")
	case statusCode >= 300 && statusCode < 400:
		fmt.Println("리다이렉션")
	case statusCode >= 400 && statusCode < 500:
		fmt.Println("클라이언트 에러")
	case statusCode >= 500:
		fmt.Println("서버 에러")
	default:
		fmt.Println("잘못된 상태 코드")
	}
}

func main() {
	handleHTTPStatus(200)  // 성공
	handleHTTPStatus(404)  // 클라이언트 에러
	handleHTTPStatus(500)  // 서버 에러
}

예제 2: 유효성 검사

package main

import (
	"fmt"
	"strings"
)

func validateUser(username, password string) error {
	if len(username) < 3 {
		return fmt.Errorf("사용자명은 3자 이상이어야 합니다")
	}
	
	if len(password) < 8 {
		return fmt.Errorf("비밀번호는 8자 이상이어야 합니다")
	}
	
	if !strings.ContainsAny(password, "0123456789") {
		return fmt.Errorf("비밀번호에 숫자가 포함되어야 합니다")
	}
	
	return nil
}

func main() {
	if err := validateUser("alice", "password123"); err != nil {
		fmt.Println("유효성 검사 실패:", err)
	} else {
		fmt.Println("유효성 검사 성공")
	}
}

// 출력: 유효성 검사 성공

예제 3: 요일별 할인율

package main

import (
	"fmt"
	"time"
)

func getDiscountRate() float64 {
	switch time.Now().Weekday() {
	case time.Monday, time.Tuesday:
		return 0.1  // 10% 할인
	case time.Wednesday:
		return 0.15 // 15% 할인
	case time.Thursday, time.Friday:
		return 0.05 // 5% 할인
	case time.Saturday, time.Sunday:
		return 0.2  // 20% 할인 (주말)
	default:
		return 0.0
	}
}

func main() {
	rate := getDiscountRate()
	fmt.Printf("오늘의 할인율: %.0f%%\n", rate*100)
}

예제 4: 나이대별 분류

package main

import "fmt"

func getAgeGroup(age int) string {
	switch {
	case age < 0:
		return "잘못된 나이"
	case age < 13:
		return "어린이"
	case age < 20:
		return "청소년"
	case age < 30:
		return "청년"
	case age < 60:
		return "중년"
	default:
		return "노년"
	}
}

func main() {
	ages := []int{5, 15, 25, 45, 65}
	
	for _, age := range ages {
		fmt.Printf("%d세: %s\n", age, getAgeGroup(age))
	}
}

// 출력:
// 5세: 어린이
// 15세: 청소년
// 25세: 청년
// 45세: 중년
// 65세: 노년

예제 5: JSON 데이터 처리

package main

import (
	"encoding/json"
	"fmt"
)

func processJSON(data []byte) {
	var result interface{}
	
	if err := json.Unmarshal(data, &result); err != nil {
		fmt.Println("JSON 파싱 실패:", err)
		return
	}
	
	switch v := result.(type) {
	case map[string]interface{}:
		fmt.Printf("객체: %d개 필드\n", len(v))
	case []interface{}:
		fmt.Printf("배열: %d개 요소\n", len(v))
	case string:
		fmt.Printf("문자열: %s\n", v)
	case float64:
		fmt.Printf("숫자: %.2f\n", v)
	case bool:
		fmt.Printf("불린: %t\n", v)
	case nil:
		fmt.Println("null")
	}
}

func main() {
	processJSON([]byte(`{"name": "Alice", "age": 30}`))
	processJSON([]byte(`[1, 2, 3, 4, 5]`))
	processJSON([]byte(`"Hello"`))
	processJSON([]byte(`42.5`))
}

// 출력:
// 객체: 2개 필드
// 배열: 5개 요소
// 문자열: Hello
// 숫자: 42.50


종합 예제

package main

import (
	"errors"
	"fmt"
)

func main() {
	// 1. 기본 if-else
	i := 0
	if i == 0 {
		fmt.Println("i == 0")
	} else if i == 1 {
		fmt.Println("i == 1")
	} else {
		fmt.Println("else")
	}
	
	fmt.Println("------")
	
	// 2. switch 문
	i = 0
	switch i {
	case 0:
		fmt.Println("i == 0")
	case 1:
		fmt.Println("i == 1")
	default:
		fmt.Println("default")
	}
	
	fmt.Println("------")
	
	// 3. if with initializer (에러 처리)
	job := func() error {
		return errors.New("fail")
	}
	
	if err := job(); err != nil {
		fmt.Println(err.Error())
	}
	
	if err := job(); err != nil {
		fmt.Println(err.Error())
	}
	
	fmt.Println("------")
	
	// 4. 표현식 switch
	score := 85
	switch {
	case score >= 90:
		fmt.Println("A")
	case score >= 80:
		fmt.Println("B")
	case score >= 70:
		fmt.Println("C")
	default:
		fmt.Println("F")
	}
	
	fmt.Println("------")
	
	// 5. type switch
	describe := func(i interface{}) {
		switch v := i.(type) {
		case int:
			fmt.Printf("정수: %d\n", v)
		case string:
			fmt.Printf("문자열: %s\n", v)
		default:
			fmt.Printf("알 수 없는 타입: %T\n", v)
		}
	}
	
	describe(42)
	describe("hello")
	describe(true)
}

// 출력:
// i == 0
// ------
// i == 0
// ------
// fail
// fail
// ------
// B
// ------
// 정수: 42
// 문자열: hello
// 알 수 없는 타입: bool


일반적인 실수

1. 괄호 사용

// ❌ 잘못됨
if (x > 0) {  // 불필요한 괄호
	// ...
}

// ✅ 올바름
if x > 0 {
	// ...
}

2. 중괄호 생략

// ❌ 잘못됨 - 컴파일 에러!
if x > 0
	fmt.Println("positive")

// ✅ 올바름
if x > 0 {
	fmt.Println("positive")
}

3. switch에서 break 사용

// ❌ 불필요 (Go는 자동으로 break)
switch x {
case 1:
	fmt.Println("one")
	break  // 불필요
case 2:
	fmt.Println("two")
	break  // 불필요
}

// ✅ 올바름
switch x {
case 1:
	fmt.Println("one")
case 2:
	fmt.Println("two")
}

4. 변수 스코프 실수

// ❌ 잘못됨
if x := 10; x > 5 {
	fmt.Println(x)
}
fmt.Println(x)  // 컴파일 에러! x는 if 블록 밖에서 사용 불가

// ✅ 올바름 - 외부에서도 사용하려면
x := 10
if x > 5 {
	fmt.Println(x)
}
fmt.Println(x)  // OK

5. fallthrough 오용

// ❌ 잘못된 사용
switch x {
case 1:
	fmt.Println("one")
	fallthrough
default:  // fallthrough는 default로 갈 수 없음
	fmt.Println("default")
}

// ✅ 올바름
switch x {
case 1:
	fmt.Println("one")
	fallthrough
case 2:
	fmt.Println("two")
}

6. 조건식에서 할당 연산자 사용

// ❌ 잘못됨
x := 5
if x = 10 {  // 컴파일 에러! = 대신 ==
	// ...
}

// ✅ 올바름
if x == 10 {  // 비교 연산자
	// ...
}

// 또는 초기화와 조건 분리
if x := 10; x == 10 {
	// ...
}


베스트 프랙티스

  1. if with initializer 활용: 에러 처리 시 스코프 제한
  2. early return: 중첩 줄이기
  3. switch 우선: 값 매칭이 많을 때는 switch 사용
  4. 표현식 switch: if-else if 체인보다 가독성 좋음
  5. type switch: 인터페이스 타입 판별에 활용
  6. fallthrough 신중히: 의도가 명확할 때만 사용
  7. 명확한 조건: 복잡한 조건은 변수로 분리
  8. default 케이스: 예외 상황 처리
  9. 짧은 변수명: 초기화 변수는 짧게 (err, ok, v 등)
  10. 일관성: 프로젝트 전체에서 동일한 스타일 유지


Early Return 패턴

// ❌ 중첩이 깊음
func process(data string) error {
	if data != "" {
		if len(data) > 10 {
			if isValid(data) {
				// 실제 로직
				return nil
			} else {
				return errors.New("invalid")
			}
		} else {
			return errors.New("too short")
		}
	} else {
		return errors.New("empty")
	}
}

// ✅ Early return으로 중첩 제거
func process(data string) error {
	if data == "" {
		return errors.New("empty")
	}
	
	if len(data) <= 10 {
		return errors.New("too short")
	}
	
	if !isValid(data) {
		return errors.New("invalid")
	}
	
	// 실제 로직
	return nil
}


참고 자료