칼럼

내가 Go 언어를 선택한 이유

 

어서와 Go

내가 수년간 마주한 PHP 를 보내고 잡은 언어는 Go 다. 많은 이들이 알다시피 현재 범용 프로그래밍 언어로써 가장 많이 쓰인다고 볼 수 있는 것이 자바라는 사실은 부정할 수 없을 것이다. 허나 지금 시점에서 자바에 진입하는 것은 미래보다는 현재를 따라가는 것에 급급하다는 생각이 들었다.

 

미래를 지향하는 내가 그저 현재만을 따라가려고 한다는 것은 내 개발 신념에 어긋나는 일이다. 따라서 나는 Go 언어에 내 개발자로서의 미래를 걸어보기로 했다. 자바스크립트 또한 내가 가진 주력 기술 중 하나이긴 하지만, 영 정감이 가질 않아서 말이다. 사실 적어도 소프트웨어 엔지니어라면 응용을 넘어 조금 더 로우 레벨로 들어가야하지 않나라는 생각도 가지고 있어서 그런 것같다. 

 

첫 프로그래밍 언어이자 첫 취업을 할 때 사용한 언어가 C 였기에 Go 를 익히는데 그다지 어려움은 많이 없었던 것 같다. 다만, 동시성에 대해서는 조금 애를 먹을 것 같다. 아무래도 백엔드 프로그래밍을 하면서 여러 개의 스레드를 사용하는 프로그램을 자주 작성할 일은 적었기도 하고 동시성과 병렬성이라는 주제에 대해서는 대학에서 운영체제 시간에 배운 이론적인 것이 전부다.

Go 는 이런 언어야!

간결한 문법

Go 는 파이썬처럼 스크립트 언어는 아니고 컴파일 언어이며, Hello, world 를 작성하기 위해서는 C 처럼 main() 함수가 필요하다곤 하지만 언어의 차원에서 객체지향의 상징인 클래스를 배제함으로써 언어 자체가 단순해졌고, 복잡하기 그지없는 객체지향적 개념을 조금이나마 줄이기 위해 노력하였다.

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, world")
}

헌데 생각해보면 표현의 방법을 달리했을 뿐, 일부 부분은 객체지향에서 가져오긴 했다. 메서드라든가, 캡슐화라든가. 물론 상속이 없기 때문에 public, protected, private 같은 키워드같은 것은 존재하지 않으며 특이하게도 소문자로 캡슐화를 할 수 있다.

 

반복문은 오직 for 문 하나다. 그 말은 while 문이 없다는 것이고, for 문을 while 처럼 쓸 수 있다. 파이썬처럼 간결하게 사용할 수 있는 언어이며, 배열의 일부를 가져오는 슬라이스 타입또한 파이썬처럼 사용할 수 있다.

오픈소스, 패키지 레지스트리 탈중앙화

PHP/Packagist, Javascript/NPM 진영은 패키지 레지스트리가 중앙 집중화 되어있다. 하지만 Go 의 경우 패키지를 레지스트리에 등록하지 않고, 깃허브에 코드를 올려놓기만해도 프로그램에 포함시켜 사용할 수 있다. 즉, 탈중앙화하여 관리될 수 있다는 이야기다. 별도로 구성된 패키지 매니저의 도움은 필요없다. 그저 go get 명령어만 있으면 패키지를 다운로드 받을 수 있다.

go get github.com/headfirstgo/keyboard
package main

import (
	"github.com/headfirstgo/keyboard"
)

func main() {
	keyboard.GetFloat()
}

간편한 동시성 제어

고루틴과 채널을 사용하면 타언어에서 복잡하게 사용하던 다중스레드를 간편하게 사용할 수 있다. 고루틴은 스레드와 비슷하지만 더 작고 가볍다고 이야기하고 있다. OS 스레드를 한 번 더 추상화한 코루틴(coroutine)이다. 따라서 스레드와 고루틴은 구분해서 이야기하는 것이 좋을 것같다. 고루틴을 사용하여 동시성을 사용할 수 있다. 또한 이제는 당연하듯이 멀티 코어를 사용하고 있으니 운영체제가 알아서 조율해서 병행적으로 실행해주기도 할 것이다.

 

채널이라는 개념을 사용하고 고루틴간의 소통을 하거나 블로킹을 통해 동기화할 수도 있다. 함수를 고루틴으로 동작시키려면 단순히 함수를 콜할때 go 키워드를 붙여주기만 하면 된다. 아래의 코드는 1초의 간격으로 1 ~ 9 까지의 숫자를 출력하며, quit 채널에 종료 시그널이 떨어지면 종료한다.

package main

import (
	"fmt"
	"time"
)

func n(ch chan int, quit chan bool, s int, e int) {
	for i := s; i < e; i++ {
		time.Sleep(time.Second)
		ch <- i
	}
	quit <- true
}

func main() {
	ch, quit := make(chan int), make(chan bool)

	go n(ch, quit, 0, 10)

	for {
		select {
		case x := <-ch:
			fmt.Println(x)
		case <-quit:
			return
		}
	}
}

동시성에 대해서는 별도의 책이 존재할 정도로 생각보다 깊은 내용인 것 같다. Go 입문서적을 끝낸 지금의 시점에서, 동시성 관련 서적을 읽어보기로 했다.

가비지 컬렉션

C 에선 제공하지 않지만, Go 에선 제공하는 것이 바로 가비지 컬렉션이다. C 를 사용해본 사람이라면 알겠지만, 메모리를 동적으로 할당하고 나면 메모리 릭을 막기 위해서 소스 코드의 차원에서 반드시 메모리를 풀어주어야 했다. 하지만 Go 에선 안 쓰는 변수는 알아서 메모리에서 해제 해주는 가비지 컬렉션이 있기때문에 알아서 관리해준다.

 

메모리를 해제하는 것까지 신경쓰면 비지니스 로직에 온전히 집중할 수 없다는 점이 C 를 범용적으로 사용하기에는 걸림돌이기도 하였다. 하드웨어 환경이 제한적인 임베디드에선 메모리를 직접 제어할 수 있기때문에 C 의 이러한 점은 장점이 되었기도 하지만 말이다. 

정적언어, 그러나 명시적 타입 선언은 지양

Go 는 컴파일 타임에 타입이 결정되는 정적 언어이다. 그러나 코딩을 할 때에는 타입을 직접 명시하지 않아도 된다. Go 에서 변수를 선언할 수 있는 방법은 여러가지다. 그러나 많은 Go 프로그래머들은 타입을 자동추론하는 기능을 사용하여 변수를 사용한다. 변수를 선언하고 할당하는 방법은 대체로 다음과 같다.

package main

import (
	"fmt"
)

func main() {
	var n int = 10
	var b = true
	s := "string"

	fmt.Printf("%#v, %#v, %#v", n, b, s)
}

타입을 꼭 명시적으로 지정해줄 필요는 없다. := 과 같이 사용하면 변수를 단축해서 선언하고 할당할 수 있다. 타입은 있지만 명시적 타입 선언은 지양한다. 물론 써도 되겠지만 대부분의 Go 프로그래머들은 단축 선언을 애용하는 듯하다. 하지만 정적 선어이기 때문에 한 번 타입이 지정되면 런타임 중에 동적 언어처럼 타입이 자동으로 바뀌진 않는다.

명시적 포인터

이건 지극히 내 개인적인 의견이지만, C 의 계보를 잇는 것은 C++ 가 아니라 Go 언어라고 생각한다. 전통적인 객체지향 체계를 벗어나고자 하면서도 타언어에서 사용하는 일부 유용한 것은 받아들였기 때문이다.

 

포인터를 명시적으로 사용하기 때문에 타 언어처럼 암묵적으로 객체가 아닌 값에 대해서는 값 복사를, 객체에 대해서는 주소를 적용하는 행위가 아니라 포인터를 명시적으로 사용하기 때문에 조금 더 의도를 명확하게 표현할 수 있다. 이는 소스 코드가 의도에 맞지않은 결과를 도출하는 것을 조금이나마 방지한다.

package main

import (
	"fmt"
)

func foo(n *int) {
	*n = 20
}

func main() {
	n := 10
	foo(&n)

	// 20
	fmt.Println(n)
}

가상머신? 그게 뭔가요

Go 로 작성된 프로그램은 가상머신 위에서 실행되지 않으며 즉각 네이티브 형태로 바뀐다. 운영체제가 실행할 수 있는 바이너리로 바뀌므로 퍼포먼스에서 우위를 점칠 수 있다. 그러면서도 윈도우, 리눅스, 맥 등 여러 운영체제에서 컴파일 할 수 있다.

 

컴퓨터의 사양은 충분히 좋아졌기에 속도를 그다지 신경쓰지 않아도 되는 상황이 조금씩 오고있다고는 해도 타언어에 비해 쉽고 빠르게 어플리케이션을 작성하면서도 자바에 가깝거나 더 그보다도 더 빠른 퍼포먼스까지 잡을 수 있다는 것은 놀라운 것이다.

많은 이들에게 사랑받는 Go 언어

Go 는 Rust, Python 과 함께 많은 사람들에게 사랑받는 언어다. Go 는 교육현장에서 직접적으로 배우는게 아니고, 기업 현장에서 사용하는 것을 마지못해 사용해야 하는 경우도 거의 없기 때문에 스스로 익히려고 하는 경우가 많다. 그래서 그런지, 많이 사용하기보다는 많이 사랑 받는 언어가 되었다.

 

클라우드 진영에서 Docker 등을 통해 메이저로 자리잡았을지도 모르지만 아직 백엔드 시장에서 자리를 크게 잡지는 못했다. 난 자바의 다음 세대가 될 백엔드 진영으로 Go 가 될 것이라고 예상한다. Go 는 생각보다 많은 웹 프레임워크를 가지고 있는데, Gin, Echo, Hugo 와 같은 것들이다. 물론, 어느 것이 제일 베스트라고는 말할 수 없을 것 같다. 왜냐하면 아직 안 써봤으니까. 게다가 Go 웹 어셈블도 프로덕션 레벨에서 사용할 수 있을 만큼의 준비가 완료되었다.

 

2020 프로그래머스 설문조사 리포트에서 Go 는 새롭게 배우고싶은 언어에서 2위를 차지했다. 파이썬이 1위인데, 이것은 아무래도 인공지능데이터 엔지니어에 대한 관심 때문일 것으로 추측된다. 파이썬은 배우기는 쉽고, 처음 개발을 접함에 있어서 재미들리기에 적절한 언어이지만 파이썬을 주력으로 사용하는 인공지능과 같은 응용분야는 파고들수록 한 없이 어려워지는, 마치 피아노(난 피아노를 10년 정도 쳤지만 때려치웠다)같은 존재다.

 

2020 스택오버플로우 설문조사에서는 3위를 차지했다. 해외와 우리나라의 유독 큰 차이점은 Rust 의 위치다. Rust 는 시스템 프로그래밍 언어인 것으로 알고있다. 현재까지 사용처는 그다지 많지 않다. 한국 개발자를 타겟으로 삼고있는 2020 프로그래머의 설문조사의 배우고 싶은 언어 랭킹에는 Rust 가 PHP 바로 위에 있는 정도로 상당히 하위권에 위치해 있는데, 해외의 경우에는 거의 최상위권에 랭크된 모습이다. Go 와 함께 Rust 는 주목받고 있는 언어지만, 여전히 지켜보아야 할 위치에 놓여있다. 

 

나는 성격상 트렌드를 따라 움직이는 편은 아니면서도 개발자이기에 트렌드를 어느 정도 살필 수 밖에 없는 위치에 놓여있다. 내가 좋다고 해서 이미 사장된 언어들을 사용하여 개발할 수는 없는 것이다. 적어도 취미로 개발을 하는게 아니라 이 일을 업으로 삼고자 한다면 그저 좋아하는 것만 추구해서 나아가기란 쉽지 않다.

마치며

나는 Go 를 접한 지 얼마 되지 않았다. Head First Go 라는 책으로 이제서야 Go 에 대해 조금 알았고, 동시성에 대해 조금 더 깊이있게 알아보고자 Go 와 함께 동시성을 이야기하는 서적을 읽고있다. The Go Programming Language 라는 레퍼런스 서적도 추천하는 것 같은데, 그건 동시성을 익히고 난 다음에 읽어봐야 겠다.

 

소개에도 적혀있는 난 현재 블록체인에 관심이 있다. 퍼블릭이든 프라이빗이든 상관없지만, Go 는 동시성 처리와 같은 언어가 가진 강점때문에 블록체인 메인넷을 만든다거나 할때 유용하게 사용될 수 있다. 비트코인, 이더리움은 C++ 로 구성되었으나, 카카오의 클레이튼처럼 Go 로 작성된 블록체인 네트워크도 있다. 또한 프라이빗 블록체인에 사용되는 하이퍼레저 패브릭의 체인코드는 Go 를 주로 사용하여 작성된다.

 

나의 장기적 목표는 혼자 블록체인 메인넷을 구축할 수 있는 수준까지 도달하는 것인데, 적어도 단기간에는 이루어내기 어려운 성과일 것으로 생각한다. 블록체인 개발자로 전향하고 싶은 개발자로서 국내 코인 중 하나인 클레이튼을 가지고 있는 카카오 그라운드X 는 내가 목표로 하고있는 기업 중 하나다. 블록체인을 다루고 있는 여타 다른 기업도 괜찮기는 하지만, 블록체인을 국민에게 서비스로서 접할 수 있게 할 수 있는 힘을 지닌 것이 카카오이기에 일단은 그렇게 정했다. 취직이 아니면 블록체인 관련 창업도 해볼까 생각 중이다.