고 기초 #3 제어 흐름 — if, for, switch

5 분 소요

#2 변수, 타입, 상수에서 값을 다루는 도구를 봤습니다. 이번엔 — 그 값들로 분기와 반복을 만드는 차례입니다.

if / else if / else #

조건 분기. 다른 언어와 거의 비슷하지만 소괄호 없음이 특징입니다.

if 기본
score := 85

if score >= 90 {
	fmt.Println("A")
} else if score >= 80 {
	fmt.Println("B")
} else {
	fmt.Println("C")
}

if (조건)이 아니라 if 조건입니다. C/Java 스타일에서 오면 처음에 어색하지만 빠르게 익숙해집니다.

짧은 선언 (short statement) #

조건 안에서 변수 하나를 선언하고 즉시 사용할 수 있습니다. Go에서 매우 자주 만나는 패턴.

if + 짧은 선언
if v, ok := lookup(key); ok {
	fmt.Println("찾음:", v)
} else {
	fmt.Println("없음")
}

v, ok := lookup(key)가 if 안에서 일어나고, vok는 if/else 블록에서만 보입니다. 블록을 빠져나가면 사라집니다.

이 패턴이 Go 코드 도처에 등장합니다. 에러 검사(#4)의 표준 모양이기도 합니다.

조건은 bool#

bool만 받음
x := 5

// if x { ... }     ✗ x는 int
if x > 0 { ... }     // OK

// if v := lookup(); v { ... }   ✗ v가 bool이 아니면 안 됨

자바스크립트의 truthy/falsy 같은 자동 변환이 없습니다. 명시적인 조건만 받습니다.

for — 유일한 반복문 #

Go에는 while이 없습니다. for 하나로 모든 반복을 표현합니다.

1) 고전 for — 세 부분 #

고전 for
for i := 0; i < 5; i++ {
	fmt.Println(i)
}
// 0, 1, 2, 3, 4

C/Java와 같은 모양입니다. 초기화 ; 조건 ; 매 반복 후 실행 순서입니다.

2) while처럼 — 조건만 #

while처럼
n := 0
for n < 5 {
	fmt.Println(n)
	n++
}

세 부분 중 조건만 있는 형태. 다른 언어의 while과 같은 의미입니다.

3) 무한 루프 — 조건도 없음 #

무한 for
for {
	// ...
	if shouldStop() {
		break
	}
}

for { }가 무한 루프. while (true)와 같은 의미입니다.

4) for range — 컬렉션 순회 #

for range 배열/슬라이스
fruits := []string{"사과", "바나나", "포도"}

for i, fruit := range fruits {
	fmt.Println(i, fruit)
}
// 0 사과
// 1 바나나
// 2 포도
for range 맵
ages := map[string]int{
	"커티스": 30,
	"앨리스": 25,
}

for name, age := range ages {
	fmt.Println(name, age)
}
for range 문자열 (rune 단위)
for i, r := range "안녕" {
	fmt.Printf("%d: %c\n", i, r)
}
// 0: 안
// 3: 녕  (UTF-8 바이트 인덱스)

for range는 매우 강력합니다. 인덱스/키만 필요하면 두 번째 변수를 빼고, 값만 필요하면 첫 번째를 _로 무시.

일부만 받기
for i := range fruits {        // 인덱스만
	fmt.Println(i)
}

for _, fruit := range fruits {  // 값만
	fmt.Println(fruit)
}

breakcontinue #

루프 흐름 제어.

break / continue
for i := 0; i < 10; i++ {
	if i == 5 {
		break       // 루프 빠져나가기
	}
	if i%2 == 0 {
		continue    // 다음 반복으로
	}
	fmt.Println(i)
}
// 1, 3

라벨로 중첩 루프 제어 #

여러 겹 중첩 루프에서 한 번에 빠져나가고 싶을 때 사용합니다.

라벨
outer:
for i := 0; i < 5; i++ {
	for j := 0; j < 5; j++ {
		if i*j > 6 {
			break outer    // outer 라벨까지 한 번에 break
		}
	}
}

자주 쓰진 않지만 깊은 중첩에서 깔끔합니다.

switch — Go의 가장 큰 차이 #

Go의 switch는 자동으로 fallthrough 하지 않습니다. C/Java와 정반대.

switch — break 자동
day := "mon"

switch day {
case "mon", "tue", "wed", "thu", "fri":
	fmt.Println("평일")
case "sat", "sun":
	fmt.Println("주말")
default:
	fmt.Println("?")
}

매 case 끝에 자동 break. 옛 C처럼 break를 적을 필요가 없습니다. 여러 값을 한 case에 콤마로 묶을 수도 있습니다.

강제 fallthrough #

정말로 다음 case로 흘려보내고 싶으면 fallthrough 키워드.

fallthrough — 명시적
n := 1

switch n {
case 1:
	fmt.Println("one")
	fallthrough
case 2:
	fmt.Println("two")
}
// one
// two

거의 안 씁니다. 의도가 분명할 때만.

조건식 switch — if/else의 짧은 형태 #

표현식 없는 switch는 if/else 사슬을 짧게 만듭니다.

조건 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")
}

각 case가 bool 표현식. 길어지는 if/else if보다 보기 좋을 때가 많습니다.

switch + 짧은 선언 #

if처럼 switch도 짧은 선언을 허용합니다.

switch 짧은 선언
switch v := getValue(); {
case v > 100:
	fmt.Println("크다")
case v > 0:
	fmt.Println("양수")
default:
	fmt.Println("0이하")
}

타입 switch — interface 분기 #

중급 #1 인터페이스에서 본격적으로 다루지만, switch가 타입 검사에도 쓰입니다.

타입 switch (미리보기)
func describe(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Println("int:", v)
	case string:
		fmt.Println("string:", v)
	case bool:
		fmt.Println("bool:", v)
	default:
		fmt.Println("unknown")
	}
}

i.(type)이라는 특별한 문법. 인터페이스가 어떤 구체 타입을 들고 있는지에 따라 분기합니다.

goto — 거의 안 씀 #

Go에 goto가 있긴 하지만 거의 안 씁니다. 정말 특수한 경우(중첩에서 빠져나가기, 정리 코드)에서만 만납니다.

짧은 if의 함정 — 중괄호 필수 #

자바스크립트와 달리 Go는 중괄호가 항상 필수입니다.

중괄호 필수
// if condition doSomething()       ✗ 컴파일 에러
if condition {
	doSomething()
}

이 디자인 덕분에 자바스크립트의 “들여쓰기 가짜 if” 같은 함정이 아예 없습니다.

자주 쓰는 패턴들 #

1) 에러 검사 #

if err != nil 패턴이 Go 코드에서 가장 자주 보이는 모양 중 하나입니다.

에러 검사
file, err := os.Open("data.txt")
if err != nil {
	return err
}
defer file.Close()
// ... 정상 처리

#4에서 자세히 다룹니다.

2) 키 존재 확인 #

map의 키 확인
ages := map[string]int{"커티스": 30}

if age, ok := ages["커티스"]; ok {
	fmt.Println(age)
}

map[키]는 두 값을 반환할 수 있습니다 — 값과 존재 여부 ok. 이 짧은 선언 패턴이 표준입니다.

3) early return #

깊은 if/else보다 일찍 return 하는 패턴.

early return
func process(s string) error {
	if s == "" {
		return fmt.Errorf("빈 문자열")
	}
	if len(s) > 100 {
		return fmt.Errorf("너무 긴 문자열")
	}

	// 본 로직 — 들여쓰기 한 단계만
	return nil
}

자바스크립트 #4 함수에서 본 패턴과 같습니다. Go에서도 권장됩니다.

마무리 #

이번 글에서 정리한 내용:

  • if 조건 { } — 소괄호 없음, 중괄호 필수
  • 짧은 선언 — if v, ok := ...; ok { }가 자주 등장
  • 조건은 bool만 — truthy/falsy 자동 변환 없음
  • for가 유일한 반복문 — while/do-while 없음
  • 4가지 형태 — 고전, 조건만, 무한, range
  • for range로 슬라이스/맵/문자열 순회 (문자열은 rune)
  • switch는 자동 break — fallthrough 명시
  • 조건식 switch로 if/else 사슬 정리
  • i.(type)으로 타입 switch (미리보기)

다음 글(#4 함수, 다중 반환, error 타입)에서는 Go의 함수 — 정의 방식, 다중 반환, 그리고 가장 중요한 에러 처리 패턴을 다룹니다.

X