[Kotlin] 코딩 컨벤션 정리

Leopold Baik (백중원)
Apr 1 · 9 min read
Photo by Petr Suma on Unsplash

코딩 컨벤션은 코드의 가독성을 증진시키고 여러 명이 협업하는 과정에서 일관된 코드 스타일을 유지하는 데 있어서 중요하다. 따라서 코딩 컨벤션을 잘 지키며 코드를 작성하는 것은 읽기 좋은 코드를 작성하는 첫걸음이 될 것이다. Kotlin은 대부분 Java의 코딩 컨벤션 규약을 따른다. 하지만 Kotlin 언어만의 특성이 있으므로 Kotlin에서 제공하는 코딩 컨벤션 규약을 살펴보며 올바른 Kotlin 코드 개발을 할 수 있도록 머릿속에 담아두자. 사실 IDE에서 가이드도 해주고 CTRL + ALT + L만 누르면 어느 정도 예쁘게 다듬어 주긴 하지만 그래도 직접 신경 써서 작성해보는 게 좋다고 생각한다.

본 포스팅에서 소개하는 내용은 필자의 주관적인 스타일은 아니고 Kotlin 공식 홈페이지에서 제공하는 가이드를 추린 것이다. 모든 내용을 담기엔 양도 많고 이미 Java에서 익숙한 내용들도 있고 해서 많은 부분 생략하였다.

만약 모든 내용에 대해서 살펴보고 싶다면 Kotlin 공식 홈페이지 또는 아래 필자가 정리한 페이지를 참고하면 된다. 정리한 내용이 깔끔하지 않아 영어가 익숙하신 분들은 Kotlin 공식 홈페이지를 참고하는 것이 더 좋을 것 같다.

본 포스팅에서 다루고자 하는 코딩 컨벤션 목록은 다음과 같다.

  • 클래스 레이아웃
  • 콜론
  • 클래스 헤더 형식
  • 제어자
  • 함수 형식
  • 제어문 형식
  • 메소드 호출 형식
  • 람다 형식
  • 주석
  • 불변성
  • 기본 인자 값
  • 람다에서의 return
  • 조건문 사용
  • if VS when
  • 루프 사용
  • 함수 vs 프로퍼티
  • infix 함수 사용
  • 팩토리 함수
  • 라이브러리 작성

클래스 레이아웃

  • 일반적으로 클래스의 내용은 다음의 순서로 정렬된다.

1. Property declarations and initializer blocks

2. Secondary constructors

3. Method declarations

4. Companion object

  • 메소드 선언을 알파벳순 또는 가시성으로 정렬하지 말고 일반 메소드와 확장 메소드의 구분을 하지 마라.
  • 대신, 관련된 것들을 한데 모아 클래스를 읽는 사람이 위에서 아래로 무슨 일이 일어나고 있는지 로직을 따라갈 수 있도록 해라.
  • 중첩 클래스들은 해당 클래스를 사용하는 코드 다음에 위치시킨다.
  • 중첩 클래스들이 외부에서 사용되도록 의도되었고 클래스 내부에서 참조되지 않는다면 companion object 다음의 마지막에 위치시켜라.

콜론

  • 아래와 같은 경우에 콜론 : 앞에 공백을 넣어라.

- 타입과 슈퍼 타입을 분리할 때

- 동일한 클래스의 다른 생성자 또는 슈퍼 클래스의 생성자에게 위임할 때

- object 키워드 다음에

  • 변수 선언과 그 타입을 분리할 때 콜론 : 앞에 공백을 두지 않는다.
  • 콜론 : 뒤에는 항상 공백을 둔다.

클래스 헤더 형식

  • 파라미터가 몇 개 없는 기본 생성자의 경우 한 줄로 작성할 수 있다.
  • 헤더가 긴 기본 생성자의 경우 들여쓰기와 함께 라인으로 구분한다. 또한 닫는 소괄호 )는 새로운 라인에 있어야 하고, 만약 상속을 사용하는 경우 슈퍼 클래스의 생성자 호출 및 인터페이스 구현은 닫는 소괄호 )와 동일한 라인에 있어야 한다.
  • 다중 인터페이스를 구현하는 경우 슈퍼 클래스의 생성자를 먼저 닫는 소괄호 )에 같은 라인으로 위치시키고 각 인터페이스는 각각 다른 라인으로 위치해야 한다.
  • 긴 슈퍼 타입 목록을 가지는 클래스의 경우 콜론 : 다음에 각 타입별로 줄 바꿈 처리하고 가로 정렬을 맞춘다.
  • 클래스 헤더가 길 경우 클래스 헤더와 몸체를 명확히 구분하려면 위의 예와 같이 클래스 헤더 다음에 빈 라인을 두거나 아래와 같이 여는 중괄호 {를 별도의 라인으로 둔다.

제어자

  • 여러 개의 제어자를 가질 경우 아래의 순서를 따른다.

함수 형식

  • 함수의 시그니처를 한 줄로 표현하기 알맞지 않다면 아래와 같은 구문을 따른다.
  • 파라미터는 4칸 공백으로 들여쓰기 한다.
  • 함수가 단일 표현식으로 구성되어있을 경우 {}return을 제거하고 =로 표현하는 방식이 좋다.

주관적인 생각으로는 타입 추론에 의해 타입을 생략하는 경우도 있지만 외부에 제공되는 API 등의 반환 타입의 경우 타입을 명시하는 것이 좋다고 생각한다. 반환 값의 구조가 복잡해질수록 코드를 읽는 사람의 두뇌에 불필요한 연산을 가하기 때문에 상황에 따라 적절히 생략하는 것이 좋은 것 같다.

제어문 형식

  • ifwhen과 같은 조건식이 멀티라인일 경우 항상 여는 중괄호 {를 실행 구문에 가깝게 둔다.
  • 각 조건식 라인은 4칸 공백으로 들여쓰기 한다.
  • when 제어문에서 분기문이 한 줄 이상일 경우 인접한 case 블록과 빈 라인으로 구분하는 것을 고려하라.
  • 짧은 분기문의 경우 중괄호 없이 조건과 동일한 라인에 둔다.

메소드 호출 형식

  • 긴 인자 목록을 가지는 메소드를 호출할 때, 여는 소괄호 다음 줄바꿈을 한 뒤 인자들을 4칸 공백으로 들여쓰기 한다.
  • 밀접하게 관련된 인자끼리 그룹핑한다.
  • 인자의 이름과 값을 구분하는 = 주변에 공백을 둔다.

람다 형식

  • 람다 표현식에서 중괄호 {} 주변에는 공백을 두어야 한다.
  • 파라미터를 나타내는 화살표 -> 주변에도 공백을 두어야 한다.
  • 호출에 사용된 람다가 한 개인 경우 가능한 한 소괄호 ()를 제거해야 한다.
  • 만약 람다에 label을 할당한다면 label과 여는 중괄호 { 사이에 공백을 두지 않는다.
  • 다중 라인 람다에서 파라미터의 이름을 선언할 때, 첫 번째 줄에 파라미터 이름과 화살표 ->를 선언하고 개행처리한다.
  • 만약 파라미터 목록이 한 줄로 표현하기 적합하지 않다면 파라미터 목록과 화살표 ->를 들여쓰기와 함께 개행처리 한다.

주석

  • 일반적으로 @param@return 태그는 사용하지 마라.
  • 대신, 파라미터 및 반환 값에 대한 설명을 주석 내용에 직접 포함시키고, 언급된 모든 파라미터에 링크를 추가한다.
  • 주석 내용의 흐름에 맞지 않는 장황한 설명이 필요한 경우에만 @param@return을 사용해라.

불변성

  • Immutable 컬렉션을 선언할 때는 항상 Collection, List, Set, Map 과 같은 인터페이스를 사용해라.
  • 컬렉션 인스턴스를 생성할 때 가능한 Immutable 컬렉션 타입으로 생성해라.
  • 기본적으로 Kotlin에서 List는 Read-only

기본 인자 값

  • 오버로딩을 선언하기 위해 기본 인자 값을 가지는 함수를 선언하는 것이 좋다.

람다에서의 return

  • 람다 안에서 label 이 붙은 return을 여러 개 사용하는 것을 피해라.
  • 하나의 탈출 포인트를 갖도록 구조를 바꾸는 것을 고려해라.
  • 만약 그것이 불가능하거나 충분히 명확하지 않다면, 람다를 익명 함수로 변환하는 것을 고려해라.
  • 람다의 마지막 문장에서 label이 붙은 return을 사용하지 마라.

조건문 사용

  • try, if, when은 아래와 같은 표현식을 사용하는 것이 좋다.
  • 아래 코드보다 위의 코드가 더 낫다.

if vs when

  • 조건이 2개라면 when 보다 if를 사용해라.
  • 위의 코드보다 if (x == null) … else … 가 낫다.
  • when은 조건이 3개 이상일 때나 더 많은 선택지가 있을 때 사용해라.

루프 사용

  • 루프에는 filter, map 과 같은 고차 함수를 사용하는 것이 좋다.
  • 예외적으로 forEach의 경우 forEach의 리시버가 Nullable 이거나 forEach가 긴 호출 체인의 일부로 사용되는 경우가 아니라면 일반적인 for 루프가 더 좋다.
  • 복수의 고차 함수를 이용한 복잡한 표현과 루프를 선택할 때에는 각 상황에서 실행되는 operation들의 비용을 이해해야 하고 성능 고려 사항들을 유념해라.

함수 vs 프로퍼티

  • 몇몇 경우에 인자 없는 함수는 Read-only 프로퍼티로 교체될 수 있다.
  • 의미론적으로는 비슷할지라도 언제 어느 것을 더 선호할 것인가에 대한 몇몇 양식적인 관습들이 있다.
  • 아래 알고리즘의 경우 함수보다 프로퍼티가 좋다.

- throw를 하지 않음

- 계산이 저렴하거나 한 번 실행 후 캐시 됨

- 객체의 상태가 변경되지 않은 경우 호출에 대해 동일한 결과를 반환

infix 함수 사용

  • 유사한 역할을 수행하는 두 객체에서 동작할 때만 infix로 선언해라.
  • 좋은 예제 : and, to, zip
  • 나쁜 예제 : add
  • 리시버 객체를 변형시키는 경우 메소드를 infix로 선언하지 마라.

팩토리 함수

  • 클래스의 팩토리 함수를 선언한다면 클래스와 동일한 이름을 피해라.
  • 팩토리 함수의 동작이 왜 특별한지 명확하게 하기 위해 고유한 이름을 사용해라.
  • 정말 특별한 의미가 없는 경우에만 클래스와 동일한 이름을 사용할 수 있다.
  • 만약 여러 개의 오버로딩된 생성자를 가진 객체가 서로 다른 슈퍼 클래스 생성자를 호출하지 않고 기본 인자 값을 가진 단일 생성자로 축소할 수 없는 경우 오버로딩된 생성자를 팩토리 함수로 교체하는 것이 좋다.

라이브러리 작성

  • 라이브러리를 작성할 때 API의 안전성을 보장하기 위해 아래의 추가적인 규칙들을 따르는 것이 좋다.

- 항상 멤버의 가시성을 명시적으로 지정해라(의도치 않게 public API로 선언되어 노출되지 않도록)

- 항상 함수의 반환 타입과 속성 타입을 명시적으로 지정해라(구현 변경 시 의도치 않게 반환 타입이 변경되지 않도록)

- 새로운 주석이 필요 없는 override 된 것들을 제외하고 모든 public 멤버에 대한 주석을 제공해라 (라이브러리 문서 생성을 지원하기 위해)

마치며

Kotlin으로 코드를 작성하면서 어떻게 작성해야 읽기 편할까 고민하다가 Kotlin에서 가이드하고 있는 코딩 컨벤션 가이드를 살펴봤다. 기본적으로 Java의 코딩 컨벤션을 따르기 때문에 이미 올바르게 사용하고 있는 내용들이 많았지만 그렇지 않은 경우도 많았던 것 같다. 그래도 이왕 Kotlin으로 코드를 작성한다면 Kotlin의 코딩 컨벤션을 잘 따르는 것이 좋을 것 같다.

Leopold Baik (백중원)

Written by

사람들에게 사랑받는 서비스를 만드는 게 목표이자 독서와 개발이 취미인 평범한 개발자. 소소한 내용들을 가끔씩 공유하고 있습니다.