├── 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 | ![view_lifecycle](https://github.com/user-attachments/assets/079dc610-8b5d-4486-a99e-a5b82704f58a) 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 | --------------------------------------------------------------------------------