001. 코틀린 개요와 특징 그리고 철학

코틀린 개요와 특징 그리고 철학

코틀린은 Java Platform을 대상으로 하는 새로운 프로그래밍 언어이다.

Java 코드와의 상호 운용이 가능하며, 간결하고 안전하며 또 실용적이다.

위의 특징으로 코틀린은 현재 Java가 주로 사용되는 서버사이드의 개발은 물론 Android 앱을 개발하는 등 거의 모든 곳에서 사용할 수 있다.

코틀린은 모든 Java 기반의 라이브러리 및 프레임워크에서 Java와 동일한 수준의 성능으로 잘 동작한다.

이 코틀린은 몇몇 특징에 대해 알아보자.

코틀린 맛보기

1
2
3
4
5
6
7
8
9
10
data class Person(val name: String,  // “data” class
val age: Int? = null) // Nullable type (Int?); the default value for the argument

fun main(args: Array<String>) { // Top-level function
val persons = listOf(Person("Alice"),
Person("Bob", age = 29)) // Named argument

val oldest = persons.maxBy { it.age ?: 0 } // Lambda expression; Elvis operator
println("The oldest is: $oldest") // String template
}

위의 예제를 통해 코틀린을 파악해보자.

위의 예제는 Person 이라는 클래스를 정의하고 persons라는 배열(정확히는 Collection)을 만들며, 가장 age가 높은 클래스를 찾아 출력해준다.

좀 더 자세히 보자면 nameage라는 속성을 가진 데이터 클래스를 먼저 선언한 뒤, age는 기본값으로 null이 부여되는 것을 확인할 수 있다.

즉 데이터 클래스 객체 Aliceage는 null로 초기화되어 있다.

maxBy 함수를 통해 persons 중 가장 age의 값이 높은 객체를 찾아내는데, 이를 통해 Kotlin도 lambda를 지원함을 파악할 수 있다.

?: 연산자를 통해 객체의 age가 null일 경우 0으로 초기화하는 것도 파악할 수 있는데, 이 연산자를 Elvis Operator 라고 부른다.

Elvis Operator 는 앞의 값이 참이면 참인 값을 그대로 반환하고 아닌 경우에는 지정한 값을 반환한다.

위의 특징에서 Null-Safty에 대해 코틀린이 어떤 관점으로 접근하고 있는지 파악할 수 있다. 억지로 NullPointerException이 그립다면 !!연산자로 강제 호출해보자

위의 코드를 실행하면 아래와 같은 결과를 출력해낼 것이다.

1
The oldest is: Person(name=Bob, age=29)

아래 링크로 가면 온라인 환경에서 코틀린 코드를 실행해볼 수 있다.

Kotlin Playground

코틀린의 주요 특징

  1. 다양한 플랫폼에서 동작
  2. 정적 타입 지원
  3. 함수형 프로그래밍 / 객체지향 프로그래밍
  4. 오픈소스

다양한 플랫폼에서 동작

코틀린은 Java가 구동되는 모든 환경에서 동작할 수 있다.

Java 사용처에서 코틀린을 사용하면 간결하고 생산적이며 좀 더 안전한 변경을 가능하게 해준다.

일반적으로 사용되는 영역은 크게 두 가지로 첫 번째는 흔히 BackEnd라고 부르는 서버의 코드를 작성할 수 있는 것이며, 두 번째는 Android 애플리케이션 개발이다.

Building web applications with Spring Boot and Kotlin

Get Started with Kotlin on Android

정적 타입 지원

Java와 마찬가지로 코틀린도 정적 언어의 특징을 지니고 있다.

따라서 컴파일시에 자료형이 결정되며, 이를 통해 컴파일러는 사용자가 접근할 수 있는 메소드와 필드가 현재 사용중인 객체에 존재하는 지 검증할 수 있다.

반대는 당연히 동적 언어. Javascript나 Python이 동적 언어에 해당한다.

동적 언어를 활용하면 모든 타입의 데이터를 저장하거나 반환받을 수 있고, 코드 길이가 짧아지고 데이터 구조가 상대적으로 유연해지는 등의 장점이 있지만,

오타와 같은 문제를 컴파일시 확인할 수 없고, 런타임에러를 야기할 수 있다.

반면, 코틀린은 타입 추론을 제공하므로 Java와 달리 모든 변수에 대해 타입을 정의할 필요는 없다.

아래의 코드가 제일 대표적인 예시다.

1
val x = 1

위의 코드를 보면 x라는 변수를 선언하였고, 1로 초기화되고 있기때문에 컴파일러는 정수로 판단하여 Int 타입으로 결정짓는다.

정적 언어를 쓰면서 얻을 수 있는 장점은 아래와 같다.

  1. Performance : 함수 호출시 호출방법을 컴파일시 미리 판단할 수 있으므로 실행 속도가 빠르다.
  2. Reliability : 데이터 정합성을 확인할 수 있으므로, 런타임 오류를 줄일 수 있다.
  3. Maintainability : 익숙하지 않은 코드를 유지보수하더라도, 어떤 종류의 객체들과 유동적으로 작업하는 지 파악하기 쉽다.
  4. Tool support : 신뢰할 수 있는 리팩토링, 정밀한 코드의 완성도 등 IDE의 기능을 사용할 수 있다.

위의 예제에서 보았듯이 코틀린의 타입 추론 덕분에 데이터 타입을 명시적으로 작성할 필요가 없어 코드의 길이를 줄일 수 있다.

또한 코틀린을 활용하다보면 class, interface, generic 등의 Java를 할 줄 안다면 친숙한 개념을 발견할 수 있다.

Java와 달리 코틀린에서 가장 중요한 것은 Nullable 타입과 함수형타입 에 대해 지원한다는 것이다.

함수형 프로그래밍과 객체지향 프로그래밍

Java를 개발해보았다면 OOP 개념에 대해 잘 알고 있겠지만, 함수형 프로그래밍은 조금 낯선 개념일 수 있다.

함수형 프로그래밍의 주요 개념을 아래와 같이 확인할 수 있다.

  • 함수를 Value 자체로 사용한다. 따라서 변수에 저장하거나, 파라미터로 넘기거나, 반환값으로 사용할 수 있다.
  • 불변이다. 따라서 생성 후 상태의 변경이 불가능하다는 것이 보장된다.
  • 동일한 입력을 통해 동일한 결과를 반환하는 함수만의 순수한 기능을 사용할 수 있다.

함수형 프로그래밍을 사용하면 어떤 장점을 얻을 수 있을까?

첫 번째, 좀 더 우아하고 간결한 코드를 작성할 수 있으며 좀 더 추상화에 무게를 둠에 따라 코드의 중복을 회피할 수 있다.

아래의 예제를 보자.

1
2
fun findAlice() = findPerson { it.name == "Alice" }
fun findBob() = findPerson { it.name == "Bob" }

위의 두 함수는 유사한 작업을 진행하지만, 비교 대상이 다르기 때문에 서로 다른 기능을 가지고 있다고 볼 수 있다.

즉, 사람을 찾는다 라는 공통 부분을 함수로 추출하여 lambda로 표현한 것이다.

두 번째, 안전한 멀티스레딩을 구현할 수 있다.

보통 데이터의 동기화 이슈로 멀티스레드를 구현하기 까다로운데, 함수형 프로그래밍에선 불변의 데이터 구조를 보장받으며, 순수한 기능 하나만 사용하여 동기화 이슈를 최소화 할 수 있다.

세 번째, 테스트 코드 작성이 용이하다.

동일한 입력에 대해 동일한 결과를 반환하는 특징을 이용하여 별도의 설정 코드 없이도 테스트를 진행할 수 있다.

일반적으로 함수형 스타일은 대부분의 프로그래밍 언어가 지원하고 있으며, 코틀린은 아래의 기능을 통해 함수형 스타일의 코드를 작성할 수 있다.

  • 함수를 파라미터와 반환 타입으로 사용할 수 있다.
  • Lambda 표현식을 통해 코드 블럭을 넘길 수 있다.
  • 간결한 문법으로 불변 객체를 생성할 수 있다.
  • 표준 라이브러리에서 함수형 스타일의 API를 풍부하게 지원한다.

오픈소스

컴파일러와 라이브러리 등 모든 도구를 포함한 코틀린은 완전히 오픈소스로 제공된다.

오픈소스 라이센스는 Apache2로 어떤 목적으로든 마음대로 사용할 수 있게 개방되어있다.

Kotlin Repository

코틀린 기반 애플리케이션

앞서 언급한 일반적인 코틀린 사용처인 서버와 Android에 대해 알아보자.

서버 사이드 개발에서의 코틀린

서버 사이드 프로그래밍이란 단어 자체가 매우 광범위한 개념이므로 아래와 같은 유형의 애플리케이션을 포함한다고 이해하자.

  • 브라우저를 통해 접근할 수 있는 HTML 페이지를 반환하는 웹 애플리케이션
  • HTTP 기반 통신을 통해 JSON을 반환하는 모바일 애플리케이션의 API 서버
  • RPC 프로토콜 기반의 (또 다른 마이크로서비스와 통신하는) 마이크로서비스

개발자들은 위의 해당하는 서버 사이드 프로그램에 대해 Java기반으로 구축해왔으며, 이 과정을 통해 거대한 프레임워크 및 기술을 축적해왔다.

이러한 기존의 거대한 시스템이 존재하는 이상, 새로운 코드 혹은 언어는 기존 시스템과 통합되어야하는 미션이 존재한다.

코틀린은 Java와의 원활한 상호 운용성을 보장하기 때문에 기존 서비스의 코드를 코틀린으로 마이그레이션을 하든 Java로 유지하든 상관없이 동작한다.

코틀린은 기존 시스템에 대한 통합 및 호환을 지원함과 동시에 새로운 기능을 가능케해준다.

예를 들어 HTML 생성 라이브러리를 이용하면 아래와 같이 외부 템플릿 언어를 간결하게 구현하여 대체할 수 있다.

1
2
3
4
5
6
7
8
9
10
fun renderPersonList(persons: Collection<Person>) =
createHTML().table {
for (person in persons) {
tr {
td { +person.name }
td { +person.age }
}
}
}
}

코틀린의 깔끔하고 간결한 DSL을 이용한 또 다른 예는 Kotlin SQL Framework를 들 수 있다.

이 프레임워크를 이용하면 SQL Data 구조를 설명하고 접근하기 위한 쿼리를 만들 수 있다.

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
object CountryTable : IdTable() {
val name = varchar("name", 250).uniqueIndex()
val iso = varchar("iso", 2).uniqueIndex()
}
class Country(id: EntityID) : Entity(id) {
var name: String by CountryTable.name
var iso: String by CountryTable.iso
}
val russia = Country.find {
CountryTable.iso.eq("ru")
}.first()
println(russia.name)

Kotlin SQL Framework Repository

Android 개발에서의 코틀린

코틀린은 Android 프레임워크를 지원하는 특수 컴파일러 플러그인과의 결합으로 Android 개발을 훨씬 생산적으로 할 수 있게 해준다.

일반적인 Java 기반에서의 개발보다 훨씬 적은 코드를 통해 개발을 진행할 수 있으며, Anko 라이브러리 등을 통해 많은 Android 표준 API를 사용할 수 있다.

아래 Anko 라이브러리의 예제를 확인해보자.

1
2
3
4
5
6
verticalLayout {
val name = editText()
button("Say Hello") {
onClick { toast("Hello, ${name.text}!") }
}
}

Anko Repository

Android 개발에 코틀린을 사용하게 되면, 애플리케이션에 대해 좀 더 나은 안정성을 보장할 수 있다.

Android 개발 경험이 있다면 ANR 등의 다이얼로그가 노출되는 경우를 겪어보았을 것이다.

ANR은 별도의 예외 처리를 진행하지 않을 경우 죽, 처리되지않은 예외가 발생할 때 노출되게 된다.

이 예외는 대부분 NPE인 경우가 많고, 코틀린은 개발자로 하여금 Null에 대해 사전에 처리하도록 알려준다.

계속해서 언급했듯 Java(정확히는 Java 6)와 완전 호환되기때문에 최신 버전의 Android를 대상으로하는 개발이 아니더라도 코틀린 사용엔 전혀 지장이 없다.

ANR : Application Not Responding

코틀린의 철학

코틀린은 보통 상호운용성에 중점을 둔 간결하고 안전한 언어로 표현된다.

정확히 어떤 철학을 지닌 언어인지 알아보자.

실용성 - Pragmatic

코틀린은 현실 세계의 문제들을 해결하기 위해 고안된 실용적인 언어로, 다른 프로그래밍 언어들을 통해 검증된 성공적인 기능과 솔루션에 의존하여 디자인되었다.

따라서 언어의 복잡성이 줄어들고, 익숙한 개념들을 통해 쉽게 익힐 수 있다.

또한 코틀린은 특정 프로그래밍 스타일(함수형 프로그래밍과 같은)과 패러다임에 대한 강제성이 없다.

언어로서의 코틀린 뿐만 아니라 코틀린을 개발할 수 있는 환경도 IntelliJ IDEA 플러그인에서의 지원을 통해 실용성을 강조하고 있다.

간결성 - Concise

개발자들은 새로운 코드의 작성보다 기존에 작성된 코드의 읽기에 더 많은 시간을 소모한다고 한다.

당연하게도 코드가 간결하고, 간단할수록 무슨 동작을 의미하는 지 파악하기가 쉽기때문에, 코틀린은 작성되는 모든 코드가 의미있는 코드가 되도록 노력한다.

즉, 불필요한 보일러 플레이트는 이미 코틀린의 표준 라이브러리를 통해 포함되어있다.

그렇다고 소스코드의 최소화를 목적으로 하지 않으며, 연산자 오버로딩을 지원은 하되 신규 연산자를 정의하진 못하도록 하는 등 최대한 간결성을 보장한다.

안정성 - Safe

일반적으로 프로그래밍 언어가 안전한다는 것은 프로그램상의 특정 오류를 방지하기 위해 설계되었음을 말한다.

그러나 프로그래밍 언어가 모든 오류를 방지할 수는 없으며, 컴파일러를 통해 개발자에게 적절한 정보를 전달해주어야 한다.

코틀린은 Java보다 안전한 언어를 만들기 위해 노력하였으며, 런타임에 실패하는 경우보다 컴파일시 실패하는 경우를 더 깊이 체크하므로서 안정성을 높이고 있다.

즉, 최소한의 비용으로 NPE를 제거하기위해 노력하며, Nullable 데이터들에 대한 지원을 통해서도 NPE를 방지하고 있다.

1
2
val s: String? = null // May be null
val s2: String = "" // May not be null

추가적으로 코틀린은 Nullable 데이터를 처리할 수 있는 여러 방법을 제공한다.

이를 통해 NPE를 회피할 수 있게해준다.

코틀린은 ClassCastException 또한 회피한다. 이는 객체 타입을 검증하지 않고 호환되지 않는 타입에 캐스팅할 경우 발생한다.

아래와 같이 타입을 검증하면 별도의 캐스팅 작업 없이 해당 타입의 멤버변수 혹은 멤버함수에 접근이 가능하다.

1
2
if (value is String) // Checks the type
println(value.toUpperCase()) // Uses the method of the type

상호운용성 - Interoperable

코틀린이 Java와 완벽하게 호환이 되는 만큼, 기존 라이브러리도 당연히 활용할 수 있다.

코틀린이 Java의 코드를 호출하고, Java에서 코틀린을 호출하는 것 또한 가능하다.

단순히 호출만 가능한 것이 아니라 클래스를 상속하거나 인터페이스를 구현하는 것도 가능하다.