Kotlin 공식 문서 살펴보기 (4) - Coding conventions 2

주석

  • 긴 주석은 /** 로 시작해서 각 줄마다 *로 시작

      /**
        * 여러 줄에 걸친
        * 문서 주석 예제입니다.
        */
    
  • 짧은 주석은 한 줄 가능

      /** 짧은 문서 주석 */
    
  • 보통 @param, @return 태그를 사용 지양

  • 매개변수와 반환 값 설명을 주석에 직접 설명

  • 매개변수가 언급될 때마다 링크 추가

  • @param, @return은 본문 흐름에 맞지 않는 긴 설명이 필요할 때만 사용

      // 아래 방법은 지양하도록 한다.
        
      /**
        * Returns the absolute value of the given number.
        * @param number The number to return the absolute value for.
        * @return The absolute value.
        */
      fun abs(number: Int) { ... }
        
      // 아래 방법을 사용하도록 한다.
        
      /**
       * Returns the absolute value of the given [number].
       */
      fun abs(number: Int) { ... }
    

중복 구성 피하기

Unit 반환 타입

  • 함수가 Unit 을 반환할 경우, 반환 타입은 반드시 생략

      fun foo() { // Unit 이 생략됨
          ...
      }
    

세미콜론

  • 가능하면 세미콜론 생략

문자열 템플릿

  • 단순한 변수를 넣을 때 중괄호 사용 지양

  • 중괄호는 오직 긴 표현식을 쓸 때만 사용

      println("$name hjas ${children.size} children")
    

관용적 사용

불변성

  • 변경 가능한 데이터 사용보다 변경 불가능한 데이터 사용 선호

  • 초기화 후에 수정되지 않는다면, 항상 지역 변수나 속성은 val로 선언

  • 변경되지 않는 콜렉션을 선언할 때는 항상 변경 불가능한 콜렉션 인터페이스(Collection, List, Set, Map) 사용

  • 콜렉션 인스턴스를 생성하기 위해 팩토리 함수를 사용할 때는 가능하면 변경할 수 없는 콜렉션을 반환하도록 함

      // Bad
      fun validateValue(actualValue: String, allowedValues: HashSet<String>) { ... }
        
      // Good
      fun validateValue(actualValue: String, allowedValues: Set<String>) { ... }
        
      // Bad
      val allowedValues = arrayListOf("a", "b", "c")
        
      // Good
      val allowedValues = listOf("a", "b", "c")
    

매개변수 기본값

  • 오버로드된 함수를 선언하는 것보다 매개변수 기본값이 선언된 함수를 선언하는 것 선호

      // Bad
      fun foo() = foo("a")
      fun foo(a: String) { ... }
        
      // Good
      fun foo(a: String = "a") { ... }
    

Type aliases

  • 함수형 타입 또는 여러 번 사용되는 타입 매개변수가 있는 타입이 있을 경우, 타입 별명 정의

      typealias MouseClickHandler = (Any, MouseEvent) -> Unit
      typealias PersonIndex = Map<String, Person>
    
  • 이름 충돌을 막기 위해 private 또는 내부 타입 별명을 사용할 때는 패키지와 import에서 언급된 import ... as ... 선호

람다 매개변수

  • 짧고 중첩되지 않은 람다에서는 it 사용 권장
  • 매개변수가 있는 중첩 람다에서는 항상 매개변수 명시적 선언

명명된 인자

  • 메서드에서 같은 기본형의 여러 매개변수가 있거나 Boolean 타입 매개변수가 있을 때는 이름을 가진 인자 사용

      drawSquare(x = 10, y = 10, width = 100, height = 100, fill = true)
    

조건문

  • try, if, when 의 표현 형식 사용 선호

      if (x)
      	return foo()
      else
      	return bar()
        
      // Preferred
      return if (x) foo() else bar()
    
      when (x) {
          0 -> return "zero"
          else -> return "nonzero"
      }
        
        
      // Preferred
      return when(x) {
          0 -> "zero"
          else -> "nonzero"
      }
    

if vs. when

  • 이진 조건에서는 when 대신 if 사용

      if (x == null) ... else ...
    
  • 3개 이상의 조건일 있을 때는 when

      when (x) {
          1 -> ...
          2 -> ...
          3 -> ...
          else -> ...
      }
    

조건에서 null일 수 있는 Boolean 값

  • null일 수 있는 Boolean 값을 조건문에서 사용할 때는 if (value == true) 또는 if (value == false) 검사 사용

루프

  • 루프보다 고차원 함수(filter, map 등) 사용 선호
  • 예외
    • forEach
    • 일반적인 for 루프 선호
    • forEach 수신자가 nullable 하거나 긴 호출 체인의 일부일 때 제외

범위에서 루프

  • 열린 범위에서는 until 사용

      for (i in 0..n - 1) { .. } // bad
      for (i in 0 until n) { .. } // good
    

문자열

  • 문자열 연결보다 문자열 템플릿 선호

  • \n 를 일반 문자열 리터럴에 포함하는 것보다 여러 줄 문자열 선호

  • 결과 문자열에 내부 들여쓰기가 필요하지 않을 경우, 여러 줄의 들여쓰기를 유지할 때 trimIndent 사용

  • 내부 들여쓰기가 필요할 때는 trimMargin 사용

      println("""
      	Not
      	trimmed
      	text
      	"""
             )
        
      println("""
      	Trimmed
      	Text
      	""".trimIndent()
             )
        
      println()
        
      val a = """Trimmed to margin text:
      		  |if(a > 1) {
      		  |    return a
      		  |}""".trimMargin()
        
      println(a)
    
      // 출력 결과
        
          Not
          trimmed
          text
            
      Trimmed
      text
        
      Trimmed to margin text:
      if(a > 1) {
      	return a
      }
    

함수 vs. 속성

  • 어떤 경우에는 매개변수가 없는 함수는 읽기 전용 속성으로 교체 가능
  • 함수보다 속성을 선호하는 경우
    • throw 하지 않음
    • 계산 비용이 저렴하거나 처음 실항할 때 캐싱됨
    • 객체 상태가 바뀌지 않을 경우 호출할 때 동일한 결과 반환

중위 함수

  • 비슷한 역할을 하는 두 객체에서 작동할 때만 infix 함수 선언
    • 좋은 예: and, to, zip
    • 나쁜 예: add
  • 수신자 객체를 변경한다면 infix로 선언하지 않도록 함

팩토리 함수

  • 클래스를 위해 팩토리 함수를 선언할 때 클래스와 같은 이름을 쓰지 않도록 함

  • 팩토리 함수 동작이 특별한 이유를 명확히 하는 고유한 이름 사용

  • 특별한 의미가 없을 때만 클래스와 같은 이름 사용 가능

      class Point(val x: Double, val y: Double) {
          companion object {
              fun fromPolar(angle: Double, radius: Double) = Point(...)
          }
      }
    
  • 다른 슈퍼클래스 생성자를 호출하지 않고 여러 오버로드된 생성자가 있는 클래스가 있고 기본 인자 값이 있는 하나의 생성자로 줄일 수 없는 경우에는 오버로드된 생성자를 팩토리 함수로 바꾸는 것이 좋음

플랫폼 타입

  • 플랫폼 타입의 표현식을 반환하는 public 함수나 메서드는 kotlin 타입을 명시적으로 선언해야 함

      fun apiCall(): String = MyJavaApi.getProperty("name")
    
  • 플랫폼 타입의 표현식으로 초기화되는 모든 패키지 레벨 또는 클래스 레벨의 속성은 kotlin 타입을 명시적으로 선언해야 함

      class Person {
          val name: String = MyJavaApi.getProperty("name")
      }
    
  • 플랫폼 타입의 표현식으로 초기화된 로컬 값은 타입 선언이 있을수도 있고 없을수도 있음

      fun main() {
          val name = MyJavaApi.getProperty("name")
          println(name)
      }
    

스코프 함수

  • 주어진 객체의 컨텍스트에서 코드 블록을 실행하는 함수
  • let, run, with, apply, also