[Go] 조건문
개요
조건문은 프로그램의 실행 흐름을 제어하는 핵심 구문입니다. Go는 if와 switch 두 가지 조건문을 제공하며, 각각 고유한 특징과 장점이 있습니다.
특징
- 괄호 생략: 조건식을 괄호
()로 감싸지 않음 - 중괄호 필수: 코드 블록은 반드시 중괄호
{}로 감싸야 함 - 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 {
// ...
}
베스트 프랙티스
- if with initializer 활용: 에러 처리 시 스코프 제한
- early return: 중첩 줄이기
- switch 우선: 값 매칭이 많을 때는 switch 사용
- 표현식 switch: if-else if 체인보다 가독성 좋음
- type switch: 인터페이스 타입 판별에 활용
- fallthrough 신중히: 의도가 명확할 때만 사용
- 명확한 조건: 복잡한 조건은 변수로 분리
- default 케이스: 예외 상황 처리
- 짧은 변수명: 초기화 변수는 짧게 (err, ok, v 등)
- 일관성: 프로젝트 전체에서 동일한 스타일 유지
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
}