Naver Tech Concert Day-1

06. Android Kotlin을 통한 개발 전략

  • 발표자 : 신동길 (네이버 / 네이버앱개발)
  • 동영상 : https://tv.naver.com/v/4655590/list/272653
  • 슬라이드 : https://www.slideshare.net/NaverEngineering/15android-kotlin
  • 세션설명 : 코틀린을 안드로이드에 적용하기 위해서는 안드로이드에 맞는 모듈의 개발, 거버넌스, 코딩 패턴, 디자인 패턴 등의 전략적 고려 사항이 필요합니다. 네이버앱에 코틀린을 적용한 경험을 토대로, 기존 프로젝트(또는 신규 프로젝트)에 코틀린을 적용할 때 반드시 고려해야 할 사항과 적용하면서 나온 사례를 통해서 최적화된 적용 방법을 공유합니다.

네이버 테크 콘서트 앱는 머티리얼 디자인만 사용하여 개발함.

자바를 컨버팅해 코틀린을 적용한 경우 심플하지 않고 문제도 많았음.

컨버팅시 물음표를 너무 많이 만듬.

코틀린 코드를 자바에서 사용할 경우 코틀린 코드에 붙어야할 부분도 많음.

코틀린을 배워도 자바 스타일로 짜는 경우가 많음. (코드를 너무 줄여서 가독성이 떨어진다는 평들)

코틀린으로도 긴 소스를 유지보수 명목으로 전달받았을 때 사례

  • 정적 호출이 많아 클래스명부터 쓰는 코드들이 많음.
  • 위의 문제로 alias 목적으로 함수 작성하여 사용.

아래 3코드는 동일한 기능임. (적어도 1번째는 피하도록 해보자.)

  • Java
if(LoginManager.getInstance().isLoggedIn() == false) {
    LoginManager.getInstance().loginWithDialog(this, ActivityCode.INAPP_WEBVIEW_BOOKMARK_LOGIN);
}
  • Kotlin
if(naverLoggined == true) {
    naverLogin.loginWithDialog(this, ActivityCode.INAPP_WEBVIEW_BOOKMARK_LOGIN)
}
  • Kotlin (DSL)
isNaverLoggined {
    naverLogin.loginWithDialog(this, ActivityCode.INAPP_WEBVIEW_BOOKMARK_LOGIN)
}

코틀린의 유용한 기술들

  • 람다 (Java 8, Javascript)
  • 오퍼레이터 (C++)
  • 오버로딩 (C++)
  • Method Extension (C#)
  • Property (Object Pascal)
  • DataStream Model (Direct X, java 8)

함수형

함수의 파라미터와 리턴형으로 함수를 표현한 자료형.

모든 함수는 함수형으로 형으로 표현 가능.

함수, 람다, 익명함수, 함수 참조 등을 변수로 선언 & 전달할 때 사용.

val onClickListener = object: View.OnClickListener {
    override fun onClick(view: View?): Unit { }
}
typealias onClick = (View?)->Unit

val runable = object: Runnable() {
    override fun run(): Unit { }
}
typealias aliasRun = ()->Unit

람다 식(Lamda Expression)

함수를 불필요한 부분 생략하고 기호화하여 간결화한 것.

Expression 이므로 함수와 달리 코드 중간에서 값을 리턴하지는 못함.

val buttonClick: onClick = { view -> /* Code */ }
val buttonClick2: onClick = fun(view: View?) { /* Code */ }

연산자 재정의

ExpressionTranslated to
a + ba.plus(b)
a += ba.plusAssing(b)
a()a.invoke()
a == ba?.equals(b) ?: (b == null)
a[i]a.get(i)

프로퍼티

  • 변수에 대한 처리와 변수 값을 묶어 놓은 것
  • 함수는 내부 저장을 안하지만, 프로퍼티는 내부에 저장하는 필드를 두어 중복 동작을 막을 수 있음.
  • 프로퍼티는 작성하는 사람은 복잡하지만 쓰는 사람을 위한 개념.
fun loadNaverAppIcon(): Drawable? { return icon }

val naverAppIcon: Drawable? = null
get() {
    if(field != null) return field
    // Code
    return field
}

자바 코드 변환

예전에는 모든 프로퍼티를 nullable (? 붙여서)로 해주었으나, 이젠 컴파일 에러 나도록 변환해 줌. (개발자가 생각해보고 고쳐야 함.)

멤버 변수의 초기화

  • Optional(?)

    • Null로 초기화하고 나중에 값을 지정

      private var mTitle: TextView? = null
  • lateinit (기존 코드 바꿀때는 정석으로 보임)

    • 초기 값을 지정하지 않고 생략 후 나중에 대입

    • 변수 접근시 초기화 여부를 확인하지 않음

      private lateinit var mTitle: TextView
  • by lazy (delegator)

    • 사용 시점 초기화 됨.

    • 코드 블록으로 초기화 하므로 다른 처리 가능

      private val mTitle: TextView by lazy { findViewById<TextView>(R.id.txt_name) }
  • init{} (Activity나 Fragment는 onCreate 시점에 init을 해야해서 적절하지 않음. View 정도에나 적절할 듯)

    • 초기 값을 지정하지 않고, 이 함수에서 지정 가능

    • 객체의 초기화 시에 값을 지정할 수 있음.

      init {
          mTitle = findViewById(R.id.txt_name)
      }

Nullable 처리

  • Java

    public View getView(int position, View convertView, ViewGroup parent) { }
  • Kotlin

    override fun getView(position: Int, convertView: View, parent: ViewGroup): View { }

위에서 convertView는 null이 가능해 @Nullable를 붙여줘야 컨버팅시 nullable이 가능해짐.

클래스 중첩 최적화

코틀린은 한 파일에 여러 개의 객체 선언 가능하나, 최대한 빼내는게 깔끔함.

opinion 으로 스태틱 쓰는 경우도.

해당 파일에서만 쓸 경우 private 붙임.

internal class Monet {
    fun request(): ImageRequest = ImageRequest()
    inner class ImageRequest { }
}
inner class ImageRequest { }

internal class Monet {
    fun request(): ImageRequest = ImageRequest()
}

Optional(?.) 최적화

  • ? 선언
    • 꼭 필요한 경우만 쓰고 init/late init/lazy 로 피할 수 있으면 피해라
    • !!는 확실히 검증된 경우만 사용
  • let, apply 표준확장 함수
    • 동일 변수에 2개 이상의 ?.를 사용할 때 쓰며, 과용하면 오히려 어려워짐.
  • Elvis(?:)
    • 조건문 내에 쓸 때 생각해볼 것

체인닝을 하는 건 좋지만, 길어지면 해석이 어려워짐.

네이버 앱의 브라우징 제일 큰 클래스의 경우 3800 라인 정도였으나, 코틀린 변경 후 2500 라인으로 줄어들음.

코드 효율화를 위한 툴킷 정의와 활용

  • 패키지 레벨 전역 변수 함수

    • Global Context : Application Context, Handler 등등
    • Systems Managers : ConnectionManager, ActivityManager 등등
    • System Util Functions : Screen info, dp2px 등등
    • 3가지 초기화 방식 (단순변수, 지연 초기화된 변수, Property)
  • 연산자

    • setOnClickListener 등을 plusAssign 연산자 등으로 대체 가능
    • get/setExtra 관련 함수들도 get/set 연산자 등으로 대체 가능
  • 고차함수 정의 (DSL 스타일)

    • Orientation 예제

      val isPortrait: Boolean
      	inline get() = appContext.resources.configuration.orientation == Configureation.ORIENTATION_PORTRAIT
      
      inline fun isPortrait(block:()->Unit) = if(isPortrait == true) block() else Unit
      inline fun isPortrait(blocks:Pair<()->Unit, ()->Unit>) = if(isPortrait == true) blocks.first() else blocks.second()
      
      fun testOrientation() {
          isPortrait{ showToast("Screen is portrait!") }
          
          isPortrait{ 
              showToast("Screen is portrait!") 
          } others {
              showToast("Screen is landscape!") 
          }
      }
    • API Levels 예제

      inline fun sdkRange(sdk: IntRange, block: ()->Unit): Any = if(SDK_INT in sdk) { block() } else Unit
      inline fun sdkFrom(sdk: Int, block: ()->Unit): Any = if(SDK_INT >= sdk) { block() } else Unit
      inline fun sdkTo(sdk: Int, block: ()->Unit): Any = if(SDK_INT <= sdk) { block() } else Unit
      
      const val OS_N = android.os.Build.VERSION_CODES.N
      
      sdkFrom(OS_N) { /* New OS */ }
      sdkRange(JB..KK) { /* Old OS */ }
    • Catch all Exceptions 예제

      var safeReportDebugProc: ((String?)->Unit)? = { }
      inline fun safe(block:()->Unit): Throwable? {
          val result = try {
              block()
              null
          } catch (e:Throwable) {
              e.printStackTrace()
              safeReportDebugProc?.invoke(e.message?:"")
              e
          }
          return result
      }
      
      fun testSafe() {
          safe {
              val file = File("")
              FileInputStream(file).use {
                  it.read()
              }
          }
      }

팀 개발 공동 개발을 위한 거버넌스

  • 너무 다양한 스타일이 존재
  • 개발자의 언어 이해 능력의 차이가 나므로 스타일을 어느 정도 정할 필요 있음.
  • 어떤 스타일의 금지가 아니라 어떤 패턴의 코드에는 어떤 스타일을 우선할 것을 권장하게 정의
  • 시스템 클래스에 대한 함수확장이나 전역변수의 대한 가이드 라인도 어느 정도 필요

Q&A

Q : Kotlin 기본 내장 함수인 let, apply, with, run 등은 비슷하게 사용할 수 있어 보이는데, 규칙 같은걸 정하는지?

A : 각 함수마다 개념적인 정의대로 사용하며, 될 수 있으면 중첩하지 않도록 사용 중. 네이버도 적용 중이라 완벽히 정해지지 않았음.

Q : 확장 함수 사용시 해당 클래스의 메소드인지 확장함수의 메소드인지 구분짓는 규칙이 있는지?

A : interface를 쓰면 됨. Kotlin에서는 interface에 companion이나 함수도 정의 가능하고, class에 다중 상속도 허용되는 구조.

'IT > 행사' 카테고리의 다른 글

[행사] Microsoft Azure Everywhere  (0) 2019.01.11
[행사] Naver Tech Concert Day-1 06  (0) 2019.01.07
[행사] Naver Tech Concert Day-1 04  (0) 2019.01.04
[행사] Naver Tech Concert Day-1 03  (0) 2019.01.04
[행사] Naver Tech Concert Day-1 02  (0) 2019.01.04

+ Recent posts