GN⁺: 가끔 Go는 No-Go가 되어야 합니다
(brainbaking.com)- 저자가 Go 언어를 수년간 사용한 후 Java로 전환하게 되면서 느낀 Go 언어의 한계점과 문제점을 설명하는 글
- Go가 단순하고 지루한(boring) 언어라는 특징이 장점이 아닌 단점이 될 수 있다는 관점을 제시함
- Go의 철학: Google의 Go 설계 팀은 단순함과 제한성을 강조했지만, 이는 사용자가 직접 해결해야 하는 반복 작업을 야기함
1. Go는 "재미없다"는 점이 단점일 수 있음
-
Russ Cox의 주장:
- Go는 "재미없다(boring)"는 점이 장점이라고 강조
- 루프는
for
하나뿐이며, 필터, 맵, 리듀스 같은 기능은 기본 제공되지 않음 - 대부분의 다른 언어에서 제공하는 다양한 고급 기능이 없는 것이 단순함의 일부로 간주됨
-
Reddit 사용자 의견:
- "재미없음"과 "강력함"의 경계가 모호
- Go의 기본 기능 부족은 언젠가 언어에 추가될 가능성이 높다고 주장
-
서드파티 패키지 의존:
- 부족한 기능을 보완하기 위해 자주 사용하는
samber/lo
패키지:- 필터, 맵, 검색 등의 필수 기능 포함
- GitHub에서 18.1k 스타를 받고, 12.6k 이상의 프로젝트에서 사용
- 일부 기능은
slices
패키지로 추가되었으나 여전히 기능적으로 부족함
- 부족한 기능을 보완하기 위해 자주 사용하는
-
작성자의 불만:
- 반복적인 루프 작성 강요
- 필터 및 맵과 같은 작업을 간결하게 처리하기 어려움
- 별도의 리시버 메서드로 추출할 수는 있지만, 코드의 깔끔함을 저해
- Go의 단순함은 많은 경우 장점이지만, 기본적인 편의 기능 부족은 생산성과 코드 가독성을 떨어뜨리는 단점이 될 수 있음
2. Clean Code 원칙을 방해함
-
에러 처리 문제:
- 함수에서 반환 값으로
error
를 포함하는 경우가 대부분:-
if err != nil
패턴을 반복적으로 사용해야 함 - 코드 정리 과정에서 오히려 코드가 더 복잡해지는 문제 발생
-
- HTTP 핸들러 코드가 간단한 프로젝트에서도 20줄 이상으로 증가
- 원래 목표는 4줄 정도로 유지
-
panic()
과 복구 미들웨어를 고려할 정도로 에러 처리 방식에 좌절
- 함수에서 반환 값으로
-
짧은 이름 권장:
- 변수, 메서드, 함수 이름에 짧은 이름 사용 권장:
-
c
,a
같은 이름이 무엇을 의미하는지 불명확 - 예:
c
는 Command, Controller, Argument, Amendment 중 무엇인가? - 긴 이름을 사용하면 더 명확해질 수 있지만, Go의 철학은 짧은 이름을 선호
-
- 이로 인해 팀 코드 리뷰 중 테스트 메서드 이름 등에 대한 끝없는 논쟁 유발
- 변수, 메서드, 함수 이름에 짧은 이름 사용 권장:
- Go의 철학은 코드의 간결함과 단순함을 강조하지만, 결과적으로 클린 코드 원칙에 반하는 복잡성과 비효율성을 초래할 수 있음
3. 의도적으로 작은 언어 철학과 DIY 문화
-
기본 기능 부족:
- 간단한 HTTP 핸들러를 구현하는 것은 쉽지만, 기본 미들웨어(예: 지수 백오프, 교차 사이트 설정 등)가 필요할 경우 여러 패키지를 찾아야 함
- 이러한 패키지들이 (1) 유지보수되고 있는지, (2) 기대대로 작동하는지 확신하기 어려움
-
반복 작업 증가:
- 단순함을 유지하려는 Go의 설계 철학이 오히려 개발자에게 "바퀴를 재발명"하라는 요구로 이어짐
- 예: 간단한 필터 기능조차 직접 구현해야 하는 상황 발생
-
미성숙한 패키지 생태계:
- 많은 GitHub 프로젝트가 방치되거나 소수 버전만 릴리스된 상태
- 상대적으로 젊은 언어라는 점에서 .NET/Java와 비교가 불공정할 수 있지만, 현실적으로 Go의 패키지 안정성과 성숙도가 부족함
-
ORM의 한계:
- Go의 주요 ORM 패키지(Gorm)는 Hibernate나 Entity Framework에 비해 기능적으로 뒤처짐
- 이상한 동작과 문서화 부족 문제 존재
- Go 커뮤니티의 반응: "Go에선 ORM이 필요 없음, Do It Yourself!"
- Go의 단순성은 프로젝트와 팀에 따라 장점일 수 있지만, 기본 제공되지 않는 기능의 부족은 생산성과 개발 경험에 부정적인 영향을 미칠 수 있음.
4. Go에서 하나의 방식만 존재하지 않음
-
일관성과 통일성의 오해:
- 테이블 테스트(Table Test)
-
stretchr/testify
와 같은 테스트 스위트 사용 (557k 프로젝트에서 사용 중) - 테이블 테스트 내의 커스텀 서브테스트 작성
-
- 이로 인해 "통일된 방식"이라는 Go의 철학과 현실 사이에 괴리가 있음
- 테이블 테스트(Table Test)
-
팀 내 갈등 유발:
- 팀 간 테스트 스타일 및 구현 방식에 대한 논의가 오히려 증가
- Go의 철학과 설계 팀 자체도 일관성이 부족:
- 예: Getter 메서드 네이밍에 대한 불일치
-
기능 거부와 패키지 의존:
- Go 팀은 어서션(assertion) 기능 추가를 거부하며, 프로그래머의 부족함을 문제로 삼는 태도 비판받음
- 결과적으로 필요한 기능을 사용하기 위해 또 다른 패키지를 설치(
go get
)해야 함
- Go는 단순성과 통일성을 지향하지만, 실제로는 다양한 구현 방식과 이에 따른 논쟁이 존재하며, 언어 설계 철학의 모호함이 문제를 악화시키고 있음
5. Go의 디버깅은 재미없음
-
디버깅 중 표현식 평가 불가:
- 디버깅 세션에서 표현식을 평가하거나 객체의 커스텀 문자열 표현을 확인할 수 없음
- 런타임에 객체 상태를 명확히 파악하기 어려움
-
스택트레이스와 로그의 비직관성:
- 대규모 테스트(예: CI에서 수천 개 테스트 실행) 실패 시 혼란스러운 스택트레이스 및 로그 제공
- 결과적으로 디버깅이 어려워지고 생산성이 저하됨
-
C 스타일 디버깅 경험:
- Go의 디버깅 도구 체인이 C 기반으로 동작:
- C와 유사한 원시적 디버깅 경험 제공
- 개발자 친화적이지 않음
- Go의 디버깅 도구 체인이 C 기반으로 동작:
-
Rust와의 비교:
- Rust는 Go의 한계를 개선:
- 명확하고 유용한 에러 정보를 제공
- 에러 메시지에 정확한 수정 제안 포함
- Rust는 Go의 한계를 개선:
- Go의 디버깅 경험은 최적화된 바이너리 제공에 중점을 둔 설계 철학에 기반하나, 이는 개발자 경험을 희생시키는 결과를 초래함. 디버깅 효율성을 중시하는 환경에서는 대안 언어를 고려하는 것이 바람직할 수 있음
요약: Go의 적합성과 한계
-
Go 내장 도구의 장점:
- 패키지 관리, 테스트, 성능 모니터링을 위한 기본 도구 체인 제공
- 별도 설정 없이 사용 가능하여 초기 개발 환경 설정 간소화
-
한계:
-
"지루한 코드"와 반복 작업:
- Go의 도구 체인은 기능적이지만, 코드 작성 시 반복 작업(Plumbing Code)을 강요
- 예: 단조로운 구문과 제한된 기능이 작업 흥미를 떨어뜨림
-
"import cycle not allowed":
- 테스트에서 순환 의존(import cycle)을 허용하지 않음
- 도메인 주도 설계(DDD) 작업 시 구조적 제약으로 인해 복잡도 증가
-
struct
임베딩 기법의 의존성:- 이상하고 제한적인
struct
임베딩 메커니즘으로 인한 사용상의 고통
- 이상하고 제한적인
-
"지루한 코드"와 반복 작업:
-
적합한 활용 영역:
- 인프라스트럭처 개발에 적합:
- Docker, Drone, Hugo 등 시스템 수준의 툴이 Go로 작성됨
- 경량 서버 및 CLI 애플리케이션 개발에 유용
- 인프라스트럭처 개발에 적합:
-
부적합한 활용 영역:
- 복잡한 엔터프라이즈 응용 프로그램(예: ERP 시스템) 개발:
- 제한된 언어 철학과 도구로 인해 대규모 비즈니스 로직 관리 비효율
- 복잡한 엔터프라이즈 응용 프로그램(예: ERP 시스템) 개발:
- Go는 특정 작업(특히 인프라 관련)에서는 뛰어난 효율을 제공하지만, 복잡한 비즈니스 도메인 애플리케이션에는 적합하지 않은 도구임. CTO가 구글 기술 스택에 치우친 경우에도 기술 선택에 신중해야 함
if err != nil {} 이거는 솔직히 좀 귀찮긴 합니다. 지적된 단점들도 동의하구요. 그래도 이 언어가 지향하는 바를 명확히 이해하고 어떤 점을 더 활용할지 고민한다면 지적된 단점에도 불구하고 더 잘 활용 할 수 있을 것 같습니다. C랑 비슷한데 GC가 있고, 제한적이지만 제네릭까지 지원되는데다 크로스 컴파일까지 된다! 이렇게 보면 또 혜자스러운 언어가 아닐까요?
Go 를 쓰면서 느낀점은, 그동안 얼마나 암묵적인 에러핸들링을 하고있었나 하고 생각이 들더라구요.
물론 에러핸들링을 한 포인트에서 처리하는 것이 구조적으로 깔끔하게 보일 수 있겠지만, 명시적으로 에러응답이 가능한 동작임을 드러내면서 더 안전한 방식으로 코드를 만들게 되는 것 같다는 생각이 듭니다.
자바에서 go로 넘어가면서 처음엔 비슷한 느낌을 받았던 것 같네요.
지금은 자바에 사용한 시간이 아깝다고 생각할 정도로 go를 즐기고 있습니다. 복잡한 비즈니스 어플리케이션에 적합하지 않다는건 그 어플리케이션이 시스템을 단순화하는데 충분히 고민하지 않았다라는 생각이 들게 만듭니다.
Hacker News 의견
-
Java 개발자가 Go에 Java 스타일을 강요하면 문제가 발생함
- Go 철학은 단기적으로는 덜 유용하지만, 장기적으로는 큰 차이를 만듦
- JVM 생태계에 깊이 빠진 사람들은 Go를 즐기기 어려움
-
많은 개발자들이 너무 이른 추상화를 시도함
- 간단한 반복이 충분할 때 불필요한 추상화를 만듦
-
Go의 표준 라이브러리는 크지만 모든 것을 할 수 있을 만큼 크지는 않음
- 프로젝트마다 바퀴를 재발명하는 경향이 있음
- Go는 서버/CLI 애플리케이션에 이상적임
-
프로그래밍 언어 선택보다 더 큰 도전 과제가 존재함
- Go의 메커니즘과 철학에 적응하는 것이 중요함
-
Go를 좋아하는 이유를 이해하기 어려움
- 언어 자체보다는 도구 체인과 배포의 용이함을 선호함
-
Go의 핵심 팀이 잘못된 결정을 되돌리는 것이 좌절감을 줌
- 좋은 패키지 시스템과 도구가 존재함
- 복잡한 ERP 시스템에는 Java가 더 나은 선택일 수 있음
-
Go는 UNIX와 같은 문제를 가짐
- 복잡성을 사용자에게 떠넘기는 경향이 있음
- Java의 런타임은 Go에 비해 빠르게 발전하고 있음