[2026] C++ 개발자를 위한 2주 완성 Go 언어(Golang) 마스터 커리큘럼

[2026] C++ 개발자를 위한 2주 완성 Go 언어(Golang) 마스터 커리큘럼

이 글의 핵심

C++ 개발자를 위한 2주 완성 Go 언어(Golang) 마스터 커리큘럼의 시리즈 안내, 들어가며: 복잡함에서 심플함으로, 실무에서 겪은 문제를 실전 코드와 함께 설명합니다.

시리즈 안내

📚 Go 2주 완성 시리즈 - 커리큘럼 메인 | 전체 목차 보기

이 글은 전체 커리큘럼 개요입니다. 각 Day별 상세 내용은 아래 링크를 참고하세요.

시작하기: #01 기본 문법 →


들어가며: 복잡함에서 심플함으로

C++의 강력함은 그대로 유지하면서, 복잡한 빌드 시스템과 포인터 연산의 피로도에서 벗어나고 싶으신가요? 심플한 문법과 강력한 동시성(Concurrency) 처리로 클라우드 네이티브 시대의 대세가 된 Go 언어. 기존 C/C++ 지식을 레버리지하여 단 2주 만에 Go 언어의 핵심을 마스터할 수 있는 pkglog 독점 커리큘럼을 소개합니다. 이 커리큘럼의 특징:

  • C++과의 직접 비교: 매 단계마다 C++ 코드와 Go 코드를 나란히 비교
  • 실무 중심: 이론보다는 바로 적용 가능한 패턴과 예제
  • 14일 집중 코스: 하루 2–3시간 투자로 완성하는 체계적 학습 경로
  • 최종 프로젝트: REST API 서버 구축으로 모든 개념 통합 관련 글: C++ 개발자의 뇌 구조로 이해하는 Go 언어, C++ vs Go 성능·동시성.

실무에서 겪은 문제

C++로는 빌드·링크·플랫폼별 분기까지 신경 쓰다 보면, “작은 서비스 하나”에도 설정 파일과 스크립트가 불어납니다. Go로 옮기면 단일 정적 바이너리, 일관된 포맷(gofmt), 동시성 기본기(고루틴·채널) 덕분에 배포·운영 단계가 단순해지는 경우가 많습니다. 반대로 저수준 제어·템플릿 메타프로그래밍이 필요하면 여전히 C++가 맞는 영역입니다. 전환 시 흔한 시행착오:

  • 에러 처리: if err != nil을 빠뜨려 조용히 실패하는 코드 — #05에서 패턴을 익히면 줄일 수 있습니다.
  • 동시성: 뮤텍스만 익숙하면 채널·select를 과소평가하기 쉽습니다 — #06을 권장합니다.
  • 종료 처리: HTTP 서버를 끌 때 Shutdown 없이 프로세스만 죽이면 연결이 끊깁니다 — #09에서 정리합니다. 아래 커리큘럼을 순서대로 밟으면 위 함정을 줄이는 데 도움이 됩니다.

목차

  1. 1주 차: 패러다임의 전환과 기본기 다지기
  2. 2주 차: Go 언어의 꽃, 동시성과 실전 에코시스템
  3. 학습 팁과 추천 자료
  4. 정리: 2주 후 당신이 할 수 있는 것

시리즈 전체 글 목록

1주 차:


📌 1주 차: 패러다임의 전환과 기본기 다지기

C++의 잔재(상속, 예외 처리 등)를 덜어내고, Go 언어 특유의 심플함에 적응하는 주간입니다. 아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

flowchart LR
    A["Day 1-2br/기본 문법"] --> B["Day 3-4br/메모리·자료구조"]
    B --> C["Day 5-6br/객체지향"]
    C --> D["Day 7br/인터페이스"]
    style A fill:#e1f5ff
    style B fill:#e1f5ff
    style C fill:#e1f5ff
    style D fill:#e1f5ff

Day 1~2: Go 언어의 철학과 기본 문법

📖 상세 글 보기: [Go 2주 완성 #01] Day 1~2: Go 언어의 철학과 기본 문법 주제: C++ 개발자의 시선에서 본 Go 언어의 첫인상 핵심 내용:

  • Go 설치 및 툴체인(go build, go run, go fmt) 소개
  • 변수 선언의 차이 (auto vs :=)
  • C++의 while을 대체하는 강력한 for
  • 가비지 컬렉터(GC)의 도입: newdelete로부터의 해방 C++ vs Go 비교: 변수 선언 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// C++: 변수 선언
int x = 10;
auto y = 20;  // 타입 추론
const int MAX = 100;
std::vector<int> vec = {1, 2, 3};

아래 코드는 go를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: 변수 선언
var x int = 10
y := 20  // 짧은 선언 (타입 추론)
const MAX = 100
// 여러 변수 동시 선언
var (
    name string = "Go"
    version int = 1
)
slice := []int{1, 2, 3}

C++ vs Go 비교: 반복문 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// C++: 다양한 반복문
for (int i = 0; i < 10; i++) {
    std::cout << i << "\n";
}
while (condition) {
    // ...
}
for (const auto& item : container) {
    // ...
}

다음은 go를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: for 하나로 모든 반복 처리
for i := 0; i < 10; i++ {
    fmt.Println(i)
}
// while 대신 for (조건만)
for condition {
    // ...
}
// range로 컨테이너 순회
for i, item := range slice {
    fmt.Println(i, item)
}
// 무한 루프
for {
    // break로 탈출
}

학습 포인트:

  • Go는 while, do-while이 없습니다. for만으로 모든 반복을 처리합니다.
  • :=는 함수 내에서만 사용 가능하며, 타입을 자동 추론합니다.
  • go fmt는 코드 포맷을 자동으로 맞춰주므로, 팀 내 코딩 스타일 논쟁이 사라집니다.

Day 3~4: 메모리와 자료구조 (포인터, 배열, 슬라이스)

📖 상세 글 보기: [Go 2주 완성 #02] Day 3~4: 메모리와 자료구조 주제: 포인터 연산은 없지만 포인터는 있다? 핵심 내용:

  • Go의 포인터(*, &)와 Call by Value / Call by Reference
  • C++ std::vector와 Go Slice의 결정적 차이 (Capacity와 Length의 이해)
  • Map 구조의 활용 C++ vs Go 비교: 포인터 다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++: 포인터와 참조
void increment(int* p) {
    (*p)++;
}
void increment_ref(int& r) {
    r++;
}
int main() {
    int x = 10;
    increment(&x);      // 포인터
    increment_ref(x);   // 참조
    
    int* p = new int(42);
    delete p;  // 수동 해제
}

아래 코드는 go를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: 포인터 (연산 없음, GC 자동 해제)
// 함수 정의 및 구현
func increment(p *int) {
    *p++  // 포인터 연산 없음, 역참조만 가능
}
func main() {
    x := 10
    increment(&x)
    
    p := new(int)  // GC가 자동 해제
    *p = 42
    // delete 불필요
}

핵심 차이점:

  • Go는 포인터 연산(p++, p + 1)이 불가능합니다. 안전성을 위해 제거되었습니다.
  • 참조(&)는 없고, 포인터만 있습니다. 함수 인자로 수정이 필요하면 포인터를 전달합니다.
  • new로 할당해도 delete 불필요. GC가 자동으로 수거합니다. C++ vs Go 비교: 동적 배열 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++: std::vector
#include <vector>
#include <iostream>
int main() {
    std::vector<int> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);
    
    std::cout << "Size: " << vec.size() << "\n";
    std::cout << "Capacity: " << vec.capacity() << "\n";
    
    // 범위 기반 for
    for (const auto& v : vec) {
        std::cout << v << " ";
    }
}

다음은 go를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: Slice (동적 배열)
// 함수 정의 및 구현
func main() {
    var slice []int  // nil 슬라이스
    slice = append(slice, 1)
    slice = append(slice, 2)
    slice = append(slice, 3)
    
    fmt.Println("Length:", len(slice))
    fmt.Println("Capacity:", cap(slice))
    
    // range로 순회
    for i, v := range slice {
        fmt.Println(i, v)
    }
    
    // 슬라이싱
    sub := slice[1:3]  // [2, 3]
}

Slice의 핵심 개념:

  • Length: 현재 원소 개수 (len(slice))
  • Capacity: 재할당 없이 담을 수 있는 최대 개수 (cap(slice))
  • append로 추가 시 capacity를 초과하면 자동으로 재할당(보통 2배)
  • 슬라이싱(slice[1:3])은 원본 배열을 공유하므로 주의 필요 C++ vs Go 비교: Map 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++: std::map / std::unordered_map
#include <unordered_map>
int main() {
    std::unordered_map<std::string, int> m;
    m[key1] = 100;
    m[key2] = 200;
    
    // 키 존재 확인
    if (m.find("key1") != m.end()) {
        std::cout << "Found: " << m[key1] << "\n";
    }
}

아래 코드는 go를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: map
func main() {
    m := make(map[string]int)
    m[key1] = 100
    m[key2] = 200
    
    // 키 존재 확인 (두 번째 반환값)
    if v, ok := m[key1]; ok {
        fmt.Println("Found:", v)
    }
    
    // 키 삭제
    delete(m, "key1")
}

Day 5~6: 클래스(Class) 없는 객체지향 프로그래밍

📖 상세 글 보기: [Go 2주 완성 #03] Day 5~6: 클래스 없는 객체지향 주제: 상속(Inheritance)을 버리고 합성(Composition)을 취하다 핵심 내용:

  • struct의 정의와 활용
  • 메서드(Method)와 리시버(Receiver)의 개념 (포인터 리시버 vs 값 리시버)
  • 객체의 합성(Embedding)을 통한 코드 재사용 패턴 C++ vs Go 비교: 클래스와 메서드 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++: 클래스 기반 객체지향
class Counter {
private:
    int count;
    
public:
    Counter() : count(0) {}
    
    void Increment() {
        count++;
    }
    
    int GetCount() const {
        return count;
    }
    
    void Reset() {
        count = 0;
    }
};
// 사용
Counter c;
c.Increment();
std::cout << c.GetCount() << "\n";

다음은 go를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: 구조체 + 메서드
// 타입 정의
type Counter struct {
    count int  // 소문자 = private (패키지 외부 접근 불가)
}
// 생성자 관례 (NewXxx 함수)
func NewCounter() *Counter {
    return &Counter{count: 0}
}
// 포인터 리시버 - 필드 수정
func (c *Counter) Increment() {
    c.count++
}
// 값 리시버 - 읽기 전용
func (c Counter) GetCount() int {
    return c.count
}
// 포인터 리시버 - 필드 수정
func (c *Counter) Reset() {
    c.count = 0
}
// 사용
c := NewCounter()
c.Increment()
fmt.Println(c.GetCount())

리시버 선택 가이드:

  • 포인터 리시버 (c *Counter): 필드를 수정하거나, 구조체가 큰 경우
  • 값 리시버 (c Counter): 읽기 전용이고, 구조체가 작은 경우
  • 일관성: 한 타입의 메서드는 모두 포인터 또는 모두 값 리시버로 통일하는 것이 관례 C++ vs Go 비교: 상속 vs 합성 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++: 상속 기반 재사용
class Animal {
public:
    virtual void Speak() {
        std::cout << "Some sound\n";
    }
};
class Dog : public Animal {
public:
    void Speak() override {
        std::cout << "Woof!\n";
    }
    
    void Fetch() {
        std::cout << "Fetching...\n";
    }
};
// 사용
Dog d;
d.Speak();   // "Woof!"
d.Fetch();

다음은 go를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: 합성(Embedding) 기반 재사용
// 타입 정의
type Animal struct {
    Name string
}
func (a Animal) Speak() {
    fmt.Println("Some sound")
}
type Dog struct {
    Animal  // 임베딩 - Animal의 메서드가 Dog에 포함됨
    Breed string
}
// Dog의 고유 메서드
func (d Dog) Fetch() {
    fmt.Println("Fetching...")
}
// Animal.Speak 오버라이드
func (d Dog) Speak() {
    fmt.Println("Woof!")
}
// 사용
d := Dog{
    Animal: Animal{Name: "Buddy"},
    Breed:  "Golden Retriever",
}
d.Speak()  // "Woof!" (오버라이드됨)
d.Fetch()

핵심 차이점:

  • Go는 상속이 없습니다. 대신 구조체 임베딩으로 메서드를 재사용합니다.
  • 임베딩된 타입의 메서드가 자동으로 외부 타입에 “승격”됩니다.
  • 같은 이름의 메서드를 정의하면 오버라이드 효과를 낼 수 있습니다.

Day 7: 다형성의 재해석, 인터페이스(Interface)

📖 상세 글 보기: [Go 2주 완성 #04] Day 7: 다형성의 재해석, 인터페이스 주제: 가상 함수(Virtual Function) 없이 다형성 구현하기 핵심 내용:

  • implements 키워드가 없는 암시적 인터페이스 (Duck Typing)
  • 빈 인터페이스 interface{}와 타입 단언(Type Assertion)
  • Go 라이브러리에서 흔히 쓰이는 소형 인터페이스(예: io.Reader, io.Writer) 설계 패턴 C++ vs Go 비교: 다형성 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++: 가상 함수로 다형성
class Shape {
public:
    virtual double Area() const = 0;
    virtual ~Shape() = default;
};
class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double Area() const override {
        return 3.14159 * radius * radius;
    }
};
class Rectangle : public Shape {
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    
    double Area() const override {
        return width * height;
    }
};
// 다형성 사용
void printArea(const Shape& s) {
    std::cout << "Area: " << s.Area() << "\n";
}
int main() {
    Circle c(5.0);
    Rectangle r(4.0, 6.0);
    printArea(c);
    printArea(r);
}

다음은 go를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: 인터페이스로 다형성 (명시적 상속 불필요)
// 타입 정의
type Shape interface {
    Area() float64
}
type Circle struct {
    Radius float64
}
// Circle은 Area()를 구현하므로 자동으로 Shape 인터페이스 만족
func (c Circle) Area() float64 {
    return 3.14159 * c.Radius * c.Radius
}
type Rectangle struct {
    Width, Height float64
}
// Rectangle도 Area()를 구현하므로 Shape 인터페이스 만족
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}
// 다형성 사용
func printArea(s Shape) {
    fmt.Printf("Area: %.2f\n", s.Area())
}
func main() {
    c := Circle{Radius: 5.0}
    r := Rectangle{Width: 4.0, Height: 6.0}
    printArea(c)
    printArea(r)
}

핵심 차이점:

  • Go는 명시적 상속 선언이 없습니다. 메서드만 구현하면 자동으로 인터페이스를 만족합니다.
  • “Duck Typing”: “오리처럼 걷고 오리처럼 운다면 오리다”
  • 인터페이스는 작게 만드는 것이 Go의 철학입니다. (예: io.ReaderRead 메서드 하나만) 표준 라이브러리 인터페이스 예시 다음은 go를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Go: io.Reader 인터페이스 (메서드 하나)
type Reader interface {
    Read(p []byte) (n int, err error)
}
// *os.File, *bytes.Buffer, *strings.Reader 등이 모두 io.Reader 만족
func processData(r io.Reader) {
    data, err := io.ReadAll(r)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(data))
}
// 사용
f, _ := os.Open("file.txt")
defer f.Close()
processData(f)  // *os.File은 io.Reader
buf := bytes.NewBufferString("hello")
processData(buf)  // *bytes.Buffer도 io.Reader

빈 인터페이스와 타입 단언 다음은 go를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: interface{} (모든 타입 수용, C++의 void*와 유사하지만 타입 안전)
func printAny(v interface{}) {
    // 타입 단언
    if s, ok := v.(string); ok {
        fmt.Println("String:", s)
    } else if i, ok := v.(int); ok {
        fmt.Println("Int:", i)
    }
}
// 타입 스위치
func describe(v interface{}) {
    switch t := v.(type) {
    case string:
        fmt.Printf("String: %s\n", t)
    case int:
        fmt.Printf("Int: %d\n", t)
    default:
        fmt.Printf("Unknown type: %T\n", t)
    }
}

📌 2주 차: Go 언어의 꽃, 동시성

일상 비유로 이해하기: 동시성은 주방에서 여러 요리를 동시에 하는 것과 비슷합니다. 한 명의 요리사(싱글 스레드)가 국을 끓이다가 불을 줄이고, 그 사이에 야채를 썰고, 다시 국을 확인하는 식이죠. 반면 병렬성은 요리사 여러 명(멀티 스레드)이 각자 다른 요리를 동시에 만드는 겁니다. 과 실전 에코시스템 OS 스레드나 Mutex로 고통받던 과거를 뒤로하고, 우아한 동시성 제어와 실무 적용법을 배웁니다. 아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

flowchart LR
    A["Day 8-9br/에러 처리"] --> B["Day 10-11br/고루틴·채널"]
    B --> C["Day 12-13br/의존성·테스팅"]
    C --> D["Day 14br/실전 프로젝트"]
    style A fill:#fff4e1
    style B fill:#fff4e1
    style C fill:#fff4e1
    style D fill:#ffcccc

Day 8~9: 예외(Exception) 처리의 새로운 접근

📖 상세 글 보기: [Go 2주 완성 #05] Day 8~9: 예외 처리의 새로운 접근 주제: try-catch는 잊어라, Go의 명시적 에러 핸들링 핵심 내용:

  • 다중 반환값(Multiple Return Values)을 활용한 에러 전달
  • if err != nil 패턴의 철학
  • 자원 해제를 보장하는 마법의 키워드 defer (C++의 RAII 패턴 대체)
  • panicrecover (왜 일반적인 예외 처리로 쓰면 안 되는가?) C++ vs Go 비교: 예외 처리 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++: try-catch 예외 처리
#include <stdexcept>
#include <fstream>
void processFile(const std::string& path) {
    std::ifstream file(path);
    if (!file) {
        throw std::runtime_error("Failed to open file");
    }
    
    std::string line;
    while (std::getline(file, line)) {
        if (line.empty()) {
            throw std::invalid_argument("Empty line");
        }
        // 처리...
    }
    // RAII로 자동 닫힘
}
int main() {
    try {
        processFile("data.txt");
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << "\n";
        return 1;
    }
    return 0;
}

다음은 go를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: error 반환과 명시적 처리
func processFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("failed to open file: %w", err)
    }
    defer file.Close()  // defer로 자원 해제 보장
    
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        if line == "" {
            return fmt.Errorf("empty line found")
        }
        // 처리...
    }
    
    if err := scanner.Err(); err != nil {
        return fmt.Errorf("scan error: %w", err)
    }
    return nil
}
func main() {
    if err := processFile("data.txt"); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
}

defer의 활용: RAII 대체 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// C++: RAII로 자동 정리
void criticalSection() {
    std::lock_guard<std::mutex> lock(mtx);  // 생성자에서 락
    // 작업...
    // 소멸자에서 자동 언락
}
void fileOperation() {
    std::ifstream f("file.txt");
    // 작업...
    // 소멸자에서 자동 close
}

다음은 go를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: defer로 명시적 정리
func criticalSection() {
    mu.Lock()
    defer mu.Unlock()  // 함수 종료 시 자동 실행
    // 작업...
}
func fileOperation() error {
    f, err := os.Open("file.txt")
    if err != nil {
        return err
    }
    defer f.Close()  // return, panic 모두에서 실행
    // 작업...
    return nil
}

defer의 실행 순서 (LIFO)

// Go: defer는 LIFO (Last In First Out)
func example() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    fmt.Println("함수 본문")
}
// 출력:
// 함수 본문
// 3
// 2
// 1

panic과 recover (제한적 사용)

// Go: panic/recover - 복구 가능한 심각한 오류에만 사용
func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic recovered: %v", r)
        }
    }()
    
    if b == 0 {
        panic("division by zero")  // 일반적으로는 error 반환 권장
    }
    
    return a / b, nil
}
func main() {
    result, err := safeDivide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}

Go의 에러 처리 철학:

  • 명시성: 에러가 발생할 수 있는 모든 곳에서 명시적으로 처리
  • 투명성: 호출 스택을 따라 에러가 전파되는 경로가 코드에 명확히 드러남
  • panic은 예외적 상황: 복구 불가능한 프로그래밍 오류에만 사용

Day 10~11: 고루틴(Goroutine)과 채널(Channel)

📖 상세 글 보기: [Go 2주 완성 #06] Day 10~11: 고루틴과 채널 주제: C++ 개발자가 Go에 열광하는 진짜 이유, 동시성 프로그래밍 핵심 내용:

  • OS 스레드(std::thread)와 고루틴의 무게 차이 (수만 개의 고루틴 띄워보기)
  • “공유 메모리로 통신하지 말고, 통신으로 메모리를 공유하라”
  • Channel을 이용한 고루틴 간의 안전한 데이터 동기화
  • select 문을 활용한 다중 채널 제어 C++ vs Go 비교: 스레드 생성 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++: std::thread (OS 스레드, 무거움)
#include <thread>
#include <iostream>
#include <vector>
void worker(int id) {
    std::cout << "Worker " << id << " running\n";
}
int main() {
    std::vector<std::thread> threads;
    
    // 10개 스레드 생성 (각 1~8MB 스택)
    for (int i = 0; i < 10; i++) {
        threads.emplace_back(worker, i);
    }
    
    // 모든 스레드 대기
    for (auto& t : threads) {
        t.join();
    }
    
    return 0;
}

다음은 go를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: 고루틴 (경량 스레드, 수 KB 스택)
// 함수 정의 및 구현
func worker(id int) {
    fmt.Printf("Worker %d running\n", id)
}
func main() {
    // 10,000개 고루틴도 가볍게 생성 가능
    for i := 0; i < 10000; i++ {
        go worker(i)  // go 키워드로 고루틴 생성
    }
    
    // 고루틴 완료 대기 (간단한 예시)
    time.Sleep(time.Second)
}

핵심 차이점:

  • 고루틴: 수 KB 스택으로 시작, 필요 시 자동 확장. M:N 스케줄링으로 OS 스레드보다 훨씬 가볍습니다.
  • 생성 비용: std::thread는 OS 스레드 생성 비용이 큽니다. 고루틴은 거의 무료입니다.
  • 수량: C++에서는 수백 개 스레드가 한계. Go는 수만~수십만 고루틴도 가능합니다. C++ vs Go 비교: 동기화 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++: Mutex로 공유 메모리 보호
#include <mutex>
#include <thread>
std::mutex mtx;
int counter = 0;
void increment(int n) {
    for (int i = 0; i < n; i++) {
        std::lock_guard<std::mutex> lock(mtx);
        counter++;
    }
}
int main() {
    std::thread t1(increment, 1000);
    std::thread t2(increment, 1000);
    t1.join();
    t2.join();
    std::cout << "Counter: " << counter << "\n";
}

다음은 go를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: 채널로 통신 (권장 패턴)
func increment(ch chan int, n int) {
    for i := 0; i < n; i++ {
        ch <- 1  // 채널에 값 전송
    }
}
func main() {
    ch := make(chan int)
    counter := 0
    
    // 고루틴 2개 시작
    go increment(ch, 1000)
    go increment(ch, 1000)
    
    // 결과 수신
    for i := 0; i < 2000; i++ {
        counter += <-ch  // 채널에서 값 수신
    }
    
    fmt.Println("Counter:", counter)
}

채널의 기본 개념 다음은 go를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: 채널 생성과 사용
func main() {
    // 버퍼 없는 채널 (동기)
    ch := make(chan int)
    
    go func() {
        ch <- 42  // 전송 (수신자가 받을 때까지 블록)
    }()
    
    value := <-ch  // 수신
    fmt.Println(value)
    
    // 버퍼 있는 채널 (비동기)
    buffered := make(chan int, 3)
    buffered <- 1  // 버퍼가 차기 전까지 블록 안 됨
    buffered <- 2
    buffered <- 3
    
    fmt.Println(<-buffered)  // 1
    fmt.Println(<-buffered)  // 2
}

select 문: 다중 채널 제어 다음은 go를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: select로 여러 채널 동시 대기
func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "from ch1"
    }()
    
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "from ch2"
    }()
    
    // 먼저 준비된 채널에서 수신
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        case <-time.After(3 * time.Second):
            fmt.Println("timeout")
            return
        }
    }
}

실전 패턴: 워커 풀 다음은 go를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: 워커 풀 패턴
func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        time.Sleep(time.Second)  // 작업 시뮬레이션
        results <- job * 2
    }
}
func main() {
    numJobs := 10
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
    
    // 3개 워커 시작
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    // 작업 전송
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)
    
    // 결과 수집
    for a := 1; a <= numJobs; a++ {
        <-results
    }
}

Day 12~13: 의존성 관리와 테스팅

📖 상세 글 보기: [Go 2주 완성 #07] Day 12~13: 의존성 관리와 테스팅 주제: CMake와 vcpkg보다 수백 배 쉬운 패키지 관리 핵심 내용:

  • Go Modules (go.mod, go.sum) 기초 사용법
  • C++에서는 복잡했던 외부 라이브러리 가져오기 (go get)
  • 서드파티 프레임워크 없이 go test로 끝내는 유닛 테스트(Unit Test) 작성법 C++ vs Go 비교: 의존성 관리 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# C++: CMake + vcpkg/Conan
# CMakeLists.txt 작성
# find_package() 설정
# vcpkg install 또는 conan install
# 빌드 시스템 설정...

다음은 간단한 bash 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# Go: 모듈 초기화 및 의존성 추가
go mod init myproject
go get github.com/gin-gonic/gin@latest
go mod tidy  # 불필요한 의존성 정리

go.mod 파일 예시 아래 코드는 go를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

// go.mod
module myproject
go 1.21
require (
    github.com/gin-gonic/gin v1.9.1
    github.com/stretchr/testify v1.8.4
)

핵심 차이점:

  • Go는 빌드 시스템이 내장되어 있습니다. CMake, Make 등 불필요.
  • go get으로 의존성을 추가하면 go.mod에 자동 기록됩니다.
  • go.sum은 체크섬으로 의존성 무결성을 보장합니다. C++ vs Go 비교: 유닛 테스트 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++: Google Test 사용 예시
#include <gtest/gtest.h>
int Add(int a, int b) {
    return a + b;
}
TEST(MathTest, AddPositive) {
    EXPECT_EQ(Add(2, 3), 5);
}
TEST(MathTest, AddNegative) {
    EXPECT_EQ(Add(-2, -3), -5);
}
int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

다음은 go를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: 내장 testing 패키지 (외부 프레임워크 불필요)
// math.go
package math
func Add(a, b int) int {
    return a + b
}
// math_test.go (같은 패키지, _test.go 접미사)
package math
import "testing"
func TestAddPositive(t *testing.T) {
    result := Add(2, 3)
    expected := 5
    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}
func TestAddNegative(t *testing.T) {
    result := Add(-2, -3)
    expected := -5
    if result != expected {
        t.Errorf("Add(-2, -3) = %d; want %d", result, expected)
    }
}

테스트 실행 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# Go: 테스트 실행 (매우 간단)
go test                    # 현재 패키지 테스트
go test ./....             # 모든 하위 패키지 테스트
go test -v                 # 상세 출력
go test -cover             # 커버리지 측정
go test -bench=.           # 벤치마크 실행

테이블 주도 테스트 (Table-Driven Test) 다음은 go를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Go: 테이블 주도 테스트 패턴
func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -2, -3, -5},
        {"zero", 0, 0, 0},
        {"mixed", -5, 10, 5},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d", 
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

벤치마크

// Go: 벤치마크 (함수명 BenchmarkXxx)
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}
// 실행: go test -bench=.
// 출력: BenchmarkAdd-8   1000000000   0.25 ns/op

Day 14: 실전 미니 프로젝트 (REST API 서버 구축)

📖 상세 글 보기: [Go 2주 완성 #08] Day 14: 실전 미니 프로젝트 주제: 배운 것을 하나로 엮는 실전 프로젝트 핵심 내용:

  • net/http 표준 라이브러리만을 이용한 초간단 웹 서버 띄우기
  • JSON 데이터 직렬화/역직렬화 (encoding/json)
  • 동시성(Goroutine)을 활용한 백그라운드 작업 처리 실습 프로젝트: 간단한 TODO API 서버 다음은 go를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// main.go
package main
import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "sync"
)
// Todo 구조체
type Todo struct {
    ID        int    `json:"id"`
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}
// 인메모리 저장소 (실전에서는 DB 사용)
type TodoStore struct {
    mu    sync.RWMutex
    todos map[int]*Todo
    nextID int
}
func NewTodoStore() *TodoStore {
    return &TodoStore{
        todos: make(map[int]*Todo),
        nextID: 1,
    }
}
func (s *TodoStore) Create(title string) *Todo {
    s.mu.Lock()
    defer s.mu.Unlock()
    
    todo := &Todo{
        ID:        s.nextID,
        Title:     title,
        Completed: false,
    }
    s.todos[s.nextID] = todo
    s.nextID++
    return todo
}
func (s *TodoStore) GetAll() []*Todo {
    s.mu.RLock()
    defer s.mu.RUnlock()
    
    result := make([]*Todo, 0, len(s.todos))
    for _, todo := range s.todos {
        result = append(result, todo)
    }
    return result
}
func (s *TodoStore) Update(id int, completed bool) error {
    s.mu.Lock()
    defer s.mu.Unlock()
    
    todo, ok := s.todos[id]
    if !ok {
        return fmt.Errorf("todo not found")
    }
    todo.Completed = completed
    return nil
}
// HTTP 핸들러
type TodoHandler struct {
    store *TodoStore
}
func NewTodoHandler(store *TodoStore) *TodoHandler {
    return &TodoHandler{store: store}
}
// GET /todos - 모든 TODO 조회
func (h *TodoHandler) GetTodos(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    
    todos := h.store.GetAll()
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(todos)
}
// POST /todos - 새 TODO 생성
func (h *TodoHandler) CreateTodo(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    
    var req struct {
        Title string `json:"title"`
    }
    
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    
    if req.Title == "" {
        http.Error(w, "Title required", http.StatusBadRequest)
        return
    }
    
    todo := h.store.Create(req.Title)
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(todo)
}
func main() {
    store := NewTodoStore()
    handler := NewTodoHandler(store)
    
    // 라우팅 설정
    http.HandleFunc("/todos", func(w http.ResponseWriter, r *http.Request) {
        switch r.Method {
        case http.MethodGet:
            handler.GetTodos(w, r)
        case http.MethodPost:
            handler.CreateTodo(w, r)
        default:
            http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        }
    })
    
    // 백그라운드 작업 예시 (고루틴 활용)
    go func() {
        ticker := time.NewTicker(10 * time.Second)
        defer ticker.Stop()
        
        for range ticker.C {
            todos := store.GetAll()
            log.Printf("Current todos count: %d", len(todos))
        }
    }()
    
    // 서버 시작
    addr := ":8080"
    log.Printf("Server starting on %s", addr)
    if err := http.ListenAndServe(addr, nil); err != nil {
        log.Fatal(err)
    }
}

테스트 코드 다음은 go를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// main_test.go
package main
import (
    "bytes"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"
)
func TestCreateTodo(t *testing.T) {
    store := NewTodoStore()
    handler := NewTodoHandler(store)
    
    // 요청 생성
    reqBody := `{"title":"Test Todo"}`
    req := httptest.NewRequest(http.MethodPost, "/todos", 
        bytes.NewBufferString(reqBody))
    req.Header.Set("Content-Type", "application/json")
    
    // 응답 기록
    w := httptest.NewRecorder()
    handler.CreateTodo(w, req)
    
    // 검증
    if w.Code != http.StatusCreated {
        t.Errorf("Expected status %d, got %d", http.StatusCreated, w.Code)
    }
    
    var todo Todo
    if err := json.NewDecoder(w.Body).Decode(&todo); err != nil {
        t.Fatal(err)
    }
    
    if todo.Title != "Test Todo" {
        t.Errorf("Expected title 'Test Todo', got '%s'", todo.Title)
    }
}

실행 방법 아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 서버 실행
go run main.go
# 다른 터미널에서 테스트
curl http://localhost:8080/todos
curl -X POST http://localhost:8080/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"Learn Go"}'
# 테스트 실행
go test -v

C++와 비교한 장점:

  • 빌드 속도: C++의 긴 컴파일 시간 vs Go의 초고속 빌드
  • 의존성: 복잡한 CMake 설정 vs 한 줄 go get
  • 동시성: 복잡한 스레드 풀 vs 간단한 go 키워드
  • 배포: 단일 바이너리로 크로스 컴파일 가능

학습 팁과 추천 자료

효과적인 학습 전략

  1. C++ 사고방식 매핑: Go를 배울 때 “C++에서는 이렇게 했는데”를 항상 떠올리세요.
  2. 작은 프로그램 많이 작성: 매일 10~20줄 짜리 프로그램을 5개 이상 작성하세요.
  3. 표준 라이브러리 탐색: fmt, os, io, net/http, encoding/json 등을 직접 사용해보세요.
  4. 에러 처리 습관화: if err != nil 패턴을 자연스럽게 받아들이세요.
  5. 고루틴 실험: 수백, 수천 개의 고루틴을 띄워보며 가벼움을 체감하세요.

추천 학습 자료

공식 자료:

  • Tour of Go - 대화형 Go 튜토리얼
  • Effective Go - Go 스타일 가이드
  • Go by Example - 예제 중심 학습 스타일 가이드:
  • Uber Go Style Guide
  • Google Go Style Guide 실전 프로젝트 아이디어:
  • CLI 도구 (파일 처리, 로그 분석기)
  • HTTP 클라이언트 (API 테스터)
  • 간단한 웹 크롤러
  • 채팅 서버 (WebSocket + 고루틴)
  • 데이터베이스 마이그레이션 도구

C++ 개발자가 Go에서 주의할 점

아래 코드는 mermaid를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart TD
    A[C++ 습관] --> B{Go에서 문제?}
    B -->|Yes| C[수정 필요]
    B -->|No| D[그대로 사용]
    
    C --> E[RAII → defer]
    C --> F[예외 → error 반환]
    C --> G[상속 → 합성]
    C --> H[템플릿 → 인터페이스/제네릭]
    
    D --> I[포인터 개념]
    D --> J[반복문 로직]
    D --> K[조건문 구조]

자주 하는 실수:

  1. defer를 루프 안에서 사용: 함수 종료 시에만 실행되므로 리소스 누수 발생
  2. 에러 무시: _로 에러를 무시하면 디버깅 어려움
  3. 루프 변수 클로저: 고루틴에서 루프 변수를 캡처할 때 주의
  4. 채널 닫기 누락: 수신자가 영원히 대기할 수 있음
  5. 포인터 vs 값 혼동: 메서드가 필드를 수정하는지에 따라 리시버 타입 선택

정리: 2주 후 당신이 할 수 있는 것

습득 가능한 핵심 스킬

이 커리큘럼을 완료하면 다음을 할 수 있습니다:

  • Go 문법 완전 이해: 변수, 함수, 구조체, 인터페이스
  • 메모리 관리: GC 환경에서의 효율적인 메모리 사용
  • 에러 처리: if err != nil 패턴과 에러 래핑
  • 동시성 프로그래밍: 고루틴과 채널을 활용한 병렬 처리
  • 테스트 작성: 유닛 테스트와 벤치마크
  • REST API 서버: 실무에 바로 적용 가능한 웹 서버 구축
  • 의존성 관리: Go Modules로 라이브러리 관리

C++에서 Go로 전환 체크리스트

메모리·리소스:

  • GC가 메모리를 관리한다는 사실 수용
  • 파일·락 등은 defer로 정리
  • 루프 안에서는 명시적 Close 호출 타입·다형성:
  • 인터페이스는 메서드 집합일 뿐
  • 명시적 implements 없이 덕 타이핑
  • 제네릭보다 인터페이스 우선 고려 에러 처리:
  • 예외 대신 error 반환
  • fmt.Errorf로 에러 래핑
  • panic은 복구 불가능한 경우만 동시성:
  • 채널 우선, Mutex는 필요 시
  • 루프 변수는 인자로 전달
  • 채널 사용 후 close 호출

다음 단계

2주 커리큘럼을 완료했다면:

  1. 실전 프로젝트: 기존 C++ 프로젝트의 일부를 Go로 재작성
  2. 고급 패턴: Context, 미들웨어, 의존성 주입
  3. 성능 최적화: 프로파일링, 메모리 최적화
  4. 프레임워크: Gin, Echo 등 웹 프레임워크 학습
  5. 클라우드: Docker, Kubernetes와 Go 통합

마무리

C++의 복잡함에서 Go의 심플함으로 전환하는 것은 단순히 새로운 언어를 배우는 것 이상입니다. “적게 쓰고 많이 얻는다”는 Go의 철학을 체득하면, 더 빠르고 안전한 코드를 작성할 수 있습니다. CMake 설정에 시간을 쏟는 대신 비즈니스 로직에 집중하고, 복잡한 스레드 동기화 대신 채널로 우아하게 통신하세요. 2주 후, 당신은 클라우드 네이티브 시대의 필수 언어를 마스터한 개발자가 되어 있을 것입니다. 다음 읽을 글: C++ 개발자의 뇌 구조로 이해하는 Go 언어에서 더 깊이 있는 개념 매핑을 확인하세요. 시리즈 시작하기: [Go 2주 완성 #01] Day 1~2: Go 언어의 철학과 기본 문법부터 시작하세요! 시리즈 전체 목차: Go 2주 완성 시리즈 인덱스에서 전체 글 목록과 추천 학습 경로를 확인하세요.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


자주 묻는 질문 (FAQ)

Q. C++ 경험이 많으면 Go를 더 빨리 배울 수 있나요?

A. 네. 포인터, 메모리 관리, 동시성 개념에 익숙하다면 Go의 개념을 빠르게 이해할 수 있습니다. 다만 C++의 일부 습관(예외 처리, 상속)은 의도적으로 버려야 합니다.

Q. Go 제네릭이 없다면 타입 안전성은 어떻게 보장하나요?

A. Go 1.18부터 제네릭이 추가되었습니다. 그 전에는 인터페이스와 타입 단언으로 처리했습니다. 현재는 [T any] 같은 타입 파라미터를 사용할 수 있습니다.

Q. 실무에서 Go는 어떤 분야에 많이 쓰이나요?

A. 클라우드 인프라(Docker, Kubernetes), 마이크로서비스, API 서버, CLI 도구, DevOps 도구에 널리 사용됩니다. Google, Uber, Netflix 등에서 대규모로 활용 중입니다.

Q. C++보다 Go가 느리지 않나요?

A. 단일 스레드 성능은 C++이 우수하지만, 동시성 처리가 필요한 서버 애플리케이션에서는 Go가 더 효율적일 수 있습니다. GC 오버헤드는 있지만, 개발 생산성과 안전성을 고려하면 충분히 합리적인 트레이드오프입니다.

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3