budtree
나를 조금만 더 완성해보는 날
budtree
전체 방문자
오늘
어제
  • 분류 전체보기 (77)
    • 💝 Computer Science (5)
      • OS (1)
      • Network (1)
      • Database (3)
    • 🐤 study (21)
      • kubernetes🕸️ (0)
      • Spring Boot🍃 (1)
      • JPA (2)
      • Infra (2)
      • HTML | CSS (3)
      • Java (6)
      • Kotlin (3)
      • etc (4)
    • 💻 Project (3)
      • memoir & diary 📚 (1)
      • class (0)
      • project (2)
    • 🔥 Problem Solving (38)
      • programmers (30)
      • SQL (8)
      • BOJ (0)
    • ✨ daily (10)
      • diary (5)
      • exercise (5)
      • travel (0)
      • review (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 카카오코딩테스트
  • 헬스장
  • 2018 KAKAO BLIND RECRUITMENT
  • 피티
  • Summer/Winter Coding
  • 프린이
  • 블랙멀티짐
  • 프로그래머스
  • 자바
  • 코린이
  • 카카오코테
  • 개린이
  • 취업
  • 취업준비
  • 헬린이
  • 코틀린
  • ArrayList
  • kotlin
  • HashMap
  • 일기
  • java
  • 코딩테스트
  • 월간코드챌린지
  • 코테
  • 월간 코드 챌린지
  • pt
  • css
  • programmers
  • 서울대입구 헬스장
  • 카카오

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
budtree

나를 조금만 더 완성해보는 날

🐤 study/Kotlin

[당근테크 밋업 요약] 코프링 | 코틀린의 철학 | 코틀린의 장점 | 코틀린 특징(#살아있다 #자프링외길12년차 #코프링2개월생존기) -1편

2022. 11. 17. 01:12
image

코틀린의 철학

: 코틀린은 자바와의 상호운용성에 초점을 맞춘 실용적이고 간결하며 안전한 언어이다. - Kotlin in Action



코틀린의 철학 : 간결성

개발자들은 코드를 작성하는 시간보다, 기존의 코드를 읽는데 시간을 더 많이 쓴다.

  • 코드는 간결할수록 내용을 파악하기 쉽고, 유지보수에도 좋다.
  • 그 중 핵심은 언어의 간결성(의도 파악, 쉬운 구문 구조, 부가적인 코드 최대한 제거)

코틀린은 프로그램작성에 있어 부수적인 요소들을 최대한 줄이고자 노력했다.

예제1)

data class Person(
    val id: UUID,
    val firstname: String,
    val lastname: String,
    val address: Address
)

다음과 같이, data class를 작성하게 되면 getter, setter, equals, toString 메소드를 컴파일 시점 에 자동으로 생성해준다. (자바는 외부 라이브러리(lombok)의 도움을 받아서 생성)


예제2)

Syntax sugar가 자바에 비해 많이 들어가 있다.
(Syntax sugar: 읽는 사람 또는 작성하는 사람이 편하게 디자인 된 문법)

fun double(x: Int): Int = x * 2

코드가 간결하면, 개발자의 생산성을 올려줄 수 있고 개발 진행 속도를 빠르게 할 수 있다.




2. 코틀린의 철학 : 안정성

소프트웨어 내에서의 발생할 문제점을 사전에 막기 위해 방어코드를 많이 넣는다. → 안정성 확보

더 큰 안정성을 확보하기 위해서는 프로그램에 더 많은 코드를 덧붙여야 한다.(생산성 하락을 감수해야 함)

→ 안정성, 생산성 사이에서의 TradeOff

코틀린은 이 tradeoff의 비용을 많이 줄일 수 있는 기능을 제공한다.


1) Null이 될 수 없는 값을 추적

val nullable: String?= null

kolin에서의 type system은 null이 될 수 없는 값을 추적한다.

실행 시점에 NullPointException이 발생할 수 있는 값을 금지할 수 있다.


2) 타입 검사와 캐스트가 한 연산자에 의해 이루어지며, ClassCastException 발생을 방지

val value = loadValue()
if(value is String){
    value.uppercase(Locale.getDefault())
}

3) break문이 없어도 되며, 열거형 같은 특별한 타입과 함께 쓰면 모든 값이 평가되었는지 확인한다.

val message = when (state) {
    State.IDLE -> "idle"
    State.RUNNING -> "running"
    State.FINISHED -> "finished"
}

이 모든 과정이 컴파일 단계에서 제공되기 때문에, 생산성의 저하를 낮추면서 안정성을 확보할 수 있다.



Kotlin code 예제


1. Null을 안전하게 다루기

kotlin type system은 null이 될 수 없는 값을 추적한다.

실행 시점에 NullpointException이 발생할 수 있는 코드의 사용을 금지한다.

Post타입의 뒤에 ?가 붙어있는데 이 ArrayList에 담겨있는 값이 Null이 될 수 있다는 것을 의미한다.

이를 사용하기 위해선 NULL이 아니라는 것을 사전에 확인해주지 않으면 컴파일 자체가 되지 않는다.


첫번째 방법으로는, 자바에서처럼 if문을 사용해서 Null을 확인해주는 방법이 있다.

fun from(posts: Array<Post?>): Array<PostDto> {
    return posts.map({ post ->
        if(post == null)
            throw Error("")
        }
        if(post.id == null) {
            throw Error("")
        }
        PostDto(
            post,
            post.text,
            post.author_id
        )
    }).toTypedArray()
}

두번째 방법으로는 Kotlin에서 제공하는 호출 연산자를 이용한다. ?.을 이용해 post 객체가 null일 수 있음을 의미하고, null이 아닌 경우에는 id를 가져올 수 있고, null일 경우에는 Null 처리를 해준다.

fun from(posts: Array<Post?>): Array<PostDto> {
    return posts.map({ post ->
        if(post?.id == null) {
            throw Error("")
        }
        PostDto(
            post.id,
            post.text,
            post.author_id
        )
    }).toTypedArray()
}

세 번째 방법으로는, 엘비스 연산자를 이용해 null 처리를 할 수 있다. post 값이 null이 아니라면 id 값을 가져오고, id 값이 null이라면 예외를 발생한다는 의미로 해석이 가능하다.

fun from(posts: Array<Post?>): Array<PostDto> {
    return posts.map({ post ->
        PostDto(
            post?.id?: throw Error(""),
            post.text,
            post.author_id
        )
    }).toTypedArray()
}

null이 들어가지 않을것이라 단정할 수 있는 경우엔 !!를 사용한다.

fun from(posts: Array<Post?>): Array<PostDto> {
    return posts.map({ post ->
        PostDto(
            post?.id!!,
            post.text,
            post.author_id
        )
    }).toTypedArray()
}

이 id값은 null일 수 없음을 단정한다. 만약에 null이 경우 runtimeException이 발생한다.



2. 문(statement)과 식(expression)

fun mapItem(items : NewsItem<*>) = NewsItemDto {
    if(item is NewsItem.NewTopic) {
        return NewsItemDto(item.content.title, item.content.author.username)
    } else if(item is NewsItem.NewPost) {
        return NewsItemDto(item.content.text, item.content.author)
    } else {
        throw IllegalArgumentException("")
    }

위의 코드는 분기문을 이용해 전달받은 id 타입이 무엇인지에 따라 데이터를 변환한다.

자바에서는 모든 제어구조가 문이지만, 코틀린에서는 loop를 제외한 나머지 제어구조가 식으로 이루어져 있다.

(식은 값을 만들어내고, 문은 값을 만들어낼 수 없다.)

따라서 kotlin에서의 if 문은 식이기 때문에, 평가된 값을 즉시 반환할 수 있다.

즉, return문을 사용하지 않고 함수의 반환값으로 if문을 사용할 수 있다.

예시)

fun mapItem(items : NewsItem<*>) = if(item is NewsItem.NewTopic) {
    NewsItemDto(item.content.title, item.content.author.username)
} else if(item is NewsItem.NewPost) {
    NewsItemDto(item.content.text, item.content.author)
} else {
    throw IllegalArgumentException("")
}



3. 봉인해서 까먹지 말기

코틀린에서는 자바의 Switch문과 비슷한 when문을 제공한다. 결과값을 그 즉시 반환할 수 있고, 패턴매칭을 이용해 풍부한 조건을 사용할 수 있다.

넘긴 아이템 객체의 타입을 파악해, 다른 방식으로 데이터를 변환할 수 있다

kotlin을 통해 훨씬 더 if문 보다 훨씬 더 간단하게 사용할 수 있다.

abstract class NewsItem<out C> {
    val type : String
        get() = javaClass.simpleName
    abstract val content: C

    data class NewTopic(...) : NewsItem<TopicDetails>()
    data class NewPost(...) : NewsItem<Post>()
}

fun mapItem(items : NewsItem<*>) = when(item) {
    is NewsItem.NewTopic -> NewsItemDto(item.c...t.title, item.c...r.username)
    is NewsItem.NewPost -> NewsItemDto(item.c...t.text, item.c...t.author)
    else -> throw IllegalArgumentException("")
}

item객체의 타입이 무엇이냐에 따라 다른 객체를 리턴해주고 있다. newsitem의 경우에는 추상클래스로 정의가 되어있고, 하위에 topic과 post를 보관하는 구조이다.

이 상태에서 else branch를 지우게 된다면, 추가하라는 에러가 뜬다. 컴파일러가 하위 클래스의 종류를 알지 못해서 그런다.


sealed class NewsItem<out C> {
    val type : String
        get() = javaClass.simpleName
    abstract val content: C

    data class NewTopic(...) : NewsItem<TopicDetails>()
    data class NewPost(...) : NewsItem<Post>()
}

fun mapItem(items : NewsItem<*>) = when(item) {
    is NewsItem.NewTopic -> NewsItemDto(item.c...t.title, item.c...r.username)
    is NewsItem.NewPost -> NewsItemDto(item.c...t.text, item.c...t.author)
}

sealed class는 제한된 클래스의 계층구조를 만들때 사용한다. 즉, 클래스 외부에 자신을 상속한 클래스를 둘 수 없고, 상속하려면 반드시 Sealed class 내부에 중첩해서 상속해야 한다.


따라서, when식에서 sealed class에 모든 하위 클래스를 처리한다면 else 분기가 필요가 없다. 컴파일러에서 sealed class의 자식클래스가 2개만 있다는 것을 알고 있기 때문이다.

class Post(
    val id: Long? = null,
    val text: String,
    val author: AggregateReference<User, Long>,
    val topic: AggregateReference<Topic, Long>,
    val createdAt: Date = Date(),
    val updatedAt: Date = Date()
) {
    constructor(
        text: String, author: AggregateReference<Uer, Long>,
        topic: AggregateReference<Topic, Long>, createdAt: Date
    ): this(null, text, author, topic, createdAt, createdAt)
    constructor(
        text: String, author: AggregateReference<Uer, Long>,
        topic: AggregateReference<Topic, Long>
    ): this(text, author, topic, Date())
}

이 post에는 3개의 생성자가 생성되어있다. 이를 안전하고 간결하게 만들어보자.


Post(null, "", Ref.to(authorId), Ref.to(topicId), Date(), Date())
Post("", Ref.to(authorId), Ref.to(topicId), Date())
Post("", Ref.to(authorId), Ref.to(topicId))

생성자를 이용해 객체를 생성하는 코드는 다음과 같다.

authorId, topicId는 같은 Long type이기 때문에 문제없이 들어간다. 실수할 여지가 발생한다.

코틀린에서는 Named parameter라고 해서 생성자에 들어가는 값을 명시할 수 있다. 이름을 명시한다면, 추후에 생성자가 변경되었을 때 원하는 속성에 명확하게 바인딩 할 수 있기 때문에 안전하게 관리할 수 있다.

기본인자도 있다. 인자에 기본값을 부여해 굳이 생성자를 overloading하지 않고도 사용할 수 있다.

class Post(
    val id: Long?= null,
    val text: String,
    val author: AggregateReference<User, Long>,
    val topic: AggregateReference<Topic, Long>,
    val createdAt: Date = Date(),
    val updatedAt: Date = Date()
)

Post(
    text="..",
    author=AggregateReference.to(authorId),
    topic = AggregateReference.to(topicId)
)

named parameter와 기본인자를 사용해 생성자를 overloading하지 않고도 함축적으로 사용할 수 있다.

전달되지 않은 나머지 인자 (id, createdAt, updatedAt)은 기본값을 이용해 사용할 수 있다.

즉, 빌더 패턴을 언어레벨에서 사용할 수 있다.



4. 함수타입

코틀린에서는 함수형 인터페이스를 선언하기 위해 인터페이스 앞에 fun이라는 키워드를 붙여야 한다. AuthorMapper는 람다를 통해 간단하게 작성할 수 있다.

함수형 인터페이스란?

1개의 추상 메소드를 갖고 있는 인터페이스

함수형 인터페이스를 사용하는 이유는 자바, 코틀린의 람다식은 함수형 인터페이스로만 접근이 되기 때문이다.


class ForumQueryService(
    val executor: ThreadPoolTaskExecutor,
    val userQueryRepository: UserQueryRepository,
    val topicQueryRepository: TopicQueryRepository,
    val postQueryRepository: PostQueryRepository
) : ForumNewsPublisher, ForumReader {

    override fun loadPosts(topicId: UUID): Posts {
      val topic = topicQueryRepository.findById(topicId)
      val posts = postQueryRepository.findByTopic(topic)
      return Posts(
          TopicDetails.of(
              topic,
              { ref ->
                  userQueryRepository.findById(ref.id!!)
              }
          ),
          posts
      )
    }
fun interface AuthorMapper {
    fun map(ref: AggregateReference<User, Long>): User
}

코틀린에서는 조금 더 함수형에 관련된 feature들을 가지고 있기 때문에 더 쉽게 작성할 수 있다.

코틀린은 함수가 1급시민이기 때문에 함수를 변수로 저장할 수도 있고, 새로운 함수를 내부에 선언할 수도 있다.

  • 1급 시민의 조건
    1. 변수에 담을 수 있다.
    2. 함수(혹은 메소드)의 인자(매개변수, Parameter)로 전달할 수 있다.
    3. 함수(혹은 메소드)의 반환값(return)으로 전달할 수 있다.

class ForumQueryService(
    val executor: ThreadPoolTaskExecutor,
    val userQueryRepository: UserQueryRepository,
    val topicQueryRepository: TopicQueryRepository,
    val postQueryRepository: PostQueryRepository
) : ForumNewsPublisher, ForumReader {

        companion object {
            fun of(
                topic: Topic,
                authorMapper: (ref:AggregateRefefence<User, Long>) -> User
            ):TopicDetails{
                    return TopicDetails(
                        id=topic.id,
                        titkle = topic.title,
                        author = authorMapper.map(topic.author),
                        createdAt = topic.createdAt,
                        updatedAt = topic.updatedAt
                    )
            }
        }
}

Id를 받아서 User로 반환하는 함수를 선언하면, 함수형 인터페이스를 작성하지 않아도 된다


어떤 함수가 다른 함수를 파라미터로 받을 수 있을때 이를 함수타입이라고 부른다.

함수형 인터페이스 없이도 새로운 함수타입을 선언해서 사용할 수 있다. 이 함수는 람다를 이용해 손쉽게 사용할 수 있다.


5. 코딩 컨벤션

코딩 컨벤션을 코틀린 공식 사이트에서 제안을 하고 있다.

이 코딩 컨벤션을 린트와 함께 사용한다면 일관된 방식으로의 코드 작성이 가능하다.

코딩 컨벤션을 통해 유지 보수 관점에서 큰 이득을 취할 수 있다.


참고

  • 당근테크 SEVER 밋업- #살아있다 #자프링외길12년차 #코프링2개월생존기

정확한 정보를 전달하고자 최선을 다하지만, 틀린 부분이 있을 수 있습니다! 
틀린 부분이 있을 시 지적해주시면 감사히 반영하겠습니다😀

'🐤 study > Kotlin' 카테고리의 다른 글

[Kotlin] 코틀린 기초문법(변수, 함수, 흐름제어, 널 안정성, 예외처리, 클래스, 프로퍼티, 상속, 인터페이스, 열거형 클래스)  (3) 2022.12.28
[Kotlin] 코틀린 이해하기 | 코틀린을 배워야 하는 이유 | 자바와 코틀린의 차이점 | 코틀린의 기능✏️[1]  (0) 2022.12.20
    '🐤 study/Kotlin' 카테고리의 다른 글
    • [Kotlin] 코틀린 기초문법(변수, 함수, 흐름제어, 널 안정성, 예외처리, 클래스, 프로퍼티, 상속, 인터페이스, 열거형 클래스)
    • [Kotlin] 코틀린 이해하기 | 코틀린을 배워야 하는 이유 | 자바와 코틀린의 차이점 | 코틀린의 기능✏️[1]
    budtree
    budtree
    개발, 운동, 일상등의 글을 올립니다.

    티스토리툴바