switch 문
switch 문이란?
switch는 하나의 값이나 조건에 따라 여러 분기 중 하나를 선택하는 제어 구조입니다. 긴 if-else if 체인을 대체하면서 훨씬 읽기 쉬운 코드를 만들 수 있습니다.
Go의 switch는 다른 언어와 비교해 몇 가지 중요한 차이점이 있습니다.
- 자동 break: 각
case가 끝나면 자동으로switch를 빠져나옵니다. C, Java처럼break를 명시할 필요가 없습니다. - 조건 없는 switch: 표현식 없이
switch만 쓰면if-else if처럼 동작합니다. - 타입 switch: 인터페이스 값의 실제 타입을 분기할 수 있는 강력한 기능입니다.
- 다중 케이스: 하나의
case에 쉼표로 여러 값을 나열할 수 있습니다.
기본 값 비교 switch
가장 일반적인 형태입니다. 표현식의 결과를 각 case 값과 비교합니다.
package main
import "fmt"
func main() {
day := "Monday"
switch day {
case "Monday":
fmt.Println("월요일 — 한 주의 시작!")
case "Friday":
fmt.Println("금요일 — 곧 주말!")
case "Saturday", "Sunday":
fmt.Println("주말 — 휴식!")
default:
fmt.Println("평일입니다.")
}
// 초기화 구문 포함 switch
switch n := 42; {
case n < 0:
fmt.Println("음수")
case n == 0:
fmt.Println("0")
case n < 100:
fmt.Println("두 자리 수")
default:
fmt.Println("세 자리 수 이상")
}
}
실행 결과:
월요일 — 한 주의 시작!
두 자리 수
조건 없는 switch (표현식 없는 switch)
switch 뒤에 표현식을 쓰지 않으면 if-else if 처럼 각 case가 독립적인 불리언 조건이 됩니다. 긴 조건 체인을 깔끔하게 표현할 때 유용합니다.
package main
import "fmt"
func classify(score int) string {
switch {
case score >= 90:
return "A — 최우수"
case score >= 80:
return "B — 우수"
case score >= 70:
return "C — 양호"
case score >= 60:
return "D — 미흡"
default:
return "F — 불합격"
}
}
func bmi(weight, height float64) string {
bmiVal := weight / (height * height)
switch {
case bmiVal < 18.5:
return fmt.Sprintf("%.1f — 저체중", bmiVal)
case bmiVal < 25.0:
return fmt.Sprintf("%.1f — 정상", bmiVal)
case bmiVal < 30.0:
return fmt.Sprintf("%.1f — 과체중", bmiVal)
default:
return fmt.Sprintf("%.1f — 비만", bmiVal)
}
}
func main() {
scores := []int{95, 83, 72, 61, 45}
for _, s := range scores {
fmt.Printf("점수 %d: %s\n", s, classify(s))
}
fmt.Println()
fmt.Println("BMI:", bmi(70, 1.75))
fmt.Println("BMI:", bmi(50, 1.75))
}
실행 결과:
점수 95: A — 최우수
점수 83: B — 우수
점수 72: C — 양호
점수 61: D — 미흡
점수 45: F — 불합격
BMI: 22.9 — 정상
BMI: 16.3 — 저체중
다중 케이스
하나의 case에 쉼표로 여러 값을 나열하면 그 중 하나라도 일치할 때 실행됩니다.
package main
import "fmt"
func isVowel(ch byte) bool {
switch ch {
case 'a', 'e', 'i', 'o', 'u',
'A', 'E', 'I', 'O', 'U':
return true
default:
return false
}
}
func dayType(day string) string {
switch day {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
return "평일"
case "Saturday", "Sunday":
return "주말"
default:
return "알 수 없음"
}
}
func main() {
for _, ch := range []byte{'a', 'b', 'E', 'z'} {
fmt.Printf("%c: 모음=%v\n", ch, isVowel(ch))
}
days := []string{"Monday", "Saturday", "Wednesday", "Sunday"}
for _, d := range days {
fmt.Printf("%s: %s\n", d, dayType(d))
}
}
실행 결과:
a: 모음=true
b: 모음=false
E: 모음=true
z: 모음=false
Monday: 평일
Saturday: 주말
Wednesday: 평일
Sunday: 주말
fallthrough 키워드
Go의 switch는 기본적으로 각 case 실행 후 자동으로 빠져나옵니다. fallthrough 키워드를 사용하면 다음 case를 조건 없이 실행합니다.
주의: fallthrough는 다음 case의 조건을 확인하지 않고 무조건 실행합니다. 사용 빈도는 낮고, 대부분의 경우 다중 케이스(case 1, 2, 3:)로 대체합니다.
package main
import "fmt"
func main() {
n := 1
switch n {
case 1:
fmt.Println("케이스 1 실행")
fallthrough
case 2:
fmt.Println("케이스 2 실행 (fallthrough로 도달)")
fallthrough
case 3:
fmt.Println("케이스 3 실행 (fallthrough로 도달)")
case 4:
fmt.Println("케이스 4 실행 (도달 안 됨)")
}
fmt.Println("---")
// fallthrough 실용 예: 버전 마이그레이션
version := 1
switch version {
case 1:
fmt.Println("v1 → v2 마이그레이션 적용")
fallthrough
case 2:
fmt.Println("v2 → v3 마이그레이션 적용")
fallthrough
case 3:
fmt.Println("v3 → v4 마이그레이션 적용")
case 4:
fmt.Println("최신 버전, 마이그레이션 불필요")
}
}
실행 결과:
케이스 1 실행
케이스 2 실행 (fallthrough로 도달)
케이스 3 실행 (fallthrough로 도달)
---
v1 → v2 마이그레이션 적용
v2 → v3 마이그레이션 적용
v3 → v4 마이그레이션 적용
타입 switch
타입 switch 는 Go의 독특한 기능입니다. 인터페이스 변수가 실제로 어떤 타입인지 확인하고 분기합니다. interface{} (또는 Go 1.18 이후 any) 타입의 값을 처리할 때 매우 유용합니다.
package main
import "fmt"
func describe(i interface{}) string {
switch v := i.(type) {
case int:
return fmt.Sprintf("정수: %d", v)
case float64:
return fmt.Sprintf("실수: %.2f", v)
case string:
return fmt.Sprintf("문자열: %q (길이: %d)", v, len(v))
case bool:
return fmt.Sprintf("불리언: %v", v)
case []int:
return fmt.Sprintf("정수 슬라이스: %v (길이: %d)", v, len(v))
case nil:
return "nil 값"
default:
return fmt.Sprintf("알 수 없는 타입: %T", v)
}
}
func main() {
values := []interface{}{
42,
3.14,
"Hello, Go!",
true,
[]int{1, 2, 3},
nil,
struct{ Name string }{"Gopher"},
}
for _, v := range values {
fmt.Println(describe(v))
}
}
실행 결과:
정수: 42
실수: 3.14
문자열: "Hello, Go!" (길이: 10)
불리언: true
정수 슬라이스: [1 2 3] (길이: 3)
nil 값
알 수 없는 타입: struct { Name string }
switch v := i.(type) 구문에서 v는 각 case 안에서 해당 타입으로 자동 변환됩니다. case int: 블록 안의 v는 int 타입이고, case string: 블록 안의 v는 string 타입입니다.
실전 예제: HTTP 메서드 라우터
package main
import (
"fmt"
"strings"
)
type Request struct {
Method string
Path string
Body string
}
type Response struct {
StatusCode int
Body string
}
// 간단한 HTTP 라우터 시뮬레이션
func router(req Request) Response {
// 경로 정규화
path := strings.ToLower(strings.TrimRight(req.Path, "/"))
switch req.Method {
case "GET":
switch path {
case "/users":
return Response{200, `[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]`}
case "/health":
return Response{200, `{"status":"ok"}`}
default:
return Response{404, `{"error":"not found"}`}
}
case "POST":
switch path {
case "/users":
if req.Body == "" {
return Response{400, `{"error":"body required"}`}
}
return Response{201, `{"id":3,"message":"created"}`}
default:
return Response{404, `{"error":"not found"}`}
}
case "DELETE":
if strings.HasPrefix(path, "/users/") {
return Response{204, ""}
}
return Response{404, `{"error":"not found"}`}
case "OPTIONS":
return Response{200, ""}
default:
return Response{405, `{"error":"method not allowed"}`}
}
}
func main() {
requests := []Request{
{"GET", "/users", ""},
{"GET", "/health", ""},
{"GET", "/unknown", ""},
{"POST", "/users", `{"name":"Carol"}`},
{"POST", "/users", ""},
{"DELETE", "/users/1", ""},
{"PATCH", "/users/1", ""},
}
for _, req := range requests {
resp := router(req)
fmt.Printf("[%s] %s → %d %s\n", req.Method, req.Path, resp.StatusCode, resp.Body)
}
}
실행 결과:
[GET] /users → 200 [{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]
[GET] /health → 200 {"status":"ok"}
[GET] /unknown → 404 {"error":"not found"}
[POST] /users → 201 {"id":3,"message":"created"}
[POST] /users → 400 {"error":"body required"}
[DELETE] /users/1 → 204
[PATCH] /users/1 → 405 {"error":"method not allowed"}
실전 예제: 인터페이스 타입 분기 (이벤트 시스템)
package main
import "fmt"
// 이벤트 인터페이스
type Event interface {
EventName() string
}
// 구체 이벤트 타입들
type UserCreated struct {
UserID string
Email string
}
type UserDeleted struct {
UserID string
}
type OrderPlaced struct {
OrderID string
Amount float64
}
type PaymentFailed struct {
OrderID string
Reason string
}
func (e UserCreated) EventName() string { return "user.created" }
func (e UserDeleted) EventName() string { return "user.deleted" }
func (e OrderPlaced) EventName() string { return "order.placed" }
func (e PaymentFailed) EventName() string { return "payment.failed" }
// 이벤트 핸들러 — 타입 switch로 구체 타입에 접근
func handleEvent(event Event) {
switch e := event.(type) {
case UserCreated:
fmt.Printf("[사용자 생성] ID=%s, 이메일=%s → 환영 메일 발송\n", e.UserID, e.Email)
case UserDeleted:
fmt.Printf("[사용자 삭제] ID=%s → 관련 데이터 정리\n", e.UserID)
case OrderPlaced:
fmt.Printf("[주문 접수] ID=%s, 금액=%.0f원 → 결제 처리 시작\n", e.OrderID, e.Amount)
case PaymentFailed:
fmt.Printf("[결제 실패] 주문=%s, 사유=%s → 알림 발송\n", e.OrderID, e.Reason)
default:
fmt.Printf("[미지원 이벤트] %s\n", e.EventName())
}
}
func main() {
events := []Event{
UserCreated{UserID: "u001", Email: "alice@example.com"},
OrderPlaced{OrderID: "o001", Amount: 35000},
PaymentFailed{OrderID: "o001", Reason: "잔액 부족"},
UserDeleted{UserID: "u002"},
}
for _, e := range events {
handleEvent(e)
}
}
실행 결과:
[사용자 생성] ID=u001, 이메일=alice@example.com → 환영 메일 발송
[주문 접수] ID=o001, 금액=35000원 → 결제 처리 시작
[결제 실패] 주문=o001, 사유=잔액 부족 → 알림 발송
[사용자 삭제] ID=u002 → 관련 데이터 정리
고수 팁
1. 타입 switch에서 여러 타입 묶기
switch v := i.(type) {
case int, int32, int64:
// 이 경우 v는 interface{} 타입 — 공통 처리만 가능
fmt.Printf("정수형: %v\n", v)
case string, []byte:
fmt.Printf("텍스트형: %v\n", v)
}
2. switch로 에러 타입 분기
import "errors"
switch {
case errors.Is(err, ErrNotFound):
return http.StatusNotFound
case errors.Is(err, ErrUnauthorized):
return http.StatusUnauthorized
default:
return http.StatusInternalServerError
}
3. 컴파일러 exhaustiveness 체크 (iota + switch)
type Direction int
const (
North Direction = iota
South
East
West
)
func describe(d Direction) string {
switch d {
case North:
return "북"
case South:
return "남"
case East:
return "동"
case West:
return "서"
default:
panic(fmt.Sprintf("알 수 없는 방향: %d", d))
}
}
4. fallthrough는 최후 수단
fallthrough는 로직이 복잡해지면 버그를 만들기 쉽습니다. 99%의 경우 다중 케이스(case a, b, c:)나 함수 분리로 해결할 수 있습니다. fallthrough가 필요하다고 느껴진다면 코드 설계를 다시 검토하세요.