9.go

Episode 9: "Go의 단순함: 구글이 꿈꾼 서버 언어"

"Less is More"의 철학으로 복잡성과 싸우다


프롤로그: C++ 컴파일을 기다리며

2007년, 구글 캘리포니아 본사. 세 명의 프로그래머가 회의실에 앉아 있습니다.

컴파일 버튼을 누릅니다. 기다립니다. 5분... 10분... 15분...

cpp
// Google의 거대한 C++ 코드베이스
// 수천만 줄의 코드
// 수십 개의 템플릿 라이브러리
// 수백 개의 헤더 파일 의존성

#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
// ... 수백 개의 include ...

template<typename T, typename U, typename V>
class SuperComplexService : public BaseService<T>,
                           public ConfigurableService<U>,
                           public LoggableService<V> {
  // 컴파일하는 데만 몇 분...
};

// 빌드 시간: 45분
// 커피 마시러 가기엔 애매하고...
// 다른 일 하기엔 너무 짧고...

로버트 그리즈머(Robert Griesemer), 롭 파이크(Rob Pike), 켄 톰슨(Ken Thompson). 이 세 명은 컴퓨터 과학의 전설들입니다. 그들도 컴파일을 기다리는 것은 참을 수 없었습니다.

2000년대 후반 구글의 고민
  • 빌드 시간: 작은 변경에도 수십 분
  • 코드 복잡도: 수백 페이지 C++ 표준, 누구도 전부를 이해 못함
  • 의존성 지옥: 헤더 파일이 헤더 파일을 include하고...
  • 병렬성: 멀티코어 시대인데 멀티스레딩은 여전히 어려움

롭 파이크가 말했습니다:

"우리는 새로운 언어가 필요해. 간단하고, 빠르고, 재미있는."

이것이 Go 언어의 시작이었습니다.

이 글에서 다룰 내용
  • 구글 내부의 C++ 피로감과 새로운 언어의 필요성
  • 로버트 그리즈머, 롭 파이크, 켄 톰슨: 전설의 만남
  • 고루틴: 동시성 프로그래밍의 새로운 패러다임
  • 도커, 쿠버네티스: Go가 클라우드 시대의 언어가 된 이유

Chapter 1: 구글 내부의 C++ 피로감

소프트웨어 복잡도의 폭발

2000년대 후반, 구글의 인프라는 전례 없는 규모로 성장하고 있었습니다:

cpp
// Google의 C++ 코드베이스 (2007년경)
- 수천만 줄의 C++ 코드
- 수만 개의 서비스
- 수천 명의 엔지니어
- 하루에 수만 번의 빌드

// 문제들
- 빌드 시간: 평균 30-45분
- 컴파일러 오류: 수백 줄짜리 템플릿 에러 메시지
- 의존성 관리: nightmare
- 새 엔지니어 교육: 6개월 이상

구글이 직면한 C++의 문제들

생산성의 병목

느린 빌드:

  • 작은 변경에도 전체 리빌드 필요
  • 개발자의 시간 낭비
  • 빠른 이터레이션 불가능

과도한 복잡성:

  • C++ 표준의 방대함 (1,300+ 페이지)
  • 템플릿 메타프로그래밍의 어려움
  • "C++를 제대로 아는" 사람이 드뭄

의존성 관리:

  • 헤더 파일 include 지옥
  • 순환 의존성 문제
  • 컴파일 시간 기하급수적 증가

동시성 프로그래밍:

  • 멀티스레딩은 여전히 어려움
  • 데이터 레이스, 데드락
  • Boost.Thread도 충분치 않음

다른 언어들의 한계

구글은 다른 언어들도 시도했지만, 각각 한계가 있었습니다:

1

Python

장점: 간단하고 생산적 단점: 너무 느림, GIL로 인한 멀티코어 활용 불가

2

Java

장점: 안정적이고 성능 준수 단점: 장황함, GC pause, 스타트업 시간

3

C

장점: 빠름 단점: 너무 저수준, 메모리 관리 수동

4

C++

장점: 빠르고 강력 단점: 너무 복잡, 빌드 시간, 학습 곡선

구글이 원한 것은 명확했습니다:

go
// 우리가 원하는 언어
- C의 빠른 컴파일 속도
- C++의 실행 성능
- Python의 간결함과 생산성
- Java의 타입 안전성
- 그리고 쉬운 동시성 프로그래밍

// "그런 언어는 없었다. 그래서 만들었다."

"우리는 C++의 복잡성에 지쳤습니다. 소프트웨어 엔지니어링은 더 단순해야 합니다."

Rob PikeGo 공개 발표

Chapter 2: 전설들의 만남 - 로버트 그리즈머, 롭 파이크, 켄 톰슨

컴퓨터 과학의 드림팀

Go를 만든 세 사람은 각자 컴퓨터 역사에 이름을 남긴 인물들입니다:

Go 창시자들의 전설적 커리어

우연이 아닌 필연

켄 톰슨 (Ken Thompson):

  • Unix 창시자 (데니스 리치와 공동)
  • C 언어 개발에 기여
  • 정규표현식 발명
  • 튜링상 수상 (1983)

롭 파이크 (Rob Pike):

  • Bell Labs에서 Unix 개발
  • UTF-8 공동 설계
  • Plan 9 운영체제 개발
  • Sawzall, Limbo 언어 설계

로버트 그리즈머 (Robert Griesemer):

  • V8 JavaScript 엔진 개발 (Google)
  • Java HotSpot VM 개발 (Sun)
  • Strongtalk, Animorphic Smalltalk 개발

이들의 공통점:

시스템 프로그래밍 전문가 언어 설계 경험 단순함에 대한 신념

"복잡한 것을 만드는 건 쉽습니다. 단순한 것을 만드는 게 어렵습니다."

Ken Thompson

2007년 9월 21일, 화이트보드 앞에서

세 사람은 회의실에 모여 새로운 언어의 설계를 시작했습니다:

go
// 화이트보드에 적힌 초기 아이디어들

[ Go 설계 원칙 ]

1. 단순함 (Simplicity)
   - 배우기 쉬워야 함
   - 읽기 쉬워야 함
   - 명확해야 함

2. 빠른 컴파일
   - 의존성 관리 개선
   - 불필요한 재컴파일 제거
   - 초 단위 빌드

3. 동시성 지원
   - 언어 수준의 지원
   - 쉽고 안전해야 함
   - 멀티코어 활용

4. 타입 안전성
   - 정적 타입 시스템
   - 하지만 타입 추론으로 간결하게
   - GC로 메모리 안전성

5. 효율성
   - C에 가까운 성능
   - 작은 바이너리
   - 빠른 실행

"Less is More" 철학의 실천

Go의 설계 철학은 의도적인 단순함이었습니다:

Go가 의도적으로 제외한 것들

클래스 상속 없음:

  • 대신 composition과 interface

제네릭 없음 (2022년까지):

  • 코드 중복을 감수하더라도 단순함 유지

예외(Exception) 없음:

  • 명시적 에러 처리 (error 리턴)

연산자 오버로딩 없음:

  • a + b는 항상 같은 의미

매크로 없음:

  • 코드가 보이는 그대로

기본 인자 없음:

  • 함수 시그니처가 명확

이런 결정들은 논란을 불러일으켰지만, 명확한 의도가 있었습니다:

"언어에 기능이 추가될수록, 그 기능들의 조합이 기하급수적으로 증가합니다. 우리는 기능을 빼는 것으로 복잡성을 지수적으로 줄였습니다."

Rob PikeLess is exponentially more

빠른 컴파일의 비밀: 의존성 설계

Go의 가장 혁신적인 부분 중 하나는 의존성 시스템 설계였습니다:

go
// C/C++의 문제
#include "a.h"  // a.h가 b.h를 include
                // b.h가 c.h를 include
                // c.h가 d.h를 include...
                // 결과: 수천 개 파일이 파싱됨

// Go의 해결책
import "a"      // a만 파싱
                // a의 의존성은 컴파일러가 미리 알고 있음
                // 결과: 필요한 것만 파싱

Go의 빠른 컴파일 비밀

초 단위 빌드의 마법

설계 원칙:

  1. 순환 의존성 금지

    • 컴파일러가 순환 import를 거부
    • 의존성 그래프가 DAG (비순환 방향 그래프)
  2. 명시적 의존성

    • 사용하지 않는 import는 컴파일 에러
    • "쓰지 않으면 import하지 마라"
  3. 패키지 정보 캐싱

    • 컴파일된 패키지의 export 정보를 저장
    • 재컴파일 시 캐시 활용

결과:

  • 수십만 줄 프로젝트도 수 초 안에 빌드
  • C++의 45분 빌드가 Go에서는 5초

Chapter 3: 고루틴 - 동시성 프로그래밍의 새로운 패러다임

CSP (Communicating Sequential Processes)의 구현

Go의 가장 혁신적인 기능은 **고루틴(Goroutine)**입니다. 이는 1978년 토니 호어가 제안한 CSP 이론의 실용적 구현이었습니다:

go
// 고루틴: 놀라울 정도로 간단

func main() {
  // 일반 함수 호출
  doSomething()
  
  // 고루틴으로 실행 - 단 하나의 키워드 'go'
  go doSomething()
  
  // 수천 개의 고루틴도 가능
  for i := 0; i < 10000; i++ {
    go func(id int) {
      // 각각 독립적으로 실행
      fmt.Println("Goroutine", id)
    }(i)
  }
  
  time.Sleep(time.Second)
}

// 스레드가 아니라 고루틴
// 메모리: 스레드는 1-2MB, 고루틴은 2KB
// 생성 비용: 스레드는 무겁고, 고루틴은 가벼움
고루틴의 혁신

기존 방식 (스레드):

  • OS 스레드: 무겁고 비쌈 (1-2MB 스택)
  • 생성/전환 비용이 큼
  • 수천 개 생성하면 시스템 다운

Go 방식 (고루틴):

  • 경량 스레드: 2KB 시작 (필요시 자동 확장)
  • Go 런타임이 M:N 스케줄링
  • 수십만 개도 문제없음

결과: 동시성이 언어의 일급 시민이 됨

채널: "메모리를 공유하지 말고, 통신으로 공유하라"

고루틴만으로는 부족합니다. 고루틴 간 통신이 필요하죠. Go는 **채널(Channel)**을 제공합니다:

go
// 채널을 통한 통신

func main() {
  // 채널 생성
  messages := make(chan string)
  
  // 고루틴 1: 메시지 보내기
  go func() {
    messages <- "ping"  // 채널에 보내기
  }()
  
  // 고루틴 2: 메시지 받기
  msg := <-messages  // 채널에서 받기
  fmt.Println(msg)   // "ping"
}

// 스레드 + 뮤텍스 + 조건변수의 복잡함을
// 채널 하나로 해결

"Don't communicate by sharing memory; share memory by communicating." (메모리를 공유해서 통신하지 말고, 통신으로 메모리를 공유하라)

Rob PikeGo Concurrency Patterns

실전 예제: 웹 크롤러

go
// Go의 동시성으로 간단한 웹 크롤러

func crawl(url string, results chan<- string) {
  resp, err := http.Get(url)
  if err != nil {
    results <- fmt.Sprintf("Error: %v", err)
    return
  }
  defer resp.Body.Close()
  
  // HTML 파싱...
  results <- fmt.Sprintf("Success: %s", url)
}

func main() {
  urls := []string{
    "https://example.com",
    "https://google.com",
    "https://github.com",
    // ... 1000개의 URL
  }
  
  results := make(chan string, len(urls))
  
  // 1000개 URL을 동시에 크롤
  for _, url := range urls {
    go crawl(url, results)
  }
  
  // 결과 수집
  for i := 0; i < len(urls); i++ {
    fmt.Println(<-results)
  }
}

// 동시성 코드가 순차 코드만큼 간단함!

Go vs 다른 언어의 동시성

얼마나 간단한가?

Java (전통적 방식):

java
ExecutorService executor = Executors.newFixedThreadPool(100);
List<Future<String>> futures = new ArrayList<>();
for (String url : urls) {
  Future<String> future = executor.submit(() -> crawl(url));
  futures.add(future);
}
for (Future<String> future : futures) {
  System.out.println(future.get());
}
executor.shutdown();

Python (async/await):

python
async def crawl_all(urls):
  tasks = [crawl(url) for url in urls]
  results = await asyncio.gather(*tasks)
  return results

asyncio.run(crawl_all(urls))

Go:

go
for _, url := range urls {
  go crawl(url, results)
}

Go가 가장 간결하고 직관적!

Select: 여러 채널을 다루기

Go의 select채널 간 멀티플렉싱을 가능하게 합니다:

go
func main() {
  c1 := make(chan string)
  c2 := make(chan string)
  
  go func() {
    time.Sleep(1 * time.Second)
    c1 <- "one"
  }()
  
  go func() {
    time.Sleep(2 * time.Second)
    c2 <- "two"
  }()
  
  // 두 채널 중 먼저 응답하는 것을 선택
  for i := 0; i < 2; i++ {
    select {
    case msg1 := <-c1:
      fmt.Println("Received", msg1)
    case msg2 := <-c2:
      fmt.Println("Received", msg2)
    case <-time.After(3 * time.Second):
      fmt.Println("Timeout")
    }
  }
}

// Unix의 select(), epoll의 채널 버전
// 하지만 훨씬 더 사용하기 쉬움

Chapter 4: 도커, 쿠버네티스 - Go 생태계의 킬러 앱들

2009년, Go 1.0 이전의 조용한 시작

Go는 2009년 11월 공개되었지만, 초기 반응은 미지근했습니다:

// 2009-2012년의 비판들

"제네릭도 없는 언어를 누가 쓰냐?"
"예외 처리도 없다고? 시대착오적이다."
"구글이 만들었다고 다 성공하는 건 아니야."
"Python보다 빠르고 C보다 느린 애매한 언어."

하지만 2013년, 모든 것이 바뀝니다.

2013년, 도커의 등장

Solomon Hykes는 컨테이너 런타임을 만들고 있었습니다. 처음에는 Python으로 시작했지만, 성능과 배포의 문제로 다른 언어를 찾고 있었습니다.

1

Python으로 시작

빠른 프로토타이핑, 하지만 성능 이슈

2

C++로 재작성 고려

너무 복잡하고 시간이 오래 걸림

3

Go 발견

빠른 컴파일, 정적 바이너리, 쉬운 동시성

4

Docker를 Go로 재작성

Go의 첫 번째 킬러 앱 탄생

go
// Docker가 Go를 선택한 이유

// 1. 정적 바이너리
// 하나의 실행파일만 있으면 됨. 의존성 지옥 없음
$ file docker
docker: ELF 64-bit LSB executable, statically linked

// 2. 크로스 컴파일
// Mac에서 Linux 바이너리 빌드
$ GOOS=linux GOARCH=amd64 go build
// 간단!

// 3. 빠른 개발
// 빌드가 빠르니까 이터레이션이 빠름

// 4. 동시성
// 컨테이너 관리는 동시성 작업의 연속
// 고루틴이 완벽하게 맞음
Docker의 성공 = Go의 성공

2013년 Docker가 폭발적으로 성장하면서, Go도 함께 성장했습니다.

개발자들은 Docker 소스코드를 보며 Go를 배웠고, Go로 인프라 도구를 만들기 시작했습니다.

2014년, 쿠버네티스 - Go의 정점

Google은 내부의 컨테이너 오케스트레이션 시스템 Borg의 오픈소스 버전을 만들기로 결정합니다. 이름은 Kubernetes.

Kubernetes가 Go를 선택한 이유

Google의 선택

기술적 이유:

  • 동시성: 수천 개의 노드, 수만 개의 컨테이너 관리
  • 성능: C++ 수준의 성능 필요
  • 생산성: 빠른 개발과 이터레이션
  • 신뢰성: 타입 안전성과 메모리 안전성

전략적 이유:

  • Go는 Google이 만든 언어
  • 내부 인프라 도구들이 이미 Go로 작성됨
  • Docker가 이미 Go 생태계 구축

결과: Kubernetes는 Go 생태계의 두 번째 킬러 앱이 되었고, 클라우드 네이티브 시대의 표준이 됨

go
// Kubernetes의 핵심 구조 (간략화)

// Controller 패턴: Go의 고루틴과 채널이 완벽하게 맞음
type Controller struct {
  queue    workqueue.Interface
  informer cache.SharedIndexInformer
}

func (c *Controller) Run(stopCh <-chan struct{}) {
  defer c.queue.ShutDown()
  
  // 워커 고루틴들 시작
  for i := 0; i < 5; i++ {
    go wait.Until(c.runWorker, time.Second, stopCh)
  }
  
  <-stopCh
}

// 수천 개의 고루틴이 동시에 돌아감
// 각 Pod, Service, Deployment마다 컨트롤러가 있음
// Go가 아니었다면 이렇게 간단할 수 없었음

"Kubernetes를 Go로 만든 것은 최고의 결정 중 하나였습니다. Go의 동시성 모델 없이는 이렇게 복잡한 시스템을 관리할 수 없었을 것입니다."

Brendan BurnsKubernetes 공동 창시자

클라우드 네이티브 생태계의 표준 언어

Docker와 Kubernetes의 성공으로, Go는 클라우드 인프라의 사실상 표준 언어가 되었습니다:

Go로 작성된 주요 프로젝트들

클라우드 네이티브 생태계

컨테이너 생태계:

  • Docker, containerd, runc
  • Podman, CRI-O

오케스트레이션:

  • Kubernetes
  • Nomad, Docker Swarm

서비스 메시:

  • Istio, Linkerd
  • Consul, Envoy (일부)

모니터링:

  • Prometheus, Grafana Loki
  • Jaeger, Thanos

인프라 도구:

  • Terraform, Vault
  • etcd, CockroachDB

CI/CD:

  • Drone, Gitness
  • Argo, Flux
왜 모두 Go를 선택했을까?

공통된 이유:

  1. 정적 바이너리: 배포가 쉬움. 하나의 파일만 복사하면 됨
  2. 크로스 컴파일: Linux, Mac, Windows 바이너리를 한 번에
  3. 빠른 빌드: CI/CD 파이프라인이 빠름
  4. 동시성: 인프라 도구는 병렬 처리가 많음
  5. 낮은 메모리: 컨테이너 환경에 적합
  6. 표준 라이브러리: HTTP, JSON, TLS 등 모두 내장

결과: Go는 "클라우드 네이티브 언어"라는 별명을 얻음


Chapter 5: Go의 철학과 문화

"Gopher" 문화: 실용주의와 단순함

Go 커뮤니티는 독특한 문화를 형성했습니다. Go 프로그래머를 **"Gopher"**라고 부르는데, 이들의 가치관은 명확합니다:

단순함 > 영리함 명확함 > 간결함 실용성 > 이론
go
// Go 커뮤니티가 선호하는 코드

// ❌ 영리한 코드 (다른 언어에서는 칭찬받을 수도)
func process(data []int) []int {
  return funk.Filter(funk.Map(data, func(x int) int {
    return x * 2
  }), func(x int) bool {
    return x > 10
  }).([]int)
}

// ✅ 단순하고 명확한 코드 (Gopher 스타일)
func process(data []int) []int {
  result := make([]int, 0, len(data))
  for _, x := range data {
    doubled := x * 2
    if doubled > 10 {
      result = append(result, doubled)
    }
  }
  return result
}

// "지루한 코드가 좋은 코드다"

"명확함은 영리함보다 낫다 (Clear is better than clever)"

Rob PikeGo Proverbs

gofmt: 코드 포맷팅 논쟁의 종결

Go는 공식 코드 포매터 gofmt를 제공합니다:

bash
# gofmt: 논쟁 종결자
$ gofmt -w .

# 탭 vs 스페이스? → gofmt가 결정
# 중괄호 위치? → gofmt가 결정
# 한 줄에 몇 글자? → gofmt가 결정

# "gofmt 스타일이 완벽하지 않을 수 있다.
#  하지만 gofmt가 있다는 것은 완벽하다."
gofmt의 영향

Go 커뮤니티에서는 코드 스타일 논쟁이 없습니다. 모든 Go 코드는 똑같이 생겼습니다.

이는 다른 언어들(Prettier for JS, Black for Python, Rustfmt)에게도 영향을 주었습니다.

에러 처리: "if err != nil"의 반복

Go의 가장 논란이 되는 부분은 명시적 에러 처리입니다:

go
// Go의 에러 처리 (반복적이라는 비판)

func readConfig(path string) (*Config, error) {
  file, err := os.Open(path)
  if err != nil {
    return nil, fmt.Errorf("open file: %w", err)
  }
  defer file.Close()
  
  data, err := io.ReadAll(file)
  if err != nil {
    return nil, fmt.Errorf("read file: %w", err)
  }
  
  var config Config
  err = json.Unmarshal(data, &config)
  if err != nil {
    return nil, fmt.Errorf("parse json: %w", err)
  }
  
  return &config, nil
}

// "if err != nil"이 너무 많다는 비판

하지만 Go 커뮤니티의 반론:

명시적 에러 처리의 가치

try-catch vs if err != nil

예외(Exception)의 문제:

java
// Java: 예외가 어디서 발생할지 모름
public User getUser(int id) {
  // 이 함수가 예외를 던지나? 어떤 예외를?
  // 문서를 봐야만 알 수 있음
  return database.query("SELECT * FROM users WHERE id = ?", id);
}

Go의 명시성:

go
// Go: 에러 가능성이 시그니처에 명시됨
func getUser(id int) (*User, error) {
  // 함수 시그니처만 봐도 에러 가능성을 알 수 있음
  // 에러 처리를 강제함
  return database.Query("SELECT * FROM users WHERE id = ?", id)
}

철학: "에러는 예외적인 상황이 아니라 일상적인 흐름의 일부다"


Chapter 6: Go의 한계와 진화

제네릭 논쟁: 13년의 기다림

Go의 가장 큰 논란은 **제네릭(Generics)**의 부재였습니다:

go
// Go 1.18 이전 (2009-2022)

// 정수 슬라이스 최소값
func MinInt(slice []int) int {
  min := slice[0]
  for _, v := range slice[1:] {
    if v < min {
      min = v
    }
  }
  return min
}

// 실수 슬라이스 최소값
func MinFloat(slice []float64) float64 {
  min := slice[0]
  for _, v := range slice[1:] {
    if v < min {
      min = v
    }
  }
  return min
}

// 코드 중복! 하지만 Go는 13년간 제네릭을 거부함
Go 팀의 고민

Go 팀은 제네릭을 원했지만, 단순함을 해치지 않는 제네릭을 원했습니다.

13년 동안 수십 개의 제안이 나왔고, 모두 거부되었습니다. 이유는 "너무 복잡하다"였습니다.

2022년, Go 1.18 - 제네릭의 도입

드디어 2022년, Go 1.18에서 제네릭이 추가되었습니다:

go
// Go 1.18+ : 제네릭

// 하나의 함수로 모든 타입 지원
func Min[T constraints.Ordered](slice []T) T {
  min := slice[0]
  for _, v := range slice[1:] {
    if v < min {
      min = v
    }
  }
  return min
}

// 사용
intMin := Min([]int{3, 1, 4, 1, 5})
floatMin := Min([]float64{3.14, 1.59, 2.65})
stringMin := Min([]string{"banana", "apple", "cherry"})

// 타입 안전성 + 코드 재사용

"우리는 13년을 기다렸습니다. 서두르지 않았습니다. 단순함을 유지하는 제네릭을 찾을 때까지 기다렸습니다."

Ian Lance TaylorGo 언어 개발자

GC Pause: 레이턴시와의 싸움

Go의 가비지 컬렉터는 계속 개선되고 있습니다:

1

Go 1.0 (2012): 100ms+ pause

초기에는 긴 GC pause로 고생

2

Go 1.5 (2015): Concurrent GC

동시 GC 도입, pause 10ms 이하로 감소

3

Go 1.8 (2017): Sub-millisecond

1ms 이하로 개선

4

Go 1.19+ (2022): Soft memory limit

메모리 제한 설정 가능, 예측 가능한 동작

하지만 여전히 레이턴시가 극도로 중요한 애플리케이션에서는 Rust나 C++가 선호됩니다.


에필로그: 단순함의 승리

구글의 실험이 산업의 표준으로

2007년, 컴파일을 기다리며 시작된 실험은 클라우드 시대의 표준 언어가 되었습니다.

Go의 성과 (2024)

단순함이 이긴 세상

채택률:

  • GitHub에서 4위
  • TIOBE 인덱스 Top 10
  • 클라우드 네이티브 분야 1위

주요 사용처:

  • 클라우드 인프라 (Docker, Kubernetes)
  • 마이크로서비스
  • DevOps 도구
  • 네트워크 프로그래밍

커뮤니티:

  • 200만+ 개발자
  • 40만+ 패키지 (pkg.go.dev)
  • 연간 GopherCon 컨퍼런스

산업 채택:

  • Google, Uber, Netflix, Dropbox
  • Twitch, SoundCloud, Medium
  • 거의 모든 주요 클라우드 기업

"Less is More"의 증명

Go는 증명했습니다: 언어가 단순할수록 생산성이 높아진다는 것을.

go
// Go의 핵심 키워드: 단 25개
// (C++는 100개 이상)

break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var
Go의 교훈

복잡성은 적이다:

  • 기능이 많다고 좋은 게 아니다
  • 배우기 쉬운 것이 강력하다

명확성이 우선이다:

  • 영리한 코드보다 명확한 코드
  • 마법보다는 명시성

실용성이 핵심이다:

  • 이론적 완벽함보다 실제 문제 해결
  • 완벽한 도구보다 충분히 좋은 도구

"단순함은 복잡한 문제입니다. 하지만 단순함에 도달하면, 그 힘은 엄청납니다."

Rob PikeSimplicity is Complicated

C++에서 Go로, 그리고 미래로

2007년, 세 명의 프로그래머는 C++ 컴파일을 기다리며 꿈꿨습니다:

"더 간단하고, 더 빠르고, 더 재미있는 언어를."

2024년, 그 꿈은 이루어졌습니다.

go
package main

import "fmt"

func main() {
  fmt.Println("Hello, World!")
  
  // 단순하지만 강력함
  // 배우기 쉽지만 깊이 있음
  // 지루하지만 안정적임
  
  // 이것이 Go다
}

Go는 완벽한 언어가 아닙니다. 제네릭이 늦게 왔고, 에러 처리는 반복적이며, GC는 완벽하지 않습니다.

하지만 Go는 충분히 좋은 언어입니다. 그리고 때로는 충분히 좋은 것이 완벽한 것보다 낫습니다.

클라우드 네이티브 시대에 Go는 정확히 필요한 언어였습니다. 그리고 앞으로도 계속 그럴 것입니다.

"단순함이 복잡성을 이겼습니다. 그리고 그것으로 충분합니다."

Ken Thompson


다음 에피소드에서는 "WebAssembly: 웹 성능의 마지막 퍼즐"을 다룰 예정입니다. 브라우저에서 네이티브급 성능을 어떻게 실현했는지 살펴보겠습니다.