Skip to content

안드로이드 코드 컨벤션

Choi Woo Seok edited this page Aug 22, 2023 · 1 revision

코드 컨벤션

이 문서는 [PRND 컨벤션 스타일](https://github.com/PRNDcompany/android-style-guide/tree/main)을 기반으로 해서 만들어졌습니다.

• Boolean의 경우 if (a?.b?.isTraded ?: false) 보다는 if (a?.b?.isTraded == true) 와 같은 방식으로 구현한다.****

프로젝트 헷갈리는 네이밍

  • user(x) member(o)
    • eventFriend(x) companion(o)
  • conference(x) event(o)
  • Career(x) Activity(o)

Computed Property VS Function

  • 행위를 나타내는 개념이면 function
  • 상태나 값 등 정보를 가져오는 개념이면 Computed Property

Labmda

  • invoke() 함수는 nullable이 아닐 때에는 생략해서 사용한다.
val foo: () -> Unit
val bar: (() -> Unit)?

foo.invoke() // X
foo() // O
bar?.invoke() // O

Package

  • package 이름은 소문자로 작성한다.

ex) package kr.co.prnd.domain

  • underscore(_)는 사용하지 않는다.
// WRONG!
package kr.co.prnd.domain_module
  • 예외적으로 불가피하게 연결된 단어를 붙여서 사용해야 하는 경우에는 camelCase로 처리한다.

ex) package com.example.myProject

• [Kotlin의 코딩컨벤션](https://kotlinlang.org/docs/coding-conventions.html#naming-rules) 에서는 **camelCase를 허용하고 있고, [안드로이드의 코딩컨벤션](https://developer.android.com/kotlin/style-guide#package_names) 에서는 **항상 소문자로만 작성하도록 나와 있으나 팀 내부의 규칙에 따라 카멜 케이스를 허용할 수 있다

함수 이름

  • ViewModel을 observe()할 때 모아놓는 함수 이름
setupXXX()
  • 서버에서 데이터를 불러올 때 함수 이름
fetchXXX()
  • 서버에 저장할 때 함수 이름
saveXXX()
  • 서버에 업데이트할 때 함수 이름
updateXXX()
  • Return이 있는 데이터를 불러올 때 함수 이름
getXXX()
  • 특정 객체를 찾는 함수 이름
findXXX()
  • 복수형을 가져올 때는 뒤에 s를 붙인다
getBrands()      // O
getBrandList()   // X
  • Raw 값으로부터 enum을 찾을 때 함수 이름은 find()로 한다.
enum class Color {
    RED, BLUE, GREEN;

    fun find(rawColor: String): Color = when (rawColor) {
        "red" -> RED
        "blue" -> BLUE
        "green" -> GREEN
        else -> throw IllegalArgumentException("invalid color: $rawColor")
    }
}

Listener

Lisetner 이름

  • function을 1개만 가진 경우, fun interface OnXXXListener
  • function을 2개 이상 가진 경우, interface XXXListener

on[명사][동사]()

  • publisher가 이벤트만 전달하고 listener가 전적인 책임을 처리할 때
  • 이벤트를 handle하는 주체가 listen하고 있는 곳 일 때
fun onClick()
fun onFocusChange()
fun onScrollChange()
fun onAnimationStart()
fun onTextChange()

Formatting

개행

  • 생성자, 함수에서 Parameter를 정의할 때 한 줄로 정의 가능하면 한 줄, 그렇지 않으면 각 parameter별로 개행한다.

When Statement

  • 한 줄에 들어가는 when 분기는 중괄호를 사용하지 않는다.
when (value) {
    0 -> return
    // ...
}
  • 여러 개의 조건을 동시에 사용하는 경우 >를 포함한 블록은 내려서 작성한다.
when (value) {
    foo -> // ...
    bar,
    baz
    -> return
}

CamelCase

모든 네이밍에 있어 CamelCase로 작성한다

// Right!
XmlHttpRequest
XxxDao

// Wrong!
XMLHTTPRequest
XxxDAO

Parameter 순서

  • Context류의 parameter를 가장 앞에 위치한다. (Context, Activity, Fragment, View등)
  • Callback류의 parameter를 가장 뒤에 위치한다. (XXXListener, XXXCallback, XXXSubject등)

public Car getCar(Context context, int hashId); public void loadCar(Context context, int hashId, CarListener listener);

Key

  • Key-Value로 활용되는 컴포넌트들의 Key는 static final String KEY_XXX로 정의한다.
  • Key로 정의된 이름의 String값은 동일하게 맞춰준다.
  • Deeplink에서 사용되는 Query Parameter의 경우 aaa://bb?key1=value1&key2=value2와 같은 방식으로 전달되기 때문에 query key와 맞춰준다.

public static final String KEY_HASH_ID = "KEY_HASH_ID"; public static final String KEY_TRADE = "KEY_TRADE"; public static final String QUERY_REFERRER = "referrer"; // deeplink

public static final String KEY_HASH_ID = "KEY_HASH_ID";
public static final String KEY_TRADE = "KEY_TRADE";
public static final String QUERY_REFERRER = "referrer"; // deeplink

Key with Activity/Fragment

Activity

  • Activity를 실행할 때에는, 해당 Activity에 startActivity() 를 구현하고 이를 사용한다.
public static void startActivity(Context context, @DrawableRes int photoResourceId) {
  Intent intent = new Intent(context, PhotoDetailActivity.class);
  intent.putExtra(KEY_PHOTO_RESOURCE_ID, photoResourceId);
  context.startActivity(intent);
}

BroadcastReceiver

  • BroadcastReceiver를 실행할 때에는, 해당 Broadcast에 sendBroadcast() 를 구현하고 이를 사용한다.
class XxxBroadcastReceiver: BroadcastReceiver() {
    companion object {
        private const val ACTION = "com.example.examples.XxxBroadcastReceiver"

        fun sendBroadcast(context: Context) {
            context.sendBroadcast(android.content.Intent(ACTION))
        }
    }
}

Fragment

public static UserFragment newInstance(User user) {
	UserFragment fragment = new UserFragment();
	Bundle args = new Bundle();
	args.putParcelable(KEY_USER, user);
	fragment.setArguments(args)
	return fragment;
}

Operator

  • 많은 operator의 연산으로 줄바꿈이 필요한 경우, operator 전에 줄바꿈한다.
int longName = anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne
        + theFinalOne;
  • 연산자의 경우는 줄바꿈이 필요한 위치부터 줄바꿈한다.

Method chain

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

Util/Helper/Manager

  • 특정기능을 수행하거나 상태를 관리하거나 분리되어 동작을 수행하는 클래스에 대한 사용처별 이름을 정의한다.

Util

  • public static void AAA등으로 쓰이는 여러곳에서 사용되는 util성 기능을 보아둔 클래스
  • aa.bb.cc.util 패키지에 모두 모아둔다.
  • 예) DateFormatUtilPixelUtilBitmapUtil

Helper

  • 특정 패키지나 기능에서 한정되어 사용되는 public static void AAA 클래스
  • 공통으로 쓰이지 않고 특정 기능의 코드를 분리하기 위한 용도로 사용한다.
  • 예) NotificationChannelHelper

Manager

  • 항상 내부에서 instance로 만들어서 관리되는 용도
  • 내부적으로 state 혹은 information을 가지고 있어서 호출한 곳에서의 상태에 따라서 관리되는 값을 변경하고 반영하는 작업을 해준다.
  • 예) RegisterStepManagerRegisterCarInfoConfirmManager

Mapper

  • 데이터 소스 레이어와 데이터 레이어 간 Model 변경은 {layer}/mapper/XxxMapper.kt 파일로 관리한다.
fun ProductResponse.toData(): Product {
		...
}

if문

  • if (isChecked == false) 와 같은 코드는 명백한 Lint warning이므로 사용하지 않는다.
if (isUnchekced)
  • 조건문에 !를 넣는 것 대신 아래와 같은 규칙으로 코드를 작성한다.
  1. 조건문에서 체크되는 boolean 변수/함수는 항상 긍정문으로 작성한다. if(isChecked())
  2. 이미 부정문으로 작성되어 있는 함수가 있다면 이를 재사용하여 함수를 작성한다. if(isUnchekced())
private boolean isUnchekced(){
    return !isChecked();
}

Try / Catch

  • try/catch를 사용하지 않고, sealed class로 결과를 감싸서 관리, 반환한다.
  • 그렇지 않을 경우, nullable한 값을 반환한다.

import

enum class Color {
	RED, ORANGE, GREEN;
}

val color = Color.RED // O
val color = RED // X

Gradle

Dependencies

  • 라이브러리를 추가할때 꼭 외부 라이브러리의 라이센스 고지를 추가한다.
  • 라이브러리의 버전은 +로 적지 않고 명시한다.

implementation 'gun0912.ted:tedpermission-rx2:2.2.0' <- O //implementation 'gun0912.ted:tedpermission-rx2:2.+' <- X

IDE

아래 사항들을 제외한 이외의 설정들은 자율에 맡긴다.

Trailing comma

불필요한 code change를 줄이기 위해 trailing comma를 사용한다. 예를 들면, trailing comma를 쓰지 않은 클래스에 새로운 필드를 추가하려면 관련없는 위쪽 라인에 ,를 추가해주어야 하는데 이러한 일을 피하기 위함이다.

설정 방법

  • Preferences | Editor | Code Style | Kotlin | Other
  • Use trailing comma 체크
// 설정 전
data class User(
    val name: String,
    val email: String
)

// 설정 후
data class User(
    val name: String,
    val email: String,
)

XML 컨벤션

스크린샷 2023-07-10 오후 3.14.17.png

https://week-year.tistory.com/135

Color

  • Color 명시 → 디자이너가 정해준 이름 → snake case 로 명명

Drawables

  • 아이콘 : ic_
  • 이미지 : img_
  • 배경 : bg_

IDS

  • WHAT은 축약해서 작성한다. ex) tv_login_logo
    • textView → tv
    • imageView → iv
    • fragmentLayout → fl
    • viewPager → vp
    • editText → et
    • recyclerView → rv
    • bottomNavigationView → bnv
    • linearLayout → ll

패키지 구조

  • data

    • 리포지토리에서 반환하는 타입에 따라 패키지를 나눔
    • 반환하는 타입에서는 dto패키지 말고 다른 패키지를 안만듦

    Untitled

  • presentation

    • ui
      • login
        • uistate(패키지)
          • UserUiState
          • XxxUiState
        • viewmodel
        • activity
      • main
        • mypage
        • friends
        • 명함
    • util
      • 일반적인 유틸리티성 클래스가 아닌, BindingAdapter와 같은 공통으로 사용되는 기타 클래스를 관리한다.