본문 바로가기

Home > 열린마당 > 공개SW 소식

공개SW 소식

“떠오르는 자바 대체제” 코틀린 제대로 이해하기

OSS 게시글 작성 시각 2017-11-07 02:45:37 게시글 조회수 5629

2017년 11월 4일 (토)

ⓒ ITWorld, Martin Heller | InfoWorld



코틀린(Kotlin)은 JVM 및 안드로이드 개발에서 자바에 비해 큰 이점을 제공하며, 같은 프로젝트에서 자바와 매끄럽게 연동된다. 사용하지 않을 이유가 없다.

Image Credit : GettyImagesBank

코틀린은 JVM과 안드로이드를 위한 정적 형식 지정(statically typed) 범용 오픈소스 프로그래밍 언어로, 객체 지향과 함수형 프로그래밍의 특성을 혼합한 언어다. 상호운용성, 안전성, 명확성, 툴 지원에 중점을 두고 있다. 현재 자바스크립트용 코틀린 버전(ECMAScript 5.1)과 네이티브 코드(LLVM 사용)가 준비되고 있다.

코틀린은 인텔리J(IntelliJ) IDEA를 만든 젯브레인스(JetBrains)가 2010년 내놓았으며, 2012년부터 오픈소스화됐다. 현재 코틀린 팀은 20명 이상의 젯브레인스 출신 정규 멤버로 구성되며 깃허브의 코틀린 프로젝트에는 100여 명의 기여자가 있다. 젯브레인스는 대표 제품인 인텔리J IDEA를 포함한 많은 제품에서 코틀린을 사용한다.

코틀린의 첫인상은 군살을 뺀 자바다. 다음 스크린샷은 필자가 자바 코드 샘플(왼쪽)을 코틀린으로 자동으로 변환한 모습이다. 인스턴스화하는 자바 변수에서 보이는, 무의미한 반복이 사라진 것을 알 수 있다.

IDG

다음과 같은 자바 이디엄이

StringBuilder sb = new StringBuilder();

코틀린에서는 이렇게 된다.

val sb = StringBuilder()

함수가 fun 키워드로 정의되고, 새 줄에서 세미콜론이 선택 사항임을 볼 수 있다. val 키워드는 읽기 전용 또는 로컬 변수를 선언한다. var 키워드는 가변 속성 또는 로컬 변수를 선언한다.

그럼에도 불구하고 코틀린은 강력한 형식의 언어다. val 및 var 키워드는 형식 추론이 가능할 때만 사용할 수 있다. 그렇지 않은 경우 형식을 선언해야 한다. 형식 추론은 각 코틀린 릴리즈가 나올 때마다 개선되고 있는 것으로 보인다.

화면 양쪽에서 윗부분의 함수 선언을 비교해 보자. 자바의 반환 형식은 프로토타입보다 앞에 오지만 코틀린에서는 프로토타입보다 뒤에 오고, 파스칼처럼 콜론으로 경계를 표시한다.

이 예제에서는 명확하게 드러나지 않지만 코틀린에서는 함수가 클래스 멤버여야 한다는 자바의 요구 사항이 완화됐다. 코틀린에서 함수는 파일의 최상위 수준에서, 다른 함수 안에서 로컬로, 클래스 또는 객체 내부의 멤버 함수로, 그리고 확장 함수로 선언이 가능하다. 확장 함수는 클래스에서 상속하거나 Decorator와 같은 설계 패턴 유형을 사용할 필요 없이 새로운 기능으로 클래스를 확장하는 기능을 제공한다. C#과 비슷한 점이다.

그루비(Groovy) 팬에게 반가운 소식은 코틀린이 빌더를 구현한다는 점이다. 사실 코틀린 빌더는 형식 확인이 가능하다. 코틀린은 위임된 속성을 지원하므로 이를 사용하여 지연(lazy) 속성, 관찰 가능(observable) 속성, 거부 가능(vetoable) 속성, 매핑된 속성을 구현할 수 있다.

다른 언어에서 사용 가능한 많은 비동기 메커니즘은 코틀린 1.1의 실험적 기능인 코틀린 코루틴(coroutine)을 사용해서 라이브러리로 구현할 수 있다. 여기에는 C#과 ECMAScript의 async/await, 고(Go)의 채널, C#과 파이썬의 generators/yield가 포함된다.

코틀린의 함수형 프로그래밍
코틀린에서 최상위 함수를 허용하는 것은 함수형 프로그래밍의 시작에 불과하다. 코틀린은 고차 함수, 익명 함수, 람다, 인라인 함수, 클로저, 꼬리 회귀(tail regression) 및 제너릭도 지원한다. 즉, 코틀린은 함수형 프로그래밍의 모든 기능과 장점을 갖고 있다. 예를 들어 다음 함수형 코틀린 이디엄을 살펴보자.

코틀린에서 목록 필터링하기

val positives = list.filter { x -> x > 0 }

더 짧은 식을 만들려면 람다 함수에 매개 변수가 하나 뿐일 때는 it을 사용한다.

val positives = list.filter { it > 0 }

코틀린에서 쌍 맵/목록 순환하기

for ((k, v) in map) { println(“$k -> $v”) }

k와 v는 무엇이 되든 상관없다.

코틀린의 범위 사용

for (i in 1..100) { ... } // closed range: includes 100
for (i in 1 until 100) { ... } // half-open range: does not include 100
for (x in 2..10 step 2) { ... }
for (x in 10 downTo 1) { ... }
if (x in 1..10) { ... }

이 예제는 for 키워드와 범위 사용을 보여준다.

코틀린은 완전한 함수형 프로그래밍 언어지만 대안 프로그래밍 스타일로 자바의 객체 지향 특성을 대부분 보존하고 있으며, 이는 기존 자바 코드를 변환할 때 매우 유용하다. 코틀린에는 구조체가 있는 클래스, 그리고 중첩, 내부 및 익명 내부 클래스가 있으며 자바 8과 같이 인터페이스가 있다. 코틀린에는 new 키워드가 없다. 클래스 인스턴스를 만들려면 정규 함수처럼 구조체를 호출한다.

코틀린에서 상속은 명명된 슈퍼클래스에서 비롯되는 단일 상속이며, 모든 코틀린 클래스에는 기본 슈퍼클래스인 Any가 있다. 이 클래스는 자바 기본 클래스인 java.lang.Object와는 다르다. Any에는 세 개의 사전 정의된 멤버 함수인 equals(), hashCode(), toString()만 있다.

코틀린 클래스에는 open 키워드가 있어야 한다. 그래야 다른 클래스가 코틀린 클래스로부터 상속을 받을 수 있다. 자바 클래스는 그 반대로, final 키워드가 붙어있지 않은 한 상속이 가능하다. 슈퍼클래스 메서드를 오버라이드하려면 메서드 자체에 open 키워드 표시가 있고 서브클래스 메서드에는 override가 있어야 한다. 이러한 특성은 기본값에 의존하기보다 모든 것을 명시적으로 처리한다는 코틀린의 기본 철학에 따른 것이다. 여기서 상속을 위해 기본 클래스 멤버를 open으로 표시하고 파생 클래스 멤버를 override로 표시하는 코틀린의 명시적인 성격을 볼 수 있다. override로 자바의 여러 가지 일반적 오류를 피해갈 수 있다.

코틀린의 안전 기능
일반적인 오류를 피하는 데 있어서 코틀린은 널 포인터 참조의 위험성을 없애고 널 값 처리를 간소화하도록 설계됐다. 이를 위해 표준 형식에 대해서는 null을 잘못된 것으로 간주하고, 널 허용(nullable) 형식을 추가하고, 널 테스트를 처리하기 위한 단축 표기법을 구현한다.

예를 들어 String 형식의 일반 변수는 null을 가질 수 없다.

var a : String ="abc"
a = null // compilation error

SQL 쿼리 결과를 저장하기 위해 널을 허용해야 한다면 형식에 물음표를 추가하는 방법으로(예: String?) 널 허용 형식을 선언할 수 있다.

var b: String? ="abc"
b = null // ok

오류 방지를 위한 보호 장치는 여기에 그치지 않는다. 위험 없이 널 비허용(non-nullable) 형식을 사용할 수 있지만 그 전에 널 허용 형식에서 널 값을 테스트해야 한다.

널 테스트에 일반적으로 필요한 복잡한 구문을 피하기 위한 코틀린의 기능은 안전 호출(safe call)이다. 표기법은 ?.다. 예를 들어 b?.length는 b가 null이 아닌 경우 b.length를 반환하고 그 외의 경우 null을 반환한다. 이 식의 형식은 Int?다.

달리 말하자면 b?.length는 if (b != null) b.length else null의 단축 표기법이다. 이 구문은 특히 실패했을 가능성이 있는 일련의 데이터베이스 쿼리를 통해 객체 내용이 입력된 경우 장황한 논리를 상당부분 제거한다. 예를 들어 bob?.department?.head?.name은 Bob, department, department head가 모두 널이 아닐 경우 Bob의 부서장 이름을 반환할 것이다.

널이 아닌 값에 대해서만 특정 연산을 수행하려면 안전 호출 연산자인 ?.와 let을 함께 사용한다.

val listWithNulls: List<String?> = listOf(“A”, null)
for (item in listWithNulls) {
item?.let { println(it) } // prints A and ignores null }

널 허용 식에서 유효하지만 특수한 값을 반환하고자 하는 경우가 많은데, 일반적으로 그 이유는 널 비허용 형식에 저장하기 위해서다. 이를 위한 특수 구문으로 이른바 엘비스(Elvis) 연산자가 있다. 표기는?:이다.

val l = b?.length ?: -1

이는 다음과 동일한 구문이다.

val l: Int = if (b != null) b.length else -1

같은 맥락에서 코틀린에는 자바의 검사된 예외(checked exception)가 없다. 검사된 예외는 반드시 잡아야 하는, (예외를) 던질 수 있는 조건이다. 예를 들어 다음 JDK 시그니처를 보자.

Appendable append(CharSequence csq) throws IOException;

이는 다음과 같이 append 메서드를 호출할 때마다 IOException을 잡을 것을 요구한다.

try {
log.append(message)
}
catch (IOException e) {
// Do something with the exception
}

자바 설계자는 이게 좋은 아이디어라고 생각한 모양이다. 물론 프로그래머가 catch 절에 합리적인 뭔가를 구현한다는 전제 하에 토이 프로그램 정도에서는 실제로 유용했다. 그러나 대규모 자바 프로그램에서는 의무적인 catch 절에 //todo: handle this라는 주석 외에는 아무것도 들어있지 않는 코드를 흔히 볼 수 있다. 결국 대규모 프로그램에서 검사된 예외는 쓸모 없는 것으로 확인됐다.

안드로이드용 코틀린
2017년 5월까지 안드로이드용으로 공식 지원되는 프로그래밍 언어는 자바와 C++ 둘 뿐이었다. 구글은 2017 구글 I/O에서 안드로이드의 코틀린 지원을 공식 발표했고 안드로이드 스튜디오 3.0부터는 안드로이드 개발 툴셋에 코틀린이 포함됐다. 플러그인 형태로 이전 버전의 안드로이드 스튜디오에 코틀린을 추가할 수 있다.

코틀린은 자바와 동일한 바이트 코드로 컴파일되며 자바 클래스와 자연스럽게 상호 운용되고 자바와 툴을 공유한다. 코틀린과 자바 간 상호 호출에 따르는 오버헤드가 없으므로 현재 자바로 된 안드로이드 앱에 코틀린을 점진적으로 추가하는 것은 아주 좋은 방법이다.

핀터레스트(Pinterest)는 2016년 11월 일찌감치 코틀린으로 작성된 대표적인 안드로이드 앱이며 구글 I/O 2017의 코틀린 발표에서 주요 사례로 언급됐다. 또한 코틀린 팀은 안드로이드용 에버노트(Evernote), 트렐로(Trello), 스퀘어(Square), 코세라(Coursera) 앱을 자주 언급한다.

코틀린 대 자바
새 개발 작업에서 코틀린과 자바 중 무엇을 선택하느냐의 질문은 2016년 2월 코틀린 1.0이 처음 나왔을 때부터 있었지만 구글 I/O 발표 이후 안드로이드 커뮤니티를 뜨겁게 달구는 주제다. 간단히 답하자면 코틀린 코드가 자바 코드보다 더 안전하고 간결하며, 코틀린과 자바 파일은 안드로이드 앱에서 공존이 가능하므로 코틀린은 새 앱에서 뿐만 아니라 기존 자바 앱을 확장하는 용도로도 유용하다는 것이다.

코틀린 대신 자바를 선택할 만한 유일한 경우는 완전 초보 안드로이드 개발자인 경우다. 대부분의 안드로이드 문서와 예제가 자바로 되어 있음을 감안하면 코틀린 선택 시 어려움이 따를 것이다. 반면 안드로이드 스튜디오에서 자바를 코틀린으로 변환하려면 자바 코드를 코틀린 파일에 붙여 넣기만 하면 된다.

그 외의 안드로이드 개발 일을 하는 대부분의 사람들에게는 코틀린의 장점이 매력적이다. 자바 개발자가 코틀린을 배우는 데 소요되는 시간은 보통 몇 시간 정도다. 널 참조 오류에서 벗어나고 확장 함수를 구현하고 함수형 프로그래밍을 지원하고 코루틴을 추가하기 위해 치러야 할 대가 치고는 미미하다.

코틀린 대 스칼라
코틀린 또는 스칼라 선택에 대한 질문은 안드로이드 커뮤니티에 자주 올라오지는 않는다. 스칼라에 대한 안드로이드 툴 지원이 빈약하고 안드로이드용 스칼라 라이브러리는 비교적 큰 편이기 때문이다. 반면 스칼라 커뮤니티는 이 문제를 예민하게 받아들이고 있으며 이에 대한 해결 방안을 연구 중이다.

상황이 다른 환경도 있다. 예를 들어 아파치 스파크는 대부분 스칼라로 작성되며 스파크용 빅 데이터 애플리케이션도 스칼라로 작성되는 경우가 많다.

많은 측면에서 스칼라와 코틀린 모두 객체 지향 프로그래밍과 함수형 프로그래밍의 융합 특성이 있다. 두 언어는 예를 들어 val을 사용한 불변 선언, var를 사용한 가변 선언과 같이 많은 개념과 표기법을 공유하지만 조금씩 다른 측면도 있다. 예를 들어 람다 함수를 선언할 때 화살표를 배치하는 위치, 단일 화살표를 사용하는지 이중 화살표를 사용하는지 등이다. 코틀린 data class는 스칼라 case class에 매핑된다.

코틀린에서 널 허용 변수를 정의하는 방법은 그루비, C#, F#과 비슷해서 대부분의 사람들은 금방 익숙해진다. 반면 스칼라는 Option 모나드를 사용해 널 허용 변수를 정의하는데, 이게 상당히 어려워서 일부 저자들은 스칼라에 널 안전성(null safety)이 없다고 생각할 정도다.

스칼라의 한 가지 확실한 단점은 긴 컴파일 시간이다. 스파크 리포지토리와 같이 큰 스칼라 본문을 소스에서 빌드할 때 느낄 수 있다. 반면 코틀린은 가장 보편적인 소프트웨어 개발 시나리오에서 빠르게 컴파일되도록 설계됐으며 실제로 자바 코드보다 컴파일 속도가 더 빠른 경우가 많다.

코틀린의 자바 상호운용성
여기까지 읽었다면 아마 null 처리와 검사된 예외의 차이점을 떠올리며 코틀린이 자바 상호운용성 호출의 결과를 어떻게 처리하는지 궁금할 것이다. 코틀린은 자바 형식과 똑같이 작동하는 이른바 "플랫폼 형식"을 드러내지 않고, 안정적으로 추론한다. 이는 즉 널을 허용하지만 널 포인터 예외를 생성할 수 있다는 의미다. 또한 코틀린은 컴파일 시에 코드에 어서션(assertion)을 넣어서 실제 널 포인터 예외 트리거를 피할 수 있다. 플랫폼 형식에 대한 명시적인 언어 표현은 없지만 코틀린이 플랫폼 형식을 보고해야 하는 경우(예를 들어 오류 메시지) 형식에 !를 추가한다.

대부분의 경우 코틀린에서 자바 코드 호출은 예상한 대로 작동한다. 예를 들어 자바 클래스에 getter와 setter가 모두 있는 경우 코틀린은 이를 동일한 이름을 가진 속성으로 취급한다. 마찬가지로 부울(Boolean) 접근자 메서드는 getter 메서드와 동일한 이름을 가진 속성으로 취급된다. 예를 들면 다음과 같다.

import java.util.Calendar

fun calendarDemo() {
val calendar = Calendar.getInstance()
if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // call getFirstDayOfWeek()
calendar.firstDayOfWeek = Calendar.MONDAY // call setFirstDayOfWeek()
}

if (!calendar.isLenient) { // call isLenient()
calendar.isLenient = true // call setLenient()
}
}

이 방식은 자바의 set만 있는 속성에서는 통하지 않는다. 아직 코틀린에서 지원되지 않기 때문이다. 자바 클래스에 setter만 있는 경우 코틀린에서 속성으로 보이지 않는다.

코틀린의 자바 상호운용성은 자바 툴까지 확장된다. 코틀린은 자체 편집기나 IDE가 없다. 대신 인텔리J IDEA, 안드로이드 스튜디오, 이클립스를 포함한 인기 있는 자바 편집기와 IDE를 위한 플러그인이 있다. 코틀린에는 자체 빌드 시스템이 없고, 그래들(Gradle), 메이븐(Maven), 앤트(Ant)를 사용한다.

코틀린의 자바스크립트 상호운용성
코틀린으로 브라우저와 Node.js를 위한 코드를 작성할 수 있다. 타겟이 브라우저와 Node.js인 경우 코틀린은 JVM 바이트 코드로 컴파일되지 않고 자바스크립트 ES5.1로 트랜스파일된다.

자바스크립트는 동적 언어이므로 자바스크립트용 코틀린은 다음과 같이 JVM용 코틀린에서는 사용할 수 없는 dynamic 형식을 추가한다.

val dyn: dynamic = ...

코틀린 형식 체커는 dynamic 형식을 무시하고 자바스크립트가 런타임에 이를 처리하도록 한다. dynamic 형식의 값을 사용하는 식은 작성될 때 자바스크립트로 변환된다. 마찬가지로 코틀린은 기본적으로 이를 무시하고 자바스크립트가 런타임에 식을 해석하도록 한다.

코틀린에서 자바스크립트를 호출하는 방법은 두 가지다. dynamic 형식을 사용하거나 자바스크립트 라이브러리에 대해 엄격한 형식의 코틀린 헤더를 사용하는 것이다. 엄격한 형식의 API로 잘 알려진 서드파티 자바스크립트 프레임워크에 접근하려면 ts2kt 툴을 사용해서 DefinitelyTyped 형식 정의 리포지토리의 TypeScript 정의를 코틀린으로 변환하면 된다.

js(“…”) 함수를 사용해서 코틀린에 텍스트로 자바스크립트 코드를 집어넣을 수 있다. external 한정자를 사용하면 코틀린 코드로 된 패키지 선언을 순수 자바스크립트로 표시할 수 있다. 자바스크립트에서 코틀린 코드를 호출할 수 있지만 이 경우 정규화된 이름을 사용해야 한다.

전체적으로 코틀린은 자바에 비해 JVM에서 실행되는 코드 측면에서 여러 가지 이점을 제공하며 자바스크립트와 네이티브 코드를 생성할 수도 있다. 자바와 비교하면 코틀린은 더 안전하고 간결하며 명시적이다. 객체 지향 프로그래밍 외에 함수형 프로그래밍을 지원하고, 여러 가지 유용한 기능을 제공하며(확장 함수, 빌더, 코루틴, 람다 등) 널 허용 및 널 비허용 형식을 통해 널 안전성을 제공한다. 무엇보다 좋은 점은 자바를 이미 알고 있다면 금방 코틀린을 익혀 실무에 활용할 수 있다는 것이다.



※ 본 내용은 한국IDG(주)(http://www.itworld.co.kr)의 저작권 동의에 의해 공유되고 있습니다.
Copyright ⓒITWORLD. 무단전재 및 재배포 금지


[원문출처 : http://www.itworld.co.kr/news/107046]

맨 위로
맨 위로