diff --git a/coroutine/docs/README.md b/coroutine/docs/README.md
new file mode 100644
index 0000000..937e4ff
--- /dev/null
+++ b/coroutine/docs/README.md
@@ -0,0 +1,19 @@
+# 테스트
+- [ ] 테스트 코드를 작성해야하는 이유
+- [ ] Fake vs Mock
+- [ ] 좋은 테스트를 작성하는 법
+- [ ] 코루틴 테스트하는법
+- [ ] 플로우 테스트하는법
+- [ ] ViewModel 테스트하는법
+- [ ] 테스트하기 어려운 코드 테스트하는법
+
+# 코루틴 예외
+- [x] 코루틴 예외 전파
+- [x] 코루틴 예외 전파 방지 - SupervisorJob
+- [ ] 코루틴 예외 전파 방지 - supervisorScope
+- [ ] 코루틴 예외 전파 방지 - supervisorScope vs SupervisorJob
+- [ ] 코루틴 예외 처리 - CoroutineExceptionHandler
+- [ ] 코루틴 예외 처리 - 코루틴 스코프 함수 + try-catch
+
+# 코루틴
+- [ ] suspend function 을 잘 써보자
\ No newline at end of file
diff --git "a/coroutine/docs/coroutine/coroutine_context/\354\275\224\353\243\250\355\213\264_\353\224\224\354\212\244\355\214\250\354\262\230.md" "b/coroutine/docs/coroutine/coroutine_context/\354\275\224\353\243\250\355\213\264_\353\224\224\354\212\244\355\214\250\354\262\230.md"
new file mode 100644
index 0000000..1f6eb86
--- /dev/null
+++ "b/coroutine/docs/coroutine/coroutine_context/\354\275\224\353\243\250\355\213\264_\353\224\224\354\212\244\355\214\250\354\262\230.md"
@@ -0,0 +1,15 @@
+코루틴 컨택스트의 일종
+
+일반 디스패처 특징 : 코루틴의 실행을 요청받으면 작업 대기열에 적재한 후 스레드풀에서 사용할 수 있는 스레드로 보낸다
+무제한 디스패처 특징: 스레드 스위칭 없이 호출자 스레드에서 바로 실행
+
+무제한 디스패처: 재개될 때 코루틴을 재개하는 스레드에서 재개된
+
+일반적으로 재개될 때 코루틴은 코루틴 컨택스에 존재하는 Dispatcher 에 의해 재분배된다. (Continuation 안에 있는 coroutineContext 속에 있는 Dispatcher 를 통해)
+그러나 무제한 디스패처는 resume 될 때 재개가 실행되는 스레드에서 재실행됨 (예측이 불가능한 비동기 작업이됨 그래서 실 프로덕트 환경에서는 쓰지 않음)
+
+
+
+# CPS
+
+코루틴은 코루틴의 실행 정보를 저장하고 전달할 때 CPS(Continuation Passing Style) 기법을 채택하고 있다.
diff --git "a/coroutine/docs/coroutine/coroutine_context/\354\275\224\353\243\250\355\213\264_\354\212\244\354\275\224\355\224\204.md" "b/coroutine/docs/coroutine/coroutine_context/\354\275\224\353\243\250\355\213\264_\354\212\244\354\275\224\355\224\204.md"
new file mode 100644
index 0000000..dca45a8
--- /dev/null
+++ "b/coroutine/docs/coroutine/coroutine_context/\354\275\224\353\243\250\355\213\264_\354\212\244\354\275\224\355\224\204.md"
@@ -0,0 +1,77 @@
+# 코루틴 스코프
+
+코루틴 스코프: `스코프 범위 내에서 생성된 코루틴에게 실행 환경을 제공하고, 이 코루틴의 동작을 관리`한다.
+
+코루틴의 실행 환경을 저장하고 관리하는 CoroutineContext 를 가지고 있다.
+
+코루틴 스코프에 있는 CoroutineContext 에 만약 IO Dispatcher 가 존재한다면 해당 코루틴 스코프 내에서 만들어진 코루틴들은
+모두 IO Dispatcher 에 의해 IO 스레드에서 동작하게 될 것이다.
+
+그리고, CoroutineScope 에 있는 Job 이 있으면 모든 코루틴들은 이 Job 을 root Job 으로 삼고 있을 것이다.
+
+확인해보자
+
+```kotlin
+import kotlin.coroutines.coroutineContext
+
+suspend fun main() {
+ val scope = object : CoroutineScope {
+ override val coroutineContext: CoroutineContext = Dispatchers.Default + Job()
+ }
+
+ // launch 로 인해 생성되는 코루틴에게 coroutineContext(실행 환경) 을 제공한다.
+
+ scope.launch {
+ println(coroutineContext)
+ }
+}
+```
+
+# CoroutineScope() 팩토리 함수를 통해 스코프를 만들자
+
+기본적으로 `CoroutineScope()` 팩토리 함수에는 params로 받는 context 에 Job 이 없으면 Job() 을 통해 root Job을 만들고 있다.
+
+# 커스텀 CoroutineScope 를 만들지 말아라 !
+
+만약, 커스텀 CoroutineScope 를 만들면 어떻게 될까?
+
+```kotlin
+suspend fun main() {
+ val scope = object : CoroutineScope {
+ override val coroutineContext: CoroutineContext = Dispatchers.Default
+ }
+
+ scope.launch {
+ println(coroutineContext[Job]) // StandaloneCoroutine{Active}@4fba6dc8
+ println(coroutineContext[Job]?.parent) // null
+ }
+ delay(100)
+}
+```
+CoroutineScope 에 rootJob 이 존재하지 않게된다.
+그럼 CoroutineScope 의 책임 중 하나인 코루틴의 동작을 관리할 수 없게 된다.
+
+```kotlin
+suspend fun main() {
+ val scope = object : CoroutineScope {
+ override val coroutineContext: CoroutineContext = Dispatchers.Default
+ }
+
+ scope.cancel()
+}
+```
+만약, scope 의 cancel() 을 호출해보자
+
+바로 에러 발생..
+```
+Exception in thread "main" java.lang.IllegalStateException: Scope cannot be cancelled because it does not have a job
+```
+
+
+CoroutineScope 는 스코프 내에서 생성된 코루틴들에게 실행 환경을 제공하고 이들의 실행 범위를 제어하는 역할
+그래서 coroutineContext를 홀딩하고 있다
+1) 코루틴들에게 실행 환경을 제공
+ - Dispatcher - corotineContext 내부의 CoroutineDispatcher 가 launch 코루틴이 어느 스레드에서 돌아가도록할지 배분한다.
+ - 부모 Job 제공
+ -
+2) 실행 범위를 제어하는 역할
\ No newline at end of file
diff --git "a/coroutine/docs/coroutine/coroutine_context/\354\275\224\353\243\250\355\213\264_\354\273\250\355\203\235\354\212\244\355\212\270.md" "b/coroutine/docs/coroutine/coroutine_context/\354\275\224\353\243\250\355\213\264_\354\273\250\355\203\235\354\212\244\355\212\270.md"
new file mode 100644
index 0000000..a136131
--- /dev/null
+++ "b/coroutine/docs/coroutine/coroutine_context/\354\275\224\353\243\250\355\213\264_\354\273\250\355\203\235\354\212\244\355\212\270.md"
@@ -0,0 +1,11 @@
+# 코루틴 컨택스트
+
+코루틴 컨택스트는 코루틴의 실행 환경을 그룹화하여 저장하고 전달하는 객체이다.
+이를 활용해 코루틴의 실행 상태가 어떤지, 어떤 스레드에 분배 받을지 등 `코루틴의 작동 방식`을 정합니다.
+- Job: 코루틴의 실행 상태를 나타내고 코루틴의 실행을 제어할 수 있다.
+- Dispatcher: 코루틴이 어떤 스레드에서 보내져 동작할지 지정한다.
+- CoroutineExceptionHandler: 예외 처리기
+
+코루틴 컨택스트는 여러 Element 로 구성되어 있는데 대표적으로 Dispatcher, Job, CoroutineExceptionHandler 가 있겠다.
+코루틴은 자신의 컨택스트를 자식에게 전달한다.(자식이 부모의 컨텍스트를 상속받는다.)
+그리고, 자식은 상속받은 부모의 컨텍스트를 대체한다.
\ No newline at end of file
diff --git "a/coroutine/src/main/kotlin/com/murjune/practice/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270_\354\240\204\355\214\214.md" "b/coroutine/docs/coroutine/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270_\354\240\204\355\214\214.md"
similarity index 97%
rename from "coroutine/src/main/kotlin/com/murjune/practice/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270_\354\240\204\355\214\214.md"
rename to "coroutine/docs/coroutine/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270_\354\240\204\355\214\214.md"
index a3683fd..1bdc043 100644
--- "a/coroutine/src/main/kotlin/com/murjune/practice/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270_\354\240\204\355\214\214.md"
+++ "b/coroutine/docs/coroutine/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270_\354\240\204\355\214\214.md"
@@ -9,6 +9,16 @@
만약, 해당 글이나 테코톡을 보고 궁금하신 점이나 함께 논의하고 싶은 부분이 있다면 댓글이나 메일로 남겨주세요 😁
+> 아래 지식들을 알고 있으면 해당 글을 이해하기 쉬울 거에요!
+> - CoroutineContext
+> - Job, launch, async
+> - CoroutineScope
+> - 코루틴 구조화된 동시성
+> - 코루틴 취소 메커니즘
+> - suspend function
+> - coroutineScope
+
+
## 코루틴 예외 처리의 중요성
kotlin을 활용하는 대부분의 프로그램(대표적으로 Android) 에서는 [Coroutine](https://kotlinlang.org/docs/coroutines-overview.html) 을 활용하여 비동기 처리하고 있다.
diff --git "a/coroutine/src/main/kotlin/com/murjune/practice/exception/supervisor/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200.md" "b/coroutine/docs/coroutine/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200_SupervisorJob.md"
similarity index 91%
rename from "coroutine/src/main/kotlin/com/murjune/practice/exception/supervisor/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200.md"
rename to "coroutine/docs/coroutine/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200_SupervisorJob.md"
index abcbf0e..83e0935 100644
--- "a/coroutine/src/main/kotlin/com/murjune/practice/exception/supervisor/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200.md"
+++ "b/coroutine/docs/coroutine/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200_SupervisorJob.md"
@@ -5,14 +5,24 @@
코루틴 예외에 대한 보충 설명 및 상세한 설명을 포스팅하려 합니다.
이번 포스팅에서는 `SupervisorJob 을 활용해서 예외 전파 제한하는 방법`에 대해서 알아볼 것입니다.
-테코톡에서는 [3:48 ~ 8:12] 에 해당하는 내용입니다.
-만약, 코루틴이 예외를 어떻게 전파되는지 궁금하신 분은 이전에 포스팅한 [코루틴 예외가 전파되는 방식](https://velog.io/@murjune/kotlin-Coroutine-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%98%88%EC%99%B8%EA%B0%80-%EC%A0%84%ED%8C%8C%EB%90%98%EB%8A%94-%EB%B0%A9%EC%8B%9D%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-0lac2p97) 을 참고해주세요!
+테코톡에서는 [3:48 ~ 8:12] 에 해당하는 내용입니다.
+
+지난 시간에 배운 내용 리마인드~
+> - 코루틴 예외 전파 메커니즘
+> 1) 예외가 발생할 시, `자기 자신`을 취소시킨다. (자식 코루틴들 모두 취소)
+> 2) 예외 발생 시, `부모로 예외를 전파`시킨다. (부모, 형제 코루틴들 모두 취소)
+>
+> 좀 더 궁금하신 분은 이전에 포스팅한 [코루틴 예외가 전파되는 방식](https://velog.io/@murjune/kotlin-Coroutine-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%98%88%EC%99%B8%EA%B0%80-%EC%A0%84%ED%8C%8C%EB%90%98%EB%8A%94-%EB%B0%A9%EC%8B%9D%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-0lac2p97)을 참고해주세요
+
+
---
# 예외 전파 제한이 필요한 경우
코루틴을 활용하여 비동기 작업을 하다 보면 하나의 작업을 여러 작업으로 쪼개 병렬처리하는 경우가 종종 있습니다. 보통 suspend 함수에서 코루틴 빌더함수 [async](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) 와 코루틴 스코프 함수[coroutineScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html)를 활용하여 처리합니다.
+> async 와 coroutineScope 를 사용하여 병렬 처리하는 이유를 자세히 알고 싶으신 분은 [Kotlin Coroutine: suspend 함수를 Effective 하게 설계하자!](https://velog.io/@murjune/Kotlin-Coroutine-suspend-%ED%95%A8%EC%88%98%EB%A5%BC-Effective-%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90-fsaiexxw#3-coroutinescope-or-withcontext-%ED%95%A8%EC%88%98%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%9E%90) 에서 `2) suspend function 에서 병렬 처리할 때, CoroutineScope를 사용하지 말자` 와 `3) coroutineScope or withContext 함수를 활용하자!⭐️` 부분을 참고해주세요 😉
+
로컬 저장소의 이미지 경로를 통해 서버에 이미지들을 업로드한 후, 이미지 url을 받아오는 예제를 통해 `예외 전파 제한이 필요성`에 대해 알아볼 것이에요!😎
diff --git "a/coroutine/docs/coroutine/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200_supervisorScope.md" "b/coroutine/docs/coroutine/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200_supervisorScope.md"
new file mode 100644
index 0000000..d832f69
--- /dev/null
+++ "b/coroutine/docs/coroutine/exception/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200_supervisorScope.md"
@@ -0,0 +1,217 @@
+!youtube[3DNbRnl0im4]
+
+테코톡에서는 [9:01 ~ 9:58] 에 해당하는 내용입니다.
+지금까지 배운 내용 리마인드 해봅시다.
+
+> - SupervisorJob
+> 1) SupervisorJob 은 자식 코루틴의 예외 전파를 방지한다.
+> 2) SupervisorJob() 로 생성된 SupervisorJob 은 root Job 이 된다.
+> 3) SupervisorJob() 로 생성된 SupervisorJob 은 항상 active 하다.
+>
+> - 이전 포스팅
+ > [코루틴 예외가 전파되는 방식](https://velog.io/@murjune/kotlin-Coroutine-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%98%88%EC%99%B8%EA%B0%80-%EC%A0%84%ED%8C%8C%EB%90%98%EB%8A%94-%EB%B0%A9%EC%8B%9D%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-0lac2p97)
+ > [코루틴 예외 전파 제한 왜 하는거지?(with SupervisorJob)](https://velog.io/@murjune/kotlin-Coroutine-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%98%88%EC%99%B8-%EC%A0%84%ED%8C%8C-%EC%A0%9C%ED%95%9C-%EC%99%9C-%ED%95%98%EB%8A%94%EA%B1%B0%EC%A7%80with-SupervisorJob)
+
+## Intro
+
+[지난 포스팅](https://velog.io/@murjune/kotlin-Coroutine-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%98%88%EC%99%B8-%EC%A0%84%ED%8C%8C-%EC%A0%9C%ED%95%9C-%EC%99%9C-%ED%95%98%EB%8A%94%EA%B1%B0%EC%A7%80with-SupervisorJob)에서는 이미지를 업로드하는 예시를 통해 `SupervisorJob`로 예외 전파를 제한하는 방법을 배워봤습니다. 사실은 이미지 업로드 예시는 `SupervisorJob` 보다는 `supervisorScope` 을 활용하는 것이 더 적절하다고 했었죠?
+
+코루틴 스코프함수와 `supervisorScope`에 대해 배워본 후, 이미지 업로드 예시를 `supervisorScope` 로 리팩토링해봅시다!💪
+
+## 1. 코루틴 스코프 함수
+
+> 코루틴 스코프 함수에 대해 이미 알고 계신 독자는 이번 챕터는 넘기셔도 좋습니다 😁
+
+
+코루틴 스코프 함수는 `새로운 코루틴 스코프를 생성하는 suspend 함수` 입니다.
+`launch` 나 `async`는 CoroutineScope 확장함수이기에 일반 suspend 함수에서는 호출할 수 없습니다.
+
+```kotlin
+suspend fun foo() = coroutineScope {
+ launch { ... } // 호출 불가 ❌
+ launch { ... } // 호출 불가 ❌
+}
+```
+
+이런 경우 coroutineScope 와 같은 코루틴 스코프 함수를 활용합니다.
+(withContext, withTimeOut, supervisorScope.. 등 다양한 스코프 함수가 있습니다.)
+
+```kotlin
+suspend fun bar() = coroutineScope {
+ launch { ... } // ✅
+ launch { ... } // ✅
+}
+```
+
+좀 더 자세한 설명을 원하시면 [suspend 함수를 Effective 하게 설계하자!](https://velog.io/@murjune/Kotlin-Coroutine-suspend-%ED%95%A8%EC%88%98%EB%A5%BC-Effective-%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90-fsaiexxw)을 봐주세요 🙏
+
+# 2. 코루틴 스코프 함수의 특징
+
+코루틴 스코프 함수는 다음과 같은 특징을 가지고 있습니다.
+> 1) ⭐️ 호출자의 코루틴 컨텍스트를 받아, 호출자 코루틴과 부모-자식관계를 보장한다.
+> 2) ⭐️ 호출자 코루틴은 끝날때까지 일시 중단된다.
+> 3) ⭐️ 자식 코루틴의 작업이 끝날때까지 대기한다.
+> 4) 일반 함수와 같은 방식으로 예외를 던진다.
+> 5) 결과값을 반환한다.
+
+코루틴 빌더 함수(launch) 도 Job을 생성할 때 부모-자식관계를 보장해주는데요, `코루틴 빌더 함수` 와 `코루틴 스코프 함수`를 비교해볼까요?
+
+- 코루틴 빌더 함수 (launch)
+```kotlin
+fun main() = runBlocking {
+ launch {
+ delay(10)
+ println("after")
+ }
+ println("before") // 먼저 호출
+}
+```
+
+launch 의 경우에는 비동기적으로 실행되기 때문에 start 하자마자 바로 탈출하기 때문에 `before` 가 먼저 출력됩니다.
+
+
+
+
+
+- 코루틴 스코프 함수 (coroutineScope)
+```kotlin
+fun main() = runBlocking {
+ coroutineScope {
+ delay(10)
+ println("after") // 먼저 호출
+ }
+ println("before")
+}
+```
+
+coroutineScope 는 suspend 함수이기 때문에 종료할때까지 `호출자 코루틴은 일시중단` 됩니다.
+그리고, `after` 가 출력된 이후에 coroutineScope 종료되고 `before` 가 출력됩니다.
+
+
+
+
+
+이를 통해 코루틴 빌더 함수(launch)와 달리 코루틴 스코프 함수(coroutineScope)는 호출자 코루틴과 동기적으로 실행된다는 것을 알 수 있습니다😁
+
+## 2. supervisorScope
+
+> 1) ⭐️ 호출자의 코루틴 컨텍스트를 받아, 호출자 코루틴과 부모-자식관계를 보장한다.
+> 2) ⭐️ 호출자 코루틴은 끝날때까지 일시 중단된다.
+> 3) ⭐️ 자식 코루틴의 작업이 끝날때까지 대기한다.
+> 4) 일반 함수와 같은 방식으로 예외를 던진다.
+> 5) 결과값을 반환한다.
+
+supervisorScope 함수는 `coroutineScope` 와 같은 코루틴 스코프 함수입니다.
+그렇기에 `coroutineScope` 와 같이 위와 같은 특징을 갖는데요, 추가적으로`supervisorScope` 는 `자식 코루틴의 예외 전파를 제한`해줍니다.
+
+
+
+그 이유는 `supervisorScope` 는 내부적으로 `SupervisorJob` 을 가지고 있기 때문입니다.
+
+간단한 예시로 확인해볼까요?
+
+```kotlin
+suspend fun foo() = supervisorScope {
+ launch { println("Child 1") }
+ launch { error("예외 발생 😵") }
+ launch { println("Child 2") }
+ launch { println("Child 3") }
+}
+
+suspend fun main() {
+ println("시작")
+ foo()
+ println("끝")
+}
+```
+
+
+
+
+
+child 코루틴에서 예외가 발생해도 부모 코루틴에 예외를 전파시키지 않고 있네요!
+
+> 위 예제의 `supervisorScope` 를 `coroutineScope` 로 바꿔서 실행한 결과와 비교해보시길 추천드려요
+
+추가적으로 `supervisorScope`을 처음 사용하면 자주 오인하는 부분이 있습니다.
+
+#### supervisorScope {} 내부에서 발생하는 예외는 전파합니다. 자식 코루틴의 예외를 전파 제한합니다.
+
+```kotlin
+suspend fun foo() = supervisorScope {
+ error("예외 전파 제한 못함 🤯")
+ launch { println("Child 1") }
+ launch { println("Child 2") }
+}
+```
+
+
+
+헷갈릴 수 있는 부분이기에 주의해주세요 😎
+
+
+## 3. 이미지 업로드 예제 supervisorScope 로 리팩토링
+
+```kotlin
+suspend fun uploadImages(localImagePaths: List): List = coroutineScope {
+ val supervisor = SupervisorJob(coroutineContext.job) // 부모 코루틴 설정
+ val result = localImagePaths.map { localImagePath ->
+ async(supervisor) { uploadImage(localImagePath) } { uploadImage(localImagePath) }
+ }.map {
+ try { // await() 예외 처리
+ it.await()
+ } catch (e: IllegalStateException) {
+ null
+ }
+ }
+ supervisor.complete() // supervisor 명시적 종료
+ result
+}
+```
+[코루틴 예외 전파 제한 왜 하는거지?](https://velog.io/@murjune/kotlin-Coroutine-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%98%88%EC%99%B8-%EC%A0%84%ED%8C%8C-%EC%A0%9C%ED%95%9C-%EC%99%9C-%ED%95%98%EB%8A%94%EA%B1%B0%EC%A7%80with-SupervisorJob) 에서 사용한 다수의 이미지 업로드하는 예제에요.
+
+SupervisorJob() 으로 생성된 Job 의 특징 때문에 아래와 같은 작업을 처리해줬습니다.
+
+```kotlin
+ // 1. 부모-자식 관계 설정
+ val supervisorJob = SupervisorJob(coroutineContext.job)
+ CoroutineScope(supervisorJob).launch { .. }
+ // 2. supervisor 명시적 종료
+ supervisorJob.complete()
+```
+딱 봐도 복잡해보이죠? 😤
+이제 supervisorScope 를 활용해서 이미지 업로드 예제를 리팩토링해봅시다.
+
+```kotlin
+suspend fun uploadImages(localImagePaths: List): List = supervisorScope {
+ localImagePaths.map { localImagePath ->
+ async { uploadImage(localImagePath) } { uploadImage(localImagePath) }
+ }.map {
+ try {
+ it.await()
+ } catch (e: IllegalStateException) {
+ null
+ }
+ }
+}
+```
+
+훨씬 깔끔해졌죠? (try-catch 문 때문에 그렇게 안보일 수도 있겠지만 😅)
+이렇듯, 독립적인 작업들을 병렬 처리해야할 경우에 supervisorScope 를 많이 사용합니다.
+
+> supervisorScope 을 사용한 예시를 좀 더 보고 싶으시면 [suspend 함수를 Effective 하게 설계하자!](https://velog.io/@murjune/Kotlin-Coroutine-suspend-%ED%95%A8%EC%88%98%EB%A5%BC-Effective-%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90-fsaiexxw) 의 마지막 챕터를 봐주세요~
+
+## 4. 정리
+
+> - supervisorScope
+> 1) 호출자의 코루틴 컨텍스트를 받아, 호출자 코루틴과 부모-자식관계를 보장한다.
+> 2) 호출자 코루틴은 끝날때까지 일시 중단된다.
+> 3) 자식 코루틴의 작업이 끝날때까지 대기한다.
+
+오늘은 코루틴 스코프 함수에 대해 간략하게 알아보고 그 중 `supervisorScope` 를 활용해 예외를 전파하는 방법에 대해 알아 봤어요.
+
+`SupervisorJob`, `supervisorScope` 둘다 배워보니 `supervisorScope`가 훨씬 사용하기 쉽고 편하다는 생각이 들거에요. 그럼 예외를 전파할때는 무조건 supervisorScope 만을 사용하면 될까요? 🤔
+
+다음 포스팅에서는 `supervisorJob`과 `supervisorScope`의 차이점을 비교하고, 각각을 적절하게 사용하는 방법과 실제 사용 사례를 소개해드리겠습니다. 👋
+
+
diff --git "a/coroutine/docs/coroutine/suspend_func_effective_\355\225\230\352\262\214\354\202\254\354\232\251.md" "b/coroutine/docs/coroutine/suspend_func_effective_\355\225\230\352\262\214\354\202\254\354\232\251.md"
new file mode 100644
index 0000000..089997f
--- /dev/null
+++ "b/coroutine/docs/coroutine/suspend_func_effective_\355\225\230\352\262\214\354\202\254\354\232\251.md"
@@ -0,0 +1,494 @@
+> - 해당 포스팅에서 언급하는 코루틴은 [kotlin-coroutine](https://kotlinlang.org/docs/coroutines-overview.html) 입니다.
+> - 코루틴에 대한 기본 지식이 없다면 해당 포스팅을 이해하기 어려울 수 있습니다.
+> - 내부적으로 suspend function 이 어떻게 중단/재개되는지 다루지 않습니다.
+> - 이번 포스팅과 [suspend function 공식문서](https://kotlinlang.org/docs/composing-suspending-functions.html#structured-concurrency-with-async) 을 함께 읽는 것을 추천해요 ⭐️
+
+## Intro
+간단한 예제를 통해 기본적인 suspend function 활용법에 대해 알아보자
+
+```kotlin
+// super 개발자의 삶..🫢
+fun main() = runBlocking {
+ println("아침에 일어난다")
+ println("밥을 먹는다.")
+ delay(100)
+ println("코딩하기")
+ println("밥을 먹는다.")
+ delay(100)
+ println("코딩하기")
+ delay(100)
+ println("밥을 먹는다.")
+ delay(100)
+ println("코딩하기")
+ delay(100)
+ println("잠을 잔다.")
+}
+```
+현재 "코딩하기"와 "밥을 먹는다." 코드가 중복해서 사용되고 있다. 이런 경우, 함수화를 통해 코드구조를 개선시키고 싶을 것이다.
+
+일반 함수의 경우 일시 중단을 지원하지 않기 때문에 코루틴을 일시 중단하는 `delay()` 를 호출 할 수 없다.(참고로 delay() 함수도 suspned 함수이다.)
+
+
+
+
+
+
+
+이럴 때, `일시 중단가능한` suspend function 을 사용해 함수화를 해주면 된다.
+
+
+
+
+
+
+```kotlin
+suspend fun coding() {
+ println("코딩하기")
+ delay(100)
+}
+
+suspend fun eat() {
+ println("밥을 먹는다.")
+ delay(100)
+}
+
+fun main() = runBlocking {
+ println("아침에 일어난다")
+ eat()
+ coding()
+ eat()
+ coding()
+ eat()
+ coding()
+ println("잠을 잔다.")
+}
+```
+이처럼 suspend function 은 코루틴을 활용한 복잡한 비동기 코드를 구조화하여 재사용성과 가독성을 위해 사용된다.
+
+이제 suspend function 여러 테스트 케이스를 통해 suspend function 의 여러가지 사용법에 대해 알아보자 😁
+
+> 만약, 코루틴 테스트를 처음 접하는 독자가 있다면 다음 포스팅을 먼저 읽고 오길 추천한다
+[코루틴 테스트 쌩기초 탈출하기 💪](https://velog.io/@murjune/kotlin-Coroutine-%EC%BD%94%EB%A3%A8%ED%8B%B4-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8C%A9%EA%B8%B0%EC%B4%88-%ED%83%88%EC%B6%9C%ED%95%98%EA%B8%B0)
+
+
+## 1) Suspend 함수는 코루틴이 아니다.
+
+```kotlin
+suspend fun suspendFuncA() {
+ delay(300)
+ println("Hello")
+}
+
+suspend fun suspendFuncB() {
+ delay(200)
+ println("Odooong")
+}
+```
+
+간단한 suspend 함수 `suspendFuncA` , `suspendFuncA` 가 있다. 각각, 300ms, 200ms delay를 주었다.
+
+```kotlin
+@Test
+fun `Suspend 함수는 코루틴이 아니다`() = runTest {
+ val job = launch {
+ suspendFuncA()
+ suspendFuncB()
+ }
+ advanceUntilIdle() // 현재 testScope 내부 코루틴 작업이 모두 끝날 때까지 대기
+ currentTime shouldBe 500
+ // output: Hello Odooong
+}
+```
+해당 테스트는 몇 초 뒤에 종료될까? 🤔 300ms? 500ms?
+한 번 생각해보시죠 ㅎ ㅎ
+.
+.
+.
+.
+`suspendFuncA()` 과 `suspendFuncB()` 가 서로 독립적인 코루틴이라 생각하고, suspend 함수들이 비동기적으로 실행되어 `300ms` 만큼 시간이 걸릴 것이라 예상한 독자들도 있을 것이다.
+
+
+
+
+
+그러나, 해당 코드는 위 그림처럼 실행된다.
+** suspend function 이 종료될 때까지 호출부 코루틴(runBlocking)은 blocking ** 되기에 해당 테스트는 총 `500ms` 만큼 시간이 걸린다.
+
+위 테스트 코드는 아래 코드와 완전히 동일하다.
+
+```kotlin
+@Test
+fun `suspend function 은 코드 블록에 불과하다 - 위 테스트 함수와 완전히 동일`() = runTest {
+ val job = launch {
+ delay(300)
+ println("Hello")
+ delay(200)
+ println("Odooong")
+ }
+ advanceUntilIdle()
+ currentTime shouldBe 500
+ // output: Hello Odooong
+}
+```
+
+#### Suspend function 은 코루틴이 아니다. 호출부 코루틴 내에서 돌아가는 중단 가능한 코드 블럭에 불과하는 점을 잊지 말자
+
+> Intro 에서 다룬 예제와 비슷한데 자주 오해할 수 있는 내용이라 한 번 더 강조하기 위해 다뤘다.
+
+## 2) suspend function 에서 병렬 처리할 때, CoroutineScope를 사용하지 말자
+
+이번에는 suspendFuncA() 외 suspendFuncB() 를 병렬처리 해보자
+
+```kotlin
+@Test
+fun `비동기 처리 - 동시성`() = runTest {
+ val job = launch {
+ val childA = launch {
+ suspendFuncA()
+ }
+ val childB = launch {
+ suspendFuncB()
+ }
+ }
+ advanceUntilIdle()
+ currentTime shouldBe 300
+ // output: Odooong Hello
+}
+```
+
+빌드 시 suspendFuncA()와 suspendFuncB() 를 동시에 실행시키기 때문에 해당 테스트는 `300ms` 만큼 걸린다.
+
+
+
+이때, 인덴트 깊이가 늘어나는 것이 가독성을 해친다고 생각하여 다음과 같이 `launch{}` 을 suspend 함수 내로 분리하고 싶은 욕구가 들 수도 있다.
+
+
+
+
+
+
+
+그러나, launch() 함수는 CoroutineScope의 확장 함수이기에 suspend function에서 바로 호출이 불가능하다. launch() 함수를 사용하기 위해서는 새로운 [CoroutineScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) 를 통해 열어줘야 한다.
+
+
+
+
+
+
+**여기서, 많은 사람들이 CoroutineScope 를 사용하는 실수를 한다. **
+
+
+
+
+
+CoroutineScope 를 사용하면서 구조화된 동시성을 유지해주기 위해선 [CoroutineContext](https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html) 를 넣어줘야한다. 이때, 다음과 같이 `Dispatcher.IO` 혹은 `EmptyCoroutineContext` 를 넣어주곤 한다.
+
+
+
+
+
+해당 코드는 문제가 없을까?
+
+
+
+❌ 아니다. 해당 코드에는 2가지 문제점이 있다.
+- 1) 호출자의 코루틴과 구조화된 동시성이 깨진다
+- 2) suspend function 이 종료되어도 코루틴은 동작한다.(즉, 비동기적으로 코드가 동작한다)
+
+CoroutineScope()은 매개변수에 호출자의 [Job](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/) or Job을 포함하는 CoroutineContext를 넘겨주지 않으면, 호출부 코루틴과 독립적인 코루틴 환경을 구축한다.
+
+
+
+
+
+
+따라서, 위 그림처럼 호출부의 코루틴과의 구조화된 동시성이 깨지게 된다.
+
+```kotlin
+// suspend 키워드도 빼도 된다
+fun suspendFunc() {
+ val independantJob = CoroutineScope(Dispatchers.IO).launch {
+ delay(200)
+ println("Hellow Odooong")
+ }
+}
+
+@Test
+fun `다른 코루틴 디스패처를 사용하면 구조화된 동시성이 깨져, 독립된 코루틴이 된다`() = runTest {
+ // runTest 과 아래 suspendFunc() 에서 생성된 코루틴은 별개이다.
+ suspendFunc()
+ advanceUntilIdle()
+ currentTime shouldBe 0
+ // Hellow Odooong 이 호출되지 않음
+}
+```
+
+부모 코루틴은 자식 코루틴이 모두 종료될때까지 대기하는 특성을 가지고 있다. 그러나, CoroutineScope를 사용했기에 runTest 코루틴(부모 코루틴)과의 구조를 깨버렸기에 runTest 코루틴은 independantJob이 끝날 때까지 대기하지 않는다.
+
+
+
+
+
+
+테스트를 실행시켜보면 `advanceUntilIdle()` 를 호출했음에도 runTest 가 바로 종료되는 것을 볼 수 있다.
+
+- coroutineContext 활용
+
+
+```kotlin
+suspend fun suspendFuncAWithCoroutineScope() {
+ CoroutineScope(coroutineContext + CoroutineName("ChildA")).launch {
+ delay(300)
+ print(" Hello ")
+ }
+}
+
+suspend fun suspendFuncBWithCoroutineScope() {
+ CoroutineScope(coroutineContext + CoroutineName("ChildB")).launch {
+ delay(200)
+ print(" Odooong ")
+ }
+}
+```
+coroutineContext property 를 활용하면 현재 실행되고 있는 코루틴의 context를 가져올 수 있다. CoroutineScope 에 `coroutineContext`를 넣어주면 runTest 코루틴과 `구조화된 동시성`을 유지할 수 있다.
+
+
+
+
+
+
+그럼 테스트 코드를 다음과 같이 깔끔하게 나타낼 수 있다.
+
+```kotlin
+@Test
+fun `suspend 함수 내에 코루틴 스코프를 열어 자식 코루틴 생성 - 동시성`() = runTest {
+ suspendFuncAWithCoroutineScope()
+ suspendFuncBWithCoroutineScope()
+ advanceUntilIdle()
+ currentTime shouldBe 300
+ // Output: Odooong Hello
+}
+```
+
+> 휴! 시간도 `300ms` 로 단축했고, 가독성도 챙겼으니 해당 코드는 좋은 코드일까? 🤔
+
+
+
+❌ 언듯 보기에는 좋아보일 수 있으나, 잘못 설계한 것이다.
+동료개발자는 suspend 함수가 종료되는 시점에 내부 작업들이 끝났을 것이라 예상할 것이다.
+
+
+
+
+
+그러나, 실상은 그림과 같이 suspend 함수는 자식 코루틴을 만들고 바로 종료되고, 자식 코루틴들은 비동기적으로 실행되고 있다.
+
+
+
+이는 심각한 버그의 원인이 될 수 있으며, 어디서 발생한 버그인지 찾기도 매우 힘들다.🥲
+
+> 위 코드와 비슷한 형태로 설계된 코드가 때문에 버그가 발생하여 쌩고생한 경험이 있다 🥲
+
+- ** suspend function 에서 CoroutineScope 를 사용하지 말자 **
+- ** suspend function 실행이 종료되었을 때, 내부 코드의 실행이 완료되도록 설계하자! **
+
+## 3) coroutineScope or withContext 함수를 활용하자!⭐️
+
+suspend function 내부 동작을 병렬 처리하고 싶다면, [coroutineScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html) 를 활용하자!(대문자 CoroutineScope 말고 소문자 coroutineScope 라는 함수가 있다)
+
+```kotlin
+@Test
+fun `suspend 함수 분리 - 동시성`() = runTest {
+ mergedSuspendFunc()
+ currentTime shouldBe 300
+ // Output: Odooong Hello
+}
+
+suspend fun mergedSuspendFunc() = coroutineScope {
+ launch(CoroutineName("ChildA")) {
+ suspendFuncA()
+ }
+ launch(CoroutineName("ChildB")) {
+ suspendFuncB()
+ }
+}
+```
+coroutineScope를 사용하면 `runTest 코루틴`는 `mergedSuspendFunc()` 가 종료될 때까지 대기하고, `mergedSuspendFunc()` 내부에서는 병렬적으로 코드를 실행하도록 할 수 있다.
+
+
+
+
+
+코루틴 관계도는 다음과 같다
+
+
+
+
+
+코드 구조와 시간적 효율성 2마리 토끼를 모두 잡을 수 있게 되었다 😁
+
+> [coroutineScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html) 의 특성
+> - 호출부의 `coroutineContext` 를 상속받는 Job을 생성하기에 코루틴의 구조화된 동시성을 깨지 않는다.
+- 자식 코루틴이 모두 끝날때까지 호출부의 코루틴을 blocking 시킨다는 특징이 있다.(suspend function과 찰떡궁합이다.)
+- 코드 블럭의 마지막 값을 return 한다.
+
+ 만약, 다른 디스패처를 활용하고 싶다면 [withContext](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html)를 활용해주면 된다. 디스패터를 지정해줄 수 있다는 점을 제외하고 coroutineScope와 동일한 기능을 한다.
+
+```kotlin
+suspend fun mergedSuspendFunc() = withContext(Dispatcher.IO) {
+ launch {
+ suspendFuncA()
+ }
+ launch {
+ suspendFuncB()
+ }
+}
+```
+
+---
+
+suspend 함수 내부에서 **병렬 처리를 하고 결과값을 반환**해야 할 경우가 종종 생긴다. 이런 경우 coroutineScope 와 [async](https://www.google.com/search?q=async+coroutine&sca_esv=94e45fce1d51b060&sxsrf=ADLYWIJ8zw8Iclt335PJ9p5eyK67-80qfw%3A1719349228477&ei=7C97Zv_qHKu1vr0PkYCUgAs&ved=0ahUKEwj_kIiA0_eGAxWrmq8BHREABbAQ4dUDCA8&uact=5&oq=async+coroutine&gs_lp=Egxnd3Mtd2l6LXNlcnAiD2FzeW5jIGNvcm91dGluZTIFEAAYgAQyBRAAGIAEMgUQABiABDIEEAAYHjIGEAAYHhgPMgQQABgeMgQQABgeMgYQABgFGB4yBhAAGAUYHjIGEAAYCBgeSO8bUNACWPIacAN4AZABAZgB2QKgAbwUqgEHMS43LjQuMrgBA8gBAPgBAZgCD6ACqhDCAgoQABiwAxjWBBhHwgILEAAYgAQYsQMYgwHCAggQABiABBiiBJgDAIgGAZAGCpIHBzQuNy4zLjGgB9pI&sclient=gws-wiz-serp) 를 함께 사용하면 된다.(Like 분할정복)
+
+
+우테코 선릉 캠퍼스 크루들을 fetch 해오는 예시를 통해 알아보자!
+```kotlin
+suspend fun fetchWootecoAndroidCrews(): List {
+ delay(300)
+ return listOf("오둥이", "꼬상", "하디", "팡태", "악어", "케이엠")
+}
+
+suspend fun fetchWootecoFrontendCrews(): List {
+ delay(200)
+ return listOf("토다리", "제이드")
+}
+```
+안드 크루와 프론트 크루를 불러오는 api가 있다
+두 함수의 실행 결과는 서로 독립적이기 때문에 병렬 처리하기에 매우 적합하다 😁
+
+```kotlin
+suspend fun fetchWootecoCrews(): List = coroutineScope {
+ val androidJob = async { fetchWootecoAndroidCrews() }
+ val frontJob = async { fetchWootecoFrontendCrews() }
+ // 결과값 반환
+ androidJob.await() + frontJob.await()
+}
+```
+따라서, `async{}`로 각 함수가 서로 다른 코루틴에서 실행되도록 묶어준 후, 결과값을 반환해주는 부분에 `await()`를 호출해준다.
+
+```kotlin
+@Test
+fun `우테코 선릉 캠퍼스 크루들 불러오기`() = runTest {
+ val crews = fetchWootecoCrews()
+ currentTime shouldBe 300
+ crews shouldContainExactlyInAnyOrder listOf("오둥이", "꼬상", "하디", "팡태", "악어", "케이엠", "토다리", "제이드")
+}
+```
+
+이처럼 어떤 값을 반환하고 내부적으로 병렬처리를 하는 suspend function 을 설계할 때 `coroutineScope + async`를 활용해보자 😁
+
+필자는 우테코 쇼핑 주문하기 미션에서 적용가능한 쿠폰을 불러오는 UseCase 에서 `coroutineScope + async` 를 활용하여 병렬처리를 적용해본 적이 있다.
+
+[해당 코드](https://github.com/murjune/android-shopping-order/blob/step2/app/src/main/java/woowacourse/shopping/domain/usecase/order/LoadAvailableDiscountCouponsUseCase.kt)
+
+## 4) supervisorScope 사용을 고려해보자(심화)
+
+suspend function 내에서 `coroutineScope` 와 `async/launch` 를 활용하여 여러 api 들을 병렬 처리할 때 한가지 제약이 있다.
+100개의 api를 통합하는 suspend function이 있고, 자식 코루틴이 한개라도 exception이 터진다면 예외가 전파되어 모든 코루틴이 cancel된다는 것이다.
+
+이때, 기획에서는 통신에 성공한 데이터라도 불러와달라고 요청을 했다! 그럼 어떻게 처리하는 것이 적절할까?
+이번에는 우테코 코치님들을 불러오는 예시를 통해 알아보자
+
+```kotlin
+suspend fun fetchAndroidCoaches(): List {
+ delay(50)
+ return listOf("제이슨", "레아", "제임스")
+}
+
+suspend fun fetchFrontCoaches(): List {
+ delay(150)
+ return listOf("준", "크론")
+}
+
+suspend fun fetchBackCoaches(): List {
+ delay(70)
+ throw NoSuchElementException("제가 백엔드 코치님들은 모릅니다..")
+}
+
+
+```
+현재 `fetchBackCoaches()` 함수에서 NoSuchElementException 예외를 발생시키고 있다.
+
+```kotlin
+@Test
+fun `하나라도 예외가 발생하면, 모든 작업이 취소된다`() = runTest {
+ shouldThrow {
+ fetchErrorWootecoCoaches()
+ }
+ currentTime shouldBe 70
+}
+
+suspend fun fetchWootecoCoaches() = coroutineScope {
+ val androidJob = async { fetchAndroidCoaches() }
+ val frontJob = async { fetchFrontCoaches() }
+ val backendJob = async { fetchBackCoaches() }
+ androidJob.await() + frontJob.await() + backendJob.await()
+}
+```
+
+
+
+
+
+
+`fetchWootecoCoaches()` 를 불러올 때 예외가 `fetchBackCoaches -> coroutineScope -> runTest` 로 예외가 전파되며, 모든 작업들은 취소가 된다.
+
+
+
+
+이럴 때 에러 전파를 방지하기 위해 [supervisorScope{}](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html) 를 활용할 수 있다.
+
+> supervisorScope 는 coroutineScope 와 거의 똑같지만, 자식 코루틴의 예외 전파를 차단시킨다는 특성이 있다.
+
+이를 활용하여 다음과 같이 개선할 수 있다.
+```kotlin
+suspend fun fetchWootecoCoaches() = supervisorScope {
+ val androidJob = async { fetchAndroidCoaches() }
+ val frontJob = async { fetchFrontCoaches() }
+ val backendJob = async { fetchBackCoaches() }
+ // result
+ val backendResult = runCatching { backendJob.await() }
+ androidJob.await() + frontJob.await() + backendResult.getOrDefault(emptyList())
+}
+```
+기존 coroutineScope가 supervisorScope로 교체된 것 외에도, `await()` 하는 부분에서 runCatching으로 예외 처리를 해주는 것을 볼 수 있다.
+
+async 으로 인해 만들어진 코루틴에서 Exception이 발생하면 `1) 전파되는 예외 + 2) await() 에서 발생하는 예외`가 모두 처리 해줘야하기 때문이다.
+
+```kotlin
+@Test
+fun `supervisorScope 를 활용하여 Error 전파 방지`() = runTest {
+ val coaches = fetchWootecoCoaches()
+ currentTime shouldBe 150
+ coaches shouldContainExactlyInAnyOrder listOf("제이슨", "레아", "제임스", "준", "크론")
+}
+```
+
+
+
+
+
+
+여러 코루틴 성공한 값들만 불러와 반환하도록 개선해주었다!
+
+## 정리
+
+>
+- suspend function 코루틴이 아니다.
+- suspend function 은 호출부 코루틴의 코드블럭이다.
+- suspend function 이 종료될 때, 내부 실행 코드도 종료되도록 설계하자
+- suspend function 내부에서 병렬 처리를 할 떄, 구조화된 동시성을 보장해주기 위해 coroutineScope/withContext 를 사용하자
+- 자식 코루틴의 예외 전파를 방지하고 싶다면, coroutineScope 대신 supervisorScope를 사용하자
+
+
+긴 글 읽어주셔서 감사합니다.
+이해가 안되거나 피드백 주실 부분이 있다면 편하게 댓글로 부탁드려요!! 🙇♂️
diff --git "a/coroutine/docs/coroutine/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200_SupervisorJob_vs_supervisorScope.md" "b/coroutine/docs/coroutine/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200_SupervisorJob_vs_supervisorScope.md"
new file mode 100644
index 0000000..701ccee
--- /dev/null
+++ "b/coroutine/docs/coroutine/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\240\204\355\214\214_\353\260\251\354\247\200_SupervisorJob_vs_supervisorScope.md"
@@ -0,0 +1,172 @@
+
+!youtube[3DNbRnl0im4]
+
+테코톡에서는 [8:13 ~ 9: 00] 에 해당하는 내용입니다.
+
+지난 시간에 배운 내용 정리~
+> - SupervisorJob
+> 1) SupervisorJob 은 자식 코루틴의 예외 전파를 방지한다.
+> 2) SupervisorJob() 로 생성된 SupervisorJob 은 root Job 이 된다.
+> 3) SupervisorJob() 로 생성된 SupervisorJob 은 항상 active 하다.
+>
+> - supervisorScope
+> 1) `자식 코루틴의 예외 전파를 방지`한다.
+> 2) 호출자의 코루틴 컨텍스트를 받아, 호출자 코루틴과 부모-자식관계를 보장한다.
+> 3) 호출자 코루틴은 끝날때까지 일시 중단된다.
+> 4) 자식 코루틴의 작업이 끝날때까지 대기한다.
+
+> - 이전 포스팅
+ > [1. 코루틴 예외가 전파되는 방식](https://velog.io/@murjune/kotlin-Coroutine-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%98%88%EC%99%B8%EA%B0%80-%EC%A0%84%ED%8C%8C%EB%90%98%EB%8A%94-%EB%B0%A9%EC%8B%9D%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-0lac2p97)
+ > [2. 코루틴 예외 전파 제한 왜 하는거지?(with SupervisorJob)](https://velog.io/@murjune/kotlin-Coroutine-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%98%88%EC%99%B8-%EC%A0%84%ED%8C%8C-%EC%A0%9C%ED%95%9C-%EC%99%9C-%ED%95%98%EB%8A%94%EA%B1%B0%EC%A7%80with-SupervisorJob)
+ > [3. 코루틴 예외 전파 제한 (supervisorScope)](https://velog.io/@murjune/kotlin-Coroutine-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%98%88%EC%99%B8-%EC%A0%84%ED%8C%8C-%EC%A0%9C%ED%95%9C-supervisorScope)
+
+# Intro
+
+지난 포스팅에서 `SupervisorJob()` 과 `supervisorScope` 에 대해 알아보았습니다.
+`supervisorScope` 이 자동으로 호출자 코루틴과의 부모-자식 관계를 보장해주기에 사용하기 훨씬 편합니다.
+
+> `supervisorScope` 사용하기 편하다는 거 알겠어 그럼 `SupervisorJob()`는 언제 씀? 🤔
+
+그럼 위와 같은 생각이 들 수도 있는데요, `supervisorJob`과 `supervisorScope`의 차이점을 비교하고, 각각을 적절하게 사용하는 방법과 실제 사용 사례를 소개해드리겠습니다. 👋
+
+# 1. 일반적인 경우에는 supervisorScope 를 사용하자
+
+SupervisorJob 과 supervisorScope 를 사용해 예외 전파를 방지하는 예제입니다.
+
+```kotlin
+SupervisorJob 예시
+
+supervisorScope 예시
+```
+
+`SupervisorJob()` 을 사용하면 구조화가 무너지기에 추가적인 설정들이 필요합니다.
+확실히 `supervisorScope` 를 사용하는 것이 편해보이네요
+
+그래서, 코루틴 내부(코루틴 스코프 내부)나 suspend 함수에서는 `supervisorScope` 를 활용하여 예외 전파 방지하는 것이 훨씬 낫습니다.
+
+# 2. CoroutineScope 를 생성할 때 SupervisorJob 을 사용하자
+
+supervisorScope 는 suspend 함수이기에, 일반함수에서는 사용할 수 없다는 제약이 있습니다.
+
+> 참고) suspend 함수는 같은 suspend 함수나 코루틴 내부(코루틴 스코프 내부)에서만 호출할 수 있습니다.
+>
+> 
+
+그래서, 일반 함수에서 새롭게 코루틴을 생성하고 사용할 때는 `CoroutineScope` 의 coroutineContext 에 `SupervisorJob()` 을 지정해줘야합니다.
+
+CoroutineScope 에 SupervisorJob() 을 지정안해주면 어떤 문제가 있을까요?
+
+
+```kotlin
+val scope = CoroutineScope(CoroutineExceptionHandler { _, _ -> println("예외 발생") })
+
+fun loadImages() = scope.launch { println("이미지..") }
+
+fun loadUsers() = scope.launch { error("error 😵") }
+
+fun loadCustomers() = scope.launch { println("손님..") }
+```
+
+이미지, 유저, 손님 정보를 비동기적으로 동시에 불러오고 있습니다. 그리고, 다음 시간에 배울 CoroutineExceptionHandler 를 통해 예외 처리도 해주고 있습니다.
+
+````kotlin
+loadImages()
+loadUsers()
+loadCustomers()
+````
+실행하면 어떻게 될까요?
+
+
+
+이미지와 손님 정보를 불러오는 코루틴들이 모두 취소가 되었습니다 😨
+이는 `loadUsers()` 에서 발생한 예외가 CoroutineScope 내부에 있는 coroutineContext 의 Job 에 전파되어 CoroutineScope 가 관리하는 모든 코루틴이 취소되었기 때문입니다.
+
+
+
+> 참고로 CoroutineExceptionHandler 는 예외만 처리하는 것이지 예외 전파는 막지 못합니다.
+> 그래서, 모두 취소 된 것
+
+CoroutineScope 가 담당하는 코루틴들 중 하나의 코루틴에서 예외가 발생했다고 모든 코루틴이 취소 되는 것은 아무래도 이상합니다.😨 그래서, 이런 경우에 CoroutineScope() 의 인자에 `SupervisorJob()` 을 넣어주어 `예외 전파 제한`해주어야 합니다.
+
+```kotlin
+val scope = CoroutineScope(SupervisorJob() + CoroutineExceptionHandler { _, _ -> println("예외 발생") })
+```
+
+이제는 loadUsers() 에서 예외가 발생해도 다른 코루틴에 영향을 주지 않습니다 😁
+
+
+
+이렇듯 `CoroutineScope` 를 생성할 때, Root 코루틴 컨택스트에 SupervisorJob 을 설정해두어 예외 전파 방지하는 것이 좋습니다.
+
+그럼 이제 안드로이드에서는 SupervisorJob() 을 어떻게 사용하는지 볼까요??
+
+# 3. lifecycleScope, viewModelScope
+
+- lifecycleScope
+ 
+- viewModelScope
+ 
+
+안드로이드에서는 `viewModelScope` 와 `lifecycleScope` 를 생성할 때 coroutineContext 에 `SupervisorJob()` 를 설정해줍니다. 그래서 지금까지 ViewModel 작업할 때 하나의 코루틴에서 예외가 발생해도 다른 작업들이 취소되지 않았던 것이에요 🤭
+
+이번에는 제가 프로젝트에서 로깅 분석을 비동기 처리하기 위해 CoroutineScope 를 만들 때 SupervisorJob() 을 직접 사용한 사례를 소개해드리겠습니다~
+
+
+# 4. loggerScope, analyticsScope
+
+안드로이드에서는 사용자의 행동 분석, 에러 모니터링을 위해 Firebase Analytics, Crashlytics 를 사용하곤 합니다.
+어떤 작업에 로그를 남기거나 분석하는 작업이 실 서비스의 성능에 영향을 주면 안될 것입니다.
+따라서, 로깅 작업 같은 경우 비동기 처리하는 것이 적절할 것입니다.
+
+만약, 특정 id 에 해당하는 유저 정보를 받아오는 Repository 가 있다고 해봅시다.
+```kotlin
+suspend fun userDetail(id: String): User = coroutineScope {
+ userDataSource.userDetail(id).also {
+ launch { analytics.logUserEvent(id) }
+ }
+ }
+```
+조회한 User id를 analytics 에 로그를 남기는 작업을 비동기 처리하였습니다.
+이때, 2가지 문제점이 있습니다.
+### 1) 성능 저하
+
+비동기 처리를 하기 위해 coroutineScope 와 launch 를 사용했지만, coroutineScope 은 모든 자식이 끝날때까지 대기합니다. 따라서,launch 내부 로그 분석 작업이 끝나야 `userDetail()`가 종료됩니다.
+해당 코드는 비동기 처리 작업을 하느니만 못한 잘못된 코드입니다..😨
+
+### 2) 예외 전파
+```kotlin
+launch { analytics.logUserEvent(id) }
+```
+만약 `analytics.logUserEvent(id)` 에서 예외가 발생하면 coroutineScope 로 예외가 전파됩니다.
+user 정보를 불러오는데 성공했는데 로그 분석에 실패했다고 실 서비스 코드가 실패하는 것은 절대 안될 일입니다 😨
+
+그래서 저는 다음과 같이 `analyticsScope`라는 새로운 코루틴 스코프를 만들었습니다.
+```kotlin
+/**
+ * 비동기적으로 데이터 수집, 분석, 로깅 등의 작업을 처리하는 용도로 사용하는 CoroutineScope
+ */
+private val analyticsExcpetionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
+ Timber.e(throwable)
+}
+val analyticsScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + analyticsExcpetionHandler)
+```
+그리고 `analyticsScope`를 활용해 다음과 같이 수정하였습니다.
+```kotlin
+suspend fun userDetail(id: String): User = coroutineScope {
+ userDataSource.userDetail(id).also {
+ analyticsScope.launch { analytics.logUserEvent(id) }
+ }
+ }
+```
+이로써 실 서비스에 영향을 주지도 않고, 안전하고 효율적으로 모니터링을 할 수 있게 되었습니다 😎
+
+> 🚨 일반적으로, 코루틴의 구조화를 깨는 것은 비동기 작업을 안전하게 처리할 수 없도록 하기에 최대한 지양 해야합니다. 해당 코드는 `로깅&모니터링` 이라는 특수한 경우이기에 구조화를 깨고 독립적인 작업으로 실행한 것입니다
+
+# 정리
+
+> - `SupervisorJob`
+ > CoroutineScope 를 생성할 때 coroutineContext에 SupervisorJob 을 지정해주자.
+> - `supervisorScope`
+ > 그 외, 예외 전파 제한할 경우 사용하자. (CoroutineScope {} 내부 or suspend 함수)
+
+그럼 다음 포스팅 때는 코루틴 예외 처리하는 방법(CoroutineExceptionHandler)에 대해 소개해드리겠습니다~
\ No newline at end of file
diff --git "a/coroutine/docs/coroutine/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\262\230\353\246\254.md" "b/coroutine/docs/coroutine/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\262\230\353\246\254.md"
new file mode 100644
index 0000000..f605748
--- /dev/null
+++ "b/coroutine/docs/coroutine/\354\275\224\353\243\250\355\213\264_\354\230\210\354\231\270\354\262\230\353\246\254.md"
@@ -0,0 +1,4 @@
+# 예외 처리 방법
+1) CoroutineExceptionHandler
+2) 코루틴 스코프 함수 (coroutineScope) + try-catch
+코루틴 스코프 함수는 일반 함수와 같이 예외가 발생했을 때와 같이 그 예외를 던집니다.
\ No newline at end of file
diff --git "a/coroutine/docs/coroutine/\354\275\224\353\243\250\355\213\264\354\235\264\353\236\200?.md" "b/coroutine/docs/coroutine/\354\275\224\353\243\250\355\213\264\354\235\264\353\236\200?.md"
new file mode 100644
index 0000000..75ad1f8
--- /dev/null
+++ "b/coroutine/docs/coroutine/\354\275\224\353\243\250\355\213\264\354\235\264\353\236\200?.md"
@@ -0,0 +1,75 @@
+코루틴은 중단 가능한 작업 단위입니다.
+
+- 코루틴은 동시성 프로그래밍을 최대한 쉽게 구현할 수 있도록 구현된 도구다
+
+코루틴은 실행이 중단될 때 작업의 실행 환경을 Continuation 이라는 상태 머신에 저장을 하여 다시 resume 될 때
+중단된 지점부터 다시 실행할 수 있다는 특징이 있습니다. 이에 비해 스레드는 단순히 멈추는 것만 가능할 뿐 저장이 불가능합니다.
+
+중단됐을 때 코루틴은 어떤 자원도 사용하지 않습니다. 따라서, 코루틴은 다른 스레드에서 시작할 수도 있고, 컨티뉴에이션 객체를 통해 다시 실행될 수 있
+
+비동기적인 방식으로 코드 작성을 위해 불필요한 스레드를 많이 만들어야한다
+```kotlin
+showProgressBar()
+thread {
+ // 서버 통신
+}.join() // mainThread 블로킹
+hideProgressBar()
+```
+그래서 다음과 같이 하는게 좋음
+
+```kotlin
+import kotlin.concurrent.thread
+
+thread {
+ showProgressBar()
+ thread {
+ // 서버 통신
+ }.join() // mainThread 블로킹
+ hideProgressBar()
+}
+
+thread {
+ showProgressBar()
+ thread {
+ // 서버 통신
+ postHideProgressBar() // 만약 해당 작업을 취소하려해보자 -- 어떻게 취소할래?
+ }
+}
+```
+
+벌써 2개의 스레드를 생성하였음, 게다가 해당 thread 에서 예외가 발생하거나 작업을 취소하기도 어려움
+
+코루틴을 활용하면 어떨까?
+
+```kotlin
+launch {
+ showProgressBar()
+ withContext(Dispatchers.IO) {
+ // 서버 통신
+ }
+ hideProgressBar()
+}
+```
+해당 코드에서는 스레드를 새롭게 생성해서 사용하는 부분은 없다.
+IO 디스패처가 담당하는 스레드풀에서 이미 생성되어 있는 스레드에 코루틴을 할당하여 서버 통신을 진행하고 있다.
+ 코루틴은 IO 작업이 끝날 때까지 중단된다.
+이때 스레드가 중단되는 것이 아니라 코루틴이 중단되는 것이다.
+즉, main 스레드가 블로킹되지 않는다는 것인데 이것이 가능한 이유는 중단되는 시점에 해당 코루틴을 탈출하기 때문이다.
+그리고 IO 작업이 모두 마치면 중단되고 있던 코루틴을 메인 스레드에 분배하고 resume 시켜 중단된 지점부터 다시 작업을 이어나갈 수 있도록 한다.
+
+즉, 스레드를 블로킹시키는 것이 아닌 코루틴을 중단시키는 것이다.
+
+
+
+스레드의 단점)
+
+- 스레드가 실행되었을 때, 멈출 수 있는 방법이 없어 메모리 누수로 이어질 수 있다
+- 스레드를 많이 생성하면 비용이 많이 든다 (스레드 스택의 기본 크기는 1MB 이다. 즉 많이 잡아먹는다)
+- 스레드를 자주 전환하면 복잡도를 증가
+- 코드량이 늘어난다
+
+중단 가능하는 개념이 없다 그래서 기존 스레드 방식은 실행 흐름 제어하기가 굉장히 어렵고
+람다를 넘겨주어야 한다. 코루틴의 경우 내부적으로 Continuation 을 활용한 콜백으로 wrapping이 되어 있어 비동기적인 코드를
+개발자 마치 동기적인 방식으로 코드를 작성할 수 있습니다. (개발 편의성 및 가독성이 높다)
+
+콜백 - 특정 작업이 끝난 후 어떤 작업 실행해야하는 동기적인 코드 흐름을 구현하기 위한 방법, non-blocking 한 방법으로 사용해야함
\ No newline at end of file
diff --git "a/coroutine/docs/test/\355\205\214\354\212\244\355\212\270\353\245\274_\354\231\234\355\225\240\352\271\214?.md" "b/coroutine/docs/test/\355\205\214\354\212\244\355\212\270\353\245\274_\354\231\234\355\225\240\352\271\214?.md"
new file mode 100644
index 0000000..aa42485
--- /dev/null
+++ "b/coroutine/docs/test/\355\205\214\354\212\244\355\212\270\353\245\274_\354\231\234\355\225\240\352\271\214?.md"
@@ -0,0 +1,80 @@
+# 테스트 왜 작성할까?
+
+프로젝트의 지속가능한 성장을 위해
+
+테스트가 없는 프로젝트는 처음에는 발목 잡을 일이 없기에 빠르게 성장 가능하지만
+결국에는 많은 문제가 생기면 리팩토링 및 버그 픽스하는데 많은 시간을 할애하게 됩니다.
+이때, 테스트가 프로젝트에 안전망
+
+만약, 잘못된 테스트 질이 안좋은 테스트를 포함한다면 오히려 테스트가 없는 경우가 더 좋은 경우다
+
+# 단위 테스트
+https://www.youtube.com/watch?v=mIO4Rbe_M74
+
+> 어플리케이션 안에 있는 `개별적인 코드 단위`가 `의도한 대로 작동`하는지 확인하는 행위
+
+여러 기능이 있으면 이 하나하나의 기능이 잘 작동하는지 보는 테스트
+
+## 단위 테스트 왜 사용 해야 하는가?
+
+큰 기능을 작은 기능으로 쪼개 테스트함
+-> 원하는 부분만 테스트함으로 결과를 빠르게 볼 수 있다 (빠르게 검증)
+-> 미리 작성한 단위 테스트를 기반으로 프로덕션 코드의 리팩토링을 안정적으로 할 수 있다. (리팩토링 안정적)
+-> 단위 테스트가 실패하는 지점에서 문제점을 빠르게 찾을 수 있다. (문제점 빠르게 찾을 수 잇음)
+
+# 단위 테스트 예시
+
+장바구니 ..
+
+# First 원칙
+
+효율적이고 좋은 단위테스트를 위한 5가지 원칙
+
+F: 단위 테스트는 빨라야한다.
+I: 테스트는 독립적으로 동작해야한다 (각 테스트는 모두 독립적인 모듈이다)
+R: 어떤 상황에서든 같은 테스트 결과가 나와야 한다. (멱등성)
+S: print or log 가 아닌 테스트 자체적으로 결과가 나와야한다
+T: 적시에 테스트를 철저하게 작성해야 한다. (테스트를 미루지 말자)
+
+---
+
+# Given-When-Then 패턴: 테스트 코드의 가독성이 좋아 유지보수 굿
+테스트 코드 작성의 표현 방식
+
+Arrange(준비): 테스트 대상을 준비물
+Act(실행): 테스트 대상 시스템 메서드를 호출하고 결과, 출력값이 있다면 저장
+Assert(검증): 결과를 검증
+
+# 안좋은 단위 테스트 예시
+
+## 1. 여러 개의 준비, 실행 검증
+준비, 실행, 검증 -> 실행 -> 검증
+
+이는 단위 테스트가 아닌 통합테스트다
+
+단위테스트는 빠르고 간단하게 작성해야하는 first 원칙을 위배하고 있다.
+
+"구매를 성공하고 재고가 감소한다"
+
+실행 구절이 2줄 이상이다.
+
+비지니스 관점
+
+행동: 물품 구매
+-> 결과 1) 재고 감소
+-> 결과 2) 고객이 물품 획득
+
+즉 구매 -> 재고 감소 라는 이 사이클이 2개의 프로세스로 분리되어 실수할 위험이 있음
+-> 이는 위험하다 (실수 여지 있음)
+-> 프로덕트 코드가 잘못됐나? 의심할 수 있음
+
+## 2. 세부 구현에 의존적인 테스트
+세부구현은 언제든지 바뀔 수 있는 것 (만약, 10개까지만 장바구니 추가 가능 -> 12개로 바뀌면?)
+공개된 public 한 메서드 만을 사용하여 테스트하는 것이 좋다.
+
+
+[[10분 테코톡] 도도의 좋은 단위 테스트란
+](https://www.youtube.com/watch?v=R7spoJFfQ7U&t=55s)
+
+[[드로이드나이츠 2021] 강사룡 - Android Testing Best Practices
+](https://www.youtube.com/watch?v=D_tWlb2deX8&t=1232s)
\ No newline at end of file