프로그래밍 언어 선택에 대한 고민

Kotlin, Go, Rust를 체험해보며 개인적으로 느낀 장단점을 정리해보았습니다.

2018. 07. 08. #development #go #rust

최근 회사에서 여러 차례의 서비스 장애를 겪으며, 프로그래밍 언어 선택에 대한 고민거리가 생겼습니다.

  • 저희 팀은 메인 언어로 Groovy를 사용합니다. JVM 위에서 돌아가는 만큼 성능은 보장되지만, 언어의 발전이 사실상 멈춘 상태이며 Groovy 특유의 동적 타이핑으로 인해 컴파일 단에서 잡지 못하는 버그가 발생하는 문제가 있습니다.
  • 팀의 보조 언어 및 개인적인 주력 언어로는 Python을 사용합니다. 처음에는 독보적인 생산성을 이유로 Python을 선택하였습니다. 하지만 서비스가 점점 성장해나가며, 폭발적인 트래픽을 받아내기에는 Python의 성능이 병목이 되는 경우가 많았습니다.

제게 필요한 니즈는 세 가지로 압축되었습니다.

  • 커뮤니티가 활성화 되어있고, 지속적으로 발전하는 언어일 것
  • 정적 타이핑으로 개발자의 실수를 줄여주는 언어일 것
  • 대규모의 트래픽에도 문제없이 성능을 뽑아낼 것

이러한 니즈를 충족하는 언어를 정리해보니 Kotlin, Go, Rust정도가 있었습니다. 각 언어들을 체험해보며 개인적으로 느낀 장단점을 정리해보았습니다.

이 외에도 추천하고 싶으신 언어가 있다면 댓글로 알려주세요!

Kotlin

package hello

fun main(args: Array<String>) {
    println("Hello, world!")
}

안드로이드 진영을 중심으로, 점차 백엔드 개발자들 사이에서도 확산되고 있는 언어입니다. Java와 Scala, Groovy의 장점을 적절히 취하면서, Java의 NullPointerException 문제, 장황한 문법으로 인한 낮은 생산성 등을 문법적인 차원으로 해소하였습니다.

기존의 Java API와 거의 완전하게 호환되는 것 또한 장점입니다. '거의'라는 표현을 붙힌 이유는, 기존의 Java API들이 Null-Safety를 보장하지 않음으로서 겪는 번거로운 일들이 적지 않기 때문입니다. (Java API 호출에 대한 Kotlin 공식 문서)

백엔드 개발자 입장에서 Kotlin으로 '꼭 갈아타야 한다'라고 느낄만한 매력은 아직은 부족한 것 같습니다. 곰곰이 생각해봐도, 다른 언어와 차별화된 Kotlin 만의 킬링 포인트는 딱히 떠오르지 않습니다. 개인적으로 기대하고 있는 기능인 Coroutine 도입이 준비중에 있지만, 현재는 안정성이 보장되지 않은 실험적 기능에 불과합니다.

// 간단하게 병렬로 처리할 수 있지만, experimental 패키지를 별도로 설치해주어야합니다.
val deferred = (1..1_000_000).map { n -> async { n * n } }
val sum = deferred.sumBy { it.await() }

그 외에도 장점으로 내세웠던 함수형 프로그래밍은 Java 8에 들며 더 이상 Kotlin만의 장점은 아니게 되었으며, 오히려 Java 계열 언어에서 해결하지 못한 일부 단점들을 그대로 품고있는 경우도 있습니다.

때문에 Kotlin은 Java를 대체할 언어로는 충분한 가능성이 있다고 생각하지만, 신규 프로젝트에서의 선택은 조금은 고민의 여지가 있어보입니다.

Go

package main

import "fmt"

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

Go로 만들어 본 개인 프로젝트

단순한 문법과 interface 기반의 추상화, GoRoutine을 이용한 손쉬운 병렬 프로그래밍 등이 장점입니다. 실제로 직접 개발을 해봤을 때에도 언어의 구조에 대한 이해가 가장 쉬웠으며, 가장 쉽고 빠르게 프로젝트를 완성시킬 수 있었습니다.

이러한 간단한 문법 덕분에 컴파일 속도는 웬만한 스크립트 언어 못지않게 빠르며, 컴파일 언어인 만큼 발드 결과물의 성능 또한 우수한 편입니다. (다만 시스템 프로그래밍 언어에서 출발한 것 치고는 살짝 아쉽긴 합니다.)

하지만 Go의 언어적 장점은 동시에 단점으로 돌아오기도 합니다. 문법을 너무 단순화시킨 나머지 제너릭, 매크로가 없다보니 코드가 다소 지저분해집니다. Go 커뮤니티에서도 필요성에 대한 논쟁이 있지만, 적어도 '있으면 코드가 간결해진다'는 점은 다수가 동의하는 것 같습니다.

또한 interface 기반의 추상화는 불필요한 타입 체킹을 야기시킵니다. 함수의 파라미터로 뭐가 넘어올지 언어 차원에서 보장을 해주지 않으니, 로직 단에서 동적 타이핑 수준의 타입 체킹이 필요하기 때문입니다.

이러한 특성 때문에 Go는 개발자들 사이에서도 꽤나 호불호가 갈리는 언어입니다. Go의 단점만을 모아놓은 GitHub 레포지토리도 있으니 한 번 구경해보세요.

... 정말 사소한 문제이지만, 'Go'라는 이름 때문에 레퍼런스 검색이 힘들다는 점도 꽤나 성가십니다. 'Golang' 으로 검색하면 해결된다고는 하지만, 일부 문서를 버리는 기분이라 좀 찝찝하거든요.

Rust

fn main() {
    println!("Hello, world!");
}

Rust로 만들어 본 개인 프로젝트

Rust는 안정한 서비스를 만들기에 최고의 언어입니다. Null 체크와 각종 예외 처리를 언어 차원에서 강제하기 때문입니다.

아래의 예시처럼 대부분의 경우, 함수의 호출 결과로 데이터와 함께 성공, 실패 여부를 담고 있는 Result 타입을 반환합니다. 이는 호출부에서 예외 상황에 대한 처리를 사실상 강제하도록 만듭니다. 막말로, 어느 정도 개발을 진행한 뒤 컴파일 에러를 모두 고치고 나면 99%는 문제없이 돌아가는 수준입니다.

let f = File::open("hello.txt");
let f = match f {
    Ok(file) => file,
    Err(error) => {
        panic!("There was a problem opening the file: {:?}", error)
    },
};

성능 또한 우수합니다. 많은 벤치마크 자료들이 Rust의 압도적인 성능을 증명해주고 있습니다.

하지만 빌드 성능은 심각하게 고민해볼 필요가 있습니다. 많은 개선이 이루어지긴 했다지만, 여전히 Rust의 지옥같은 빌드 속도는 개발 생산성 하락에 크게 기여합니다. 경험담으로, 비슷한 수준의 서비스를 동일한 CI 서버에서 빌드했을 때 Go는 1분 미만, Kotlin은 약 3분이 걸린 데에 반해 Rust는 무려 20분 가까이 소요된 적이 있습니다.

또한 위 두 언어에 비해서 커뮤니티 규모도 비교적 초라합니다. 때문에 아직은 개인 프로젝트에서는 조금씩 사용하는 정도에 그치고 있는 실정입니다.

결론

이제 제 머릿속에서는,

  • 적당한 퍼포먼스와 병렬 처리가 필요하다면 Go
  • 성능과 안정성이 필요하다면 Rust
  • Java 프로젝트를 대체하는 용도로 Kotlin
  • 가벼운 프로젝트를 빠르게 개발하고 싶다면 Python (또는 JavaScript)

정도로 정리가 되었습니다.

점점 특색있는 언어들이 늘어나다보니 배워야할 것도 많아졌지만, 오히려 상황에 따라 다양한 개발을 할 수 있다는게 개발자로서는 정말 즐거운 일인 것 같습니다.

크리에이티브 커먼즈 라이선스

이 저작물은 크리에이티브 커먼즈 저작자표시-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.

© 2011 - 2020 Do Hoerin, LYnLab