Skip to content

✌ 제이콥과 브라우니의 안드로이드 컨벤션 ✌

JinHo Jeong edited this page Feb 2, 2025 · 3 revisions

1. Android Tech Stack

  • Kotlin 2.0.0v, Kotlin Coroutines, Flow
  • MVI + Orbit
  • Gradle Convention Plugin(build-logic), VersionCatalogs
  • Dagger Hilt (KSP)
  • Compose Navigation
  • Firebase Analytics, Crashlytics, App Distribution
  • Ktor + Kotlin Serialization
  • DataStore Preference
  • Material3, Lottie, Coil
  • KtLint, DeteKt

2. Naming

2-1. Model Class

  • Data 계층 중 통신 모델 클래스는 Dto로 끝나고, 내부에 저장하는 모델 클래스는 Entity로 끝나야 한다.
  • Domain 계층의 모델 클래스는 객체 이름 그대로 사용한다.
  • UI 계층의 모델클래스는 UiModel로 끝나야 한다.

2-2. Package

  • package 이름은 소문자로 작성합니다.
  • 하지만 꼭 여러 단어로 된 이름을 사용해야 한다면 camelcase로 정의하며, 모듈 네이밍의 경우 대시-를 사용합니다.

2-3. Mapper

  • 출발지 Model에서 Extension을 통해 구현한다.
  • 이름은 to + [도착지 모델]으로 구성한다.
// Do
class UserResponse {
   fun toUser(): User = User(...)
}

2-4. UseCase

  • UseCase는 [현재 동사] + [명사/대상(선택사항)] + UseCase로 작성한다.
  • UseCase 클래스는 단 하나의 invoke UseCase 함수를 가진다.
  • UseCase는 Functional Naming Rule을 사용한다.
class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository
) { 
	fun invoke() {...}
 }

2-5. Function

  • 함수 이름은 camelCase로 작성하며 일반적으로 동사 또는 동사구입니다.
  • PascalCase, underscore(_)는 사용하지 않습니다.
  • 단 Composable 함수는 class와 같은 이름 형식을 사용한다.

2-6. Constant

  • 상수 이름은 UPPER_SNAKE_CASE로 작성하며 일반적으로 명사 또는 명사구입니다.
  • 상수 값은 object 내부 또는 최상위 선언으로만 정의할 수 있습니다. 상수의 요구사항을 충족하지만 class 의 내부에 정의된 값은 상수가 아닌 이름을 사용해야 합니다.
  • 상수는 const 를 사용해야 합니다.
const val NUMBER = 5
val NAMES = listOf("Alice", "Bob")
val AGES = mapOf("Alice" to 35, "Bob" to 32)
val COMMA_JOINER = Joiner.on(',') // Joiner is immutable
val EMPTY_ARRAY = arrayOf()

2-7. Non-Constant

  • 상수가 아닌 이름은 camelCase로 작성하며 일반적으로 명사 또는 명사구입니다.
val variable = "var"
val nonConstScalar = "non-const"
val mutableCollection: MutableSet = HashSet()
val mutableElements = listOf(mutableInstance)

2-8. Backing properties

  • 개념적으로 동일한 두 개의 속성이 있지만 하나는 공용 API의 일부이고 다른 하나는 구현 세부 정보인 경우 접두사로 underscore(_)를 사용합니다.
private val _elementList = mutableListOf<Element>()

val elementList: List<Element>
      get() = _elementList

2-9. Composable Function (return type Unit)

  • Composable 함수의 이름은 첫글짜가 대문자인 PascalCase로 사용한다.
  • 동사로 시작하면 안되고 무조건 명사로만 이루어져야 한다
// DO
@Composable
fun FancyButton(text: String, onClick: () -> Unit) {

@Composable
fun BackButtonHandler(onBackPressed: () -> Unit) {

// DON'T
@Composable
fun fancyButton(text: String, onClick: () -> Unit) {

@Composable
fun RenderFancyButton(text: String, onClick: () -> Unit) {

2-10. Composable Function (other return type)

  • 반환타입이 있을시 일반 함수와 같은 이름 형식을 갖는다.
// DO
@Composable
fun defaultStyle(): Style {

// DON'T
@Composable
fun Style(): Style {

2-11. Composable Function (remember return value)

  • 반환 값을 remember하는 경우 이 이름 컨벤션을 따라간다
// DO
@Composable
fun rememberCoroutineScope(): CoroutineScope {

2-12. Composition Local

  • CompositionLocal이 들어가면 안되고 Local를 접미사로 사용하면 안된다
// DO
val LocalTheme = staticCompositionLocalOf<Theme>()

// DON'T
val ThemeLocal = staticCompositionLocalOf<Theme>()

3. Formating

3-1. 빈 블록

  • 빈 블록 또는 블록 형식 구문은 K&R 스타일이어야 합니다.
try {
    doSomething()
} catch (e: Exception) {} // WRONG!

try {
    doSomething()
} catch (e: Exception) {
} // Okay

3-2. if/else

  • 표현식으로 사용되는 if/else 조건문에서는 전체 표현식이 한 줄에 들어가는 경우에만 중괄호를 생략할 수 있습니다.
val value = if (string.isEmpty()) 0 else 1  // Okay

val value = if (string.isEmpty())  // WRONG!
    0
else
    1
  • else if 사용을 지양하고, when으로 변환하는 것을 지향한다.
  • else의 사용을 지양하고, if + return으로 치환하는 것을 지향한다.

3-3. Function

  • 함수 서명이 한 줄에 들어가지 않으면 각 매개변수 선언을 한 줄에 하나씩 표시합니다.
  • 이 형식으로 정의된 매개변수에서는 단일 들여쓰기를 사용해야 합니다. 닫는 괄호 및 반환 유형은 추가 들여쓰기 없이 한 줄에 하나씩 입력됩니다.
fun <T> Iterable<T>.joinToString(
    separator: CharSequence = ", ",
    prefix: CharSequence = "",
    postfix: CharSequence = ""
): String {
    //
}

3-4. 연산자

  • 한 줄에 들어가지 않을 경우 연산자 뒤에서 줄바꿈하고 들여쓰기를 사용합니다.
private val defaultCharset: Charset? =
    EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)

3-5. 클래스 형식

  • 각 클래스는 다음과 같은 순서를 따른다.
    1. 속성 선언과 초기화 블록
    2. 보조 생성자
    3. 메서드 선언
    4. Companion object
class MyClass {

    // 속성 선언
    private val property1: String

    // 초기화 블록
    init {
        property1 = "Hello"
        property2 = 42
    }

    // 보조 생성자
    constructor(param1: String) { ... }
    
    // 메소드 선언
    fun method1() { ... }

    // Companion object
    companion object { ... }

    // Nested classes
    class NestedClass { ... }

}

3-6. Method Chain

  • Builder등 여러 함수를 chaining으로 사용하면서 줄바꿈이 필요한 경우, .전에 줄바꿈한다.
ImageLoader
        .load(user.getProfileUrl())
        .placeholder(R.drawable.img_user_placeholder)
        .fitCenter()
        .into(binding.ivUser)

3-7. Parameter 순서

  1. Default값이 없는 parameter
  2. Modifier
  3. Default값이 있는 parameter
  4. Lambda
fun Test(
	val text: String,
	val modifier: Modifier = Modifier,
	val minLine: Int = 1,
	val content: @Composable () -> Unit
)

3-8. State를 넘기지 않기

  • Composable에 State를 그대로 넘기지 않고 필요할시 이렇게 넘기기.
//DO
@Composable
fun Main() {
	val (text, onTextChange) = remember {
		mutableStateOf("")
	}
	
	Test(text, onTextChange)
}

@Composable
fun Test(
	text: String,
	onTextChange: (String) -> Unit
) {}

// DON'T
@Composable
fun Main() {
	val text = remember {
		mutableStateOf("")
	}
	
	Test(text)
}

@Composable
fun Test(
	text: State<String>
) {}

3-9. State Hoisting

  • 여러개 State를 넘길때는 class로 묶어서 넘기기.
// Before
@Composable
fun VerticalScroller(
    scrollPosition: Int,
    scrollRange: Int,
    onScrollPositionChange: (Int) -> Unit,
    onScrollRangeChange: (Int) -> Unit
) { }

// After
@Stable
interface VerticalScrollerState {
    var scrollPosition: Int
    var scrollRange: Int
}

@Composable
fun VerticalScroller(
    verticalScrollerState: VerticalScrollerState
) {

4. 그외 지켜야할 규칙

4-1. 유틸 클래스

  • 유틸 클래스는 사용하는 클래스가 3개이상일 때 사용을 고려한다.
  • 그 이전까지는 사용 클래스 내에서 Private 함수로 Extension을 만들어 사용한다.

4-2. 클래스 라인

  • 1줄에 100자를 넘지 않도록 작성한다.
  • 코드간의 간격은 2줄이상 간격이 발생하지 않도록 한다.(최대 1줄 줄바꿈)
  • 파라미터 개수와 상관없이 100자 이상이면 개행한다.

4-3. 파라미터

  • 파라미터가 2개 이상이면 이름을 명시한다.
private fun main() {
    println(Hello(name = "jaycob", text = "world"))
}

4-4. Stable Class

@Immutable와 @Stable를 통해 recomposition 횟수를 줄이기

Reference


Compose

Android

Kotlin

Architecture