-
Go 언어 설계의 여러 결정들이 불필요하거나 기존 경험을 무시한 채 이루어졌음
-
에러 변수의 범위 관리 문제가 코드 가독성과 버그 탐색을 어렵게 만듦
-
nil의 이중성, 메모리 사용, 코드 포터블성 등 여러 부분에서 비직관성 및 현실과 맞지 않는 설계 나타남
-
defer 구문의 한계 및 표준 라이브러리의 예외 처리 방식이 예외 안전성 확보를 어렵게 만듦
-
메모리 관리 및 UTF-8 처리 부실 등 누적된 문제들이 Go 코드베이스 품질에 장기적으로 악영향을 주고 있음
Go 언어에 대한 장기적 비판
에러 변수 범위의 비직관성
- Go의 문법은 에러 변수(err)의 범위를 불필요하게 넓혀 오류 발생 가능성을 높임
- 예시 코드에서 err 변수가 함수 전체에 살아있고, 재활용되며, 이로 인해 코드 가독성과 유지보수성이 저하됨
- 숙련된 개발자는 이런 범위 문제로 인해 버그 탐색에서 오해와 시간 낭비를 경험함
- 변수를 적절히 지역적으로 한정할 수 있는 방법이 문법적으로 허용되지 않음
두 가지 형태의 nil
- Go에는 interface 타입과 포인터 타입 각각에서 nil이 다르게 동작하는 혼란이 존재함
- 아래 예시처럼 s(포인터)와 i(interface)에 nil이 할당되어도, s==i는 다르게 평가되는 등 일관성 없는 동작을 보임
- 이는 null 처리에서 일반적으로 피하고 싶은 문제로, 설계상의 충분한 고민 없이 만들어진 흔적임
코드의 이식성 한계
-
조건부 컴파일을 위한 주석 사용은 유지보수성과 포터블성 면에서 현저히 비효율적임
- 실제로 포터블 소프트웨어를 만들어본 경험이 있다면, 이러한 방식이 번거롭고 오류를 유발함을 알 수 있음
- 역사적으로 쌓은 경험(코드 포터블성, 실무적 사례들)이 무시됨
- 자세한 내용은 Go programs are not portable을 참고
append의 소유권 불명확성
-
append 함수와 슬라이스의 소유권 관계가 명확하지 않아 코드 예측이 어려움
- 예제를 통해, foo 함수에서 슬라이스를 append 시켰을 때 실제로 원본에 어떤 영향이 있는지 미리 알기 어려움
- 언어의 알아두어야 할 ‘quirk’들이 늘어나 실수를 야기함
defer 구문 설계 미흡
-
RAII(Resource Acquisition Is Initialization) 원칙처럼, 자원 해제를 명확히 지원하지 않음
- Java와 Python의 구조적 자원 관리 구문 대비, Go는 어떤 자원이 defer로 해제되어야 하는지 명확히 알 수 없음
- 예시처럼 파일 작업 시, double-close 문제까지 직접 다루어야 하며, 올바른 해제 순서와 방식이 불분명함
표준 라이브러리의 예외 처리
- Go는 명시적 예외(exception)를 지원하지 않는 구조지만, panic 등 예외 상황은 여전히 발생함
- panic이 일부 상황에서 완전히 프로그램을 종료하지 않고 잠식하는 경우도 있음
- 표준 라이브러리(fmt.Print, HTTP 서버 등)에서 예외를 무시하는 패턴이 존재해, 진정한 예외 안전성 보장이 불가
- 결국 예외 안전 코드 작성은 필수이나, 직접적으로 예외를 사용할 순 없음
UTF-8 처리와 문자열
-
string 타입에 임의의 바이너리 데이터를 넣어도 Go는 특별한 검증 없이 작동함
- 과거 UTF-8 인코딩 이전에 생성된 파일명 등이, 조용히 누락되는 사례를 겪을 수 있음
- 백업 등에서 중요 데이터가 손실될 수 있으며, 실무 상황을 반영하지 않은 간단한 처리 방식임
메모리 관리의 한계
-
RAM 사용량에 대한 직접적 제어가 어렵고, GC(가비지 컬렉션)의 신뢰성도 한계가 있음
- Go의 메모리 사용량이 증가해 장기적으로 비용 및 성능 이슈로 연결됨
- 여러 인스턴스, 컨테이너 환경에서 비용 및 확장성 문제가 실제로 발생함
결론: 더 나은 길이 있었음
- 이미 기존에 효과적으로 입증된 언어 설계들이 있었음에도 불구하고, Go는 많은 부분에서 그것을 외면함
- Java 초기안의 문제점들과는 달리, Go가 출시될 당시 이미 더 나은 접근법이 있었음
참고 자료