
Go 반복문의 단순함이 주는 강력한 힘
코드를 작성하다 보면 특정 동작을 수십 번, 수백 번 반복해야 하는 순간이 반드시 찾아와요. 다른 프로그래밍 언어에서는 while이나 do-while 같은 다양한 반복문 도구가 주어지곤 하죠. 하지만 Go 언어는 조금 다릅니다. Go는 오직 for라는 단 하나의 반복문 키워드만으로 모든 반복 로직을 해결하도록 설계되었어요.
처음 Go를 접하는 입문 개발자들은 이 단순함이 오히려 낯설게 느껴질 수 있어요. “왜 이렇게 종류가 없지?” 혹은 “복잡한 조건은 어떻게 처리해야 하나?”라는 의문이 들기도 하죠. 실제로 많은 분이 다른 언어의 습관대로 코드를 짜려다가 Go 특유의 간결한 문법에 부딪혀 헤매기도 해요. 잘못된 방식으로 반복문을 사용하면 CPU 점유율이 치솟거나, 예상치 못한 메모리 누수가 발생하여 프로그램이 멈춰버리는 낭패를 볼 수도 있어요.
하지만 걱정하지 마세요. Go의 반복문은 단순한 만큼 매우 명확하고 강력해요. 문법이 복잡하지 않기 때문에 한 번 제대로 익혀두면 코드의 가독성이 비약적으로 상승하고, 버그를 찾기도 훨씬 쉬워져요. 이 글은 여러분이 Go의 Go 반복문 치트시트를 완벽히 숙지하여, 복잡한 비즈니스 로직도 막힘없이 구현할 수 있도록 돕기 위해 작성되었어요.
이 가이드를 끝까지 읽고 나면 다음과 같은 능력을 갖추게 될 거예요.
- 기본적인 3구문 형태부터 조건 중심의 반복문까지 자유자재로 활용할 수 있어요.
- range 키워드를 사용해 슬라이스나 맵을 효율적으로 순회하는 법을 배워요.
- 무한 루프와 제어문을 활용해 고급적인 프로그래밍 패턴을 구사할 수 있어요.
- 실무에서 자주 발생하는 반복문 오류를 미리 방지할 수 있어요.
반복문 작성 전 반드시 체크해야 할 기본 개념
반복문을 본격적으로 작성하기 전에, Go가 반복을 어떻게 바라보는지 그 철학을 이해하는 것이 중요해요. Go는 복잡한 선택지를 주는 대신, 하나의 도구를 어떻게 변형해서 쓸 수 있는지를 보여주는 방식을 택했어요. 이를 제대로 활용하려면 반복문의 구성 요소와 데이터 구조에 대한 기초 지식이 탄탄해야 해요.
반복문의 3가지 핵심 구성 요소
가장 전형적인 Go의 반복문은 세 가지 부분으로 나뉩니다. 이 구조를 머릿속에 그려두면 코드를 읽는 속도가 빨라져요.
- 초기화문 (Initialization): 반복이 시작되기 직전에 단 한 번 실행되는 부분이에요. 주로 루프 카운터 변수를 선언하고 초기값을 설정해요.
- 조건식 (Condition): 매 반복이 시작되기 전에 평가되는 부분이에요. 이 결과가 true일 때만 루프 몸체가 실행돼요.
- 후처리문 (Post statement): 루프 몸체가 실행된 후, 다음 조건 검사로 넘어가기 전에 실행되는 부분이에요. 보통 카운터 변수를 증가시키는 역할을 해요.
Go에서는 세 가지 요소 중 하나라도 생략할 수 있어요. 이 유연함 덕분에 하나의 키워드로 다양한 형태의 반복을 구현할 수 있는 것이랍니다.
효율적인 반복을 위한 데이터 구조 선택 기준
반복문 자체도 중요하지만, 무엇을 반복하느냐에 따라 코드의 성능이 결정돼요. 어떤 상황에서 어떤 구조를 반복해야 하는지 아래 표를 통해 비교해 보세요.
| 데이터 구조 | 반복 시 주요 특징 | 추천 활용 상황 |
|---|---|---|
| 슬라이스 (Slice) | 인덱스 기반의 빠른 접근이 가능해요. | 연속된 데이터 목록을 처리할 때 가장 일반적이에요. |
| 맵 (Map) | 키-값 쌍으로 이루어져 있으며 순서가 보장되지 않아요. | 고유한 식별자를 통해 데이터를 찾아야 할 때 써요. |
| 문자열 (String) | 문자(rune) 단위로 분리하여 순회할 수 있어요. | 텍스트 데이터를 한 글자씩 분석할 때 유용해요. |
반복문을 설계할 때는 항상 종료 조건을 가장 먼저 고민해야 해요. 조건이 명확하지 않으면 프로그램이 영원히 멈추지 않는 무한 루프에 빠지게 되거든요. 또한, 반복문 안에서 사용하는 변수가 루프 밖에서도 필요한지, 아니면 루프 안에서만 쓰일 것인지에 따라 변수의 범위를 결정하는 습관을 들이는 것이 좋아요.
실전에서 바로 쓰는 Go 반복문 패턴 5단계
이제 이론을 넘어 실제 코드로 어떻게 구현하는지 구체적으로 살펴볼게요. Go의 반복문은 상황에 따라 형태를 자유롭게 바꿀 수 있어요. 각 단계별 패턴을 익혀두면 상황에 맞는 최적의 코드를 작성할 수 있습니다.
STEP 1. 가장 표준적인 3구문 형태 활용하기
가장 기본이 되는 형태예요. 정해진 횟수만큼 반복하거나 특정 숫자에 도달할 때까지 실행할 때 사용해요. 이 방식은 제어 변수의 생명주기를 관리하기에 매우 명확해요.
예를 들어, 1부터 10까지 숫자를 출력하고 싶다면 다음과 같이 작성해요. for i := 1; i <= 10; i++ { fmt.Println(i) }
여기서 i := 1은 시작점, i <= 10은 지속 조건, i++은 매 단계마다 값을 1씩 올리는 동작을 의미해요. 이 구조는 코드의 의도를 아주 명확하게 전달해주기 때문에 가장 권장되는 방식이에요.
STEP 2. 조건문 중심의 While 스타일 구현하기
Go에는 별도의 while 키워드가 없어요. 대신 for 문에서 초기화문과 후처리문을 생략함으로써 while과 동일한 기능을 수행해요. 특정 조건이 참인 동안 계속해서 무언가를 수행해야 할 때 아주 유용하죠.
예를 들어, 어떤 변수의 값이 100이 될 때까지 계속해서 값을 2배씩 늘려야 한다면 이렇게 쓸 수 있어요. val := 1; for val < 100 { val *= 2 }
이 방식은 루프의 진행 상태가 외부 변수에 의존적일 때 매우 효과적이에요. 조건식이 복잡해지더라도 for 하나로 처리할 수 있어 코드가 깔끔해져요.
STEP 3. 무한 루프와 제어문으로 흐름 조절하기
가끔은 조건 없이 계속 돌아가야 하는 루프가 필요해요. 서버가 클라이언트의 요청을 계속 기다리거나, 센서 데이터를 끊임없이 읽어들여야 하는 상황이 대표적이죠. 이때는 아무런 조건도 적지 않은 for {} 형태를 사용해요.
무한 루프를 사용할 때는 반드시 내부에서 break나 return을 통해 탈출할 수 있는 경로를 마련해 두어야 해요. 그렇지 않으면 프로그램이 응답하지 않는 상태가 될 수 있어요.
또한, 반복 도중 특정 조건에서 다음 차례로 넘어가고 싶다면 continue를, 아예 루프를 중단하고 싶다면 break를 적절히 섞어서 사용하세요. 더 나아가, 중첩된 루프에서 바깥쪽 루프까지 한꺼번에 탈출해야 한다면 레이블(Label)을 사용하는 고난도 기술도 있답니다.
STEP 4. Range를 이용한 스마트한 데이터 순회
Go 개발자들이 가장 사랑하는 패턴 중 하나가 바로 range 키워드예요. 슬라이스, 배열, 맵, 문자열 등을 다룰 때 인덱스를 직접 계산할 필요 없이 아주 간결하게 데이터를 하나씩 꺼내올 수 있어요.
슬라이스를 순회할 때의 예시를 볼까요? names := []string{"Alice", "Bob", "Charlie"}; for i, name := range names { fmt.Printf("%d번째 손님: %s\n", i, name) }
여기서 i는 인덱스이고 name은 해당 위치의 실제 값이에요. 만약 인덱스가 필요 없다면 언더스코어(_)를 사용하여 무시할 수 있어요. for _, name := range names { fmt.Println(name) }
이 패턴은 실수할 확률을 획기적으로 줄여주기 때문에 실무에서 거의 모든 순회 작업에 사용됩니다.
STEP 5. 맵(Map)과 채널(Channel) 순회하기
데이터 구조가 복잡해져도 range의 마법은 계속돼요. 맵을 순회할 때는 키와 값을 동시에 얻을 수 있어요. scores := map[string]int{"Kim": 90, "Lee": 85}; for name, score := range scores { fmt.Println(name, score) }
다만, 맵은 순회할 때마다 결과 순서가 달라질 수 있다는 점을 꼭 기억해야 해요. 또한, 고루틴(Goroutine) 간의 통신 도구인 채널을 순회할 때도 range를 쓸 수 있어요. 채널에 데이터가 들어오는 대로 하나씩 꺼내오다가, 채널이 닫히면(close) 자동으로 루프가 종료되죠. 이는 비동기 프로그래밍에서 매우 강력한 도구가 됩니다.
실전 활용 시나리오: 사용자 목록 처리하기
이 모든 패턴을 합쳐서 실제 상황을 가정해 볼게요. 서버에서 사용자 목록을 받아와서 특정 점수 이상의 사용자에게만 메일을 보내는 로직이에요.
// 가상의 사용자 데이터
type User struct { Name string; Score int }
users := []User{{ "Kim", 95 }, { "Lee", 70 }, { "Park", 85 }}
// 1. 슬라이스를 range로 순회
for _, user := range users {
// 2. 조건문을 통한 필터링
if user.Score >= 80 {
// 3. 특정 조건 시 동작 수행
fmt.Printf("%s님에게 축하 메일을 보냅니다!\n", user.Name)
} else {
// 4. 조건 미달 시 건너뛰기
fmt.Printf("%s님은 대상이 아닙니다.\n", user.Name)
continue
}
}
위 코드는 슬라이스 순회, 조건 필터링, 그리고 continue를 활용한 흐름 제어를 모두 담고 있어요. 이런 패턴들이 모여 견고한 프로그램을 만든답니다.
자주 하는 실수와 해결법 및 FAQ
반복문을 잘 사용한다고 자부하더라도, Go의 독특한 동작 방식 때문에 예상치 못한 버그를 만날 수 있어요. 개발자들이 가장 자주 저지르는 실수들을 정리해 보았으니, 코드를 작성할 때 스스로 체크해 보세요.
자주 하는 실수와 해결법
- ❌ 인덱스 범위를 벗어난 접근 (Out of Bounds)
왜 발생하나요? for i := 0; i <= len(slice); i++ 처럼 조건식에 등호를 잘못 넣었을 때 발생해요. 슬라이스의 마지막 인덱스는 길이-1이기 때문이죠.
✅ 해결법: 조건식을 i < len(slice)로 작성하여 범위를 엄격히 제한하세요. - ❌ 클로저(Closure) 내 변수 캡처 오류
왜 발생하나요? 반복문 안에서 고루틴을 생성하면서 루프 변수의 주소를 그대로 넘겨줄 때 발생해요. 모든 고루틴이 루프의 마지막 값만 참조하게 되는 무서운 상황이 벌어지죠.
✅ 해결법: 루프 내부에서 변수를 새롭게 선언하여 값을 복사해 전달하세요. - ❌ 맵(Map) 순회 순서에 의존하는 로직
왜 발생하나요? Go의 맵은 순서가 무작위예요. 어제는 A-B 순서로 나왔는데 오늘은 B-A 순서로 나올 수 있어요.
✅ 해결법: 결과 순서가 중요하다면 키들을 별도의 슬라이스에 담아 정렬한 뒤, 그 슬라이스를 기준으로 맵을 조회하세요. - ❌ 무한 루프에 의한 리소스 고갈
왜 발생하나요? 탈출 조건이 절대 만족될 수 없는 논리적 오류가 있거나, break 문을 빠뜨렸을 때 발생해요.
✅ 해결법: 루프를 설계할 때 반드시 탈출 경로(Exit Path)를 명시적으로 작성하고 테스트하세요. - ❌ range 사용 시 값의 복사 문제
왜 발생하나요? for _, val := range slice를 쓸 때, val은 슬라이스의 실제 요소가 아니라 값이 복사된 새로운 변수예요. val을 수정해도 원본 슬라이스는 바뀌지 않아요.
✅ 해결법: 원본을 수정해야 한다면 인덱스를 사용하여 slice[i] = newValue와 같이 직접 접근하세요.
자주 묻는 질문
Q. Go에는 while 문이 정말 없나요?
네, 맞아요. Go 설계자들은 문법을 단순하게 유지하기 위해 for 하나로 모든 것을 해결하도록 만들었어요. for 조건식 { ... } 패턴을 쓰면 기존의 while과 완전히 똑같이 작동하니 걱정하지 마세요.
Q. range를 쓸 때 인덱스가 필요 없으면 어떻게 하나요?
언더스코어(_)를 사용하면 돼요. for _, value := range items와 같이 작성하면 인덱스는 무시하고 값만 가져올 수 있어요. 이는 Go의 아주 깔끔한 관습 중 하나예요.
Q. 중첩 루프를 빠져나갈 때 가장 좋은 방법은 무엇인가요?
가장 권장되는 방법은 레이블(Label)을 사용하는 거예요. OuterLoop: for ... { for ... { break OuterLoop } } 처럼 작성하면 가장 바깥쪽 루프까지 한 번에 깔끔하게 탈출할 수 있어요.
Q. 채널을 range로 돌리면 언제 끝나나요?
채널에 더 이상 보낼 데이터가 없고, 해당 채널이 close() 함수로 닫혔을 때 루프가 자동으로 종료돼요. 채널을 닫지 않으면 루프는 영원히 데이터를 기다리며 대기하게 되니 주의하세요.
Q. 슬라이스 크기가 변하면 반복문에 문제가 생기나요?
반복 도중에 슬라이스에 요소를 추가하거나 삭제하면 인덱스가 꼬여서 예상치 못한 동작을 할 수 있어요. 가급적 반복 중에는 구조를 변경하지 않거나, 변경이 꼭 필요하다면 인덱스를 역순으로 돌리는 등의 전략이 필요해요.
완벽한 Go 반복문 활용을 위한 마무리
지금까지 Go 프로그래밍의 핵심인 Go 반복문 치트시트를 통해 다양한 패턴과 주의사항을 살펴보았어요. 반복문은 단순해 보이지만, 어떻게 쓰느냐에 따라 프로그램의 성능과 안정성을 결정짓는 매우 중요한 요소예요. 오늘 배운 내용을 바탕으로 더 깔끔하고 버그 없는 코드를 작성해 보세요.
- Go의 반복문은 for 하나로 모든 것을 해결합니다.
- 기본 3구문, 조건문 중심, 무한 루프, range 패턴을 숙지하세요.
- range 사용 시 값의 복사(Copy) 특성을 항상 주의해야 합니다.
- 탈출 조건(Break/Return)을 명확히 설계하여 무한 루프를 방지하세요.
- 맵(Map) 순회 시 순서가 보장되지 않는다는 점을 기억하세요.
오늘 바로 실천해 볼 수 있는 단계별 가이드를 제안해 드릴게요.
- 오늘 할 일: 지금 작성 중인 코드에서 for 문이 있다면, 혹시 불필요하게 복잡하지 않은지 검토해 보세요.
- 이번 주 할 일: 슬라이스와 맵을 다루는 다양한 예제를 만들어 보며 range 키워드에 익숙해지세요.
- 실행 직전 할 일: 반복문 내에서 고루틴을 사용할 계획이라면, 변수 캡처 문제가 없는지 반드시 확인하는 습관을 들이세요.
Go 프로그래밍에 대해 더 깊이 알고 싶거나, 특정 코드 패턴에서 막히는 부분이 있다면 언제든 댓글로 질문을 남겨 주세요. 여러분의 학습을 진심으로 응원합니다!
함께 읽으면 좋은 글: Go 반복문 완벽 가이드