├── Android Component
├── Activity
│ ├── enableEdgeToEdge를 통해 확장된 화면을 제공하자.md
│ └── 액티비티 launchMode로 singleTask나 singleInstance를 지정하는 것을 지양하자.md
├── CoordinatorLayout
│ └── BottomSheetBehavior를 통해 적절한 CoordinatorLayout 자식 뷰 상태를 지정하자.md
├── Intent
│ └── 인텐트에 액션과 데이터 스키마를 지정하여 다른 앱에서의 처리를 구현하자.md
├── RecyclerView를 구현할 때 Selection 라이브러리 사용을 지양하자.md
├── TextField
│ └── Compose TextField의 비동기 입력을 구현하는 다양한 방법을 구분하자.md
├── URLEncoder
│ └── URLEncoder를 통해 HTML 형식으로 인코딩하자.md
├── View
│ └── 안드로이드 View의 생명주기를 이해하자.md
├── ViewModel
│ └── Extra를 전달 받을 때 SavedStateHandle을 활용하자.md
├── WebView
│ ├── WebView에 domStorageEnable을 설정하자.md
│ ├── pauseTimers()와 resumeTimers()를 통해 WebView의 리소스를 관리하자.md
│ ├── 변경이나 오류가 자주 발생하는 화면은 웹뷰로 구현하자.md
│ └── 웹뷰의 shouldOverrideUrlLoading()을 통해 웹 페이지 또는 intent를 처리하자.md
└── isTaskRoot를 통해 첫 번째 액티비티인지 확인하자.md
├── Android Library
├── Analytics Provider Library
│ ├── 각 애널리틱스 라이브러리의 특성을 이해하자.md
│ └── 애널리틱스 이벤트나 라이브러리의 추가 및 제거에 대한 리소스를 최소화하자.md
├── CRM
│ └── CRM 툴을 통해 개인화된 마케팅 기능을 자동화하자.md
├── Glide
│ └── Glide의 onResourceReady()를 통해 load가 종료된 시점에 동작을 처리하자.md
├── Kakao SDK Library
│ └── 카카오 로그인 여부를 확인할 때 토큰 유효성을 확인하자.md
├── Lottie
│ └── Lottie 사용 시 애니메이션 비활성화 및 속도 조절 설정을 고려하자.md
├── OkHttp
│ └── Interceptor를 통해 네트워크 UserAgent를 설정하자.md
├── Orbit
│ └── Orbit으로 State와 SideEffect를 관리하자.md
└── 외부 라이브러리를 사용할 때 추상화가 되어있는 영역을 파악하자.md
├── Android Studio
├── .editorconfig 파일을 통해 ktlint 스타일을 커스텀하자.md
├── Clean Project를 통해 수정사항을 확실하게 빌드 시키자.md
├── Gradle
│ └── Gradle Type-Safe Project Accessors를 통해 멀티 모듈 의존성을 안전하게 작성하자.md
├── Kotlin Decompiler로 디컴파일된 Java 코드를 확인하자.md
├── SDK의 설정과 AndroidManifest.xml의 속성이 충돌하는 경우에 tools:replace 속성을 활용하자.md
├── local.properties와 gradle.properties 파일을 구분하여 활용하자.md
└── 안드로이드의 메모리 누수 탐지 도구를 활용하자.md
├── Android Tool
└── R8을 통해 효율적으로 앱을 최적화하자.md
├── Architecture
├── Android App Architecture
│ ├── Analytics 로직은 DataSource나 Repository로 분리하지 말자.md
│ └── Repository 또는 DataSource에서 앱 실행 중 캐시가 필요한 데이터를 저장하자.md
└── Clean Architecture
│ ├── Mapper 클래스를 통해 컴포넌트 간 의존성의 방향을 제어하자.md
│ ├── Repository를 인터페이스와 구현체로 분리하는 이유.md
│ ├── 도메인 모델에 의존값을 포함하지 말자.md
│ └── 재사용을 위해 코드를 추출하는 경우 단일 책임 원칙을 고려하자.md
├── Clean Code
├── 객체는 사용하는 경우에만 생성하자.md
├── 결과를 처리할 때는 예외보다 Failure를 활용하자.md
├── 분기 처리 시 다른 조건에 의존적인 조건은 지양하자.md
├── 사용자 정의 오류보다 표준 오류를 사용하자.md
├── 일반적인 알고리즘을 구현하는 경우 제네릭을 사용하자.md
└── 타입 파라미터의 섀도잉을 피하자.md
├── Coding Principles
├── 로직과 알고리즘을 구분하자.md
└── 설계와 아키텍처 개념을 구분하자.md
├── Compose
├── Compose 뷰에서 무거운 연산 작업을 하는 경우에는 remember에 key 값을 사용하자.md
├── Compose 컴포넌트를 구현할 때 Material에 대한 의존성을 최소화하자.md
├── Compose 화면 최초 진입 시 발생하는 사이드 이펙트는 LaunchedEffect(Unit)를 사용하지 말자.md
├── Compose에서 블러 효과를 구현하는 다양한 방식을 구분하자.md
├── Compose의 상태는 메인 스레드에서만 접근하자.md
├── DisposableEffect를 통해 생명주기에 따라 정리가 필요한 사이드 이펙트를 처리하자.md
├── ImageVector와 PainterResource를 적절히 사용하자.md
├── Layout Inspector를 이용해 컴포넌트 트리 구조와 리컴포지션 상태를 파악하자.md
├── Modifier 함수 간의 순서를 고려하자.md
├── Modifier.clip()으로 컴포넌트를 원하는 형태로 자르자.md
├── Modifier.drawWithCache()를 통해 컴포저블 뷰를 Bitmap 이미지로 변환하자.md
├── Modifier.offset()은 다른 컴포넌트와 독립적인 경우에만 사용하자.md
├── Modifier에 role을 명시하여 접근성을 개선하자.md
├── Nested Graph를 통해 복잡한 Compose 네비게이션 동작을 구현하자.md
├── PreviewParameterProvider를 통해 프리뷰의 상태별 파라미터를 주입하자.md
├── Scaffold 하단에 독립적인 버튼이 있는 경우 bottomBar를 사용하자.md
├── Scaffold에 paddingValues를 지정하여 BottomBar 크기를 고려하자.md
├── Screen 단에서의 HiltViewModel 생성을 지양하자.md
├── Slot Pattern을 통해 컴포저블의 특정 영역을 외부에서 자유롭게 구성하자.md
├── ViewModel을 공유하여 XML 기반의 뷰에서 ComposeView의 상태를 바꾸자.md
├── snapshotFlow로 State를 Flow로 변환하자.md
├── throttleClickable을 통해 중복된 클릭 이벤트를 제한하자.md
├── 비전역적인 ModalBottomSheet 사용을 지양하자.md
├── 사이드 이펙트가 발생하는 로직은 LaunchedEffect 스코프 내부에서 호출하자.md
├── 액티비티를 탐색하여 참조해야하는 경우 baseContext를 활용하자.md
└── 점진적으로 Compose로 마이그레이션하는 전략을 사용하자.md
├── Data Structure
└── 불변하면 Set, 순서가 상관 없으면 HashSet, 순서가 보장되어야 하면 MutableSet을 사용하자.md
├── Database
└── DB 작업 시 Delete와 Update를 지양하자.md
├── Git
└── Rebase와 Merge를 적절히 사용하자.md
├── Github
└── Github 라이선스 종류를 구분하자.md
├── Language
├── Java
│ └── Okhttp WebSocket을 통해 소켓 통신을 구현하자.md
└── Kotlin
│ ├── @DslMarker를 활용하여 외부 리시버 사용을 제한하자.md
│ ├── @Throws를 사용하는 경우.md
│ ├── Collection과 Sequence를 적절히 사용하자.md
│ ├── Coroutine
│ ├── Mutex를 통해 자원에 대한 동시 접근을 제한하자.md
│ ├── ViewModelScope 대신 CoroutineScope를 사용해야하는 경우.md
│ ├── withTimeout()으로 코루틴 동작에 타임아웃을 설정하자.md
│ └── 코루틴의 데이터 공유 상태로 인한 문제를 해결하는 다양한 방법을 구분하자.md
│ ├── Enum 타입에 대해 분기 처리가 복잡해지는 경우 Enum 클래스의 프로퍼티를 통해 상태를 캡슐화하자.md
│ ├── Object를 사용하는 이유.md
│ ├── Spread 연산자(*)를 이용해 배열이나 컬렉션 요소를 개별 인자로 변환하자.md
│ ├── Unit?을 리턴하지 말자.md
│ ├── close 대신 use를 사용하여 리소스를 해제하자.md
│ ├── inferred 타입으로 리턴하지 말자.md
│ ├── inner class 대신 nested class를 사용하자.md
│ ├── let을 적절한 상황에 사용하자.md
│ ├── require 함수를 통해 함수 argument에 제한을 걸자.md
│ ├── runCatching, mapCatching, recoverCatching을 통해 안전하게 예외를 처리하자.md
│ ├── sealed class와 enum class를 적절히 사용하자.md
│ ├── variance 한정자를 통해 제너릭의 타입 간 관련성을 관리하자.md
│ ├── 가변성을 제한하자.md
│ ├── 단발성 이벤트 처리 시 Flow 대신 Channel을 사용하자.md
│ ├── 멤버 확장 함수 사용을 지양하자.md
│ ├── 변수 타입이 명확하지 않은 경우 확실하게 지정하자.md
│ ├── 변수의 스코프를 최소화하자.md
│ ├── 성능이 중요한 경우에 기본 자료형 배열을 사용하자.md
│ ├── 연산자 오버로딩 시 의미에 맞게 사용하자.md
│ ├── 예외를 활용해 코드에 제한을 걸자.md
│ ├── 일반적인 프로퍼티의 행위는 프로퍼티 위임으로 추출하여 재사용하자.md
│ ├── 지역 스코프에서는 mutable 컬렉션을 사용하자.md
│ ├── 컬렉션의 처리 단계 수를 제한하자.md
│ ├── 클래스 생성 중 초기화할 수 없는 프로퍼티는 lateinit과 Delegates.notNull을 사용하자.md
│ ├── 프로퍼티와 함수를 적절히 사용하자.md
│ ├── 플랫폼 타입 사용을 지양하자.md
│ ├── 함수와 메서드의 차이를 이해하자.md
│ └── 확장 함수 구현 시 메모리를 고려하자.md
├── Network
├── GET 통신 시에 Body 요청을 지양하는 이유.md
├── URL은 소문자로 구성하되, 단어를 구분할 때는 하이픈(-)을 사용하자.md
└── 안드로이드에서 딥링크를 구현하는 다양한 방법을 구분하자.md
├── Object-Oriented Programming
└── 재사용이 필요할 때 인터페이스를 적극 활용하자.md
├── Office Life
├── QA 가능한 방법을 고려하여 기능을 개발하자.md
├── 새로운 버전의 앱을 배포할 때에는 이전 버전과의 호환성을 확인하자.md
├── 스테이징 서버를 통해 운영 서버와 유사한 환경에서 검증하자.md
├── 의사 전달 시 문서화를 습관화하자.md
├── 코드 구조를 설계할 때는 설계하지 않았을 때의 문제점을 먼저 파악하자.md
├── 프로젝트 설계 단계에서 Tech Spec 문서를 통해 목표와 개발 범위를 정리하자.md
└── 확장 가능성이 있는 기능은 서버에서 동적으로 수정 가능하도록 설계하자.md
├── README.md
└── Test
└── 단위 테스트의 장단점과 활용하기 적합한 케이스를 파악하자.md
/Android Component/Activity/enableEdgeToEdge를 통해 확장된 화면을 제공하자.md:
--------------------------------------------------------------------------------
1 | ## enableEdgeToEdge를 통해 확장된 화면을 제공하자
2 | ### [enableEdgeToEdge](https://developer.android.com/reference/kotlin/androidx/activity/ComponentActivity#(androidx.activity.ComponentActivity).enableEdgeToEdge(androidx.activity.SystemBarStyle,androidx.activity.SystemBarStyle))란?
3 | > 상단 상태바와 하단 내비게이션 바를 투명하게 설정하여 화면이 디바이스 전체를 활용할 수 있도록 하는 ComponentActivity 확장 함수
4 | - 액티비티의 `onCreate()`에서 호출
5 | - 간편하게 넓은 화면 디스플레이 설정 및 시스템 표시줄 색상 변경 가능
6 | - sdk 버전이 29보다 낮은 경우, 네비게이션 바가 반투명하게 노출될 수 있음에 주의
7 | - 경우에 따라 시스템 창 영역을 고려하여 WindowInsets를 통해 레이아웃 배치 필요
8 |
--------------------------------------------------------------------------------
/Android Component/Activity/액티비티 launchMode로 singleTask나 singleInstance를 지정하는 것을 지양하자.md:
--------------------------------------------------------------------------------
1 | ## [액티비티 launchMode로 singleTask나 singleInstance를 지정하는 것을 지양하자](https://developer.android.com/guide/topics/manifest/activity-element?hl=ko#lmode)
2 | - 일반적으로 launchMode로 `standard`나 `singleTop`을 설정하는 것이 적합
3 | - 특정 상황에서 `singleTask`나 `singleInstance` 도 사용성에 맞게 사용하면 충분히 사용 가능
4 | ### `singleTask`란?
5 | - 동일한 Task 상에서 하나의 액티비티만 존재
6 | - onNewIntent() 함수 호출
7 | - 상단에 존재하지 않아도 재사용하며 사이에 존재하는 액티비티 모두 clear
8 | - 주의점 : 백그라운드 상태의 앱을 아이콘 클릭으로 재실행하는 경우, 런처 액티비티부터 재시작 (UX 저하)
9 | ### `singleInstance`란?
10 | - 단일 Task에 최상단으로 존재
11 | - 기존에 띄워진 액티비티를 명시적으로 해제해야 하는 이슈 존재 (유지보수성 저하)
12 | - 백 버튼 클릭 시 앱 종료되므로 스택 관리가 어려움
13 |
--------------------------------------------------------------------------------
/Android Component/CoordinatorLayout/BottomSheetBehavior를 통해 적절한 CoordinatorLayout 자식 뷰 상태를 지정하자.md:
--------------------------------------------------------------------------------
1 | ## BottomSheetBehavior를 통해 적절한 CoordinatorLayout 자식 뷰 상태를 지정하자
2 | ### BottomSheetBehavior란?
3 | > CoordinatorLayout 자식 뷰가 하단에서 펼쳐지는 방식을 지정하는 플러그인
4 | - `app:layout_behavior`를 통해 지정 가능
5 | ### BottomSheetBehavior 상태 유형
6 | - `STATE_EXPANDED` : 완전히 펼쳐진 상태
7 | - `STATE_COLLAPSED` : 하단에 접혀 있는 상태
8 | - `STATE_HIDDEN` : 하단에 숨겨져 보이지 않는 상태
9 | - `STATE_HALF_EXPANDED` : 절반으로 펼쳐진 상태
10 | - `STATE_DRAGGING` : 드래깅되고 있는 상태
11 | - `STATE_SETTLING` : 드래그 및 스와이프 직후 고정된 상태
12 |
--------------------------------------------------------------------------------
/Android Component/Intent/인텐트에 액션과 데이터 스키마를 지정하여 다른 앱에서의 처리를 구현하자.md:
--------------------------------------------------------------------------------
1 | ## 인텐트에 액션과 데이터 스키마를 지정하여 다른 앱에서의 처리를 구현하자
2 | ### 1️⃣ [이메일 앱 처리](https://developer.android.com/guide/components/intents-common?hl=ko#Email)
3 | - 액션 : `ACTION_SENDTO`
4 | - 데이터 스키마 : `mailto:`
5 | ### 2️⃣ [지도 앱 처리](https://developer.android.com/guide/components/intents-common?hl=ko#Maps)
6 | - 액션 : `ACTION_VIEW`
7 | - 데이터 스키마 : `geo:{latitude},{longitude}`
8 | - 추가적으로 zoom 비율과 라벨, 상세 주소 위치 표시 등의 동작 지정 가능
9 | ### 3️⃣ [음악 및 동영상 앱 처리](https://developer.android.com/guide/components/intents-common?hl=ko#Music)
10 | - 액션 : `ACTION_VIEW`
11 | - 데이터 스키마 : `file:`, `content:`, `http:`
12 | ### 4️⃣ [전화 앱 처리](https://developer.android.com/guide/components/intents-common?hl=ko#Phone)
13 | - 액션 : `ACTION_DIAL`, `ACTION_CALL`
14 | - 데이터 스키마 : `tel:`, `voicemail:`
15 | ### 5️⃣ [문제 메시지 앱 처리](https://developer.android.com/guide/components/intents-common?hl=ko#Messaging)
16 | - 액션 : `ACTION_SENDTO`, `ACTION_SEND`, `ACTION_SEND_MULTIPLE`
17 | - 데이터 스키마 : `sms:`, `smsto:`, `mms:`, `mmsto:`
18 | ### 6️⃣ [웹 브라우저 앱 처리](https://developer.android.com/guide/components/intents-common?hl=ko#Browser)
19 | - 액션 : `ACTION_VIEW`
20 | - 데이터 스키마 : `http:`, `https:`
21 | ### 7️⃣ [연락처 앱 처리](https://developer.android.com/guide/components/intents-common?hl=ko#ViewContact)
22 | - 액션
23 | - 조회 : `ACTION_VIEW`
24 | - 편집 : `ACTION_EDIT`
25 | - 데이터 스키마 : `content:`
26 |
--------------------------------------------------------------------------------
/Android Component/RecyclerView를 구현할 때 Selection 라이브러리 사용을 지양하자.md:
--------------------------------------------------------------------------------
1 | ## RecyclerView를 구현할 때 Selection 라이브러리 사용을 지양하자
2 | - 내부 코드가 수정될 수 없어 커스텀이 어려움
3 | - 추후 요구사항이 변경되는 경우 확장성이 떨어짐
4 |
--------------------------------------------------------------------------------
/Android Component/TextField/Compose TextField의 비동기 입력을 구현하는 다양한 방법을 구분하자.md:
--------------------------------------------------------------------------------
1 | ## [Compose TextField의 비동기 입력을 구현하는 다양한 방법을 구분하자](https://github.com/orbit-mvi/orbit-mvi/issues/82)
2 | - Compose TextField는 기본적으로 비동기 입력을 지원하지 않음
3 | - 비동기 처리를 통해 입력값 처리 시 간헐적으로 커서 뒤에 새 입력값이 나타나는 현상 발생
4 |
5 | ### 방법1) remember를 통해 입력값 관리
6 | ```
7 | val state = remember { mutableStateOf(initialValue) }
8 |
9 | OutlinedTextField(
10 | value = state.value,
11 | onValueChange = {
12 | state.value = it
13 | onValueChange(it)
14 | }
15 | )
16 | ```
17 | - 코드 가독성 저하
18 | - 데이터 처리 로직의 복잡성 증가
19 |
20 | ### 방법2) Orbit Container에서 Unconfined 디스패처 사용
21 | ```
22 | container(
23 | ...
24 | settings = Container.Settings(intentDispatcher = Dispatchers.Unconfined)
25 | )
26 | ```
27 | - 텍스트 필드 입력값을 관리해야할 때마다 컨테이너 디스패처를 별도로 설정 필요 (보일러플레이트)
28 | - 다른 디스패처를 사용하기 위해서 withContext 처리 필요
29 |
30 |
31 | ```
32 | 장단점을 고려하여 기능에 적합한 처리 방식을 선택할 것
33 | 더 좋은 방법이 있다면 알려주세요
34 | ```
35 |
--------------------------------------------------------------------------------
/Android Component/URLEncoder/URLEncoder를 통해 HTML 형식으로 인코딩하자.md:
--------------------------------------------------------------------------------
1 | ## URLEncoder를 통해 HTML 형식으로 인코딩하자
2 | ### URLEncoder란?
3 | - `encode()` 함수를 통해 문자열을 HTML 형식으로 인코딩하여 반환하는 클래스
4 | - String과 CharSet 타입을 파라미터로 전달하는 함수가 가장 안정적
5 | ### 구현 예시
6 | ```
7 | URLEncoder.encode(info, StandardCharsets.UTF_8)
8 | ```
9 |
--------------------------------------------------------------------------------
/Android Component/View/안드로이드 View의 생명주기를 이해하자.md:
--------------------------------------------------------------------------------
1 | ## [안드로이드 View의 생명주기를 이해하자](https://velog.io/@haero_kim/Android-View-%EC%9D%98-%ED%95%9C-%ED%8F%89%EC%83%9D-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0)
2 | ### View가 그려지는 방식
3 | - 액티비티 `onCreate()` 내부의 `setContentView()`를 통해 루트 노드 전달
4 | - 액티비티로부터 전달된 루트 노드부터 리프 노드까지 Top-down 방식으로 레이아웃 그리기
5 | - Measure, Layout 두 단계로 구성
6 | ### Measure 단계
7 | - `measure()` 함수로 구성
8 | - 모든 뷰가 각각 자신의 크기 측정값 저장
9 | - 뷰의 `measure()` 반환 이후 자식뷰들의 값과 함께 `getMeasuredWidth()` 및 `getMeasuredHeight()` 값 설정
10 | - 부모 뷰는 자식 뷰에게 두 번 이상의 `measure()` 호출 가능
11 | ### Layout 단계
12 | - layout() 함수로 구성
13 | - Measure 단계에서 측정한 크기를 사용하여 모든 자식 뷰 위치 배정
14 |
15 |
16 | ## View Lifecycle
17 | 
18 | ### 1. `constructor()`
19 | > 생성자에 의해 생명 주기 시작
20 | ### 2. `onAttachedToWindow()`
21 | > 부모 뷰가 `addView()`를 호출하여 뷰가 윈도우에 붙을 때 호출
22 | - 뷰를 그리기 위한 Surface 부여
23 | - 리소스 할당 및 리스너 설정 가능
24 | ### 3. `onMeasure()`
25 | > `measure()` 콜백 함수로, 부모 뷰는 모든 자식 뷰의 `measure()`를 호출하고 자신의 크기 결정
26 | ### 4. `onLayout()`
27 | > `layout()` 콜백 함수로, 뷰의 크기와 위치를 지정하여 화면에 배치한 후 호출
28 | - 뷰가 그려지기 전 단계
29 | ### 5. `dispatchToDraw()`
30 | > 뷰가 다시 그려져야 할 경우에 자식 뷰들도 다시 그려지도록 명령
31 | ### 6. `onDraw()`
32 | > 실제로 뷰를 그리는 단계
33 | ### 6-1. `invalidate()`
34 | > 뷰의 속성이 변경되어 View를 다시 그리기 위해 호출하는 함수
35 | ### 6-2. `requestLayout()`
36 | > 뷰의 크기 변화가 발생할 경우 레이아웃 배치를 확인하기 위해 크기 측정부터 다시 실행하는 함수
37 |
--------------------------------------------------------------------------------
/Android Component/ViewModel/Extra를 전달 받을 때 SavedStateHandle을 활용하자.md:
--------------------------------------------------------------------------------
1 | ## Extra를 전달 받을 때 SavedStateHandle을 활용하자
2 | ### SavedStateHandle이란?
3 | > 화면에 필요한 데이터를 안전하게 저장할 수 있는 Handle
4 | - 시스템이 프로세스를 종료하더라도 동일한 정보 유지
5 | - ViewModel에만 저장하는 경우, 메모리 내에 존재하기 때문에 프로세스 종료까지 안전하지 않음
6 | - ViewModel의 생성자 매개변수를 통해 주입
7 |
--------------------------------------------------------------------------------
/Android Component/WebView/WebView에 domStorageEnable을 설정하자.md:
--------------------------------------------------------------------------------
1 | ## WebView에 domStorageEnable을 설정하자
2 | - WebView.settings.[domStorageEnable](https://developer.android.com/reference/android/webkit/WebSettings#setDomStorageEnabled(boolean)) `true` 설정 권장
3 | - 웹의 JS 쪽에서 `localStorage.getItem()` 이나 `sessionStorage.getItem()`을 호출할 경우, 해당 속성이 true로 설정되어 있지 않으면 url이 로드되지 않음
4 | - EX) 소프트베리 공식 홈페이지, 네이버 한자 사전 등
5 | - 불필요해 보이는 설정도 url 또는 웹의 내부 동작이 바뀌는 경우를 고려하여 기본적인 설정을 해놓는 것이 안정적
6 |
--------------------------------------------------------------------------------
/Android Component/WebView/pauseTimers()와 resumeTimers()를 통해 WebView의 리소스를 관리하자.md:
--------------------------------------------------------------------------------
1 | ## [`pauseTimers()`와 `resumeTimers()`를 통해 WebView의 리소스를 관리하자](https://docs.notifly.tech/ko/developer-guide/client-sdk/advanced/android-sdk-advanced#5-webview)
2 | - 안드로이드는 앱이 백그라운드에 있을 때에도 WebView의 Javascript 코드가 실행됨
3 | - 백그라운드 상태의 리소스 소모를 예방하기 위해 `pauseTimers()`와 `resumeTimers()` 사용
4 | ### 구현 예시
5 | ```
6 | override fun onPause() {
7 | super.onPause()
8 | mNotiflyWebView?.pauseTimers()
9 | }
10 |
11 | override fun onResume() {
12 | super.onResume()
13 | mNotiflyWebView?.resumeTimers()
14 | }
15 | ```
16 | - 두 메소드 모두 하나의 WebView가 아닌 전역적으로 적용
17 |
--------------------------------------------------------------------------------
/Android Component/WebView/변경이나 오류가 자주 발생하는 화면은 웹뷰로 구현하자.md:
--------------------------------------------------------------------------------
1 | ## 변경이나 오류가 자주 발생하는 화면은 웹뷰로 구현하자
2 | - 웹뷰는 앱의 배포 및 업데이트 없이 서버에서 동적으로 변경이 가능
3 | - 변경이나 오류가 잦게 발생하는 화면은 웹뷰를 통해 효율적으로 대응하는 것이 적합
4 | - 카메라, GPS 등의 네이티브 기능과 독립적인 화면인 경우에만 가능
5 | ### 웹뷰의 장점
6 | - 변경 사항에 대한 즉각적인 반영 가능
7 | - 앱 업데이트를 위한 QA, 심사, 다운로드 등의 배포 비용 절감
8 | - 오류 발생 시 대응 용이
9 |
--------------------------------------------------------------------------------
/Android Component/WebView/웹뷰의 shouldOverrideUrlLoading()을 통해 웹 페이지 또는 intent를 처리하자.md:
--------------------------------------------------------------------------------
1 | ## 웹뷰의 `shouldOverrideUrlLoading()`을 통해 웹 페이지 또는 intent를 처리하자
2 | - 웹에서 새로운 웹 페이지의 url 또는 인텐트(EX. 특정 앱 실행)를 전달할 경우에 대해 적절히 처리 필요
3 | - 불필요해보여도 웹 내부 동작이 바뀌는 경우를 고려하여 사전에 처리하는 것이 안정적
4 | ### 구현 예시
5 | ```
6 | private class CustomWebViewClient() : WebViewClient() {
7 | override fun shouldOverrideUrlLoading(
8 | view: WebView?,
9 | request: WebResourceRequest?,
10 | ) {
11 | val url = request?.url.toString()
12 |
13 | when {
14 | url.startsWith("https://") || url.startsWith("http://") -> {
15 | // 웹 페이지 url 처리
16 | }
17 |
18 | url.startsWith("intent:") -> {
19 | // intent 처리
20 | }
21 | }
22 | }
23 | }
24 | ```
25 |
--------------------------------------------------------------------------------
/Android Component/isTaskRoot를 통해 첫 번째 액티비티인지 확인하자.md:
--------------------------------------------------------------------------------
1 | ## `isTaskRoot`를 통해 첫 번째 액티비티인지 확인하자
2 | - Activity[.isTaskRoot](https://developer.android.com/reference/android/app/Activity#isTaskRoot())는 액티비티가 태스크 중 첫 번째 액티비티인지 반환
3 | ### 사용 예시
4 | ```
5 | if (isTaskRoot) {
6 | startActivity(Intent(this, MainActivity::class.java))
7 | finish()
8 | }
9 | ```
10 |
--------------------------------------------------------------------------------
/Android Library/Analytics Provider Library/각 애널리틱스 라이브러리의 특성을 이해하자.md:
--------------------------------------------------------------------------------
1 | ## 각 애널리틱스 라이브러리의 특성을 이해하자
2 | ### Amplitude
3 | - 유저 액션을 로깅 및 분석에 용이
4 | ### Firebase Analytics
5 | - 외부 유입 경로 분석에 용이
6 | ### Notifly
7 | - 유저 액션을 트리거로 활용하여 푸시 기능 구현에 용이
8 |
--------------------------------------------------------------------------------
/Android Library/Analytics Provider Library/애널리틱스 이벤트나 라이브러리의 추가 및 제거에 대한 리소스를 최소화하자.md:
--------------------------------------------------------------------------------
1 | ## [애널리틱스 이벤트나 라이브러리의 추가 및 제거에 대한 리소스를 최소화하자](https://proandroiddev.com/an-amazing-analytics-architecture-for-android-app-part-1-6c4304739de6)
2 | ### 비효율적인 애널리틱스 처리 구조
3 | - 새로운 이벤트의 추가 및 제거 리소스가 큼
4 | - 애널리틱스 라이브러리의 추가 및 제거 리소스가 큼
5 | - 이벤트를 기록하고 있는 형식 파악이 어려움
6 | ### 효율적인 애널리틱스 처리 구조
7 | - 애널리틱스 이벤트는 앱 구조와 독립적으로 분리
8 | - 이벤트와 프로퍼티에 대한 형식을 엄격하게 제한
9 | - 필요한 애널리틱스 리소스에만 접근 가능
10 | - 라이브러리가 달라도 애널리틱스 처리 로직은 동일하게 동작
11 | ### 애널리틱스 이벤트 추상 클래스 구현 예시
12 | ```
13 | abstract class AnalyticsEvent(
14 | val eventName: String,
15 | val params: Map = emptyMap(),
16 | val providers: List = listOf(
17 | AnalyticsProvider.ANALYTICS_FIREBASE,
18 | AnalyticsProvider.ANALYTICS_AMPLITUDE,
19 | )
20 | )
21 | ```
22 | ### 애널리틱스 프로퍼티 추상 클래스 구현 예시
23 | ```
24 | abstract class AnalyticsProperty(
25 | val propertyName: String,
26 | val parameter: Any,
27 | val providers: List = listOf(
28 | AnalyticsProvider.ANALYTICS_FIREBASE,
29 | AnalyticsProvider.ANALYTICS_AMPLITUDE,
30 | )
31 | )
32 | ```
33 |
--------------------------------------------------------------------------------
/Android Library/CRM/CRM 툴을 통해 개인화된 마케팅 기능을 자동화하자.md:
--------------------------------------------------------------------------------
1 | ## CRM 툴을 통해 개인화된 마케팅 기능을 자동화하자
2 | - CRM 라이브러리를 통해 특정한 유저 액션을 트리거로 이용해 알림 등의 기능 제공 가능
3 | - EX) Braze, Notifly, Bigin
4 | - 라이브러리에 따라 엠플리튜드와 호환되어 유저 액션 지정 가능
5 |
--------------------------------------------------------------------------------
/Android Library/Glide/Glide의 onResourceReady()를 통해 load가 종료된 시점에 동작을 처리하자.md:
--------------------------------------------------------------------------------
1 | ## Glide의 `onResourceReady()`를 통해 load가 종료된 시점에 동작을 처리하자
2 | - `Glide.into()` 함수 내부에 `onResourceReady()`를 구현한 CustomTarget 객체를 넘겨 load가 종료 또는 실패한 시점에 동작 처리 가능
3 | ### 구현 예시
4 | ```
5 | Glide.with(requireView())
6 | .load(imgUrl)
7 | .into(object : CustomTarget() {
8 | override fun onResourceReady(
9 | resource: Drawable,
10 | transition: Transition?
11 | ) {
12 | // Do something with the Drawable here.
13 | }
14 |
15 | override fun onLoadCleared(placeholder: Drawable?) {
16 | // Remove the Drawable provided in onResourceReady from any Views and ensure no references to it remain.
17 | }
18 | })
19 | ```
20 |
--------------------------------------------------------------------------------
/Android Library/Kakao SDK Library/카카오 로그인 여부를 확인할 때 토큰 유효성을 확인하자.md:
--------------------------------------------------------------------------------
1 | ## 카카오 로그인 여부를 확인할 때 토큰 유효성을 확인하자
2 | - 카카오에서 앱과의 연결을 끊는 경우를 고려하여 토큰 유효성을 확인하는 것이 안정적
3 | ### [구현 예시](https://developers.kakao.com/docs/latest/ko/kakaologin/android#token-presence)
4 | ```
5 | if (AuthApiClient.instance.hasToken()) {
6 | UserApiClient.instance.accessTokenInfo { _, error ->
7 | if (error != null) {
8 | if (error is KakaoSdkError && error.isInvalidTokenError() == true) {
9 | // 로그인 필요
10 | }
11 | else {
12 | // 기타 에러
13 | }
14 | }
15 | else {
16 | // 토큰 유효성 체크 성공(필요 시 토큰 갱신됨)
17 | }
18 | }
19 | }
20 | else {
21 | // 로그인 필요
22 | }
23 | ```
24 |
--------------------------------------------------------------------------------
/Android Library/Lottie/Lottie 사용 시 애니메이션 비활성화 및 속도 조절 설정을 고려하자.md:
--------------------------------------------------------------------------------
1 | ## Lottie 사용 시 애니메이션 비활성화 및 속도 조절 설정을 고려하자
2 | - [안드로이드 기기의 개발자 옵션에서는 애니메이션 속도 및 비활성화 설정 가능](https://www.howtogeek.com/175033/how-to-speed-up-any-android-phone-by-disabling-animations/)
3 | - Window animation scale, Transition animation scale, Animator duration scale
4 | - 애니메이션을 비활성화한 경우, Lottie 애니메이션이 정상적으로 재생되지 않을 수 있음에 주의
5 | - Lottie에 필수적으로 노출되어야 하는 화면이 포함된 경우, 사용자 경험을 보장할 수 있는 대체 방안 고려
6 |
--------------------------------------------------------------------------------
/Android Library/OkHttp/Interceptor를 통해 네트워크 UserAgent를 설정하자.md:
--------------------------------------------------------------------------------
1 | ## [Interceptor를 통해 네트워크 UserAgent를 설정하자](https://medium.com/mobile-app-development-publication/setting-useragent-for-android-network-9daf5264ef3f)
2 | - 안드로이드는 OkHttp에서 UserAgent를 "okhttp/버전" 형식으로 설정
3 | - iOS는 Alamofire에서 세부적으로 UserAgent 정보를 설정
4 | - 안드로이드도 세부적인 정보를 UserAgent에 담기 위해서 Interceptor 활용
5 |
6 | ### 구현 예시
7 | ```
8 | class UserAgentInterceptor @Inject(
9 | @ApplicationContext private val context: Context,
10 | ) : Interceptor {
11 | private val userAgent: String =
12 | "${context.packageManager.getApplicationLabel(context.applicationInfo)}/" +
13 | "${VERSION_NAME} " +
14 | "(${context.packageName}; " +
15 | "build:${VERSION_CODE} " +
16 | "Android SDK ${Build.VERSION.SDK_INT}) " +
17 | "${Build.BRAND} ${Build.MODEL}"
18 |
19 | override fun intercept(chain: Interceptor.Chain): Response {
20 | val requestWithUserAgent = chain.request().newBuilder()
21 | .header(USER_AGENT, userAgent)
22 | .build()
23 |
24 | return chain.proceed(requestWithUserAgent)
25 | }
26 |
27 | companion object {
28 | private const val USER_AGENT = "User-Agent"
29 | }
30 | }
31 | ```
32 |
--------------------------------------------------------------------------------
/Android Library/Orbit/Orbit으로 State와 SideEffect를 관리하자.md:
--------------------------------------------------------------------------------
1 | ## Orbit으로 State와 SideEffect를 관리하자
2 | ### Orbit이란?
3 | > Compose에서 Coroutine Block을 통해 State와 SideEffect를 관리하는 라이브러리
4 | ### Orbit 주요 개념
5 | - **container** : State와 Side Effect를 관리하는 공간으로, 주로 ViewModel과 함께 사용
6 | - **reduce** : State 값을 변경하는 스코프 함수
7 | - **postSideEffect** : Side Effect를 발생하는 함수
8 | - **intent** : `reduce`와 `postSideEffect`를 호출하는 Coroutine Block
9 | ### Orbit의 장점
10 | - 효율적인 MVI 패턴 구현
11 | - 사이드 이펙트 관리하는 로직 집약
12 | - 상태 변경 및 사이드 이펙트 관리 로직 가독성 향상
13 | - 상태 변경에 대한 용이한 비동기 처리
14 |
--------------------------------------------------------------------------------
/Android Library/외부 라이브러리를 사용할 때 추상화가 되어있는 영역을 파악하자.md:
--------------------------------------------------------------------------------
1 | ## 외부 라이브러리를 사용할 때 추상화가 되어있는 영역을 파악하자
2 | - 외부 라이브러리를 사용할 때, 추상화가 되어있는 영역을 파악해야 적합한 구조로 활용 가능
3 | - 뷰와 관련된 기능까지 추상화가 되어있는 경우, UI 레이어에서 사용하는 것이 구조 상 적합
4 | - EX) 액티비티를 전달하여 라이브러리 제공 함수 호출
5 | - 데이터 레이어 영역의 기능만 추상화가 되어있는 경우, 데이터 레이어에서 추상화하여 사용 가능
6 |
--------------------------------------------------------------------------------
/Android Studio/.editorconfig 파일을 통해 ktlint 스타일을 커스텀하자.md:
--------------------------------------------------------------------------------
1 | ## [.editorconfig 파일을 통해 ktlint 스타일을 커스텀하자](https://pinterest.github.io/ktlint/latest/rules/configuration-ktlint/)
2 | - 최상단 위치에 `.editorconfig` 파일을 생성하여 ktlint의 세부 스타일 설정 가능
3 | ### 세부 구성 설정
4 | - `ij_kotlin_allow_trailing_comma` : 파라미터 뒤에 `,` 추가 여부 설정
5 | - `ij_kotlin_allow_trailing_comma_on_call_site` : 함수 호출 시 파라미터 뒤에 `,` 추가 여부 설정
6 | - `ij_kotlin_imports_layout` : import문 순서 배치 여부 설정
7 | - `ij_kotlin_packages_to_use_import_on_demand` : wildcard import 허용 여부 설정
8 | - `indent_size` : 들여쓰기 크기 설정
9 | - `indent_style` : 들여쓰기 스타일 설정
10 | - `insert_final_newline` : 파일 마지막에 newline 추가 여부 설정
11 | - `ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than` : 함수 체이닝 시 필수 줄바꿈 여부 설정
12 | - `ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than` : 줄바꿈 파라미터 개수 설정
13 | - `ktlint_ignore_back_ticked_identifier` : 백틱 문자(``) 무시 여부 설정
14 | - `ktlint_function_naming_ignore_when_annotated_with` : 함수 네이밍 제한 설정
15 | - `ktlint_function_signature_body_expression_wrapping` : 함수 구현체 필수 래핑 여부 설정
16 | - `ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than` : 함수 구현체 필수 래핑 파라미터 개수 설정
17 | - `max_line_length` : 한 줄 최대 글자 수 설정
18 |
--------------------------------------------------------------------------------
/Android Studio/Clean Project를 통해 수정사항을 확실하게 빌드 시키자.md:
--------------------------------------------------------------------------------
1 | ## Clean Project를 통해 수정사항을 확실하게 빌드 시키자
2 | - 오탈자 정정 등의 미세한 수정사항은 Gradle에서 인식하지 못하여 빌드에 포함되지 않는 경우 존재
3 | - 수정사항을 빌드해야하는 경우 Clean Project하는 것을 습관화하여 확실하게 빌드에 포함시키는 것이 안정적
4 |
--------------------------------------------------------------------------------
/Android Studio/Gradle/Gradle Type-Safe Project Accessors를 통해 멀티 모듈 의존성을 안전하게 작성하자.md:
--------------------------------------------------------------------------------
1 | ## [Gradle Type-Safe Project Accessors를 통해 멀티 모듈 의존성을 안전하게 작성하자](https://medium.com/@ndusundayDev/simplify-dependency-declarations-with-gradles-type-safe-project-accessors-2227859330fe)
2 | - settings gradle 파일에 `enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")` 코드 추가하여 설정
3 | - gradle 7.0 이상에서 사용 가능
4 | - 모듈명을 하드 코딩이 아닌 자동 완성 기능을 통해 작성 가능
5 | - 유지보수성 증가 효과
6 | ### 기존 방식 예시
7 | ```
8 | implementation project(":data")
9 | ```
10 | ### Type-safe project accessors 사용 예시
11 | ```
12 | implementation(projects.data)
13 | ```
14 |
--------------------------------------------------------------------------------
/Android Studio/Kotlin Decompiler로 디컴파일된 Java 코드를 확인하자.md:
--------------------------------------------------------------------------------
1 | ## Kotlin Decompiler로 디컴파일된 Java 코드를 확인하자
2 | - 안드로이드 스튜디오 내장 도구를 활용하면 디컴파일된 상태의 Java 코드 확인 가능
3 | ### Kotlin Decompiler 사용법
4 | - Tools > Kotlin > Show Kotlin ByteCode > [Decompile] 버튼 클릭
5 | - Tools > Kotlin > Decompile to Java
6 |
--------------------------------------------------------------------------------
/Android Studio/SDK의 설정과 AndroidManifest.xml의 속성이 충돌하는 경우에 tools:replace 속성을 활용하자.md:
--------------------------------------------------------------------------------
1 | ## SDK의 설정과 AndroidManifest.xml의 속성이 충돌하는 경우에 `tools:replace` 속성을 활용하자
2 | ### [`tools:replace`란?](https://eonj.github.io/trouble.log/2024-04-16.android-manifest-tools-replace-conflict/)
3 | > manifest XML을 병합하는 시점에 충돌하는 노드가 정의된 경우, 해당 노드의 속성값 중에서 대체되어야 할 이름을 명시하는 속성
4 | - 자식 노드는 노드의 목록으로 병합됨
5 | ### 충돌 발생 예시
6 | - [Airbridge SDK의 백업 규칙과 `AndroidManifest.xml`의 `android:allowBackup="false"`값이 충돌하는 경우](https://help.airbridge.io/ko/developers/troubleshooting-android-sdk-v4#allowbackup-false%EA%B3%BC%EC%9D%98-%EC%B6%A9%EB%8F%8C-%EB%B0%A9%EC%A7%80)
7 |
--------------------------------------------------------------------------------
/Android Studio/local.properties와 gradle.properties 파일을 구분하여 활용하자.md:
--------------------------------------------------------------------------------
1 | ## local.properties와 gradle.properties 파일을 구분하여 활용하자
2 | ### local.properties 파일
3 | - 로컬 개발 환경에 종속적인 설정 저장
4 | - 기본적으로 `.gitignore`에 추가되어 버전 관리에 포함되지 않음
5 | - 안드로이드 플러그인에서 처리
6 | - EX) API Key 값, 로컬 SDK 경로 등
7 | ### gradle.properties 파일
8 | - Gradle 빌드 시스템의 전역 및 프로젝트별 속성 설정
9 | - 일반적으로 Git에 포함
10 | - Gradle을 통해 관리
11 | - EX) 빌드 옵션, 프로젝트 전역 변수 등
12 | ### [홈 디렉토리의 gradle.properties 파일](https://pancake.coffee/jekyll/local-gradle-properties/)
13 | - 커스텀 플러그인을 작성하는 등의 경우 local.properties를 통해 프로퍼티를 참조하기 번거로워 활용
14 | - local.properties 파일을 읽어들이는 로직 구현 필요 (원래 안드로이드 플러그인이 처리)
15 | - 다른 모든 프로젝트 디렉토리의 gradle.properties가 참조
16 | - 프로퍼티명이 중복되지 않도록 프로젝트명을 포함하여 네이밍 권장
17 | - 프로퍼티 설정에 대한 문서화 및 공유 필요
18 |
--------------------------------------------------------------------------------
/Android Studio/안드로이드의 메모리 누수 탐지 도구를 활용하자.md:
--------------------------------------------------------------------------------
1 | ## [안드로이드의 메모리 누수 탐지 도구를 활용하자](https://marchbreeze.notion.site/135b6895dba980f99a44c3bf169b73c2)
2 | ### 안드로이드 메모리 누수 탐지 도구 종류
3 | 1. Android Profiler
4 | > 앱 메모리, CPU, 네트워크 상태를 모니터링하는 도구
5 | - Heap 메모리 상태 파일을 생성하고 객체가 얼마나 유지되고 있는지 파악
6 | - 반복적인 화면 이동이나 데이터 로드 후 메모리 사용량이 감소하지 않고 계속 증가한다면 메모리 누수 가능성 존재
7 | - Eclipse 기반의 [MAT(Memory Analyzer Tool)](https://blog.naver.com/syung1104/220942770301) 활용 가능
8 | - 메모리 누수와 메모리 사용 현황의 시각적 파악 가능
9 | 2. LeakCanary
10 | > 안드로이드 메모리 누수 탐지 라이브러리
11 | - 메모리 누수를 자동으로 감지하여 화면에 표시
12 |
--------------------------------------------------------------------------------
/Android Tool/R8을 통해 효율적으로 앱을 최적화하자.md:
--------------------------------------------------------------------------------
1 | ## [R8을 통해 효율적으로 앱을 최적화하자](https://velog.io/@kingdo/%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-ProGuardR8)
2 | ### R8이란?
3 | > Google에서 개발한 Android 앱 난독화, 축소 및 최적화 도구
4 | - ProGuard보다 적은 단계를 통해 컴파일 수행
5 | - 속도 측면에서 높은 효율성을 가짐
6 | - Android 런타임과 프레임워크에 적합하게 설계되어 높은 경량화 성능을 가짐
7 | ### R8의 역할
8 | - 난독화 : 네이밍을 짧고 무의미한 문자열로 변경하여 코드 해석을 어렵게 함
9 | - 코드 축소 : 사용하지 않는 코드 자동 제거
10 | - 최적화 : 코드 재구조화를 통해 실행 시간 단축 및 앱 성능 향상
11 | - DEX 바이트코드 생성 : 빠른 컴파일 타임 제공
12 | - ProGuard 규칙 지원 : ProGuard와 호환되는 규칙을 사용하여 기존 ProGuard 설정 파일 사용 가능
13 |
--------------------------------------------------------------------------------
/Architecture/Android App Architecture/Analytics 로직은 DataSource나 Repository로 분리하지 말자.md:
--------------------------------------------------------------------------------
1 | ## Analytics 로직은 DataSource나 Repository로 분리하지 말자
2 | ### DataSource로 분리하지 않는 이유
3 | - Analytics Provider 라이브러리 내부에 DB에 접근하는 로직이 구현되어 있어 불필요
4 | ### Repository로 분리하지 않는 이유
5 | - Domain 레이어에 외부 라이브러리의 의존성이 생길 수 있음
6 | - Domain 레이어와 독립적인 클래스로 구현 필요
7 |
--------------------------------------------------------------------------------
/Architecture/Android App Architecture/Repository 또는 DataSource에서 앱 실행 중 캐시가 필요한 데이터를 저장하자.md:
--------------------------------------------------------------------------------
1 | ## [Repository 또는 DataSource에서 앱 실행 중 캐시가 필요한 데이터를 저장하자](https://developer.android.com/topic/architecture/data-layer#caches)
2 | - 앱 실행 기간동안 저장해야하는 데이터는 Repository와 DataSource에 저장하여 사용하는 것이 적합
3 | - Repository와 DataSource에 따라 데이터가 싱글톤으로 관리됨에 유의
4 | - 싱글톤으로 주입되지 않는 클래스에서 캐싱이 이루어질 경우 최신화 관련 오류가 생기기 쉬워 지양
5 | - EX) UseCase
6 | ### 구현 예시
7 | ```
8 | class NewsRepository(
9 | private val newsRemoteDataSource: NewsRemoteDataSource
10 | ) {
11 | private val latestNewsMutex = Mutex()
12 |
13 | private var latestNews: List = emptyList()
14 |
15 | suspend fun getLatestNews(refresh: Boolean = false): List {
16 | if (refresh || latestNews.isEmpty()) {
17 | val networkResult = newsRemoteDataSource.fetchLatestNews()
18 | latestNewsMutex.withLock {
19 | this.latestNews = networkResult
20 | }
21 | }
22 |
23 | return latestNewsMutex.withLock { this.latestNews }
24 | }
25 | }
26 | ```
27 |
--------------------------------------------------------------------------------
/Architecture/Clean Architecture/Mapper 클래스를 통해 컴포넌트 간 의존성의 방향을 제어하자.md:
--------------------------------------------------------------------------------
1 | ## Mapper 클래스를 통해 컴포넌트 간 의존성의 방향을 제어하자
2 | - 클래스를 다른 수준과 형식의 클래스로 변환하는 경우, 매핑하는 역할을 수행하는 Mapper 클래스를 구현하는 것을 권장
3 | - 인터페이스(EX. Gateway)와 함께 사용하여 효과 극대화 가능
4 | ### Mapper 클래스의 효과
5 | - 데이터를 포맷팅하는 로직에 대해 역할과 책임 분리(SRP)
6 | - 저수준의 코드에 변경이 발생하여도 고수준의 코드가 변경되지 않도록 예방 가능
7 | - 기능이 확장되어 새로운 형식으로 포맷해야하는 경우, 코드에 대해 확장만 이루어지며 변경은 제한 가능(OCP)
8 | ### 안드로이드 적용 예시
9 | - 데이터 모델과 도메인 모델 간 서로 변환하는 경우
10 | - 도메인 모델과 UI 모델 간 서로 변환하는 경우
11 |
--------------------------------------------------------------------------------
/Architecture/Clean Architecture/Repository를 인터페이스와 구현체로 분리하는 이유.md:
--------------------------------------------------------------------------------
1 | ## Repository를 인터페이스와 구현체로 분리시키는 이유
2 | - 구현체만 교체시키며 간단하게 테스트 가능
3 | - Domain 레이어와 Data 레이어 간의 의존성 제거 (클린 아키텍처 원칙 준수)
4 | - UseCase는 Domain 레이어에 존재하기 때문에 참조하는 Repository의 Interface는 Domain 레이어에 구현
5 | - 실제 데이터는 Data 레이어에 존재하기 때문에 Repository의 구현체는 Data 레이어에 구현
6 |
--------------------------------------------------------------------------------
/Architecture/Clean Architecture/도메인 모델에 의존값을 포함하지 말자.md:
--------------------------------------------------------------------------------
1 | ## 도메인 모델에 의존값을 포함하지 말자
2 | - 도메인 레이어는 불변적인 데이터만을 포함하는 것이 원칙
3 | - 데이터 레이어와 UI 레이어의 유지보수 용이성 증가
4 | - 의존값은 UI가 수정되었을 때에도 변할 수 있기 때문에, UI 모델로 구현하는 것이 적합
5 | - EX) 서버에서 A, B를 내려줄 때 A + B와 같은 데이터가 의존값에 해당
6 |
--------------------------------------------------------------------------------
/Architecture/Clean Architecture/재사용을 위해 코드를 추출하는 경우 단일 책임 원칙을 고려하자.md:
--------------------------------------------------------------------------------
1 | ## 재사용을 위해 코드를 추출하는 경우 단일 책임 원칙을 고려하자
2 | ### 단일 책임 원칙(SRP)이란?
3 | > 클래스를 변경하는 이유는 단 한 가지여야 함을 의미하는 SOLID 원칙
4 | - 코드 추출 여부 확인 가능
5 | - 두 Actor(변화를 만들어내는 존재)가 같은 클래스를 변경하지 않아야 함
6 | - EX) 서로의 업무와 분야에 대해 잘 모르는 부서 및 개발자
7 | ### 단일 책임 원칙에 따른 코드 추출 방식
8 | - 서로 다른 곳에서 사용하는 로직은 독립적으로 변경할 가능성이 많으므로 다른 로직으로 취급
9 | - 다른 로직은 분리하여 관리하여 재사용을 방지할 것
10 |
--------------------------------------------------------------------------------
/Clean Code/객체는 사용하는 경우에만 생성하자.md:
--------------------------------------------------------------------------------
1 | ## 객체는 사용하는 경우에만 생성하자
2 | - 객체는 최대한 사용하는 경우에만 생성하는 것이 적합
3 | - 생성하는 로직을 최소화하여 앱 성능 향상 가능
4 | EX) Dialog, Fragment 등
5 |
--------------------------------------------------------------------------------
/Clean Code/결과를 처리할 때는 예외보다 Failure를 활용하자.md:
--------------------------------------------------------------------------------
1 | ## 결과를 처리할 때는 예외보다 `Failure`를 활용하자
2 | ### 예외를 던지는 경우
3 | - 예외는 잘못된 상황을 나타내고 처리되어야 함
4 | - 정보를 전달할 때 예외를 사용하는 것은 부적합
5 | - 예외가 전파되는 과정은 비교적 추적하기 어려움
6 | - 코틀린의 예외는 unchecked이기 때문에 처리가 보장되지 않음
7 | - 예외적인 상황을 처리하기 위해 만들어졌기 때문에 비교적 느리게 동작
8 | - `try-catch` 내부에 배치된 코드는 컴파일러 최적화가 제한됨
9 | ### `Failure`(sealed class)를 반환하는 경우
10 | - 명시적
11 | - 효율적
12 | - 간단하게 처리 가능
13 |
--------------------------------------------------------------------------------
/Clean Code/분기 처리 시 다른 조건에 의존적인 조건은 지양하자.md:
--------------------------------------------------------------------------------
1 | ## 분기 처리 시 다른 조건에 의존적인 조건은 지양하자
2 | - 다른 조건에 의존적인 조건을 사용할 경우, 상단에 작성되었던 코드가 제거되거나 수정되었을 때 문제가 발생할 수 있음
3 | - 같은 맥락으로 else문 보다는 보다 명시적인 조건을 작성하는 것이 안전함
4 |
--------------------------------------------------------------------------------
/Clean Code/사용자 정의 오류보다 표준 오류를 사용하자.md:
--------------------------------------------------------------------------------
1 | ## 사용자 정의 오류보다 표준 오류를 사용하자
2 | - 표준 라이브러리 오류가 범용적이기 때문에 보다 많은 개발자가 이해 가능
3 | - 널리 알려진 규약의 요소를 재사용하면 다른 사람들이 API를 더 쉽게 배우고 이해 가능
4 |
--------------------------------------------------------------------------------
/Clean Code/일반적인 알고리즘을 구현하는 경우 제네릭을 사용하자.md:
--------------------------------------------------------------------------------
1 | ## 일반적인 알고리즘을 구현하는 경우 제네릭을 사용하자
2 | ### 제네릭 함수(Generic Function)란?
3 | > 타입 아규먼트/파라미터를 사용하는 함수
4 | ### 제네릭을 활용한 알고리즘 함수의 장점
5 | - 컴파일러에 타입 정보를 제공하여 타입을 정확하게 추측할 수 있도록 도움
6 | - 안전하고 편한 프로그래밍 가능
7 | - 구체적인 타입의 서브타입만 사용하도록 제한 가능
8 | - 반복 처리 가능
9 | - EX) Any : nullable이 아닌 타입
10 |
--------------------------------------------------------------------------------
/Clean Code/타입 파라미터의 섀도잉을 피하자.md:
--------------------------------------------------------------------------------
1 | ## 타입 파라미터의 섀도잉을 피하자
2 | ### 섀도잉(Shadowing)이란?
3 | > 파라미터가 프로퍼티 또는 상위 파라미터와 같은 이름을 가지는 것
4 | - 코드 이해를 어렵게 만듦
5 | ### 파라미터와 프로퍼티 간의 섀도잉
6 | ```
7 | class Forest(val name: String) {
8 | fun addTree(name: String) { ... }
9 | }
10 | ```
11 | ### 클래스 타입 파라미터와 함수 타입 파라미터 간의 섀도잉
12 | ```
13 | class Forest {
14 | fun addTree(tree: T) { ... }
15 | }
16 | ```
17 | - 클래스와 함수의 타입 파라미터가 독립적으로 동작
18 | - 동일한 타입이라면 함수가 클래스 타입 파라미터를 사용하는 것이 적합
19 | - 독립적이라면 타입 파라미터를 다르게 네이밍하는 것이 적합
20 |
--------------------------------------------------------------------------------
/Coding Principles/로직과 알고리즘을 구분하자.md:
--------------------------------------------------------------------------------
1 | ## 로직과 알고리즘을 구분하자
2 | ### 로직(Logic)이란?
3 | > 프로그램의 동작 방식과 프로그램이 어떻게 보여지는지를 나타내는 정보
4 | - 시간이 지나며 계속해서 변화 가능
5 | ### 알고리즘(Algorithm)이란?
6 | > 원하는 동작을 하기 위한 공통 알고리즘
7 | - 한 번 정의된 후 크게 변하지 않음
8 |
--------------------------------------------------------------------------------
/Coding Principles/설계와 아키텍처 개념을 구분하자.md:
--------------------------------------------------------------------------------
1 | ## 설계와 아키텍처 개념을 구분하자
2 | ### 설계란?
3 | > 저수준의 구조이자 세부사항
4 | ### 아키텍처란?
5 | > 고수준의 결정사항
6 | ### 설계와 아키텍처의 관계
7 | - 두 개념 모두 소프트웨어 설계의 구성요소에 해당
8 | - 개별로 존재할 수 없으며 구분이 뚜렷하지 않음
9 | - 고수준에서 저수준으로 향하는 의사결정의 연속성만 존재
10 |
--------------------------------------------------------------------------------
/Compose/Compose 뷰에서 무거운 연산 작업을 하는 경우에는 remember에 key 값을 사용하자.md:
--------------------------------------------------------------------------------
1 | ## [Compose 뷰에서 무거운 연산 작업을 하는 경우에는 remember에 key 값을 사용하자](https://velog.io/@mraz3068/Jetpack-Compose-Top-20-mistakes-6-10)
2 | - 무거운 연산 작업에 Remember를 사용하는 경우, UI 성능 저하 발생 가능
3 | - remember에 key 값을 사용하여 key 값이 변경되는 경우에만 연산 수행
4 | - 주로 UI와 관련된 무거운 연산 작업에 적용
5 |
--------------------------------------------------------------------------------
/Compose/Compose 컴포넌트를 구현할 때 Material에 대한 의존성을 최소화하자.md:
--------------------------------------------------------------------------------
1 | ## Compose 컴포넌트를 구현할 때 Material에 대한 의존성을 최소화하자
2 | - Material 컴포넌트는 요구사항에 따라 커스텀이 어려운 상황이 발생하기 쉬움
3 | - EX) 컴포넌트 높이 변경
4 | - foundation만을 사용하여 직접 컴포넌트를 커스텀하는 것이 확장성이 높음
5 | - 하지만 이는 리소스가 많이 듦
6 | - [Material에서 제공하는 영역을 파악하고 적절하게 매핑하는 것을 권장](https://thdev.tech/android/2023/01/25/Android-Compose-Scaffold/)
7 | - 같은 맥락으로 실험적 단계의 accompanist 컴포넌트를 사용하기보다 직접 커스텀하여 사용하는 것을 권장
8 | - 부수적으로 기기 호환성, 의존성 관리 용이성 측면에서의 효과도 존재
9 |
--------------------------------------------------------------------------------
/Compose/Compose 화면 최초 진입 시 발생하는 사이드 이펙트는 LaunchedEffect(Unit)를 사용하지 말자.md:
--------------------------------------------------------------------------------
1 | ## Compose 화면 최초 진입 시 발생하는 사이드 이펙트는 LaunchedEffect(Unit)를 사용하지 말자
2 | - `LaunchedEffect(Unit)`을 통해 사이드 이펙트를 처리하는 경우, pop back stack이 발생할 때마다 재호출
3 | - 최초 진입 시 일회성으로 처리해야하는 사이드 이펙트는 다른 방법으로 처리 필요
4 | - EX) 화면 진입 애널리틱스 로그, 최초 서버통신
5 | ### 대안1. ViewModel `init` 블록에서 사이드 이펙트 발생
6 | ```
7 | init {
8 | postSideEffect(InitialScreenLaunch)
9 | }
10 | ```
11 | ### 대안2. 최초 진입 여부 State 활용
12 | ```
13 | LaunchedEffect(isInitialLaunch) {
14 | if (isInitialLaunch) { ... }
15 | }
16 | ```
17 |
--------------------------------------------------------------------------------
/Compose/Compose에서 블러 효과를 구현하는 다양한 방식을 구분하자.md:
--------------------------------------------------------------------------------
1 | ## Compose에서 블러 효과를 구현하는 다양한 방식을 구분하자
2 | ### 1. `Modifier.blur()` 속성 활용
3 | - Android 12(API 31) 이후 버전에서만 적용
4 | - 기기 호환성을 위해 버전 분기 필요
5 | ### 2. Render Script 기능 활용
6 | - Android 12(API 31) 이전 버전에서만 적용
7 | - 기기 호환성을 위해 버전 분기 필요
8 | - 블러 처리 영역 Bitmap으로 변환 필요
9 | ### 3. `graphicsLayer`의 `renderEffect` 설정
10 | - Android 12(API 31) 이후 버전에서만 적용
11 | - 기기 호환성을 위해 버전 분기 필요
12 | - Border가 뚜렷하도록 구현 가능
13 | - radius, edgeTreatment, alpha, compositingStrategy를 통해 세부 조정
14 |
--------------------------------------------------------------------------------
/Compose/Compose의 상태는 메인 스레드에서만 접근하자.md:
--------------------------------------------------------------------------------
1 | ## Compose의 상태는 메인 스레드에서만 접근하자
2 | - Jetpack Compose의 상태 시스템은 기본적으로 메인(UI) 스레드에서 작동하도록 설계
3 | - `SnapshotState`는 상태의 일관성을 유지하기 위해 특정 스레드에서의 접근을 강제
4 | - 메인 스레드가 아닌 다른 스레드에서 SnapshotState에 접근하는 경우 `IllegalArgumentException(Detected multithreaded access to SnapshotStateObserver)` 발생
5 |
--------------------------------------------------------------------------------
/Compose/DisposableEffect를 통해 생명주기에 따라 정리가 필요한 사이드 이펙트를 처리하자.md:
--------------------------------------------------------------------------------
1 | ## [`DisposableEffect`](https://developer.android.com/develop/ui/compose/side-effects#disposableeffect)를 통해 생명주기에 따라 정리가 필요한 사이드 이펙트를 처리하자
2 | - 메모리 누수 및 기타 성능 문제 예방의 효과
3 | ### `DisposableEffect` 동작 방식
4 | ```
5 | DisposableEffect(key) {
6 | // 정리가 필요한 효과 초기화
7 | onDispose {
8 | // 효과에 대한 정리 (Composable 제거 시 호출)
9 | }
10 | }
11 | ```
12 | - Composable이 UI 계층에서 제거될 때 `onDispose` 블록 호출
13 | - 초기에는 초기화 로직만 수행
14 | - key값이 바뀔 때마다 `onDispose` 블록 호출 후 초기화 로직 재호출
15 | - `onDispose` 블록은 항상 람다식의 최하단에 위치 (빌드 오류로 강제됨)
16 | ### 구현 예시
17 | ```
18 | @Composable
19 | fun HomeScreen(
20 | lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
21 | onStart: () -> Unit, // Send the 'started' analytics event
22 | onStop: () -> Unit // Send the 'stopped' analytics event
23 | ) {
24 | val currentOnStart by rememberUpdatedState(onStart)
25 | val currentOnStop by rememberUpdatedState(onStop)
26 |
27 | DisposableEffect(lifecycleOwner) {
28 | val observer = LifecycleEventObserver { _, event ->
29 | if (event == Lifecycle.Event.ON_START) {
30 | currentOnStart()
31 | } else if (event == Lifecycle.Event.ON_STOP) {
32 | currentOnStop()
33 | }
34 | }
35 | lifecycleOwner.lifecycle.addObserver(observer)
36 |
37 | onDispose {
38 | lifecycleOwner.lifecycle.removeObserver(observer)
39 | }
40 | }
41 | ...
42 | }
43 | ```
44 |
--------------------------------------------------------------------------------
/Compose/ImageVector와 PainterResource를 적절히 사용하자.md:
--------------------------------------------------------------------------------
1 | ## [ImageVector와 PainterResource를 적절히 사용하자](https://proandroiddev.com/imagevector-vs-painterresources-under-the-hood-6c002abbaaf1)
2 | ### ImageVector
3 | - Image 파라미터가 변화해도 ImageVector에 대한 리컴포지션 미발생
4 | - ImageVector 클래스는 `@Immutable`
5 | - imageVector 파라미터를 사용하는 Image는 `@NonRestartableComposable`
6 | - 벡터 이미지만 처리 가능
7 | ### PainterResource
8 | - Image 파라미터 변화 시, painter에 대한 리컴포지션 발생
9 | - 다양한 형식의 이미지(vector, bitmap, png, jpg) 사용 가능
10 | - `@NonRestartableComposable`을 활용한 커스텀 컴포넌트로 성능 문제 해결 가능
11 | ### 커스텀 ImagePainter 예시
12 | ```
13 | @NonRestartableComposable
14 | @Composable
15 | fun ImagePainter(
16 | painter: Painter,
17 | contentDescription: String?,
18 | modifier: Modifier = Modifier,
19 | alignment: Alignment = Alignment.Center,
20 | contentScale: ContentScale = ContentScale.Fit,
21 | alpha: Float = DefaultAlpha,
22 | colorFilter: ColorFilter? = null
23 | ) {
24 | val semantics = if (contentDescription != null) {
25 | Modifier.semantics {
26 | this.contentDescription = contentDescription
27 | this.role = Role.Image
28 | }
29 | } else {
30 | Modifier
31 | }
32 |
33 | // Explicitly use a simple Layout implementation here as Spacer squashes any non fixed
34 | // constraint with zero
35 | Layout(
36 | {},
37 | modifier.then(semantics).clipToBounds().paint(
38 | painter,
39 | alignment = alignment,
40 | contentScale = contentScale,
41 | alpha = alpha,
42 | colorFilter = colorFilter
43 | )
44 | ) { _, constraints ->
45 | layout(constraints.minWidth, constraints.minHeight) {}
46 | }
47 | }
48 | ```
49 |
--------------------------------------------------------------------------------
/Compose/Layout Inspector를 이용해 컴포넌트 트리 구조와 리컴포지션 상태를 파악하자.md:
--------------------------------------------------------------------------------
1 | ## Layout Inspector를 이용해 컴포넌트 트리 구조와 리컴포지션 상태를 파악하자
2 | - 리컴포지션 요청 횟수 및 변경 값, Skip 횟수 등 파악 가능
3 | - Component Tree를 통해 컴포넌트 트리 구조 파악 가능
4 |
--------------------------------------------------------------------------------
/Compose/Modifier 함수 간의 순서를 고려하자.md:
--------------------------------------------------------------------------------
1 | ## Modifier 함수 간의 순서를 고려하자
2 | - Modifier 함수의 순서에 따라 UI가 달라지므로 순서에 주의 필요
3 | - EX) `clickable()`, `background()`
4 | - `clickable()`의 ripple 효과를 제거하기 전에 테스트를 수행하여 터치 타겟을 확인하는 것이 좋음
5 |
--------------------------------------------------------------------------------
/Compose/Modifier.clip()으로 컴포넌트를 원하는 형태로 자르자.md:
--------------------------------------------------------------------------------
1 | ## [`Modifier.clip()`](https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier#(androidx.compose.ui.Modifier).clip(androidx.compose.ui.graphics.Shape))으로 컴포넌트를 원하는 형태로 자르자
2 | - `Modifier.clip()`으로 인자에 지정한 Shape에 따라 컴포넌트를 자를 수 있음
3 | ### [사용 예시](https://coil-kt.github.io/coil/compose/#asyncimage)
4 | ```
5 | AsyncImage(
6 | model = ImageRequest.Builder(LocalContext.current)
7 | .data("https://example.com/image.jpg")
8 | .build(),
9 | contentDescription = stringResource(R.string.description),
10 | contentScale = ContentScale.Crop,
11 | modifier = Modifier.clip(CircleShape)
12 | )
13 | ```
14 | - Coil 사용 시 로드 이미지를 컴포넌트 크기에 알맞는 Shape으로 잘라 사용하는 경우
15 |
--------------------------------------------------------------------------------
/Compose/Modifier.drawWithCache()를 통해 컴포저블 뷰를 Bitmap 이미지로 변환하자.md:
--------------------------------------------------------------------------------
1 | ## Modifier.drawWithCache()를 통해 컴포저블 뷰를 Bitmap 이미지로 변환하자
2 | ### 컴포저블 뷰를 Bitmap 이미지로 변환하는 방법
3 | 1. Picture 객체 생성
4 | ```
5 | val picture = remember { Picture() }
6 | ```
7 | 2. 컴포저블 뷰 영역 지정
8 | ```
9 | Modifier.drawWithCache {
10 | val width = this.size.width.toInt()
11 | val height = this.size.height.toInt()
12 | onDrawWithContent {
13 | val pictureCanvas =
14 | androidx.compose.ui.graphics.Canvas(
15 | receiptPicture.beginRecording(
16 | width,
17 | height,
18 | )
19 | )
20 | draw(this, this.layoutDirection, pictureCanvas, this.size) {
21 | this@onDrawWithContent.drawContent()
22 | }
23 | receiptPicture.endRecording()
24 |
25 | drawIntoCanvas { canvas ->
26 | canvas.nativeCanvas.drawPicture(
27 | receiptPicture
28 | )
29 | }
30 | }
31 | }
32 | ```
33 | 3. Bitmap 변환
34 | ```
35 | picture.toBitmap()
36 | ```
37 |
--------------------------------------------------------------------------------
/Compose/Modifier.offset()은 다른 컴포넌트와 독립적인 경우에만 사용하자.md:
--------------------------------------------------------------------------------
1 | ## `Modifier.offset()`은 다른 컴포넌트와 독립적인 경우에만 사용하자
2 | - `offset`은 Composition 단계 이후, Layout 단계에서 반영되기 때문에 다른 컴포넌트의 배치에 영향을 주지 않음
3 | - 따라서 offset으로 인해 생긴 빈 공간에 시스템 배경 색이 노출될 수 있어 유의
4 |
--------------------------------------------------------------------------------
/Compose/Modifier에 role을 명시하여 접근성을 개선하자.md:
--------------------------------------------------------------------------------
1 | ## Modifier에 role을 명시하여 접근성을 개선하자
2 | ### [Role](https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/Role)이란?
3 | - UI 요소의 역할을 명시적으로 설정할 때 사용되는 Modifier 속성
4 | - 접근성 서비스 사용자에게 해당 요소가 어떤 유형의 인터페이스 요소인지 알려주고, 커스터마이징을 가능하게 함
5 | - 역할이 정확하지 않고 모호한 경우, Role을 명시하지 않고 프레임워크가 자동으로 설정하도록 권장
6 | ### 활용 예시
7 | ```
8 | @Composable
9 | fun ExampleButton() {
10 | Box(
11 | modifier = Modifier
12 | .size(100.dp)
13 | .role(Role.Button)
14 | .clickable {
15 | ...
16 | },
17 | ) {
18 | ...
19 | }
20 | }
21 | ```
22 |
--------------------------------------------------------------------------------
/Compose/Nested Graph를 통해 복잡한 Compose 네비게이션 동작을 구현하자.md:
--------------------------------------------------------------------------------
1 | ## Nested Graph를 통해 복잡한 Compose 네비게이션 동작을 구현하자
2 | - Nested Graph를 통해 특정 기능과 관련된 화면들을 하나의 그래프 내부에 중첩되도록 구현 가능
3 | ### [Nested Graph의 장점](https://velog.io/@kej_ad/Android-Compsoe-Jetpack-Navigation-Nested-Graph%EC%99%80-Shared-ViewModel)
4 | - 모듈화 및 재사용성 증가
5 | - 캡슐화 효과
6 | - 흐름 관리 간소화
7 | - 코드 분리 및 가독성 증가
8 | - 복잡한 플로우 처리에 용이
9 |
--------------------------------------------------------------------------------
/Compose/PreviewParameterProvider를 통해 프리뷰의 상태별 파라미터를 주입하자.md:
--------------------------------------------------------------------------------
1 | ## [PreviewParameterProvider](https://developer.android.com/reference/kotlin/androidx/compose/ui/tooling/preview/PreviewParameterProvider)를 통해 프리뷰의 상태별 파라미터를 주입하자
2 | - 일반적으로 Preview는 필요한 State를 외부에서 주입하여 사용
3 | - 대량의 파라미터가 필요하거나 하나의 파일에 여러 Preview가 필요한 경우 코드량이 비효율적으로 증가
4 | ### PreviewParameterProvider 구현 코드
5 | ```
6 | interface PreviewParameterProvider {
7 | val values: Sequence
8 | val count get() = values.count()
9 | }
10 | ```
11 | - Preview에 주입할 샘플 데이터 타입을 시퀀스로 관리
12 | - 한 번의 주입으로 여러 개의 프리뷰 구현 가능
13 | ### 구현 예시
14 | ```
15 | class UserPreviewParameterProvider : PreviewParameterProvider {
16 | override val values = sequenceOf(
17 | User("UserA"),
18 | User("UserB"),
19 | User("UserC"),
20 | )
21 | }
22 |
23 | @Preview
24 | @Composable
25 | fun UserProfilePreview(
26 | @PreviewParameter(UserPreviewParameterProvider::class) user: User,
27 | ) {
28 | UserProfile(user)
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/Compose/Scaffold 하단에 독립적인 버튼이 있는 경우 bottomBar를 사용하자.md:
--------------------------------------------------------------------------------
1 | ## Scaffold 하단에 독립적인 버튼이 있는 경우 bottomBar를 사용하자
2 | ### bottomBar이 권장되는 이유
3 | - 스크롤 시 화면에 버튼이 남아있도록 간단하게 구현 가능
4 | - 쉽게 높이 제한 관리 가능
5 | - 구조적으로 명확한 UI 컴포넌트 설계 가능
6 | - Column 또는 Row, Box를 사용할 때보다 UI 계층을 단순하게 설계 가능
7 |
--------------------------------------------------------------------------------
/Compose/Scaffold에 paddingValues를 지정하여 BottomBar 크기를 고려하자.md:
--------------------------------------------------------------------------------
1 | ## Scaffold에 paddingValues를 지정하여 BottomBar 크기를 고려하자
2 | - Scaffold의 padding으로 내부 객체로 전달되는 paddingValues를 지정 권장
3 | - BottomBar의 크기를 고려하여 동작
4 |
--------------------------------------------------------------------------------
/Compose/Screen 단에서의 HiltViewModel 생성을 지양하자.md:
--------------------------------------------------------------------------------
1 | ## [Screen 단에서의 HiltViewModel 생성을 지양하자](https://velog.io/@mraz3068/Jetpack-Compose-Top-20-mistakes-6-10)
2 | ### ViewModel이 Screen 생성자에 주입되는 경우의 문제점
3 | - Screen 프리뷰 확인 불가능
4 | - 고립된 UI Test 불가능
5 | - Screen 사용 시 ViewModel을 초기화하기 위한 Hilt 문맥 파악 필요
6 |
--------------------------------------------------------------------------------
/Compose/Slot Pattern을 통해 컴포저블의 특정 영역을 외부에서 자유롭게 구성하자.md:
--------------------------------------------------------------------------------
1 | ## [Slot Pattern을 통해 컴포저블의 특정 영역을 외부에서 자유롭게 구성하자](https://speakerdeck.com/wisemuji/songdo-compose-ui-johab-simhwa?slide=17)
2 | ### [Slot Pattern](https://developer.android.com/develop/ui/compose/layouts/basics#slot-based-layouts)이란?
3 | > 컴포저블 람다를 함수 파라미터로 활용하여 특정 영역을 외부에서 구성할 수 있도록 하는 디자인 패턴
4 | - 부모 컴포넌트는 자식 컴포넌트의 구체적인 구현에 대한 의존성 없이 UI 구조 정의 가능
5 | - 컴포넌트 간 결합 제거의 효과
6 | - 대부분의 Compose Material Design Component에서 활용
7 | ### 구현 예시
8 | ```
9 | @Composable
10 | fun Scaffold(
11 | ...
12 | topBar: @Composable () -> Unit = {},
13 | bottomBar: @Composable () -> Unit = {},
14 | snackbarHost: @Composable () -> Unit = {},
15 | floatingActionButton: @Composable () -> Unit = {},
16 | content: @Composable (PaddingValues) -> Unit,
17 | ...
18 | ) { ... }
19 | ```
20 |
--------------------------------------------------------------------------------
/Compose/ViewModel을 공유하여 XML 기반의 뷰에서 ComposeView의 상태를 바꾸자.md:
--------------------------------------------------------------------------------
1 | ## ViewModel을 공유하여 XML 기반의 뷰에서 ComposeView의 상태를 바꾸자
2 | - ViewModel을 공유하여 기존 뷰 시스템에서 ComposeView의 상태 전환 가능
3 | - ComposeView에서 ViewModel을 전달 및 주입 받아 `collectAsState`로 값 수집
4 |
--------------------------------------------------------------------------------
/Compose/snapshotFlow로 State를 Flow로 변환하자.md:
--------------------------------------------------------------------------------
1 | ## `snapshotFlow`로 State를 Flow로 변환하자
2 | - snapshowFlow는 State 객체를 Cold Flow로 변환
3 | ### [사용 예시](https://developer.android.com/develop/ui/compose/layouts/pager?hl=ko#get-notified)
4 | ```
5 | LaunchedEffect(pagerState) {
6 | snapshotFlow { pagerState.currentPage }.collect { page ->
7 | // Do something with each page change
8 | }
9 | }
10 | ```
11 | - Flow가 제공하는 기능을 사용해야하는 경우
12 | - 페이지 변경 시 애널리틱스 이벤트 전송 등 변수의 변경사항을 관찰하고 반응해야하는 경우
13 |
--------------------------------------------------------------------------------
/Compose/throttleClickable을 통해 중복된 클릭 이벤트를 제한하자.md:
--------------------------------------------------------------------------------
1 | ## throttleClickable을 통해 중복된 클릭 이벤트를 제한하자
2 | - 기본으로 제공되는 `Modifier.clickable`은 짧은 시간 내 발생하는 중복 이벤트를 제한하지 않음
3 | - API 호출 등의 중복 이벤트로 인해 이슈 발생 가능
4 | - throttle 처리를 통해 특정 시간 내 발생한 중복 이벤트는 무시하도록 구현하여 안정성 개선 권장
5 | ### throttleClickable 구현 예시
6 | ```
7 | @Composable
8 | fun Modifier.throttleClickable(
9 | enabled: Boolean = true,
10 | onClickLabel: String? = null,
11 | role: Role? = null,
12 | throttleLevel: Long = DELAY_CLICK_LISTENER,
13 | onClick: () -> Unit,
14 | ): Modifier {
15 | val lastClickTime = remember { mutableLongStateOf(0L) }
16 |
17 | return this.clickable(
18 | enabled = enabled,
19 | onClickLabel = onClickLabel,
20 | role = role,
21 | ) {
22 | val currentTime: Long = System.currentTimeMillis()
23 |
24 | if (currentTime - lastClickTime.longValue >= throttleLevel) {
25 | onClick()
26 | lastClickTime.longValue = currentTime
27 | }
28 | }
29 | }
30 |
31 | @Composable
32 | fun Modifier.throttleClickable(
33 | interactionSource: MutableInteractionSource?,
34 | indication: Indication?,
35 | enabled: Boolean = true,
36 | onClickLabel: String? = null,
37 | role: Role? = null,
38 | throttleLevel: Long = DELAY_CLICK_LISTENER,
39 | onClick: () -> Unit,
40 | ): Modifier {
41 | val lastClickTime = remember { mutableLongStateOf(0L) }
42 |
43 | return this.clickable(
44 | interactionSource = interactionSource,
45 | indication = indication,
46 | enabled = enabled,
47 | onClickLabel = onClickLabel,
48 | role = role,
49 | ) {
50 | val currentTime: Long = System.currentTimeMillis()
51 |
52 | if (currentTime - lastClickTime.longValue >= throttleLevel) {
53 | onClick()
54 | lastClickTime.longValue = currentTime
55 | }
56 | }
57 | }
58 | ```
59 |
--------------------------------------------------------------------------------
/Compose/비전역적인 ModalBottomSheet 사용을 지양하자.md:
--------------------------------------------------------------------------------
1 | ## 비전역적인 ModalBottomSheet 사용을 지양하자
2 | - Fragment에서 ModalBottomSheet의 리소스를 관리하는 등 전역적이지 않은 ModalBottomSheet 사용은 지양할 것
3 | ### Fragment에서 ModalBottomSheet 사용하면 안 되는 이유
4 | - **생명주기 관리의 복잡성 증가**
5 | - 프래그먼트 상태 변경 시 ModalBottomSheet 생명주기 관리가 어려움
6 | - 화면 전환 시 ModalBottomSheet가 닫히거나 재생성될 수 있음
7 | - **백스택 처리의 어려움**
8 | - 프래그먼트의 백스택 관리 시스템과 모달 시트와의 상호작용이 충돌할 수 있음
9 | - **UI 복잡도 증가**
10 | - ModalBottomSheet가 특정 프래그먼트에 의존적으로 동작하여 재사용성과 유지보수성 문제 야기
11 | ### 대안
12 | - Activity 또는 ViewModel에서 ModalBottomSheet의 리소스 관리
13 | - Navigation Component 활용
14 |
--------------------------------------------------------------------------------
/Compose/사이드 이펙트가 발생하는 로직은 LaunchedEffect 스코프 내부에서 호출하자.md:
--------------------------------------------------------------------------------
1 | ## 사이드 이펙트가 발생하는 로직은 LaunchedEffect 스코프 내부에서 호출하자
2 | - 컴포저블에서 사이드 이펙트가 발생하는 것은 최대한 지양
3 | - 사이드 이펙트를 활용해야하는 경우도 존재
4 | - EX) 스낵바, 상태에 따라 화면 분기 등
5 | - LaunchedEffect 내부에서 로직을 호출하면 Recomposition으로 인한 중복 호출을 예방할 수 있음
6 |
--------------------------------------------------------------------------------
/Compose/액티비티를 탐색하여 참조해야하는 경우 baseContext를 활용하자.md:
--------------------------------------------------------------------------------
1 | ## 액티비티를 탐색하여 참조해야하는 경우 baseContext를 활용하자
2 | - LocalContext.current를 활용하는 경우 강제 타입 캐스팅으로 인한 문제 발생 가능
3 | - baseContext를 활용하여 액티비티를 탐색하는 경우 더 안정적
4 | ### [구현 예시](https://github.com/google/accompanist/blob/6611ebda55eb2948eca9e1c89c2519e80300855a/permissions/src/main/java/com/google/accompanist/permissions/PermissionsUtil.kt#L99)
5 | ```
6 | internal fun Context.findActivity(): Activity {
7 | var context = this
8 | while (context is ContextWrapper) {
9 | if (context is Activity) return context
10 | context = context.baseContext
11 | }
12 | throw IllegalStateException("Permissions should be called in the context of an Activity")
13 | }
14 | ```
15 | - while 문에 조건을 추가하여 무한루프 동작 예방
16 |
--------------------------------------------------------------------------------
/Compose/점진적으로 Compose로 마이그레이션하는 전략을 사용하자.md:
--------------------------------------------------------------------------------
1 | ## 점진적으로 Compose로 마이그레이션하는 전략을 사용하자
2 | ### 점진적 Compose Migration 단계
3 | 1. 신규 기능 Compose 개발
4 | - Base가 되는 ComposeActivity/ComposeFragment 활용
5 | 2. 기존 화면에 Compose 신규 컴포넌트 추가
6 | - ComposeView 활용
7 | 3. 기존 컴포넌트 Compose로 교체
8 | - AbstractComposeView 활용
9 | 4. ViewHolder Item Compose로 교체
10 | - 리스트 뷰에 대한 마이그레이션 부담 최소화 (특히, 여러 ViewHolder를 사용하는 경우)
11 | 5. 기존 화면 모두 Compose로 교체
12 | 6. Compose 미지원 뷰 호스팅
13 | - AndroidView Compose Interop API 활용
14 |
--------------------------------------------------------------------------------
/Data Structure/불변하면 Set, 순서가 상관 없으면 HashSet, 순서가 보장되어야 하면 MutableSet을 사용하자.md:
--------------------------------------------------------------------------------
1 | ## 불변하면 `Set`, 순서가 상관 없으면 `HashSet`, 순서가 보장되어야 하면 `MutableSet`을 사용하자
2 | ### Set
3 | - **Immutable** : 불변성을 보장할 수 있어 안정적
4 | ### HashSet
5 | - **Mutable** : 데이터를 추가하거나 제거해야하는 경우 사용
6 | - **순서 미보장** : 원소들의 순서가 상관 없는 경우 사용
7 | - **빠른 속도** : 해시 테이블을 기반으로 효율적인 데이터 검색, 삽입, 제거 가능
8 | ### MutableSet
9 | - **Mutable** : 데이터를 추가하거나 제거해야하는 경우 사용
10 | - **순서 보장** : 원소들의 순서를 보장해야하는 경우 사용
11 |
--------------------------------------------------------------------------------
/Database/DB 작업 시 Delete와 Update를 지양하자.md:
--------------------------------------------------------------------------------
1 | ## DB 작업 시 Delete와 Update를 지양하자
2 | - Delete와 Update 쿼리는 데이터베이스에 기록을 남기지 않고 데이터를 조작
3 | - 무결성 검사에 문제가 발생할 확률이 큼
4 | - 테이블을 분리하는 등의 대안을 통해 내역을 남기면서 데이터 관리 필요
5 |
--------------------------------------------------------------------------------
/Git/Rebase와 Merge를 적절히 사용하자.md:
--------------------------------------------------------------------------------
1 | ## [Rebase와 Merge를 적절히 사용하자](https://devowen.com/430)
2 | ### Merge
3 | > 두 개 이상의 브랜치에서 작업한 히스토리가 모두 **보존**되도록 합치는 작업
4 | - 장점
5 | - 단순한 사용법
6 | - 히스토리 기록에 용이
7 | - 한번의 충돌 해결로 머지 가능
8 | - 단점
9 | - 커밋 개수가 기하급수적으로 증가
10 | - 커밋 로그의 가독성 저하
11 | ### Rebase
12 | > 브랜치의 베이스를 재설정하여 다시 커밋을 **재작성**하는 작업
13 | - 장점
14 | - 커밋 로그의 가독성 증가
15 | - 간결하게 히스토리 관리 가능
16 | - 단점
17 | - 커밋을 재작성할 때마다 각각의 커밋에서 충돌 해결 필요
18 | - 이미 원격에 저장된 커밋과 로컬의 커밋이 충돌할 가능성 존재
19 | - 데이터 손실 발생 가능
20 | - 규칙
21 | - 원격에 반영된 커밋은 리베이스하지 말자
22 | - 리베이스 전에 백업 브랜치를 만들자
23 |
24 | ```
25 | 각 방식의 장단점을 파악하고 프로젝트에 적합한 브랜치 컨벤션을 사용할 것
26 | 실무에서는 깔끔한 커밋 로그 관리를 위해 rebase가 권장되는 경우가 많음
27 | ```
28 |
--------------------------------------------------------------------------------
/Github/Github 라이선스 종류를 구분하자.md:
--------------------------------------------------------------------------------
1 | ## [Github 라이선스 종류를 구분하자](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository#choosing-the-right-license)
2 | - 자세한 내용은 [오픈소스SW 라이선스 종합정보시스템의 라이선스 비교표](https://olis.or.kr/license/compareGuide.do?source=post_page-----ae29925e8ff4--------------------------------) 참고
3 | ### Apache 2.0
4 | - 사용 시 저작권 명시 필요
5 | - 구글 안드로이드 오픈소스의 기본 라이선스
6 | - EX) Kotlin
7 | ### MIT
8 | - 사용 시 저작권 명시 필요
9 | - 제약 조건이 느슨하여 많은 오픈소스에서 선택
10 | - EX) React, Angular, Vue
11 | ### BSD
12 | - 사용 시 조건부로 저작권 명시 필요
13 | - EX) Nginx
14 | ### GPL
15 | - 사용 시 저작권 명시 필요
16 | - 사용 시 전체 소스코드 무료 공개 필요
17 |
--------------------------------------------------------------------------------
/Language/Java/Okhttp WebSocket을 통해 소켓 통신을 구현하자.md:
--------------------------------------------------------------------------------
1 | ## OkHttp [WebSocket](https://square.github.io/okhttp/3.x/okhttp/okhttp3/WebSocket.html)을 통해 소켓 통신을 구현하자
2 | ### 1. Android Studio 설정
3 | - AndroidManifest 인터넷 권한 허용
4 | ```
5 |
6 | ```
7 | - OkHttp 의존성 추가
8 | ```
9 | dependencies {
10 | implementation 'com.squareup.okhttp3:okhttp:{latest_version}'
11 | ...
12 | }
13 | ```
14 | ### 2. Listener 구현체 생성
15 | ```
16 | class TestSocketListener : WebSocketListener() {
17 | override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
18 | super.onClosed(webSocket, code, reason)
19 | }
20 |
21 | override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
22 | super.onClosing(webSocket, code, reason)
23 | }
24 |
25 | override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
26 | super.onFailure(webSocket, t, response)
27 | }
28 |
29 | override fun onMessage(webSocket: WebSocket, text: String) {
30 | super.onMessage(webSocket, text)
31 | }
32 |
33 | override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
34 | super.onMessage(webSocket, bytes)
35 | }
36 |
37 | override fun onOpen(webSocket: WebSocket, response: Response) {
38 | super.onOpen(webSocket, response)
39 | }
40 | }
41 | ```
42 | - `onClosed` : 소켓 연결 종료 시 호출되는 콜백 함수
43 | - `onClosing` : 원격 서버로부터 소켓 연결 종료 메시지 수신 시 호출되는 콜백 함수
44 | - `onFailure` : 메시지를 읽는 과정 또는 네트워크 상 오류로 인해 소켓이 닫히는 경우 호출되는 콜백 함수
45 | - `onMessage` : 원격 서버로부터 메시지를 수신한 경우 호출되는 콜백 함수
46 | - `onOpen` : 원격 서버와 소켓 연결이 완료된 경우 호출되는 콜백 함수
47 | ### 3. WebSocket 객체 생성
48 | ```
49 | private lateinit var webSocket: WebSocket
50 |
51 | fun openSocket() {
52 | val request = Request
53 | .Builder()
54 | ...
55 | .url(SOCKET_BASE_URL)
56 | .build()
57 |
58 | val listener: WebSocketListener = TestSocketListener()
59 |
60 | webSocket = okHttpClientBuilder.newWebSocket(
61 | request = request,
62 | listener = listener,
63 | )
64 | }
65 |
66 | fun sendMessage(message: String) {
67 | webSocket.send(message)
68 | }
69 |
70 | fun closeSocket() {
71 | webSocket.close(1000, null)
72 | }
73 | ```
74 | - `newWebSocket` : 웹 소켓 객체 생성
75 | - `send` : 메시지 전송 요청
76 | - `close` : 소켓 닫기 요청
77 |
78 | ```
79 | 위와 같은 동작을 기반으로 프로덕트와 아키텍처에 적합하게 사용할 것
80 | ```
81 |
--------------------------------------------------------------------------------
/Language/Kotlin/@DslMarker를 활용하여 외부 리시버 사용을 제한하자.md:
--------------------------------------------------------------------------------
1 | ## @DslMarker를 활용하여 외부 리시버 사용을 제한하자
2 | ### 리시버를 명시적으로 참조해야하는 이유
3 | - 가독성 및 코드 간략화를 위해 리시버를 제거하는 것이 일반적
4 | - 스코프 내부에 둘 이상의 리시버가 존재하는 경우 등 리시버를 명시적으로 참조하는 것이 안정적
5 | - 같은 맥락으로 apply 보다 also, let을 사용하여 명시적으로 리시버를 지정하는 것이 안정적
6 | - Nullable 처리 시 적합
7 | - 어떤 리시버의 함수인지 명확하게 명시하여 가독성 향상
8 | ### DSL Marker란?
9 | > 외부 리시버 사용을 제한하는 메타 어노테이션(어노테이션을 위한 어노테이션)
10 | ```
11 | @DslMarker
12 | annotation class HtmlDsl
13 |
14 | fun table(f: TableDsl.() -> Unit) { ... }
15 |
16 | @HtmlDsl
17 | class TableDsl { ... }
18 | ```
19 | - 가장 가까운 리시버만 사용 강제
20 | - 명시적인 외부 리시버 사용 제한
21 |
--------------------------------------------------------------------------------
/Language/Kotlin/@Throws를 사용하는 경우.md:
--------------------------------------------------------------------------------
1 | ## `@Throws`를 사용하는 경우
2 | - 함수가 특정 Exception을 `throw`하는 것을 명시하고 강제해야하는 경우
3 | ### 사용 예시
4 | ```
5 | @Throws(IOException::class)
6 | fun test() { ... }
7 | ```
8 | ### 컴파일 코드
9 | ```
10 | public static final void test() throws IOException { ... }
11 | ```
12 |
--------------------------------------------------------------------------------
/Language/Kotlin/Collection과 Sequence를 적절히 사용하자.md:
--------------------------------------------------------------------------------
1 | ## [Collection과 Sequence를 적절히 사용하자](https://bcp0109.tistory.com/359)
2 | ### Lazy Evaluation이란?
3 | > 특정 로직 또는 연산을 즉시 수행하지 않고 실제 사용되기 전까지 미루는 것
4 | - 중간 단계의 결과 생략
5 |
6 | ### Eager Evaluation
7 | > 특정 로직 또는 연산을 연산을 즉시 수행하는 것
8 |
9 | ### Collection과 Sequence의 연산 방식
10 | - Collection : Eager Evaluation
11 | - Sequence : Lazy Evaluation
12 |
13 | ### Sequence를 사용해야하는 경우
14 | - 불필요한 연산을 생략하여 성능을 향상시켜야 하는 경우
15 |
16 | ### Collection을 사용해야하는 경우
17 | - 적은 데이터 또는 연산이 단순한 컬렉션을 처리해야하는 경우
18 | - Sequence는 오버헤드 발생 가능
19 |
--------------------------------------------------------------------------------
/Language/Kotlin/Coroutine/Mutex를 통해 자원에 대한 동시 접근을 제한하자.md:
--------------------------------------------------------------------------------
1 | ## [Mutex](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/)를 통해 자원에 대한 동시 접근을 제한하자
2 | - Mutex의 상태 : locked, unlocked
3 | - 재진입 불가능한 성질(Non-Reentrant)을 가짐
4 | - locked 상태에서 `lock` 호출 시 suspend 처리
5 | - 코루틴 중단 시 mutex 해제가 불가능
6 | - 락을 두 번 걸지 않고 중단 함수를 호출하지 않도록 주의
7 | ### Mutex 제공 함수
8 | - `lock` : lock this mutex
9 | - `unlock` : unlock this mutex
10 | - `withLock` : `lock`와 `unlock`을 자동적으로 수행하는 스코프 함수
11 | - 예외 발생 시 자동적으로 `unlock` 처리하여 안정적으로 동시 접근 문제 해결 가능
12 |
--------------------------------------------------------------------------------
/Language/Kotlin/Coroutine/ViewModelScope 대신 CoroutineScope를 사용해야하는 경우.md:
--------------------------------------------------------------------------------
1 | ## ViewModelScope 대신 CoroutineScope를 사용해야하는 경우
2 | - ViewModelScope는 ViewModel이 파괴될 경우 내부 동작이 취소됨
3 | - 내부적으로 `onClear()`에서 할당된 job을 `cancel()`함
4 | - ViewModel이 파괴된 후에도 태스크가 진행되어야 한다면 CoroutineScope를 사용하는 것이 적합
5 |
--------------------------------------------------------------------------------
/Language/Kotlin/Coroutine/withTimeout()으로 코루틴 동작에 타임아웃을 설정하자.md:
--------------------------------------------------------------------------------
1 | ## `withTimeout()`으로 코루틴 동작에 타임아웃을 설정하자
2 | ### 구현 코드
3 | ```
4 | suspend fun withTimeout(
5 | timeMillis: Long,
6 | block: suspend CoroutineScope.() -> T
7 | ): T
8 | ```
9 | - 타임아웃 발생 시 `TimeoutCancellationException`을 던짐
10 | ### 사용 예시
11 | ```
12 | withTimeout(500L) {
13 | repository.getDataFromServer()
14 | }
15 | ```
16 | - API 호출에 시간 제한을 설정하는 경우
17 | - 백그라운드 동작에 시간 제한을 설정하는 경우
18 |
--------------------------------------------------------------------------------
/Language/Kotlin/Coroutine/코루틴의 데이터 공유 상태로 인한 문제를 해결하는 다양한 방법을 구분하자.md:
--------------------------------------------------------------------------------
1 | ## 코루틴의 데이터 공유 상태로 인한 문제를 해결하는 다양한 방법을 구분하자
2 | ### `synchronized` 블록 또는 동기화된 컬렉션
3 | - 블록 내부에서 중단 함수 사용 불가
4 | - 데드락 발생 가능
5 | ### 원자값
6 | - 하나의 연산에서만 원자성을 가지므로 전체 연산에 대해 원자성을 보장하지 않음
7 | ### 싱글 스레드로 제한된 디스패처 (권장)
8 | - 멀티 스레딩의 이점 활용 불가
9 | ### Mutex (권장)
10 | - 코루틴 중단 시 Mutex 해제 불가
11 | ### Semaphore
12 | - 둘 이상의 접근 허용 가능
13 | - 공유 상태로 인해 생기는 문제를 해결할 수는 없지만 동시 요청 처리 수 제한에 용이
14 |
15 | ```
16 | 각 방법의 특징을 파악하여 상황에 적합한 방식을 선택할 것
17 | ```
18 |
--------------------------------------------------------------------------------
/Language/Kotlin/Enum 타입에 대해 분기 처리가 복잡해지는 경우 Enum 클래스의 프로퍼티를 통해 상태를 캡슐화하자.md:
--------------------------------------------------------------------------------
1 | ## Enum 타입에 대해 분기 처리가 복잡해지는 경우 Enum 클래스에 프로퍼티를 추가하여 상태를 캡슐화하자
2 | - Enum의 함수 또는 프로퍼티를 활용하여 상수 특성 캡슐화 가능
3 | - 가독성 및 유지보수성 증가 효과
4 | - 경우에 따라 지나치게 복잡한 enum class는 sealed class로 리팩토링 권장
5 | ### 프로퍼티 추가 없이 Enum 타입을 분기 처리하는 예시
6 | ```
7 | Text(
8 | text = "text",
9 | color = if (EnumState.Selected) Green else Gray,
10 | )
11 | ```
12 | ### Enum 클래스에 프로퍼티를 추가하여 상태를 캡슐화하는 예시
13 | ```
14 | Text(
15 | text = "text",
16 | color = EnumState.Selected.color,
17 | )
18 | ```
19 |
--------------------------------------------------------------------------------
/Language/Kotlin/Object를 사용하는 이유.md:
--------------------------------------------------------------------------------
1 | ## Object를 사용하는 이유
2 | ### 컴파일 코드
3 | ```
4 | public final class Singleton {
5 | public static final Singleton INSTANCE;
6 |
7 | static {
8 | Singleton var0 = new Singleton();
9 | INSTANCE = var0;
10 | }
11 | }
12 | ```
13 | - 내부적으로 INSTANCE 객체가 생성되어 간편하게 싱글톤 구현 가능
14 | - Kotlin 파일에서 인스턴스를 참조하지 않고도 싱글톤 리소스에 접근 가능 (가독성 개선)
15 | - Java 파일에서는 `Singleton.INSTANCE`을 통해 인스턴스 접근 가능
16 |
--------------------------------------------------------------------------------
/Language/Kotlin/Spread 연산자(*)를 이용해 배열이나 컬렉션 요소를 개별 인자로 변환하자.md:
--------------------------------------------------------------------------------
1 | ## Spread 연산자(`*`)를 이용해 배열이나 컬렉션 요소를 개별 인자로 변환하자
2 | ### [Spread 연산자(`*`)](https://kotlinlang.org/docs/functions.html#named-arguments)란?
3 | > 배열이나 컬렉션 앞에 별 기호(`*`)를 붙여 요소들을 개별 인자로 나열하는 Kotlin 연산자
4 | - 함수 호출, 배열 조작 등의 동작을 가독성 높은 코드로 작성 가능
5 | ### 사용 예시
6 | - 여러 인자를 전달하는 함수를 호출하는 경우
7 | ```
8 | val numbers = arrayOf(1, 2, 3, 4, 5)
9 | println(*numbers)
10 |
11 | // 출력 : 1 2 3 4 5
12 | ```
13 | - 배열을 결합하는 경우
14 | ```
15 | val arrayA = arrayOf(1, 2, 3)
16 | val arrayB = arrayOf(*arrayA, 4, 5)
17 | ```
18 | - `varargs` 타입의 인자를 전달하는 경우
19 | ```
20 | getString("message", *formatArgs.toTypedArray())
21 | ```
22 | ### 주의 사항
23 | - 배열 복사를 기반으로 동작하기 때문에 메모리 사용량 증가에 유의
24 | - 큰 배열이나 컬렉션을 스프레드 하는 경우 성능에 주의
25 | - 이 외에도 성능에 민감한 기능을 구현하는 경우 다른 방법 고려 권장
26 |
--------------------------------------------------------------------------------
/Language/Kotlin/Unit?을 리턴하지 말자.md:
--------------------------------------------------------------------------------
1 | ## `Unit?`을 리턴하지 말자
2 | - Unit?은 Boolean으로 대체하는 것이 적합
3 | ### `Unit?`을 처리하는 코드
4 | ```
5 | isValid ?: return
6 | ```
7 | - 모호한 표현으로 오해의 소지 있음
8 | - 예측하기 어려운 오류 발생 가능
9 |
10 | ### `Boolean`을 처리하는 코드
11 | ```
12 | if (!isValid) return
13 | ```
14 | - 개선된 가독성과 안정성
15 |
--------------------------------------------------------------------------------
/Language/Kotlin/close 대신 use를 사용하여 리소스를 해제하자.md:
--------------------------------------------------------------------------------
1 | ## `close` 대신 `use`를 사용하여 리소스를 해제하자
2 | ### 명시적으로 해제해야하는 리소스
3 | - `AutoCloseable`을 상속받는 `Closeable` 인터페이스의 구현체 해당
4 | - 가비지 컬렉터는 속도가 느리기 때문에 리소스 유지 비용을 최소화하기 위해 명시적으로 해제 필요
5 | - EX) `InputStream`, `OutputStream`, `Connection`, `FileReader`, `BufferedReader`, `CSSParser`, `Socket`, `Scanner`
6 | ### `close`를 사용하는 경우
7 | ```
8 | try {
9 | // reader 리소스 사용
10 | } finally {
11 | reader.close()
12 | }
13 | ```
14 | - 복잡성 높음
15 | - 리소스를 닫을 때 발생하는 예외 처리 불가능
16 | - 하나의 오류만 전파 가능
17 | ### `use`를 사용하는 경우
18 | ```
19 | reader.use { reader ->
20 | // reaer 리소스 사용
21 | }
22 | ```
23 | - 가독성 개선
24 | - 모든 오류 전파 가능
25 | ### `useLines`를 사용하는 경우
26 | ```
27 | File(path).useLines { lines ->
28 | // 파일 한 줄에 대한 처리
29 | }
30 | ```
31 | - 파일을 한 줄씩 읽어 들이는 경우 사용
32 |
--------------------------------------------------------------------------------
/Language/Kotlin/inferred 타입으로 리턴하지 말자.md:
--------------------------------------------------------------------------------
1 | ## inferred 타입으로 리턴하지 말자
2 | - inferred 타입은 불필요한 제한을 증가시키고 예측하지 못한 결과를 야기할 수 있음
3 | - 외부 API 구현 시 명시적으로 확인 가능한 리턴 타입을 반환하는 것이 안정적
4 | - 타입을 확실하게 지정해야 하는 경우, 명시적으로 타입 지정하는 것이 원칙
5 |
6 | ### 예시
7 | ```
8 | val DEFAULT_CAR: Car = Fiat126P()
9 |
10 | interface CarFactory {
11 | fun produce(): Car = DEFAULT_CAR
12 | }
13 | ```
14 | - `DEFAULT_CAR` 타입이 명시적이라는 판단으로 함수의 리턴 타입 제거
15 | - `DEFAULT_CAR`는 타입 추론이 될 것이라는 판단으로 상수 타입 제거
16 | ```
17 | val DEFAULT_CAR = Fiat126P()
18 |
19 | interface CarFactory {
20 | fun produce() = DEFAULT_CAR
21 | }
22 | ```
23 | - Fiat126P 외의 자동차 생산 불가능
24 | - 특히, 라이브러리 또는 모듈 내부 코드를 수정할 수 없는 경우 명시적인 타입으로 수정 불가능
25 |
--------------------------------------------------------------------------------
/Language/Kotlin/inner class 대신 nested class를 사용하자.md:
--------------------------------------------------------------------------------
1 | ## inner class 대신 nested class를 사용하자
2 | - inner class는 outer class를 참조하고 있기 때문에, 메모리 누수가 발생 가능
3 | - outer class를 참조해야하는 경우에는 nested class의 인자로 값을 넘겨받아 사용할 것
4 |
--------------------------------------------------------------------------------
/Language/Kotlin/let을 적절한 상황에 사용하자.md:
--------------------------------------------------------------------------------
1 | ## `let`을 적절한 상황에 사용하자
2 | ### `let`을 지양해야 하는 경우
3 | - 불변 변수의 null check : 디컴파일한 코드를 보면 변수 리소스만 늘어남
4 | - 변수의 내부 변수 값을 설정하는 경우 : `run`이 적합
5 | - `let`을 사용한 변수에 chaining을 해야하는 경우 : `also`가 적합
6 | ### `let`을 사용하면 좋은 경우
7 | - mutable 변수의 null check
8 | - 스코프 내부에서 외부 스코프의 값을 적용해야 하는 경우
9 | - 불필요한 nullable chain을 제거하는 경우
10 | - 연산을 아규먼트 처리 후로 이동시킬 때
11 | - 데코레이터를 사용해서 객체를 wrapping 할 때
12 |
--------------------------------------------------------------------------------
/Language/Kotlin/require 함수를 통해 함수 argument에 제한을 걸자.md:
--------------------------------------------------------------------------------
1 | ## require 함수를 통해 함수 argument에 제한을 걸자
2 | ### require 함수란?
3 | > 제한을 확인하고 만족하지 못할 경우 예외를 던지는 조건 함수
4 | - 매개변수의 값이 true인지 확인하고, false면 lazyMessage를 호출하고 `throw IllegalArgumentException`
5 | - 입력 유효성 검사 코드라 함수의 가장 앞부분에 배치되어 쉽게 확인 가능
6 | - 코드를 읽지 않는 사람을 위해 제한에 대한 별도 표시 필요
7 | ### 함수 원형
8 | ```
9 | public inline fun require(value: Boolean): Unit {
10 | contract {
11 | returns() implies value
12 | }
13 | require(value) { "Failed requirement." }
14 | }
15 | ```
16 | ```
17 | public inline fun require(value: Boolean, lazyMessage: () -> Any): Unit {
18 | contract {
19 | returns() implies value
20 | }
21 | if (!value) {
22 | val message = lazyMessage()
23 | throw IllegalArgumentException(message.toString())
24 | }
25 | }
26 | ```
27 |
--------------------------------------------------------------------------------
/Language/Kotlin/runCatching, mapCatching, recoverCatching을 통해 안전하게 예외를 처리하자.md:
--------------------------------------------------------------------------------
1 | ## runCatching, mapCatching, recoverCatching을 통해 안전하게 예외를 처리하자
2 | - Result 클래스를 활용한 함수를 통해 간결하게 성공 또는 실패에 대한 처리 가능
3 | ### runCatching
4 | > 코드 블록을 실행하고 발생한 예외를 Result 객체로 캡슐화
5 | - 성공 : `Result.success(value)` 반환
6 | - 실패 : `Result.failure(exception)` 반환
7 | ```
8 | runCatching {
9 | ...
10 | }.onSuccess { value ->
11 | ...
12 | }.onFailure { exception ->
13 | ...
14 | }
15 | ```
16 | ### mapCatching
17 | > Result 객체의 값을 변환하여 반환하며, 예외 발생 시 실패 상태로 처리
18 | ```
19 | runCatching {
20 | ...
21 | }.mapCatching { value ->
22 | value.toInt()
23 | }.onSuccess { value ->
24 | ...
25 | }.onFailure { exception ->
26 | ...
27 | }
28 | ```
29 | ### recoverCatching
30 | > Result가 실패 상태일 때 예외 처리 후 새 값을 변환하며, 예외 처리 중 다시 예외 발생 시 실패 상태 유지
31 | ```
32 | runCatching {
33 | ...
34 | }.recoverCatching { exception ->
35 | ...
36 | value
37 | }.onSuccess { value ->
38 | ...
39 | }.onFailure { exception ->
40 | ...
41 | }
42 | ```
43 | ### 활용 예시
44 | ```
45 | fun parseNumber(input: String): Int {
46 | return runCatching { input.toInt() }
47 | .mapCatching { it * 2 }
48 | .recoverCatching { 0 }
49 | .getOrThrow() // 성공 시 값 반환, 실패 시 예외 던짐
50 | }
51 | ```
52 | - input이 "123"인 경우 : 246 반환
53 | - input이 "abc"인 경우 : 0 반환
54 |
--------------------------------------------------------------------------------
/Language/Kotlin/sealed class와 enum class를 적절히 사용하자.md:
--------------------------------------------------------------------------------
1 | ## sealed class와 enum class를 적절히 사용하자
2 | - 이전에는 enum class를 사용할 경우, 불필요한 else문 처리가 필요하여 sealed class 사용이 권장됨
3 | - 최신 코틀린 버전에서는 enum class도 else문 처리를 할 필요가 없으며, 안드로이드 스튜디오에서도 경고를 노출하지 않음
4 | ```
5 | 클래스에 추가 정보를 저장하지 않거나 여러 인스턴스를 생성하지 않아도 되는 경우에는 enum class를 사용하는 것이 적합
6 | ```
7 |
--------------------------------------------------------------------------------
/Language/Kotlin/variance 한정자를 통해 제너릭의 타입 간 관련성을 관리하자.md:
--------------------------------------------------------------------------------
1 | ## variance 한정자를 통해 제너릭의 타입 간 관련성을 관리하자
2 | ### 한정자가 없는 타입 파라미터 `T`
3 | - **Invariant(불공변성)** : 타입 간 관련성이 없음
4 | ```
5 | class Cup
6 | val anys: Cup = Cup() // 오류
7 | val nothings: Cup = Cup() // 오류
8 | ```
9 | ### `out` 한정자가 있는 타입 파라미터 `T`
10 | - **Convariant(공변성)** : `T` 타입이 서브타입인 경우 서브타입에 해당
11 | ```
12 | class Cup
13 | val anys: Cup = Cup() // OK
14 | val nothings: Cup = Cup() // 오류
15 | ```
16 | ### `in` 한정자가 있는 타입 파라미터 `T`
17 | - **Contravariant(반공변성)** : `T` 타입이 서브타입인 경우 슈퍼타입에 해당
18 | ```
19 | class Cup
20 | val anys: Cup = Cup() // 오류
21 | val nothings: Cup = Cup() // OK
22 | ```
23 |
--------------------------------------------------------------------------------
/Language/Kotlin/가변성을 제한하자.md:
--------------------------------------------------------------------------------
1 | ## 가변성을 제한하자
2 | ### 가변성의 문제점
3 | - 상태에 대한 코드 이해 및 변경 추적, 디버깅이 어려움
4 | - 상태에 대한 일관성 보장이 어려움
5 | - 멀티스레드 환경에서 적절한 동기화 필요
6 | - 모든 상태에 대한 테스트가 어려움
7 | ```
8 | 결론 : 가변성 증가 -> 일관성 문제 발생 및 코드 복잡성 증가
9 | ```
10 | ### Kotlin에서 가변성을 제한하는 방식
11 | - 읽기 전용 프로퍼티 `val`
12 | - mutable 컬렉션과 immutable(읽기 전용) 컬렉션 구분
13 | - 데이터 클래스 `copy()` 활용
14 |
--------------------------------------------------------------------------------
/Language/Kotlin/단발성 이벤트 처리 시 Flow 대신 Channel을 사용하자.md:
--------------------------------------------------------------------------------
1 | ## 단발성 이벤트 처리 시 Flow 대신 Channel을 사용하자
2 | ### Flow vs Channel
3 | - Flow : 상태와 지속적 데이터 흐름에 적합
4 | - 여러 구독자 간 데이터 공유와 재처리 가능
5 | - [Channel](https://kotlinlang.org/docs/channels.html) : 단발성 이벤트 전달에 최적화
6 | - Buffer가 가득 차면 작업이 중단되고 구독자가 나타날 때까지 기다림
7 | - 이벤트의 한 번 처리와 소비를 보장
8 | ### Channel을 통해 단발성 이벤트 처리 시 효과
9 | - 의도치 않은 이벤트의 중복 처리 및 누락 예방
10 |
--------------------------------------------------------------------------------
/Language/Kotlin/멤버 확장 함수 사용을 지양하자.md:
--------------------------------------------------------------------------------
1 | ## 멤버 확장 함수 사용을 지양하자
2 | - 확장 함수는 클래스 멤버 또는 인터페이스 내부에 정의 가능
3 | ### 멤버 확장 함수의 문제점
4 | - 가시성 제한의 효과가 없음
5 | - 확장 함수의 사용 형태를 어렵게 만듦
6 | - 가시성을 제한하기 위해서는 한정자 사용 권장(클래스 내부에 확장 함수를 배치한다고 외부에서의 사용을 제한하는 것이 아님)
7 | - 레퍼런스(`::`) 미지원
8 | - 리시버가 여러 개인 경우, 어떤 리시버가 선택될지 혼동 야기
9 | - 외부 클래스를 리시버로 받는 경우, 동작이 명확하지 않음
10 | - 경험이 적은 개발자의 경우, 직관적이지 않을 수 있음
11 |
--------------------------------------------------------------------------------
/Language/Kotlin/변수 타입이 명확하지 않은 경우 확실하게 지정하자.md:
--------------------------------------------------------------------------------
1 | ## 변수 타입이 명확하지 않은 경우 확실하게 지정하자
2 | - 일반적으로 타입 추론은 코드 간략화 및 가독성 향상 등의 장점을 지니지만 남용되어서는 안 됨
3 | - 타입이 명확하지 않은 경우 확실하게 지정하는 것이 효율적
4 | ### 변수 타입 지정의 장점
5 | - 가독성 향상
6 | - 반환 타입을 파악하기 위한 공수 절약 가능
7 | - 코드 정의로 쉽게 이동할 수 없는 환경에서의 가독성 향상
8 | - EX) Github
9 | - 플랫폼 타입, inferred 타입 처리 등의 측면에서 안정성 향상
10 | ### 타입이 불명확한 코드
11 | ```
12 | val data = getSomeData()
13 | ```
14 | ### 타입을 명확하게 지정한 코드
15 | ```
16 | val data: UserData = getSomeData()
17 | ```
18 |
--------------------------------------------------------------------------------
/Language/Kotlin/변수의 스코프를 최소화하자.md:
--------------------------------------------------------------------------------
1 | ## 변수의 스코프를 최소화하자
2 | ### 요소의 스코프란?
3 | > 요소를 볼 수 있는(visible) 컴퓨터 프로그램 영역
4 | - 변수의 스코프를 최소화하는 것이 효율적
5 | - 변수를 사용되는 반복문 내부에 작성 권장
6 | - 프로퍼티보다 지역 변수 권장
7 | - 변수를 정의할 때 초기화하는 것이 권장
8 | ### 변수의 스코프를 최소화해야하는 이유
9 | - 요소가 존재하는 시점 파악과 프로그램 추적 및 관리에 용이 → 가독성 및 안정성 증가
10 | - mutable 프로퍼티의 변경 추적에 용이 → 유지보수성 증가
11 | - 다른 개발자에 의해 변수가 잘못 사용될 가능성 제한
12 | - 람다의 변수 캡처링에 의한 문제 예방
13 |
--------------------------------------------------------------------------------
/Language/Kotlin/성능이 중요한 경우에 기본 자료형 배열을 사용하자.md:
--------------------------------------------------------------------------------
1 | ## 성능이 중요한 경우에 기본 자료형 배열을 사용하자
2 | - 일반적인 경우, List를 사용하는 것이 권장됨
3 | - 다양한 기능 제공
4 | - 많은 곳에 쉽게 사용될 수 있음
5 | - 성능이 중요한 경우, 기본 자료형을 활용하느 배열 및 Array를 사용하는 것이 권장됨
6 | - 라이브러리 개발자, 게임 개발자, 고급 그래픽 처리 등의 경우 특히 권장됨
7 | ### 기본 자료형의 특징
8 | - 가벼움
9 | - 접근할 때 추가 비용이 없어 빠름
10 |
--------------------------------------------------------------------------------
/Language/Kotlin/연산자 오버로딩 시 의미에 맞게 사용하자.md:
--------------------------------------------------------------------------------
1 | ## 연산자 오버로딩 시 의미에 맞게 사용하자
2 | ### 연산자 오버로딩의 문제점
3 | - 연산자가 일반적인 의미로 사용되지 않음
4 | - 코틀린의 모든 연산자는 구체적인 이름을 가진 함수에 대한 별칭에 해당
5 | - 모든 연산자는 연산자 대신 함수로도 호출 가능
6 | - 연산자를 볼 때마다 개별적인 의미를 이해해야 하기 때문에 가독성이 떨어짐
7 | - EX) `!` 연산자를 팩토리얼 계산에 사용하는 경우
8 | ### 연산자 오버로딩 대신 사용해야하는 방식
9 | - 이름이 있는 일반 함수
10 | - infix 확장 함수
11 | - 톱레벨 함수
12 | ### 연산자 오버로딩을 사용해야하는 경우
13 | - 도메인 특화 언어(DSL)을 설계하는 경우
14 |
--------------------------------------------------------------------------------
/Language/Kotlin/예외를 활용해 코드에 제한을 걸자.md:
--------------------------------------------------------------------------------
1 | ## 예외를 활용해 코드에 제한을 걸자
2 | - 확실하게 코드가 동작하도록 보장하는 경우에는 예외를 활용하여 제한하는 것이 안정적
3 | ### 장점
4 | - 문서를 읽지 않은 개발자도 문제 확인 가능
5 | - 예외를 던져 예상하지 못한 동작으로 인해 발생하는 문제 예방 및 안정성 향상
6 | - 코드 자체 검사 효과로 단위 테스트의 리소스 감소
7 | - 스마트 캐스트 기능 활용
8 | ### 코드 동작을 제한하는 방법
9 | - `require` 블록 : 아규먼트 제한
10 | - `check` 블록 : 상태 동작 제한
11 | - `assert` 블록 : true인지 확인 (테스트 모드에서만 작동)
12 | - Elvis 연산자 : return 또는 throw와 함께 활용
13 |
--------------------------------------------------------------------------------
/Language/Kotlin/일반적인 프로퍼티의 행위는 프로퍼티 위임으로 추출하여 재사용하자.md:
--------------------------------------------------------------------------------
1 | ## 일반적인 프로퍼티의 행위는 프로퍼티 위임으로 추출하여 재사용하자
2 | ### 프로퍼티 위임이란?
3 | > 다른 객체의 메서드를 활용해서 프로퍼티의 getter(`getValue`)와 setter(`setValue`)를 만드는 방식
4 | - 자주 반복될 것 같은 패턴의 프로퍼티는 프로퍼티 위임으로 추출 가능
5 | - `getValue`와 `setValue`는 context와 프로퍼티 레퍼런스의 경계도 함께 사용 가능
6 | ### 프로퍼티 위임 예시
7 | - `lazy` : 처음 사용하는 요청이 들어올 때 초기화되는 지연 프로퍼티
8 | - `Delegates.observable` : 변화가 있을 때 이를 감지하는 프로퍼티
9 | - `Delegates.vetoable`
10 | - `Delegates.notNull`
11 | ### 프로퍼티 위임을 활용한 패턴
12 | - 뷰 바인딩
13 | - 리소스 바인딩
14 | - 의존성 주입
15 | - 데이터 바인딩
16 |
--------------------------------------------------------------------------------
/Language/Kotlin/지역 스코프에서는 mutable 컬렉션을 사용하자.md:
--------------------------------------------------------------------------------
1 | ## 지역 스코프에서는 mutable 컬렉션을 사용하자
2 | - 가변 컬렉션이 immutable 컬렉션보다 성능이 뛰어남
3 | - 지역 스코프에서는 가변성을 제한할 필요가 없기 때문에 mutable 컬렉션 사용이 권장됨
4 | - 특히, utils에서는 요소 삽입이 자주 발생하여 가변 컬렉션 사용이 권장됨
5 | ### immutable 컬렉션이 성능이 낮은 이유
6 | - 새로운 컬렉션을 생성 및 추가하는 복제 비용이 높음
7 | ### immutable 컬렉션을 사용해야하는 경우
8 | - 가변성을 제한해야하는 경우
9 | - 컬렉션 변경과 관련된 세부적인 처리가 필요한 경우
10 |
--------------------------------------------------------------------------------
/Language/Kotlin/컬렉션의 처리 단계 수를 제한하자.md:
--------------------------------------------------------------------------------
1 | ## 컬렉션의 처리 단계 수를 제한하자
2 | ### 컬렉션 처리 메서드가 비용이 많이 드는 이유
3 | - 전체 컬렉션에 대한 반복 처리
4 | - 중간 컬렉션 및 객체 생성
5 |
6 | ```
7 | 따라서 적절한 컬렉션 처리 함수를 통해 처리 단계 수를 제한하는 것이 권장됨
8 | ```
9 |
10 | ### 처리 예시
11 | ```
12 | // 01. worst case
13 | fun List.getNames(): List = this
14 | .map { it.name }
15 | .filter { it != null }
16 | .map { it!! }
17 |
18 | // 02. better case
19 | fun List.getNames(): List = this
20 | .map { it.name }
21 | .filterNotNull()
22 |
23 | // 03. best case
24 | fun List.getNames(): List = this
25 | .mapNotNull { it.name }
26 | ```
27 | - 어떤 컬렉션 처리 메서드가 있는지 확인하는 것이 유용
28 | - 안드로이드 스튜디오에서 경고를 통해 어떤 메서드가 권장되는지 어느 정도 알려줌
29 |
--------------------------------------------------------------------------------
/Language/Kotlin/클래스 생성 중 초기화할 수 없는 프로퍼티는 lateinit과 Delegates.notNull을 사용하자.md:
--------------------------------------------------------------------------------
1 | ## 클래스 생성 중 초기화할 수 없는 프로퍼티는 `lateinit`과 `Delegates.notNull`을 사용하자
2 | - 클래스 생성 중 초기화할 수 없는 경우 일반적으로 `lateinit` 활용
3 | - JVM에서 기본 타입과 연결된 타입으로 프로퍼티를 초기화하는 경우 프로퍼티 위임(`Delegates.notNull`) 활용
4 | - **기본 타입 EX)** Int, Long, Double, Boolean 타입
5 | - **구현 EX)** `private var test: Int by Delegates.notNull()`
6 | - `lateinit`보다 느리게 동작
7 | - 사용하기 전 반드시 초기화가 되어 있을 경우에만 사용
8 |
--------------------------------------------------------------------------------
/Language/Kotlin/프로퍼티와 함수를 적절히 사용하자.md:
--------------------------------------------------------------------------------
1 | ## 프로퍼티와 함수를 적절히 사용하자
2 | ### 프로퍼티를 사용해야하는 경우
3 | - 상태 집합을 나타내는 경우
4 | - 상태를 get 또는 set 해야하는 경우
5 | - 상태와 관련되지 않은 특별한 이유가 없다면 함수가 아닌 프로퍼티 권장
6 |
7 | ### 함수를 사용해야하는 경우
8 | - 행동을 나타내는 경우
9 | - 연산 비용이 높거나 선형적 복잡도보다 복잡도가 높은 경우
10 | - 관습적으로 프로퍼티 사용 비용은 적어야 함
11 | - 비즈니스 로직을 포함하는 경우
12 | - 관습적으로 프로퍼티는 단순한 동작을 수행
13 | - 결정적이지 않은 경우
14 | - 같은 동작을 연속적으로 두 번 수행했을 때 다른 값이 나오는 경우 함수를 사용하지 않음
15 | - 변환의 경우
16 | - 관습적으로 변환이 필요한 경우 변환 함수 사용
17 | - EX) `Int.toDouble()`
18 | - 게터에서 프로퍼티의 상태 변경이 일어나야 하는 경우
19 | - 관습적으로 getter를 통해 상태 변화를 일으키지 않음
20 |
--------------------------------------------------------------------------------
/Language/Kotlin/플랫폼 타입 사용을 지양하자.md:
--------------------------------------------------------------------------------
1 | ## 플랫폼 타입 사용을 지양하자
2 | ### 플랫폼 타입(Platform Type)이란?
3 | > 다른 프로그래밍 언어로부터 전달되어 nullable인지 아닌지 알 수 없는 타입
4 | - 타입 이름 뒤에 ! 기호를 붙여 표기
5 | - EX) `String!`
6 | ### 플랫폼 타입의 문제점
7 | - 설계자가 명시적으로 어노테이션 또는 주석을 표시하지 않으면 동작이 nullable하게 변할 가능성 존재
8 | - NPE 발생 위험성 존재
9 | - 자바 코드에 `@Nullable`, `@NotNull`을 작성하여 null 여부를 명시하는 것이 안전
10 |
--------------------------------------------------------------------------------
/Language/Kotlin/함수와 메서드의 차이를 이해하자.md:
--------------------------------------------------------------------------------
1 | ## 함수와 메서드의 차이를 이해하자
2 | ### 함수(function)란?
3 | - `fun` 키워드 또는 리터럴을 사용하여 정의
4 | - EX) 톱레벨 함수, 클래스 멤버 함수, 함수 내부 지역 함수, 익명 함수, 람다 표현식
5 | ### 메서드(method)란?
6 | - 클래스와 연결된 함수
7 | - 클래스 인스턴스가 있어야 참조 가능
8 | - EX) 멤버 함수, 확장 함수(논란의 여지는 있음)
9 |
--------------------------------------------------------------------------------
/Language/Kotlin/확장 함수 구현 시 메모리를 고려하자.md:
--------------------------------------------------------------------------------
1 | ## 확장 함수 구현 시 메모리를 고려하자
2 | - 확장 함수는 static으로 컴파일되기 때문에 비교적 큰 메모리를 차지
3 | - 메모리에 예민한 기능이라면 확장 함수를 사용해도 문제가 없을지 검토 필요
4 |
--------------------------------------------------------------------------------
/Network/GET 통신 시에 Body 요청을 지양하는 이유.md:
--------------------------------------------------------------------------------
1 | ## GET 통신 시에 Body 요청을 지양하는 이유
2 | - 예전 HTTP에서 GET 통신에 Body 요청은 적합하지 않다고 규정하고 이를 처리하지 않음
3 | - 2014년부터 의미론적 관점에서도 GET 요청에 body가 들어가도 상관이 없어짐
4 | - 요청 메시지에 대한 프레임은 메서드의 의미론적 관점과 독립적으로 구분
5 | - 기존에는 금지가 되었던 개념이기 때문에 여전히 거절될 수 있어 지양
6 |
--------------------------------------------------------------------------------
/Network/URL은 소문자로 구성하되, 단어를 구분할 때는 하이픈(-)을 사용하자.md:
--------------------------------------------------------------------------------
1 | ## URL은 소문자로 구성하되, 단어를 구분할 때는 하이픈(-)을 사용하자
2 | ### URL은 소문자로 구성하자
3 | - 대문자로 인해 중복된 페이지로 인식할 가능성 존재 (case-sensitive)
4 | ### URL에서 단어를 구분해야할 때는 `-`을 사용하자
5 | - 검색 엔진에서 명확하게 단어 구분 가능
6 | - CamelCase 또는 `_`은 지양
7 |
--------------------------------------------------------------------------------
/Network/안드로이드에서 딥링크를 구현하는 다양한 방법을 구분하자.md:
--------------------------------------------------------------------------------
1 | ## 안드로이드에서 딥링크를 구현하는 다양한 방법을 구분하자
2 | ### 딥링크란?
3 | > 앱의 특정 페이지로 연결되는 URL
4 | ### 1️⃣ 안드로이드 딥링크
5 | - 인텐트 필터를 통해 특정 활동 지정
6 | - 다이얼로그를 통해 처리할 앱 선택
7 | - iOS에 호환되는 유니버셜 링크는 따로 구현 필요
8 | ### 2️⃣ 안드로이드 앱링크
9 | - 웹 사이트 URL을 기반으로 구현된 딥링크
10 | - 실행할 앱 선택하지 않고 즉시 실행
11 | - 앱 미설치 시 웹페이지 연결
12 | ### 3️⃣ 다이나믹 링크
13 | - 하나의 링크로 여러 OS에 호환
14 | - 파이어베이스를 통해 제공
15 | - 2025년 8월 25일 지원 중단
16 | - Adjust, AppsFlyer, Bitly, Branch, Kochava로 대체 가능
17 |
--------------------------------------------------------------------------------
/Object-Oriented Programming/재사용이 필요할 때 인터페이스를 적극 활용하자.md:
--------------------------------------------------------------------------------
1 | ## 재사용이 필요할 때 인터페이스를 적극 활용하자
2 | - 일반 클래스를 재사용하는 경우, 코드의 복잡도가 높아지면서 가독성이 저하될 수 있음
3 | - 인터페이스를 분리하여 역할을 명시적으로 작성
4 | - 역할과 구현의 명확한 분리
5 | - 유연하고 확장성 있는 프로그램 설계
6 |
--------------------------------------------------------------------------------
/Office Life /QA 가능한 방법을 고려하여 기능을 개발하자.md:
--------------------------------------------------------------------------------
1 | ## QA 가능한 방법을 고려하여 기능을 개발하자
2 | - QA 일정에 차질이 발생하지 않도록 방지 가능
3 | - EX) 테스트 코드, 디버그 모드 설정 등
4 | - 검증 가능한 경로가 없다면 사전에 전달하여 대체 방안에 대해 의논 가능하도록 하자
5 |
--------------------------------------------------------------------------------
/Office Life /새로운 버전의 앱을 배포할 때에는 이전 버전과의 호환성을 확인하자.md:
--------------------------------------------------------------------------------
1 | ## 새로운 버전의 앱을 배포할 때에는 이전 버전과의 호환성을 확인하자
2 | - 강제 업데이트가 아닌 경우, 배포 이후에도 이전 버전의 사용자들이 원활하게 앱 사용이 가능한지 확인 필요
3 | - 개발 시작 전 호환성을 확인하여 대응책을 사전에 마련하는 것이 안정적
4 | - EX) DTO 호환성, UI 호환성 등
5 |
--------------------------------------------------------------------------------
/Office Life /스테이징 서버를 통해 운영 서버와 유사한 환경에서 검증하자.md:
--------------------------------------------------------------------------------
1 | ## 스테이징 서버를 통해 운영 서버와 유사한 환경에서 검증하자
2 | ### [스테이징 서버(Staging Server)란?](https://velog.io/@ryucherry/Server-%EB%A1%9C%EC%BB%AC%EC%84%9C%EB%B2%84-%EA%B0%9C%EB%B0%9C%EC%84%9C%EB%B2%84-%EC%8A%A4%ED%85%8C%EC%9D%B4%EC%A7%95%EC%84%9C%EB%B2%84-%EC%9A%B4%EC%98%81%EC%84%9C%EB%B2%84-%EB%9E%80)
3 | > 운영 서버 환경과 거의 동일한 환경의 서버로, 운영 서버에서 사용되는 데이터를 실질적으로 운영 서버에 반영하기 전에 테스트를 거치는 곳
4 | - 테스트 서버, QA 서버라고도 부름
5 |
--------------------------------------------------------------------------------
/Office Life /의사 전달 시 문서화를 습관화하자.md:
--------------------------------------------------------------------------------
1 | ## 나의 의견이나 결정된 사항을 회사 구성원에게 전달할 때에는 문서화한 파일을 전달하자
2 | - 구두로만 전달하는 정보는 기억에서 휘발되기 쉬움
3 | - 문서를 통해 다른 파트 구성원의 이해를 도울 수 있음
4 | - 특히, 배경 지식이 부족한 타 파트와 관련된 정보를 전달하는 경우에는 정리된 글을 참고하여 보다 논리적인 소통 가능
5 |
--------------------------------------------------------------------------------
/Office Life /코드 구조를 설계할 때는 설계하지 않았을 때의 문제점을 먼저 파악하자.md:
--------------------------------------------------------------------------------
1 | ## 코드 구조를 설계할 때는 설계하지 않았을 때의 문제점을 먼저 파악하자
2 | - 현재 구조에 대한 문제점을 먼저 파악해야 요구사항에 적합한 디자인 패턴 선택 가능
3 | - 활용하는 디자인 패턴의 근본적 이점과 동작 원리 이해 가능
4 |
--------------------------------------------------------------------------------
/Office Life /프로젝트 설계 단계에서 Tech Spec 문서를 통해 목표와 개발 범위를 정리하자.md:
--------------------------------------------------------------------------------
1 | ## 프로젝트 설계 단계에서 Tech Spec 문서를 통해 목표와 개발 범위를 정리하자
2 | - 명확하고 일관된 방향성의 요구사항 정의
3 | - 기획적 또는 개발적 리스크 식별
4 | - 효율적 알고리즘 및 아케틱처 설계
5 | - 변경 작업 최소화로 인한 개발 속도 및 효율성 향상
6 | - 테스트 케이스의 기반이 되는 문서 제공
7 | - 유지보수 또는 변경사항 발생 시 참고 문서로 활용
8 |
--------------------------------------------------------------------------------
/Office Life /확장 가능성이 있는 기능은 서버에서 동적으로 수정 가능하도록 설계하자.md:
--------------------------------------------------------------------------------
1 | ## 확장 가능성이 있는 기능은 서버에서 동적으로 수정 가능하도록 설계하자
2 | - 앱의 배포나 강제 없데이트 없이 서버 변경만으로 기능 확장 가능
3 | - 배포 비용 절감 및 유연성 증가의 효과
4 | - 오류 발생 시 신속하게 대응 가능
5 | - 서버 통신 횟수를 최소화해야 하며, 서버와 클라이언트의 역할 분리에 유의
6 | ### 적용 예시
7 | - 카테고리, 필터, 정렬 방식 등의 동적 UI 설정
8 | - 기능 활성화 및 비활성화 플래그 설정
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TIL: Today I Learned
2 | - [신입 안드로이드 개발자](https://github.com/b1urrrr)가 오늘 하루 배운 일을 러프하게 문서로 작성하는 곳입니다.
3 | - 잘못된 정보나 오탈자는 Issue 또는 b1urrr@naver.com로 문의해주세요!
4 |
5 |
6 |
7 | ## COMMIT CONVENTION
8 | - ➕ **[ADD]** : TIL 문서나 부수적 코드 추가
9 | - ✅ **[MOD]** : TIL 문서 및 내부 파일 수정
10 | - 🗑 **[DEL]** : 쓸모없는 코드 및 파일 삭제
11 | - ✏️ **[CORRECT]** : 문법 오류 해결, 타입 변경, 이름 변경 등의 작은 수정
12 | - 📄 **[DOCS]** : README 등의 부수적 문서 개정
13 | - 🚚 **[MOVE]** : 문서 파일 및 코드 이동
14 | - 🪧 **[RENAME]** : 파일 이름 변경
15 | - 🔀 **[MERGE]** : 다른 브랜치와 병합
16 | - ♻️ **[REFACTOR]** : 전면 수정
17 |
18 |
19 |
20 | ## INDEX
21 | ### Day 01 (~24.06.07.)
22 | | Category | Title | Link |
23 | | :------: | :---: | :--: |
24 | | Office Life | 의사 전달 시 문서화를 습관화하자 | [🔗](https://github.com/b1urrrr/til/blob/17251a0563249c35fc57780b17ff20cc2dfd3ee0/Office%20Life%20/%EC%9D%98%EC%82%AC%20%EC%A0%84%EB%8B%AC%20%EC%8B%9C%20%EB%AC%B8%EC%84%9C%ED%99%94%EB%A5%BC%20%EC%8A%B5%EA%B4%80%ED%99%94%ED%95%98%EC%9E%90.md) |
25 | | Kotlin | Object를 사용하는 이유 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/Object%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%20%EC%9D%B4%EC%9C%A0.md) |
26 | | Kotlin | 확장 함수 구현 시 메모리를 고려하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%ED%99%95%EC%9E%A5%20%ED%95%A8%EC%88%98%20%EA%B5%AC%ED%98%84%20%EC%8B%9C%20%EB%A9%94%EB%AA%A8%EB%A6%AC%EB%A5%BC%20%EA%B3%A0%EB%A0%A4%ED%95%98%EC%9E%90.md) |
27 | | Clean Architecture | 도메인 모델에 의존값을 포함하지 말자 | [🔗](https://github.com/b1urrrr/til/blob/main/Architecture/Clean%20Architecture/%EB%8F%84%EB%A9%94%EC%9D%B8%20%EB%AA%A8%EB%8D%B8%EC%97%90%20%EC%9D%98%EC%A1%B4%EA%B0%92%EC%9D%84%20%ED%8F%AC%ED%95%A8%ED%95%98%EC%A7%80%20%EB%A7%90%EC%9E%90.md) |
28 | | Coroutine | ViewModelScope 대신 CoroutineScope를 사용해야 하는 경우 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/Coroutine/ViewModelScope%20%EB%8C%80%EC%8B%A0%20CoroutineScope%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC%ED%95%98%EB%8A%94%20%EA%B2%BD%EC%9A%B0.md) |
29 |
30 |
31 | ### Day 02 (24.06.12.)
32 | | Category | Title | Link |
33 | | :------: | :---: | :--: |
34 | | Clean Architecture | Repository를 인터페이스와 구현체로 분리하는 이유 | [🔗](https://github.com/b1urrrr/til/blob/main/Architecture/Clean%20Architecture/Repository%EB%A5%BC%20%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EC%99%80%20%EA%B5%AC%ED%98%84%EC%B2%B4%EB%A1%9C%20%EB%B6%84%EB%A6%AC%ED%95%98%EB%8A%94%20%EC%9D%B4%EC%9C%A0.md) |
35 |
36 |
37 | ### Day 03 (24.06.13.)
38 | | Category | Title | Link |
39 | | :------: | :---: | :--: |
40 | | Object-Oriented Programming | 재사용이 필요할 때 인터페이스를 적극 활용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Object-Oriented%20Programming/%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%9D%B4%20%ED%95%84%EC%9A%94%ED%95%A0%20%EB%95%8C%20%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC%20%EC%A0%81%EA%B7%B9%20%ED%99%9C%EC%9A%A9%ED%95%98%EC%9E%90.md) |
41 |
42 |
43 | ### Day 04 (24.06.14.)
44 | | Category | Title | Link |
45 | | :------: | :---: | :--: |
46 | | Network | GET 통신 시에 Body 요청을 지양하는 이유 | [🔗](https://github.com/b1urrrr/til/blob/main/Network/GET%20%ED%86%B5%EC%8B%A0%20%EC%8B%9C%EC%97%90%20Body%20%EC%9A%94%EC%B2%AD%EC%9D%84%20%EC%A7%80%EC%96%91%ED%95%98%EB%8A%94%20%EC%9D%B4%EC%9C%A0.md) |
47 | | Kotlin | @Throws를 사용하는 경우 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%40Throws%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%20%EA%B2%BD%EC%9A%B0.md) |
48 |
49 |
50 | ### Day 05 (24.06.17.)
51 | | Category | Title | Link |
52 | | :------: | :---: | :--: |
53 | | Clean Code | 분기 처리 시 다른 조건에 의존적인 조건은 지양하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Clean%20Code/%EB%B6%84%EA%B8%B0%20%EC%B2%98%EB%A6%AC%20%EC%8B%9C%20%EB%8B%A4%EB%A5%B8%20%EC%A1%B0%EA%B1%B4%EC%97%90%20%EC%9D%98%EC%A1%B4%EC%A0%81%EC%9D%B8%20%EC%A1%B0%EA%B1%B4%EC%9D%80%20%EC%A7%80%EC%96%91%ED%95%98%EC%9E%90.md) |
54 |
55 |
56 | ### Day 06 (24.06.19.)
57 | | Category | Title | Link |
58 | | :------: | :---: | :--: |
59 | | Data Structure | 불변하면 Set, 순서가 상관 없으면 HashSet, 순서가 보장되어야 하면 MutableSet을 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Data%20Structure/%EB%B6%88%EB%B3%80%ED%95%98%EB%A9%B4%20Set,%20%EC%88%9C%EC%84%9C%EA%B0%80%20%EC%83%81%EA%B4%80%20%EC%97%86%EC%9C%BC%EB%A9%B4%20HashSet,%20%EC%88%9C%EC%84%9C%EA%B0%80%20%EB%B3%B4%EC%9E%A5%EB%90%98%EC%96%B4%EC%95%BC%20%ED%95%98%EB%A9%B4%20MutableSet%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
60 |
61 |
62 | ### Day 07 (24.06.20.)
63 | | Category | Title | Link |
64 | | :------: | :---: | :--: |
65 | | Kotlin | 플랫폼 타입 사용을 지양하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%ED%94%8C%EB%9E%AB%ED%8F%BC%20%ED%83%80%EC%9E%85%20%EC%82%AC%EC%9A%A9%EC%9D%84%20%EC%A7%80%EC%96%91%ED%95%98%EC%9E%90.md) |
66 |
67 |
68 | ### Day 08 (24.06.21.)
69 | | Category | Title | Link |
70 | | :------: | :---: | :--: |
71 | | Coroutine | withTimeout()으로 코루틴 동작에 타임아웃을 설정하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/Coroutine/withTimeout()으로%20코루틴%20동작에%20타임아웃을%20설정하자.md) |
72 | | Android App Architecture | Repository 또는 DataSource에서 앱 실행 중 캐시가 필요한 데이터를 저장하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Architecture/Android%20App%20Architecture/Repository%20또는%20DataSource에서%20앱%20실행%20중%20캐시가%20필요한%20데이터를%20저장하자.md) |
73 |
74 |
75 | ### Day 09 (24.06.24.)
76 | | Category | Title | Link |
77 | | :------: | :---: | :--: |
78 | | Compose | Modifier.offset()은 다른 컴포넌트와 독립적인 경우에만 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Modifier.offset()%EC%9D%80%20%EB%8B%A4%EB%A5%B8%20%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80%20%EB%8F%85%EB%A6%BD%EC%A0%81%EC%9D%B8%20%EA%B2%BD%EC%9A%B0%EC%97%90%EB%A7%8C%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
79 |
80 |
81 | ### Day 10 (24.06.25.)
82 | | Category | Title | Link |
83 | | :------: | :---: | :--: |
84 | | Compose | Modifier.clip()으로 컴포넌트를 원하는 형태로 자르자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Modifier.clip()%EC%9C%BC%EB%A1%9C%20%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%A5%BC%20%EC%9B%90%ED%95%98%EB%8A%94%20%ED%98%95%ED%83%9C%EB%A1%9C%20%EC%9E%90%EB%A5%B4%EC%9E%90.md) |
85 | | Android Studio | Clean Project를 통해 수정사항을 확실하게 빌드 시키자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Studio/Clean%20Project%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EC%88%98%EC%A0%95%EC%82%AC%ED%95%AD%EC%9D%84%20%ED%99%95%EC%8B%A4%ED%95%98%EA%B2%8C%20%EB%B9%8C%EB%93%9C%20%EC%8B%9C%ED%82%A4%EC%9E%90.md) |
86 |
87 |
88 | ### Day 11 (24.06.26.)
89 | | Category | Title | Link |
90 | | :------: | :---: | :--: |
91 | | Clean Code | 사용자 정의 오류보다 표준 오류를 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Clean%20Code/%EC%82%AC%EC%9A%A9%EC%9E%90%20%EC%A0%95%EC%9D%98%20%EC%98%A4%EB%A5%98%EB%B3%B4%EB%8B%A4%20%ED%91%9C%EC%A4%80%20%EC%98%A4%EB%A5%98%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
92 |
93 |
94 | ### Day 12 (24.06.27.)
95 | | Category | Title | Link |
96 | | :------: | :---: | :--: |
97 | | Compose | snapshotFlow로 State를 Flow로 변환하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/snapshotFlow%EB%A1%9C%20State%EB%A5%BC%20Flow%EB%A1%9C%20%EB%B3%80%ED%99%98%ED%95%98%EC%9E%90.md) |
98 |
99 |
100 | ### Day 13 (24.07.01.)
101 | | Category | Title | Link |
102 | | :------: | :---: | :--: |
103 | | Android Studio | Kotlin Decompiler로 디컴파일된 Java 코드를 확인하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Studio/Kotlin%20Decompiler%EB%A1%9C%20%EB%94%94%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%90%9C%20Java%20%EC%BD%94%EB%93%9C%EB%A5%BC%20%ED%99%95%EC%9D%B8%ED%95%98%EC%9E%90.md) |
104 |
105 |
106 | ### Day 14 (24.07.02.)
107 | | Category | Title | Link |
108 | | :------: | :---: | :--: |
109 | | Network | URL은 소문자로 구성하되, 단어를 구분할 때는 하이픈(-)을 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Network/URL%EC%9D%80%20%EC%86%8C%EB%AC%B8%EC%9E%90%EB%A1%9C%20%EA%B5%AC%EC%84%B1%ED%95%98%EB%90%98%2C%20%EB%8B%A8%EC%96%B4%EB%A5%BC%20%EA%B5%AC%EB%B6%84%ED%95%A0%20%EB%95%8C%EB%8A%94%20%ED%95%98%EC%9D%B4%ED%94%88(-)%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
110 | | Network | 안드로이드에서 딥링크를 구현하는 다양한 방법을 구분하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Network/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%EC%97%90%EC%84%9C%20%EB%94%A5%EB%A7%81%ED%81%AC%EB%A5%BC%20%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94%20%EB%8B%A4%EC%96%91%ED%95%9C%20%EB%B0%A9%EB%B2%95%EC%9D%84%20%EA%B5%AC%EB%B6%84%ED%95%98%EC%9E%90.md) |
111 |
112 |
113 | ### Day 15 (24.07.03.)
114 | | Category | Title | Link |
115 | | :------: | :---: | :--: |
116 | | Clean Code | 객체는 사용하는 경우에만 생성하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Clean%20Code/%EA%B0%9D%EC%B2%B4%EB%8A%94%20%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%20%EA%B2%BD%EC%9A%B0%EC%97%90%EB%A7%8C%20%EC%83%9D%EC%84%B1%ED%95%98%EC%9E%90.md?plain=1) |
117 |
118 |
119 | ### Day 16 (24.07.04.)
120 | | Category | Title | Link |
121 | | :------: | :---: | :--: |
122 | | Kotlin | Unit?을 리턴하지 말자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/Unit%3F%EC%9D%84%20%EB%A6%AC%ED%84%B4%ED%95%98%EC%A7%80%20%EB%A7%90%EC%9E%90.md) |
123 |
124 |
125 | ### Day 17 (24.07.05.)
126 | | Category | Title | Link |
127 | | :------: | :---: | :--: |
128 | | Kotlin | inner class 대신 nested class를 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/inner%20class%20%EB%8C%80%EC%8B%A0%20nested%20class%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md?plain=1) |
129 | | Clean Code | 타입 파라미터의 섀도잉을 피하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Clean%20Code/%ED%83%80%EC%9E%85%20%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EC%9D%98%20%EC%84%80%EB%8F%84%EC%9E%89%EC%9D%84%20%ED%94%BC%ED%95%98%EC%9E%90.md) |
130 |
131 |
132 | ### Day 18 (24.07.08.)
133 | | Category | Title | Link |
134 | | :------: | :---: | :--: |
135 | | Compose | Compose의 상태는 메인 스레드에서만 접근하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Compose%EC%9D%98%20%EC%83%81%ED%83%9C%EB%8A%94%20%EB%A9%94%EC%9D%B8%20%EC%8A%A4%EB%A0%88%EB%93%9C%EC%97%90%EC%84%9C%EB%A7%8C%20%EC%A0%91%EA%B7%BC%ED%95%98%EC%9E%90.md) |
136 |
137 |
138 | ### Day 19 (24.07.09.)
139 | | Category | Title | Link |
140 | | :------: | :---: | :--: |
141 | | Android Component | isTaskRoot를 통해 첫 번째 액티비티인지 확인하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/isTaskRoot%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EC%B2%AB%20%EB%B2%88%EC%A7%B8%20%EC%95%A1%ED%8B%B0%EB%B9%84%ED%8B%B0%EC%9D%B8%EC%A7%80%20%ED%99%95%EC%9D%B8%ED%95%98%EC%9E%90.md) |
142 |
143 |
144 | ### Day 20 (24.07.10.)
145 | | Category | Title | Link |
146 | | :------: | :---: | :--: |
147 | | Android Component | RecyclerView를 구현할 때 Selection 라이브러리 사용을 지양하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/RecyclerView%EB%A5%BC%20%EA%B5%AC%ED%98%84%ED%95%A0%20%EB%95%8C%20Selection%20%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%20%EC%82%AC%EC%9A%A9%EC%9D%84%20%EC%A7%80%EC%96%91%ED%95%98%EC%9E%90.md) |
148 |
149 |
150 | ### Day 21 (24.07.11.)
151 | | Category | Title | Link |
152 | | :------: | :---: | :--: |
153 | | WebView | WebView에 domStorageEnable을 설정하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/WebView/WebView%EC%97%90%20domStorageEnable%EC%9D%84%20%EC%84%A4%EC%A0%95%ED%95%98%EC%9E%90.md) |
154 |
155 |
156 | ### Day 22 (24.07.12.)
157 | | Category | Title | Link |
158 | | :------: | :---: | :--: |
159 | | OkHttp | Interceptor를 통해 네트워크 UserAgent를 설정하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Library/OkHttp/Interceptor%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC%20UserAgent%EB%A5%BC%20%EC%84%A4%EC%A0%95%ED%95%98%EC%9E%90.md) |
160 |
161 |
162 | ### Day 23 (24.07.15.)
163 | | Category | Title | Link |
164 | | :------: | :---: | :--: |
165 | | Kotlin | inferred 타입으로 리턴하지 말자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/inferred%20%ED%83%80%EC%9E%85%EC%9C%BC%EB%A1%9C%20%EB%A6%AC%ED%84%B4%ED%95%98%EC%A7%80%20%EB%A7%90%EC%9E%90.md) |
166 |
167 |
168 | ### Day 24 (24.07.16.)
169 | | Category | Title | Link |
170 | | :------: | :---: | :--: |
171 | | Clean Code | 결과를 처리할 때는 예외보다 Failure를 활용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Clean%20Code/%EA%B2%B0%EA%B3%BC%EB%A5%BC%20%EC%B2%98%EB%A6%AC%ED%95%A0%20%EB%95%8C%EB%8A%94%20%EC%98%88%EC%99%B8%EB%B3%B4%EB%8B%A4%20Failure%EB%A5%BC%20%ED%99%9C%EC%9A%A9%ED%95%98%EC%9E%90.md) |
172 |
173 |
174 | ### Day 25 (24.07.17.)
175 | | Category | Title | Link |
176 | | :------: | :---: | :--: |
177 | | Office Life | 코드 구조를 설계할 때는 설계하지 않았을 때의 문제점을 먼저 파악하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Office%20Life%20/%EC%BD%94%EB%93%9C%20%EA%B5%AC%EC%A1%B0%EB%A5%BC%20%EC%84%A4%EA%B3%84%ED%95%A0%20%EB%95%8C%EB%8A%94%20%EC%84%A4%EA%B3%84%ED%95%98%EC%A7%80%20%EC%95%8A%EC%95%98%EC%9D%84%20%EB%95%8C%EC%9D%98%20%EB%AC%B8%EC%A0%9C%EC%A0%90%EC%9D%84%20%EB%A8%BC%EC%A0%80%20%ED%8C%8C%EC%95%85%ED%95%98%EC%9E%90.md) |
178 | | Android App Architecture | Analytics 로직은 DataSource나 Repository로 분리하지 말자 | [🔗](https://github.com/b1urrrr/til/blob/main/Architecture/Android%20App%20Architecture/Analytics%20%EB%A1%9C%EC%A7%81%EC%9D%80%20DataSource%EB%82%98%20Repository%EB%A1%9C%20%EB%B6%84%EB%A6%AC%ED%95%98%EC%A7%80%20%EB%A7%90%EC%9E%90.md) |
179 |
180 |
181 | ### Day 26 (24.07.18.)
182 | | Category | Title | Link |
183 | | :------: | :---: | :--: |
184 | | Compose | Layout Inspector를 이용해 컴포넌트 트리 구조와 리컴포지션 상태를 파악하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Layout%20Inspector%EB%A5%BC%20%EC%9D%B4%EC%9A%A9%ED%95%B4%20%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%20%ED%8A%B8%EB%A6%AC%20%EA%B5%AC%EC%A1%B0%EC%99%80%20%EB%A6%AC%EC%BB%B4%ED%8F%AC%EC%A7%80%EC%85%98%20%EC%83%81%ED%83%9C%EB%A5%BC%20%ED%8C%8C%EC%95%85%ED%95%98%EC%9E%90.md) |
185 | | Compose | Scaffold에 paddingValues를 지정하여 BottomBar 크기를 고려하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Scaffold%EC%97%90%20paddingValues%EB%A5%BC%20%EC%A7%80%EC%A0%95%ED%95%98%EC%97%AC%20BottomBar%20%ED%81%AC%EA%B8%B0%EB%A5%BC%20%EA%B3%A0%EB%A0%A4%ED%95%98%EC%9E%90.md) |
186 |
187 |
188 | ### Day 27 (24.07.19.)
189 | | Category | Title | Link |
190 | | :------: | :---: | :--: |
191 | | Analytics Provider Library | 애널리틱스 이벤트나 라이브러리의 추가 및 제거에 대한 리소스를 최소화하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Library/Analytics%20Provider%20Library/%EC%95%A0%EB%84%90%EB%A6%AC%ED%8B%B1%EC%8A%A4%20%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%82%98%20%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EC%9D%98%20%EC%B6%94%EA%B0%80%20%EB%B0%8F%20%EC%A0%9C%EA%B1%B0%EC%97%90%20%EB%8C%80%ED%95%9C%20%EB%A6%AC%EC%86%8C%EC%8A%A4%EB%A5%BC%20%EC%B5%9C%EC%86%8C%ED%99%94%ED%95%98%EC%9E%90.md) |
192 |
193 |
194 | ### Day 28 (24.07.22.)
195 | | Category | Title | Link |
196 | | :------: | :---: | :--: |
197 | | Kotlin | 가변성을 제한하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%EA%B0%80%EB%B3%80%EC%84%B1%EC%9D%84%20%EC%A0%9C%ED%95%9C%ED%95%98%EC%9E%90.md) |
198 |
199 |
200 | ### Day 29 (24.07.24.)
201 | | Category | Title | Link |
202 | | :------: | :---: | :--: |
203 | | Glide | Glide의 onResourceReady()를 통해 load가 종료된 시점에 동작을 처리하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Library/Glide/Glide%EC%9D%98%20onResourceReady()%EB%A5%BC%20%ED%86%B5%ED%95%B4%20load%EA%B0%80%20%EC%A2%85%EB%A3%8C%EB%90%9C%20%EC%8B%9C%EC%A0%90%EC%97%90%20%EB%8F%99%EC%9E%91%EC%9D%84%20%EC%B2%98%EB%A6%AC%ED%95%98%EC%9E%90.md) |
204 |
205 |
206 | ### Day 30 (24.07.29.)
207 | | Category | Title | Link |
208 | | :------: | :---: | :--: |
209 | | Compose | 점진적으로 Compose로 마이그레이션하는 전략을 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/%EC%A0%90%EC%A7%84%EC%A0%81%EC%9C%BC%EB%A1%9C%20Compose%EB%A1%9C%20%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98%ED%95%98%EB%8A%94%20%EC%A0%84%EB%9E%B5%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
210 |
211 |
212 | ### Day 31 (24.07.30.)
213 | | Category | Title | Link |
214 | | :------: | :---: | :--: |
215 | | Kakao SDK Library | 카카오 로그인 여부를 확인할 때 토큰 유효성을 확인하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Library/Kakao%20SDK%20Library/%EC%B9%B4%EC%B9%B4%EC%98%A4%20%EB%A1%9C%EA%B7%B8%EC%9D%B8%20%EC%97%AC%EB%B6%80%EB%A5%BC%20%ED%99%95%EC%9D%B8%ED%95%A0%20%EB%95%8C%20%ED%86%A0%ED%81%B0%20%EC%9C%A0%ED%9A%A8%EC%84%B1%EC%9D%84%20%ED%99%95%EC%9D%B8%ED%95%98%EC%9E%90.md) |
216 | | Office Life | QA 가능한 방법을 고려하여 기능을 개발하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Office%20Life%20/QA%20%EA%B0%80%EB%8A%A5%ED%95%9C%20%EB%B0%A9%EB%B2%95%EC%9D%84%20%EA%B3%A0%EB%A0%A4%ED%95%98%EC%97%AC%20%EA%B8%B0%EB%8A%A5%EC%9D%84%20%EA%B0%9C%EB%B0%9C%ED%95%98%EC%9E%90.md) |
217 |
218 |
219 | ### Day 32 (24.07.31.)
220 | | Category | Title | Link |
221 | | :------: | :---: | :--: |
222 | | Kotlin | 클래스 생성 중 초기화할 수 없는 프로퍼티는 lateinit과 Delegates.notNull을 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%ED%81%B4%EB%9E%98%EC%8A%A4%20%EC%83%9D%EC%84%B1%20%EC%A4%91%20%EC%B4%88%EA%B8%B0%ED%99%94%ED%95%A0%20%EC%88%98%20%EC%97%86%EB%8A%94%20%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0%EB%8A%94%20lateinit%EA%B3%BC%20Delegates.notNull%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
223 |
224 |
225 | ### Day 33 (24.08.01.)
226 | | Category | Title | Link |
227 | | :------: | :---: | :--: |
228 | | Kotlin | close 대신 use를 사용하여 리소스를 해제하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/close%20%EB%8C%80%EC%8B%A0%20use%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC%20%EB%A6%AC%EC%86%8C%EC%8A%A4%EB%A5%BC%20%ED%95%B4%EC%A0%9C%ED%95%98%EC%9E%90.md) |
229 |
230 |
231 | ### Day 34 (24.08.02.)
232 | | Category | Title | Link |
233 | | :------: | :---: | :--: |
234 | | WebView | 웹뷰의 shouldOverrideUrlLoading()을 통해 웹 페이지 또는 intent를 처리하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/WebView/%EC%9B%B9%EB%B7%B0%EC%9D%98%20shouldOverrideUrlLoading()%EC%9D%84%20%ED%86%B5%ED%95%B4%20%EC%9B%B9%20%ED%8E%98%EC%9D%B4%EC%A7%80%20%EB%98%90%EB%8A%94%20intent%EB%A5%BC%20%EC%B2%98%EB%A6%AC%ED%95%98%EC%9E%90.md) |
235 |
236 |
237 | ### Day 35 (24.08.06.)
238 | | Category | Title | Link |
239 | | :------: | :---: | :--: |
240 | | Intent | 인텐트에 액션과 데이터 스키마를 지정하여 다른 앱에서의 처리를 구현하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/Intent/%EC%9D%B8%ED%85%90%ED%8A%B8%EC%97%90%20%EC%95%A1%EC%85%98%EA%B3%BC%20%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%8A%A4%ED%82%A4%EB%A7%88%EB%A5%BC%20%EC%A7%80%EC%A0%95%ED%95%98%EC%97%AC%20%EB%8B%A4%EB%A5%B8%20%EC%95%B1%EC%97%90%EC%84%9C%EC%9D%98%20%EC%B2%98%EB%A6%AC%EB%A5%BC%20%EA%B5%AC%ED%98%84%ED%95%98%EC%9E%90.md) |
241 |
242 |
243 | ### Day 36 (24.08.07.)
244 | | Category | Title | Link |
245 | | :------: | :---: | :--: |
246 | | CRM | CRM 툴을 통해 개인화된 마케팅 기능을 자동화하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Library/CRM/CRM%20%ED%88%B4%EC%9D%84%20%ED%86%B5%ED%95%B4%20%EA%B0%9C%EC%9D%B8%ED%99%94%EB%90%9C%20%EB%A7%88%EC%BC%80%ED%8C%85%20%EA%B8%B0%EB%8A%A5%EC%9D%84%20%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EC%9E%90.md) |
247 |
248 |
249 | ### Day 37 (24.08.08.)
250 | | Category | Title | Link |
251 | | :------: | :---: | :--: |
252 | | Kotlin | 변수 타입이 명확하지 않은 경우 확실하게 지정하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%EB%B3%80%EC%88%98%20%ED%83%80%EC%9E%85%EC%9D%B4%20%EB%AA%85%ED%99%95%ED%95%98%EC%A7%80%20%EC%95%8A%EC%9D%80%20%EA%B2%BD%EC%9A%B0%20%ED%99%95%EC%8B%A4%ED%95%98%EA%B2%8C%20%EC%A7%80%EC%A0%95%ED%95%98%EC%9E%90.md) |
253 |
254 |
255 | ### Day 38 (24.08.09.)
256 | | Category | Title | Link |
257 | | :------: | :---: | :--: |
258 | | Compose | ViewModel을 공유하여 XML 기반의 뷰에서 ComposeView의 상태를 바꾸자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/ViewModel%EC%9D%84%20%EA%B3%B5%EC%9C%A0%ED%95%98%EC%97%AC%20XML%20%EA%B8%B0%EB%B0%98%EC%9D%98%20%EB%B7%B0%EC%97%90%EC%84%9C%20ComposeView%EC%9D%98%20%EC%83%81%ED%83%9C%EB%A5%BC%20%EB%B0%94%EA%BE%B8%EC%9E%90.md) |
259 |
260 |
261 | ### Day 39 (24.08.12.)
262 | | Category | Title | Link |
263 | | :------: | :---: | :--: |
264 | | CoordinatorLayout | BottomSheetBehavior를 통해 적절한 CoordinatorLayout 자식 뷰 상태를 지정하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/CoordinatorLayout/BottomSheetBehavior%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EC%A0%81%EC%A0%88%ED%95%9C%20CoordinatorLayout%20%EC%9E%90%EC%8B%9D%20%EB%B7%B0%20%EC%83%81%ED%83%9C%EB%A5%BC%20%EC%A7%80%EC%A0%95%ED%95%98%EC%9E%90.md) |
265 |
266 |
267 | ### Day 40 (24.08.14.)
268 | | Category | Title | Link |
269 | | :------: | :---: | :--: |
270 | | Kotlin | @DslMarker를 활용하여 외부 리시버 사용을 제한하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%40DslMarker%EB%A5%BC%20%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC%20%EC%99%B8%EB%B6%80%20%EB%A6%AC%EC%8B%9C%EB%B2%84%20%EC%82%AC%EC%9A%A9%EC%9D%84%20%EC%A0%9C%ED%95%9C%ED%95%98%EC%9E%90.md) |
271 |
272 |
273 | ### Day 41 (24.08.16.)
274 | | Category | Title | Link |
275 | | :------: | :---: | :--: |
276 | | Office Life | 새로운 버전의 앱을 배포할 때에는 이전 버전과의 호환성을 확인하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Office%20Life%20/%EC%83%88%EB%A1%9C%EC%9A%B4%20%EB%B2%84%EC%A0%84%EC%9D%98%20%EC%95%B1%EC%9D%84%20%EB%B0%B0%ED%8F%AC%ED%95%A0%20%EB%95%8C%EC%97%90%EB%8A%94%20%EC%9D%B4%EC%A0%84%20%EB%B2%84%EC%A0%84%EA%B3%BC%EC%9D%98%20%ED%98%B8%ED%99%98%EC%84%B1%EC%9D%84%20%ED%99%95%EC%9D%B8%ED%95%98%EC%9E%90.md) |
277 |
278 |
279 | ### Day 42 (24.08.20.)
280 | | Category | Title | Link |
281 | | :------: | :---: | :--: |
282 | | URLEncoder | URLEncoder를 통해 HTML 형식으로 인코딩하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/URLEncoder/URLEncoder%EB%A5%BC%20%ED%86%B5%ED%95%B4%20HTML%20%ED%98%95%EC%8B%9D%EC%9C%BC%EB%A1%9C%20%EC%9D%B8%EC%BD%94%EB%94%A9%ED%95%98%EC%9E%90.md) |
283 |
284 |
285 | ### Day 43 (24.08.21.)
286 | | Category | Title | Link |
287 | | :------: | :---: | :--: |
288 | | Analytics Provider Library | 각 애널리틱스 라이브러리의 특성을 이해하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Library/Analytics%20Provider%20Library/%EA%B0%81%20%EC%95%A0%EB%84%90%EB%A6%AC%ED%8B%B1%EC%8A%A4%20%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EC%9D%98%20%ED%8A%B9%EC%84%B1%EC%9D%84%20%EC%9D%B4%ED%95%B4%ED%95%98%EC%9E%90.md) |
289 |
290 |
291 | ### Day 44 (24.08.22.)
292 | | Category | Title | Link |
293 | | :------: | :---: | :--: |
294 | | Office Life | 스테이징 서버를 통해 운영 서버와 유사한 환경에서 검증하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Office%20Life%20/%EC%8A%A4%ED%85%8C%EC%9D%B4%EC%A7%95%20%EC%84%9C%EB%B2%84%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EC%9A%B4%EC%98%81%20%EC%84%9C%EB%B2%84%EC%99%80%20%EC%9C%A0%EC%82%AC%ED%95%9C%20%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%20%EA%B2%80%EC%A6%9D%ED%95%98%EC%9E%90.md) |
295 |
296 |
297 | ### Day 45 (24.08.27.)
298 | | Category | Title | Link |
299 | | :------: | :---: | :--: |
300 | | Activity | 액티비티 launchMode로 singleTask나 singleInstance를 지정하는 것을 지양하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/Activity/%EC%95%A1%ED%8B%B0%EB%B9%84%ED%8B%B0%20launchMode%EB%A1%9C%20singleTask%EB%82%98%20singleInstance%EB%A5%BC%20%EC%A7%80%EC%A0%95%ED%95%98%EB%8A%94%20%EA%B2%83%EC%9D%84%20%EC%A7%80%EC%96%91%ED%95%98%EC%9E%90.md) |
301 |
302 |
303 | ### Day 46 (24.08.29.)
304 | | Category | Title | Link |
305 | | :------: | :---: | :--: |
306 | | Compose | Screen 단에서의 HiltViewModel 생성을 지양하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Screen%20%EB%8B%A8%EC%97%90%EC%84%9C%EC%9D%98%20HiltViewModel%20%EC%83%9D%EC%84%B1%EC%9D%84%20%EC%A7%80%EC%96%91%ED%95%98%EC%9E%90.md) |
307 |
308 |
309 | ### Day 47 (24.09.19.)
310 | | Category | Title | Link |
311 | | :------: | :---: | :--: |
312 | | Compose | Modifier 함수 간의 순서를 고려하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Modifier%20%ED%95%A8%EC%88%98%20%EA%B0%84%EC%9D%98%20%EC%88%9C%EC%84%9C%EB%A5%BC%20%EA%B3%A0%EB%A0%A4%ED%95%98%EC%9E%90.md) |
313 |
314 |
315 | ### Day 48 (24.09.20.)
316 | | Category | Title | Link |
317 | | :------: | :---: | :--: |
318 | | Kotlin | 멤버 확장 함수 사용을 지양하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%EB%A9%A4%EB%B2%84%20%ED%99%95%EC%9E%A5%20%ED%95%A8%EC%88%98%20%EC%82%AC%EC%9A%A9%EC%9D%84%20%EC%A7%80%EC%96%91%ED%95%98%EC%9E%90.md) |
319 |
320 |
321 | ### Day 49 (24.09.23.)
322 | | Category | Title | Link |
323 | | :------: | :---: | :--: |
324 | | Kotlin | 컬렉션의 처리 단계 수를 제한하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%EC%BB%AC%EB%A0%89%EC%85%98%EC%9D%98%20%EC%B2%98%EB%A6%AC%20%EB%8B%A8%EA%B3%84%20%EC%88%98%EB%A5%BC%20%EC%A0%9C%ED%95%9C%ED%95%98%EC%9E%90.md) |
325 |
326 |
327 | ### Day 50 (24.09.24.)
328 | | Category | Title | Link |
329 | | :------: | :---: | :--: |
330 | | Kotlin | 성능이 중요한 경우에 기본 자료형 배열을 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%EC%84%B1%EB%8A%A5%EC%9D%B4%20%EC%A4%91%EC%9A%94%ED%95%9C%20%EA%B2%BD%EC%9A%B0%EC%97%90%20%EA%B8%B0%EB%B3%B8%20%EC%9E%90%EB%A3%8C%ED%98%95%20%EB%B0%B0%EC%97%B4%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
331 |
332 |
333 | ### Day 51 (24.09.25.)
334 | | Category | Title | Link |
335 | | :------: | :---: | :--: |
336 | | Kotlin | 지역 스코프에서는 mutable 컬렉션을 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%EC%A7%80%EC%97%AD%20%EC%8A%A4%EC%BD%94%ED%94%84%EC%97%90%EC%84%9C%EB%8A%94%20mutable%20%EC%BB%AC%EB%A0%89%EC%85%98%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
337 |
338 |
339 | ### Day 52 (24.09.26.)
340 | | Category | Title | Link |
341 | | :------: | :---: | :--: |
342 | | Kotlin | 함수와 메서드의 차이를 이해하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%ED%95%A8%EC%88%98%EC%99%80%20%EB%A9%94%EC%84%9C%EB%93%9C%EC%9D%98%20%EC%B0%A8%EC%9D%B4%EB%A5%BC%20%EC%9D%B4%ED%95%B4%ED%95%98%EC%9E%90.md) |
343 |
344 |
345 | ### Day 53 (24.09.30.)
346 | | Category | Title | Link |
347 | | :------: | :---: | :--: |
348 | | Kotlin | let을 적절한 상황에 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/let%EC%9D%84%20%EC%A0%81%EC%A0%88%ED%95%9C%20%EC%83%81%ED%99%A9%EC%97%90%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
349 |
350 |
351 | ### Day 54 (24.10.02.)
352 | | Category | Title | Link |
353 | | :------: | :---: | :--: |
354 | | Compose | 사이드 이펙트가 발생하는 로직은 LaunchedEffect 스코프 내부에서 호출하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/%EC%82%AC%EC%9D%B4%EB%93%9C%20%EC%9D%B4%ED%8E%99%ED%8A%B8%EA%B0%80%20%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94%20%EB%A1%9C%EC%A7%81%EC%9D%80%20LaunchedEffect%20%EC%8A%A4%EC%BD%94%ED%94%84%20%EB%82%B4%EB%B6%80%EC%97%90%EC%84%9C%20%ED%98%B8%EC%B6%9C%ED%95%98%EC%9E%90.md) |
355 |
356 |
357 | ### Day 55 (24.10.04.)
358 | | Category | Title | Link |
359 | | :------: | :---: | :--: |
360 | | Compose | 비전역적인 ModalBottomSheet 사용을 지양하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/%EB%B9%84%EC%A0%84%EC%97%AD%EC%A0%81%EC%9D%B8%20ModalBottomSheet%20%EC%82%AC%EC%9A%A9%EC%9D%84%20%EC%A7%80%EC%96%91%ED%95%98%EC%9E%90.md) |
361 |
362 |
363 | ### Day 56 (24.10.07.)
364 | | Category | Title | Link |
365 | | :------: | :---: | :--: |
366 | | Database | DB 작업 시 Delete와 Update를 지양하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Database/DB%20%EC%9E%91%EC%97%85%20%EC%8B%9C%20Delete%EC%99%80%20Update%EB%A5%BC%20%EC%A7%80%EC%96%91%ED%95%98%EC%9E%90.md) |
367 |
368 |
369 | ### Day 57 (24.10.08.)
370 | | Category | Title | Link |
371 | | :------: | :---: | :--: |
372 | | Kotlin | 변수의 스코프를 최소화하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%EB%B3%80%EC%88%98%EC%9D%98%20%EC%8A%A4%EC%BD%94%ED%94%84%EB%A5%BC%20%EC%B5%9C%EC%86%8C%ED%99%94%ED%95%98%EC%9E%90.md) |
373 |
374 |
375 | ### Day 58 (24.10.10.)
376 | | Category | Title | Link |
377 | | :------: | :---: | :--: |
378 | | Kotlin | Collection과 Sequence를 적절히 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/Collection%EA%B3%BC%20Sequence%EB%A5%BC%20%EC%A0%81%EC%A0%88%ED%9E%88%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
379 |
380 |
381 | ### Day 59 (24.10.11.)
382 | | Category | Title | Link |
383 | | :------: | :---: | :--: |
384 | | Kotlin | sealed class와 enum class를 적절히 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/sealed%20class%EC%99%80%20enum%20class%EB%A5%BC%20%EC%A0%81%EC%A0%88%ED%9E%88%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
385 |
386 |
387 | ### Day 60 (24.10.14.)
388 | | Category | Title | Link |
389 | | :------: | :---: | :--: |
390 | | ViewModel | Extra를 전달 받을 때 SavedStateHandle을 활용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/ViewModel/Extra%EB%A5%BC%20%EC%A0%84%EB%8B%AC%20%EB%B0%9B%EC%9D%84%20%EB%95%8C%20SavedStateHandle%EC%9D%84%20%ED%99%9C%EC%9A%A9%ED%95%98%EC%9E%90.md) |
391 |
392 |
393 | ### Day 61 (24.10.15.)
394 | | Category | Title | Link |
395 | | :------: | :---: | :--: |
396 | | Compose | Modifier.drawWithCache()를 통해 컴포저블 뷰를 Bitmap 이미지로 변환하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Modifier.drawWithCache()%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EC%BB%B4%ED%8F%AC%EC%A0%80%EB%B8%94%20%EB%B7%B0%EB%A5%BC%20Bitmap%20%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A1%9C%20%EB%B3%80%ED%99%98%ED%95%98%EC%9E%90.md) |
397 |
398 |
399 | ### Day 62 (24.10.16.)
400 | | Category | Title | Link |
401 | | :------: | :---: | :--: |
402 | | Kotlin | 예외를 활용해 코드에 제한을 걸자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%EC%98%88%EC%99%B8%EB%A5%BC%20%ED%99%9C%EC%9A%A9%ED%95%B4%20%EC%BD%94%EB%93%9C%EC%97%90%20%EC%A0%9C%ED%95%9C%EC%9D%84%20%EA%B1%B8%EC%9E%90.md) |
403 |
404 |
405 | ### Day 63 (24.10.18.)
406 | | Category | Title | Link |
407 | | :------: | :---: | :--: |
408 | | Compose | 액티비티를 탐색하여 참조해야하는 경우 baseContext를 활용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/%EC%95%A1%ED%8B%B0%EB%B9%84%ED%8B%B0%EB%A5%BC%20%ED%83%90%EC%83%89%ED%95%98%EC%97%AC%20%EC%B0%B8%EC%A1%B0%ED%95%B4%EC%95%BC%ED%95%98%EB%8A%94%20%EA%B2%BD%EC%9A%B0%20baseContext%EB%A5%BC%20%ED%99%9C%EC%9A%A9%ED%95%98%EC%9E%90.md) |
409 |
410 |
411 | ### Day 64 (24.10.21.)
412 | | Category | Title | Link |
413 | | :------: | :---: | :--: |
414 | | Android Library | 외부 라이브러리를 사용할 때 추상화가 되어있는 영역을 파악하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Library/%EC%99%B8%EB%B6%80%20%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%A0%20%EB%95%8C%20%EC%B6%94%EC%83%81%ED%99%94%EA%B0%80%20%EB%90%98%EC%96%B4%EC%9E%88%EB%8A%94%20%EC%98%81%EC%97%AD%EC%9D%84%20%ED%8C%8C%EC%95%85%ED%95%98%EC%9E%90.md) |
415 |
416 |
417 | ### Day 65 (24.10.22.)
418 | | Category | Title | Link |
419 | | :------: | :---: | :--: |
420 | | Compose | Compose에서 블러 효과를 구현하는 다양한 방식을 구분하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Compose%EC%97%90%EC%84%9C%20%EB%B8%94%EB%9F%AC%20%ED%9A%A8%EA%B3%BC%EB%A5%BC%20%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94%20%EB%8B%A4%EC%96%91%ED%95%9C%20%EB%B0%A9%EC%8B%9D%EC%9D%84%20%EA%B5%AC%EB%B6%84%ED%95%98%EC%9E%90.md) |
421 |
422 |
423 | ### Day 66 (24.10.23.)
424 | | Category | Title | Link |
425 | | :------: | :---: | :--: |
426 | | Compose | Nested Graph를 통해 복잡한 Compose 네비게이션 동작을 구현하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Nested%20Graph%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EB%B3%B5%EC%9E%A1%ED%95%9C%20Compose%20%EB%84%A4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98%20%EB%8F%99%EC%9E%91%EC%9D%84%20%EA%B5%AC%ED%98%84%ED%95%98%EC%9E%90.md) |
427 |
428 |
429 | ### Day 67 (24.10.28.)
430 | | Category | Title | Link |
431 | | :------: | :---: | :--: |
432 | | WebView | pauseTimers()와 resumeTimers()를 통해 WebView의 리소스를 관리하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/WebView/pauseTimers()%EC%99%80%20resumeTimers()%EB%A5%BC%20%ED%86%B5%ED%95%B4%20WebView%EC%9D%98%20%EB%A6%AC%EC%86%8C%EC%8A%A4%EB%A5%BC%20%EA%B4%80%EB%A6%AC%ED%95%98%EC%9E%90.md) |
433 |
434 |
435 | ### Day 68 (24.10.29.)
436 | | Category | Title | Link |
437 | | :------: | :---: | :--: |
438 | | Orbit | Orbit으로 State와 SideEffect를 관리하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Library/Orbit/Orbit%EC%9C%BC%EB%A1%9C%20State%EC%99%80%20SideEffect%EB%A5%BC%20%EA%B4%80%EB%A6%AC%ED%95%98%EC%9E%90.md) |
439 |
440 |
441 | ### Day 69 (24.10.31.)
442 | | Category | Title | Link |
443 | | :------: | :---: | :--: |
444 | | Compose | Compose 뷰에서 무거운 연산 작업을 하는 경우에는 remember에 key 값을 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Compose%20%EB%B7%B0%EC%97%90%EC%84%9C%20%EB%AC%B4%EA%B1%B0%EC%9A%B4%20%EC%97%B0%EC%82%B0%20%EC%9E%91%EC%97%85%EC%9D%84%20%ED%95%98%EB%8A%94%20%EA%B2%BD%EC%9A%B0%EC%97%90%EB%8A%94%20remember%EC%97%90%20key%20%EA%B0%92%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
445 |
446 |
447 | ### Day 70 (24.11.01.)
448 | | Category | Title | Link |
449 | | :------: | :---: | :--: |
450 | | Kotlin | require 함수를 통해 함수 argument에 제한을 걸자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/require%20%ED%95%A8%EC%88%98%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%ED%95%A8%EC%88%98%20argument%EC%97%90%20%EC%A0%9C%ED%95%9C%EC%9D%84%20%EA%B1%B8%EC%9E%90.md) |
451 |
452 |
453 | ### Day 71 (24.11.05.)
454 | | Category | Title | Link |
455 | | :------: | :---: | :--: |
456 | | Test | 단위 테스트의 장단점과 활용하기 적합한 케이스를 파악하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Test/%EB%8B%A8%EC%9C%84%20%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%9D%98%20%EC%9E%A5%EB%8B%A8%EC%A0%90%EA%B3%BC%20%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0%20%EC%A0%81%ED%95%A9%ED%95%9C%20%EC%BC%80%EC%9D%B4%EC%8A%A4%EB%A5%BC%20%ED%8C%8C%EC%95%85%ED%95%98%EC%9E%90.md) |
457 |
458 |
459 | ### Day 72 (24.11.07.)
460 | | Category | Title | Link |
461 | | :------: | :---: | :--: |
462 | | Kotlin | 연산자 오버로딩 시 의미에 맞게 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%EC%97%B0%EC%82%B0%EC%9E%90%20%EC%98%A4%EB%B2%84%EB%A1%9C%EB%94%A9%20%EC%8B%9C%20%EC%9D%98%EB%AF%B8%EC%97%90%20%EB%A7%9E%EA%B2%8C%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
463 |
464 |
465 | ### Day 73 (24.11.08.)
466 | | Category | Title | Link |
467 | | :------: | :---: | :--: |
468 | | Kotlin | 프로퍼티와 함수를 적절히 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0%EC%99%80%20%ED%95%A8%EC%88%98%EB%A5%BC%20%EC%A0%81%EC%A0%88%ED%9E%88%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
469 |
470 |
471 | ### Day 74 (24.11.11.)
472 | | Category | Title | Link |
473 | | :------: | :---: | :--: |
474 | | Clean Code | 일반적인 알고리즘을 구현하는 경우 제네릭을 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Clean%20Code/%EC%9D%BC%EB%B0%98%EC%A0%81%EC%9D%B8%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%84%20%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94%20%EA%B2%BD%EC%9A%B0%20%EC%A0%9C%EB%84%A4%EB%A6%AD%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
475 |
476 |
477 | ### Day 75 (24.11.12.)
478 | | Category | Title | Link |
479 | | :------: | :---: | :--: |
480 | | Compose | Scaffold 하단에 독립적인 버튼이 있는 경우 bottomBar를 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Scaffold%20%ED%95%98%EB%8B%A8%EC%97%90%20%EB%8F%85%EB%A6%BD%EC%A0%81%EC%9D%B8%20%EB%B2%84%ED%8A%BC%EC%9D%B4%20%EC%9E%88%EB%8A%94%20%EA%B2%BD%EC%9A%B0%20bottomBar%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
481 |
482 |
483 | ### Day 76 (24.11.13.)
484 | | Category | Title | Link |
485 | | :------: | :---: | :--: |
486 | | Kotlin | Enum 타입에 대해 분기 처리가 복잡해지는 경우 Enum 클래스에 프로퍼티를 추가하여 상태를 캡슐화하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/Enum%20%ED%83%80%EC%9E%85%EC%97%90%20%EB%8C%80%ED%95%B4%20%EB%B6%84%EA%B8%B0%20%EC%B2%98%EB%A6%AC%EA%B0%80%20%EB%B3%B5%EC%9E%A1%ED%95%B4%EC%A7%80%EB%8A%94%20%EA%B2%BD%EC%9A%B0%20Enum%20%ED%81%B4%EB%9E%98%EC%8A%A4%EC%9D%98%20%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EC%83%81%ED%83%9C%EB%A5%BC%20%EC%BA%A1%EC%8A%90%ED%99%94%ED%95%98%EC%9E%90.md) |
487 |
488 |
489 | ### Day 77 (24.11.15.)
490 | | Category | Title | Link |
491 | | :------: | :---: | :--: |
492 | | Android Studio | 안드로이드의 메모리 누수 탐지 도구를 활용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Studio/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%EC%9D%98%20%EB%A9%94%EB%AA%A8%EB%A6%AC%20%EB%88%84%EC%88%98%20%ED%83%90%EC%A7%80%20%EB%8F%84%EA%B5%AC%EB%A5%BC%20%ED%99%9C%EC%9A%A9%ED%95%98%EC%9E%90.md) |
493 |
494 |
495 | ### Day 78 (24.11.18.)
496 | | Category | Title | Link |
497 | | :------: | :---: | :--: |
498 | | View | 안드로이드 View의 생명주기를 이해하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/View/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%20View%EC%9D%98%20%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0%EB%A5%BC%20%EC%9D%B4%ED%95%B4%ED%95%98%EC%9E%90.md) |
499 |
500 |
501 | ### Day 79 (24.11.19.)
502 | | Category | Title | Link |
503 | | :------: | :---: | :--: |
504 | | TextField | Compose TextField의 비동기 입력을 구현하는 다양한 방법을 구분하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/TextField/Compose%20TextField%EC%9D%98%20%EB%B9%84%EB%8F%99%EA%B8%B0%20%EC%9E%85%EB%A0%A5%EC%9D%84%20%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94%20%EB%8B%A4%EC%96%91%ED%95%9C%20%EB%B0%A9%EB%B2%95%EC%9D%84%20%EA%B5%AC%EB%B6%84%ED%95%98%EC%9E%90.md) |
505 |
506 |
507 | ### Day 80 (24.11.20.)
508 | | Category | Title | Link |
509 | | :------: | :---: | :--: |
510 | | Git | Rebase와 Merge를 적절히 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Git/Rebase%EC%99%80%20Merge%EB%A5%BC%20%EC%A0%81%EC%A0%88%ED%9E%88%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
511 |
512 |
513 | ### Day 81 (24.11.22.)
514 | | Category | Title | Link |
515 | | :------: | :---: | :--: |
516 | | Office Life | 확장 가능성이 있는 기능은 서버에서 동적으로 수정 가능하도록 설계하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Office%20Life%20/%ED%99%95%EC%9E%A5%20%EA%B0%80%EB%8A%A5%EC%84%B1%EC%9D%B4%20%EC%9E%88%EB%8A%94%20%EA%B8%B0%EB%8A%A5%EC%9D%80%20%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C%20%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C%20%EC%88%98%EC%A0%95%20%EA%B0%80%EB%8A%A5%ED%95%98%EB%8F%84%EB%A1%9D%20%EC%84%A4%EA%B3%84%ED%95%98%EC%9E%90.md) |
517 |
518 |
519 | ### Day 82 (24.12.04.)
520 | | Category | Title | Link |
521 | | :------: | :---: | :--: |
522 | | Office Life | 프로젝트 설계 단계에서 Tech Spec 문서를 통해 목표와 개발 범위를 정리하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Office%20Life%20/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%20%EC%84%A4%EA%B3%84%20%EB%8B%A8%EA%B3%84%EC%97%90%EC%84%9C%20Tech%20Spec%20%EB%AC%B8%EC%84%9C%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EB%AA%A9%ED%91%9C%EC%99%80%20%EA%B0%9C%EB%B0%9C%20%EB%B2%94%EC%9C%84%EB%A5%BC%20%EC%A0%95%EB%A6%AC%ED%95%98%EC%9E%90.md) |
523 |
524 |
525 | ### Day 83 (24.12.06.)
526 | | Category | Title | Link |
527 | | :------: | :---: | :--: |
528 | | Kotlin | 단발성 이벤트 처리 시 Flow 대신 Channel을 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%EB%8B%A8%EB%B0%9C%EC%84%B1%20%EC%9D%B4%EB%B2%A4%ED%8A%B8%20%EC%B2%98%EB%A6%AC%20%EC%8B%9C%20Flow%20%EB%8C%80%EC%8B%A0%20Channel%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
529 |
530 |
531 | ### Day 84 (24.12.09.)
532 | | Category | Title | Link |
533 | | :------: | :---: | :--: |
534 | | Coding Principles | 로직과 알고리즘을 구분하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Coding%20Principles/%EB%A1%9C%EC%A7%81%EA%B3%BC%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%84%20%EA%B5%AC%EB%B6%84%ED%95%98%EC%9E%90.md) |
535 | | Clean Architecture | 재사용을 위해 코드를 추출하는 경우 단일 책임 원칙을 고려하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Architecture/Clean%20Architecture/%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%9D%84%20%EC%9C%84%ED%95%B4%20%EC%BD%94%EB%93%9C%EB%A5%BC%20%EC%B6%94%EC%B6%9C%ED%95%98%EB%8A%94%20%EA%B2%BD%EC%9A%B0%20%EB%8B%A8%EC%9D%BC%20%EC%B1%85%EC%9E%84%20%EC%9B%90%EC%B9%99%EC%9D%84%20%EA%B3%A0%EB%A0%A4%ED%95%98%EC%9E%90.md) |
536 |
537 |
538 | ### Day 85 (24.12.10.)
539 | | Category | Title | Link |
540 | | :------: | :---: | :--: |
541 | | Kotlin | 일반적인 프로퍼티의 행위는 프로퍼티 위임으로 추출하여 재사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/%EC%9D%BC%EB%B0%98%EC%A0%81%EC%9D%B8%20%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0%EC%9D%98%20%ED%96%89%EC%9C%84%EB%8A%94%20%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0%20%EC%9C%84%EC%9E%84%EC%9C%BC%EB%A1%9C%20%EC%B6%94%EC%B6%9C%ED%95%98%EC%97%AC%20%EC%9E%AC%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
542 |
543 |
544 | ### Day 86 (24.12.11.)
545 | | Category | Title | Link |
546 | | :------: | :---: | :--: |
547 | | Kotlin | variance 한정자를 통해 제너릭의 타입 간 관련성을 관리하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/variance%20%ED%95%9C%EC%A0%95%EC%9E%90%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EC%A0%9C%EB%84%88%EB%A6%AD%EC%9D%98%20%ED%83%80%EC%9E%85%20%EA%B0%84%20%EA%B4%80%EB%A0%A8%EC%84%B1%EC%9D%84%20%EA%B4%80%EB%A6%AC%ED%95%98%EC%9E%90.md) |
548 |
549 |
550 | ### Day 87 (24.12.13.)
551 | | Category | Title | Link |
552 | | :------: | :---: | :--: |
553 | | Compose | Compose 화면 최초 진입 시 발생하는 사이드 이펙트는 LaunchedEffect(Unit)를 사용하지 말자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Compose%20%ED%99%94%EB%A9%B4%20%EC%B5%9C%EC%B4%88%20%EC%A7%84%EC%9E%85%20%EC%8B%9C%20%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94%20%EC%82%AC%EC%9D%B4%EB%93%9C%20%EC%9D%B4%ED%8E%99%ED%8A%B8%EB%8A%94%20LaunchedEffect(Unit)%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80%20%EB%A7%90%EC%9E%90.md) |
554 |
555 |
556 | ### Day 88 (24.12.16.)
557 | | Category | Title | Link |
558 | | :------: | :---: | :--: |
559 | | Coroutine | 코루틴의 데이터 공유 상태로 인한 문제를 해결하는 다양한 방법을 구분하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/Coroutine/%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%9D%98%20%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EA%B3%B5%EC%9C%A0%20%EC%83%81%ED%83%9C%EB%A1%9C%20%EC%9D%B8%ED%95%9C%20%EB%AC%B8%EC%A0%9C%EB%A5%BC%20%ED%95%B4%EA%B2%B0%ED%95%98%EB%8A%94%20%EB%8B%A4%EC%96%91%ED%95%9C%20%EB%B0%A9%EB%B2%95%EC%9D%84%20%EA%B5%AC%EB%B6%84%ED%95%98%EC%9E%90.md) |
560 | | Coroutine | Mutex를 통해 자원에 대한 동시 접근을 제한하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/Coroutine/Mutex%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EC%9E%90%EC%9B%90%EC%97%90%20%EB%8C%80%ED%95%9C%20%EB%8F%99%EC%8B%9C%20%EC%A0%91%EA%B7%BC%EC%9D%84%20%EC%A0%9C%ED%95%9C%ED%95%98%EC%9E%90.md) |
561 |
562 |
563 | ### Day 89 (24.12.19.)
564 | | Category | Title | Link |
565 | | :------: | :---: | :--: |
566 | | Android Studio | .editorconfig 파일을 통해 ktlint 스타일을 커스텀하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Studio/.editorconfig%20%ED%8C%8C%EC%9D%BC%EC%9D%84%20%ED%86%B5%ED%95%B4%20ktlint%20%EC%8A%A4%ED%83%80%EC%9D%BC%EC%9D%84%20%EC%BB%A4%EC%8A%A4%ED%85%80%ED%95%98%EC%9E%90.md) |
567 |
568 |
569 | ### Day 90 (24.12.23.)
570 | | Category | Title | Link |
571 | | :------: | :---: | :--: |
572 | | Compose | Compose 컴포넌트를 구현할 때 Material에 대한 의존성을 최소화하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Compose%20%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%A5%BC%20%EA%B5%AC%ED%98%84%ED%95%A0%20%EB%95%8C%20Material%EC%97%90%20%EB%8C%80%ED%95%9C%20%EC%9D%98%EC%A1%B4%EC%84%B1%EC%9D%84%20%EC%B5%9C%EC%86%8C%ED%99%94%ED%95%98%EC%9E%90.md) |
573 | | Github | Github 라이선스 종류를 구분하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Github/Github%20%EB%9D%BC%EC%9D%B4%EC%84%A0%EC%8A%A4%20%EC%A2%85%EB%A5%98%EB%A5%BC%20%EA%B5%AC%EB%B6%84%ED%95%98%EC%9E%90.md) |
574 |
575 |
576 | ### Day 91 (24.12.25.)
577 | | Category | Title | Link |
578 | | :------: | :---: | :--: |
579 | | Compose | ImageVector와 PainterResource를 적절히 사용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/ImageVector%EC%99%80%20PainterResource%EB%A5%BC%20%EC%A0%81%EC%A0%88%ED%9E%88%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90.md) |
580 | | Coding Principles | 설계와 아키텍처 개념을 구분하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Coding%20Principles/%EC%84%A4%EA%B3%84%EC%99%80%20%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%20%EA%B0%9C%EB%85%90%EC%9D%84%20%EA%B5%AC%EB%B6%84%ED%95%98%EC%9E%90.md) |
581 |
582 |
583 | ### Day 92 (24.12.29.)
584 | | Category | Title | Link |
585 | | :------: | :---: | :--: |
586 | | Clean Architecture | Mapper 클래스를 통해 컴포넌트 간 의존성의 방향을 제어하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Architecture/Clean%20Architecture/Mapper%20%ED%81%B4%EB%9E%98%EC%8A%A4%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%20%EA%B0%84%20%EC%9D%98%EC%A1%B4%EC%84%B1%EC%9D%98%20%EB%B0%A9%ED%96%A5%EC%9D%84%20%EC%A0%9C%EC%96%B4%ED%95%98%EC%9E%90.md) |
587 |
588 |
589 | ### Day 93 (25.01.02.)
590 | | Category | Title | Link |
591 | | :------: | :---: | :--: |
592 | | Gradle | Gradle Type-Safe Project Accessors를 통해 멀티 모듈 의존성을 안전하게 작성하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Studio/Gradle/Gradle%20Type-Safe%20Project%20Accessors%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EB%A9%80%ED%8B%B0%20%EB%AA%A8%EB%93%88%20%EC%9D%98%EC%A1%B4%EC%84%B1%EC%9D%84%20%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C%20%EC%9E%91%EC%84%B1%ED%95%98%EC%9E%90.md) |
593 |
594 |
595 | ### Day 94 (25.01.04.)
596 | | Category | Title | Link |
597 | | :------: | :---: | :--: |
598 | | Compose | Slot Pattern을 통해 컴포저블의 특정 영역을 외부에서 자유롭게 구성하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Slot%20Pattern%EC%9D%84%20%ED%86%B5%ED%95%B4%20%EC%BB%B4%ED%8F%AC%EC%A0%80%EB%B8%94%EC%9D%98%20%ED%8A%B9%EC%A0%95%20%EC%98%81%EC%97%AD%EC%9D%84%20%EC%99%B8%EB%B6%80%EC%97%90%EC%84%9C%20%EC%9E%90%EC%9C%A0%EB%A1%AD%EA%B2%8C%20%EA%B5%AC%EC%84%B1%ED%95%98%EC%9E%90.md) |
599 |
600 |
601 | ### Day 95 (25.01.06.)
602 | | Category | Title | Link |
603 | | :------: | :---: | :--: |
604 | | Android Tool | R8을 통해 효율적으로 앱을 최적화하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Tool/R8%EC%9D%84%20%ED%86%B5%ED%95%B4%20%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9C%BC%EB%A1%9C%20%EC%95%B1%EC%9D%84%20%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EC%9E%90.md) |
605 |
606 |
607 | ### Day 96 (25.01.09.)
608 | | Category | Title | Link |
609 | | :------: | :---: | :--: |
610 | | Compose | throttleClickable을 통해 중복된 클릭 이벤트를 제한하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/throttleClickable%EC%9D%84%20%ED%86%B5%ED%95%B4%20%EC%A4%91%EB%B3%B5%EB%90%9C%20%ED%81%B4%EB%A6%AD%20%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A5%BC%20%EC%A0%9C%ED%95%9C%ED%95%98%EC%9E%90.md) |
611 |
612 |
613 | ### Day 97 (25.01.11.)
614 | | Category | Title | Link |
615 | | :------: | :---: | :--: |
616 | | Kotlin | runCatching, mapCatching, recoverCatching을 통해 안전하게 예외를 처리하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/runCatching%2C%20mapCatching%2C%20recoverCatching%EC%9D%84%20%ED%86%B5%ED%95%B4%20%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C%20%EC%98%88%EC%99%B8%EB%A5%BC%20%EC%B2%98%EB%A6%AC%ED%95%98%EC%9E%90.md) |
617 |
618 |
619 | ### Day 98 (25.01.13.)
620 | | Category | Title | Link |
621 | | :------: | :---: | :--: |
622 | | Compose | Modifier에 role을 명시하여 접근성을 개선하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/Modifier%EC%97%90%20role%EC%9D%84%20%EB%AA%85%EC%8B%9C%ED%95%98%EC%97%AC%20%EC%A0%91%EA%B7%BC%EC%84%B1%EC%9D%84%20%EA%B0%9C%EC%84%A0%ED%95%98%EC%9E%90.md) |
623 |
624 |
625 | ### Day 99 (25.01.17.)
626 | | Category | Title | Link |
627 | | :------: | :---: | :--: |
628 | | Activity | enableEdgeToEdge를 통해 확장된 화면을 제공하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/Activity/enableEdgeToEdge%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%ED%99%95%EC%9E%A5%EB%90%9C%20%ED%99%94%EB%A9%B4%EC%9D%84%20%EC%A0%9C%EA%B3%B5%ED%95%98%EC%9E%90.md) |
629 |
630 |
631 | ### Day 100 (25.01.20.)
632 | | Category | Title | Link |
633 | | :------: | :---: | :--: |
634 | | WebView | 변경이나 오류가 자주 발생하는 화면은 웹뷰로 구현하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Component/WebView/%EB%B3%80%EA%B2%BD%EC%9D%B4%EB%82%98%20%EC%98%A4%EB%A5%98%EA%B0%80%20%EC%9E%90%EC%A3%BC%20%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94%20%ED%99%94%EB%A9%B4%EC%9D%80%20%EC%9B%B9%EB%B7%B0%EB%A1%9C%20%EA%B5%AC%ED%98%84%ED%95%98%EC%9E%90.md) |
635 | | Android Studio | SDK의 설정과 AndroidManifest.xml의 속성이 충돌하는 경우에 tools:replace 속성을 활용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Studio/SDK%EC%9D%98%20%EC%84%A4%EC%A0%95%EA%B3%BC%20AndroidManifest.xml%EC%9D%98%20%EC%86%8D%EC%84%B1%EC%9D%B4%20%EC%B6%A9%EB%8F%8C%ED%95%98%EB%8A%94%20%EA%B2%BD%EC%9A%B0%EC%97%90%20tools%3Areplace%20%EC%86%8D%EC%84%B1%EC%9D%84%20%ED%99%9C%EC%9A%A9%ED%95%98%EC%9E%90.md) |
636 |
637 |
638 | ### Day 101 (25.01.22.)
639 | | Category | Title | Link |
640 | | :------: | :---: | :--: |
641 | | Kotlin | Spread 연산자(*)를 이용해 배열이나 컬렉션 요소를 개별 인자로 변환하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Kotlin/Spread%20%EC%97%B0%EC%82%B0%EC%9E%90(*)%EB%A5%BC%20%EC%9D%B4%EC%9A%A9%ED%95%B4%20%EB%B0%B0%EC%97%B4%EC%9D%B4%EB%82%98%20%EC%BB%AC%EB%A0%89%EC%85%98%20%EC%9A%94%EC%86%8C%EB%A5%BC%20%EA%B0%9C%EB%B3%84%20%EC%9D%B8%EC%9E%90%EB%A1%9C%20%EB%B3%80%ED%99%98%ED%95%98%EC%9E%90.md) |
642 |
643 |
644 | ### Day 102 (25.01.29.)
645 | | Category | Title | Link |
646 | | :------: | :---: | :--: |
647 | | Compose | PreviewParameterProvider를 통해 프리뷰의 상태별 파라미터를 주입하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/PreviewParameterProvider%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%ED%94%84%EB%A6%AC%EB%B7%B0%EC%9D%98%20%EC%83%81%ED%83%9C%EB%B3%84%20%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EB%A5%BC%20%EC%A3%BC%EC%9E%85%ED%95%98%EC%9E%90.md) |
648 | | Android Studio | local.properties와 gradle.properties 파일을 구분하여 활용하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Studio/local.properties%EC%99%80%20gradle.properties%20%ED%8C%8C%EC%9D%BC%EC%9D%84%20%EA%B5%AC%EB%B6%84%ED%95%98%EC%97%AC%20%ED%99%9C%EC%9A%A9%ED%95%98%EC%9E%90.md) |
649 | | Compose | DisposableEffect를 통해 생명주기에 따라 정리가 필요한 사이드 이펙트를 처리하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Compose/DisposableEffect%EB%A5%BC%20%ED%86%B5%ED%95%B4%20%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0%EC%97%90%20%EB%94%B0%EB%9D%BC%20%EC%A0%95%EB%A6%AC%EA%B0%80%20%ED%95%84%EC%9A%94%ED%95%9C%20%EC%82%AC%EC%9D%B4%EB%93%9C%20%EC%9D%B4%ED%8E%99%ED%8A%B8%EB%A5%BC%20%EC%B2%98%EB%A6%AC%ED%95%98%EC%9E%90.md) |
650 |
651 |
652 | ### Day 103 (25.02.01.)
653 | | Category | Title | Link |
654 | | :------: | :---: | :--: |
655 | | Java | OkHttp WebSocket을 통해 소켓 통신을 구현하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Language/Java/Okhttp%20WebSocket%EC%9D%84%20%ED%86%B5%ED%95%B4%20%EC%86%8C%EC%BC%93%20%ED%86%B5%EC%8B%A0%EC%9D%84%20%EA%B5%AC%ED%98%84%ED%95%98%EC%9E%90.md) |
656 |
657 |
658 | ### Day 104 (25.02.03.)
659 | | Category | Title | Link |
660 | | :------: | :---: | :--: |
661 | | Lottie | Lottie 사용 시 애니메이션 비활성화 및 속도 조절 설정을 고려하자 | [🔗](https://github.com/b1urrrr/til/blob/main/Android%20Library/Lottie/Lottie%20%EC%82%AC%EC%9A%A9%20%EC%8B%9C%20%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98%20%EB%B9%84%ED%99%9C%EC%84%B1%ED%99%94%20%EB%B0%8F%20%EC%86%8D%EB%8F%84%20%EC%A1%B0%EC%A0%88%20%EC%84%A4%EC%A0%95%EC%9D%84%20%EA%B3%A0%EB%A0%A4%ED%95%98%EC%9E%90.md) |
662 |
670 |
671 |
672 |
--------------------------------------------------------------------------------
/Test/단위 테스트의 장단점과 활용하기 적합한 케이스를 파악하자.md:
--------------------------------------------------------------------------------
1 | ## 단위 테스트의 장단점과 활용하기 적합한 케이스를 파악하자
2 | ### 단위 테스트의 효과
3 | - 유스케이스 테스트
4 | - 오류 케이스 및 잠재적 문제 테스트
5 | - 엣지 케이스 및 잘못된 아규먼트 테스트
6 | - EX) `Int.MAX_VALUE`
7 | - 수동 테스트보다 빠르게 검증 가능
8 | - 체계적으로 정립된 아키텍처 사용하는 것이 강제됨
9 | ### 단위 테스트의 단점
10 | - 단위 테스트 구축에 많은 시간 소요
11 | - 테스트 활용 가능한 아키텍처 환경 구축 필요
12 | - 남은 개발 과정에 대한 확실한 이해 필요
13 | ### 단위 테스트에 적합한 코드의 특징
14 | - 복잡한 로직
15 | - 수정이 잦고 리팩토링이 일어날 가능성 존재
16 | - 비즈니스 로직
17 | - 공용 API 로직
18 | - 문제가 자주 발생하는 로직
19 | - 수정이 필요한 프로덕션 환경의 오류
20 |
--------------------------------------------------------------------------------