diff --git "a/WONYOUNG-HC/Week04/\355\201\264\353\235\274\354\235\264\354\226\270\355\212\270\354\227\220\354\204\234 \354\244\221\353\263\265\353\220\234 token refresh \354\262\230\353\246\254.md" "b/WONYOUNG-HC/Week04/\355\201\264\353\235\274\354\235\264\354\226\270\355\212\270\354\227\220\354\204\234 \354\244\221\353\263\265\353\220\234 token refresh \354\262\230\353\246\254.md"
new file mode 100644
index 0000000..86096a1
--- /dev/null
+++ "b/WONYOUNG-HC/Week04/\355\201\264\353\235\274\354\235\264\354\226\270\355\212\270\354\227\220\354\204\234 \354\244\221\353\263\265\353\220\234 token refresh \354\262\230\353\246\254.md"
@@ -0,0 +1,359 @@
+프로젝트를 진행하면서 인증 방식으로 JWT를 사용했다. JWT에서 access token이 만료된 경우 refresh token을 사용해 새로운 access token을 발급받는 전략을 사용하고 있다.
+
+https://youthing.tistory.com/215
+(위의 글의 작성해 주신 분이 만들어준 token을 사용한다 ㅎ)
+
+axios interceptor를 사용해서 access token이 만료된 에러 코드가 발생한 새로운 access token을 발급하는 요청을 전송한다. 하지만 문제가 발생한 상황은 한 번에 여러 요청을 보내는 경우 새로운 access token 발급 요청이 중복되어 전송이 되었다.
+
+이번에 작성하는 글은 JWT, axios에 대해 정리하고, 중복된 요청에 대해 해결한 방법에 대한 내용이다.
+
+## JWT (Json Web Token)
+
+### 인증과 인가
+
+JWT은 웹에서 인증 방식이다. 여기서 인증과 인가의 개념이 등장하는데, 간단히 정리하면 **인증은 사용자의 신원을 확인**하는 과정이고, **인가는 인증된 사용자에게 권한을 허락**하는 행위이다.
+
+티스토리에 본인이 작성한 글을 수정해야 한다면, 글을 수정한 사람이 본인이 맞는지 확인하는 과정이 필요하다. 여기서 **로그인을 통해 해당 글을 본인이 작성한것이지 확인하는 것이 인증**이고, **티스토리에서 글의 수정을 허용하는것이 인가**이다.
+
+
+
+로그인을 하면 서버에서는 access token을 발행하여 클라이언트에게 전달하고, 클라이언트는 access token을 사용해 서버로부터 인증을 받는다.
+
+### reresh token
+
+하지만 JWT 방식에서 access token이 해커에게 탈취당하면, 해커가 탈취한 access token을 통해 사용자 행세를 할 수 있다. 그래서 access token에는 **만료 시간**을 설정한다. 지금 필자가 하고있는 프로젝트에서는 access token을 발급하고 30분 이후에는 token을 사용할 수 없다. 그러면 access token이 만료된 30분 이후에는 로그아웃이 되고, 사용자는 30분마다 로그인을 해야 하는 번거로움이 있다.
+
+이런 문제를 해결하기 위해 **refresh token**이 등장한다.
+
+
+
+이 방식은 사용자가 로그인하면 서버에서 클라이언트에서 **access token**과 **refresh token** 두 개를 전달한다. 클라이언트는 서버로부터 받은 access token을 통해서 인가 요청을 보낸다. 그리고 기존 access token이 만료되면 서버에서는 기존의 access token이 만료되었다는 응답을 준다. 그러면 클라이언트는 처음 서버로부터 받은 **refresh token을 통해서 새로운 access token 발급을 요청**한다.
+
+이렇게 refresh token을 통해 access token의 짧은 만료 시간에서 발생할 수 있는 문제점을 보완해준다.
+
+## axios
+
+### fetch API와 axios 라이브러리
+
+클라이언트에서 서버에게 HTTP 요청을 보내는 가장 기본적인 방법은 fetch API를 사용하는 것이다. 하지만 fetch API를 사용하기에는 불편한 점이 몇몇 존재한다. 우선 body가 JSON 형식이기에 항상 string으로 변환이 필요하다. 하지만 axios 라이브러리를 사용하면 body를 객체로 접근할 수 있어 편리하다. 이뿐만 아니라 사용 방법 자체도 axios가 fetch보다 간단하다.
+
+또한 fetch에서 요청 성공이란 서버로부터 응답이 도착하면 성공으로 간주한다. 즉 서버가 status code를 4xx, 5xx 번대로 보내도 응답이 왔다고 판단하여 요청 성공으로 간주한다. 그래서 예외 처리를 하기 위해서는 status code에 대해 직접 문기분을 작성해야 가능하다. 하지만 axios에서는 2xx 번대 응답만 성공으로 간주하고 나머지는 모두 에러처리 하여 예외 처리가 fetch보다 용이해진다.
+
+그리고 이번 글에서 가장 중요한 **interceptors**에 대한 기능 유무이다.
+
+### axios interceptors
+
+axios 공식 문서에서 interceptors에 대한 설명은 **"then 또는 catch\*\***로 처리되기 전에 요청과 응답을 가로챌 수 있습니다."\*\*라고 나와 있다. 아래는 공식 문서에 나와있는 예시이다.
+
+```js
+// 요청 인터셉터 추가하기
+axios.interceptors.request.use(
+ function (config) {
+ // 요청이 전달되기 전에 작업 수행
+ return config;
+ },
+ function (error) {
+ // 요청 오류가 있는 작업 수행
+ return Promise.reject(error);
+ }
+);
+
+// 응답 인터셉터 추가하기
+axios.interceptors.response.use(
+ function (response) {
+ // 2xx 범위에 있는 상태 코드는 이 함수를 트리거 합니다.
+ // 응답 데이터가 있는 작업 수행
+ return response;
+ },
+ function (error) {
+ // 2xx 외의 범위에 있는 상태 코드는 이 함수를 트리거 합니다.
+ // 응답 오류가 있는 작업 수행
+ return Promise.reject(error);
+ }
+);
+```
+
+axios에서 요청과 응답에 대한 interceptors를 제공한다. interceptors.request.use 함수는 콜백 함수 두 개를 인자로 받고, 첫 번째 콜백 함수는 **정상적인 요청 또는 응답**에 대한 처리, 두 번째 콜백 함수는 **에러가 발생한 경우에 대한 처리**이다.
+
+request에 대한 interceptors는 사용자가 요청을 보낸 이후 **서버로 전송되기 직전에 실행**되고, response에 대한 interceptros는 서버로부터 응답이 도착하고, **axios 함수를 반환하기 직전에 실행**된다.
+
+프로젝트에서 request interceptor는 헤더에 access token을 포함시킬 때 사용한다.
+
+```js
+axios.interceptors.request.use((config) => {
+ const token = localStorage.getItem("token");
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+});
+```
+
+인가가 필요한 요청을 보낼 때마다 일일이 헤더에 access token을 저장하지 않고, request interceptros를 사용해 모든 요청에 대해 access token을 헤더에 첨부하여 보낼 수 있다.
+
+그러면 **response interceptors**는 언제 사용할 수 있을까? 바로 **access token이 만료되어 refresh token을 통해 새로운 access token을 요청**할 때 사용할 수 있다.
+
+```js
+axios.interceptors.response.use(
+ (res) => res,
+ async (error) => {
+ const { config, response } = error;
+
+ // access token이 만료되어 에러가 발생한 경우
+ if (response.data.code === 'T-001') {
+ try {
+ // 새로운 access token을 발급 받는 요청을 전송
+ const response = await api.post('/v1/api/auth/reissue');
+
+ // 새롭게 발급 받은 access token을 사용
+ config.headers.Authorization = `Bearer ${response.data.accessToken}`;
+ localStorage.setItem('token', response.data.accessToken);
+
+ // 실패한 요청을 다시 전송
+ return axios(config);
+ } catch (err) {
+ // 새로운 access token을 발급받다 실패한 경우 access token을 지우고 메인 페이지로 이동
+ localStorage.clear();
+ window.location.replace('/');
+ }
+ }
+ }
+
+ // access token 만료로 인한 실패가 아닌 경우 에러로 처리
+ return Promise.reject(error);
+ },
+);
+```
+
+response에서 에러가 발생한 경우 interceptor를 실행한다. config는 HTTP 요청에 대한 정보들이 담긴 객체이고, response는 실패한 요청에 대한 응답이다.
+
+프로젝트에서 우리 서버는 access token이 만료된 경우 'T-001'을 response의 data에 담아 전송해 준다. 이를 통해 interceptor에서 어떤 에러가 발생했는지 탐지가 가능하고, **access token이 만료된 에러에 대한 분기를 처리해준다.**
+
+클라이언트에서 access code는 local stroage에 저장하지만, refresh token은 cookie에 저장되어 있기 때문에 refresh token이 필요한 요청은 별도로 처리하지 않고 전송한다.
+
+성공적으로 새로운 access token을 발행받으면 local storage에 새로운 aceess token을 저장하고, 실패한 요청을 새로운 access token으로 다시 실행한다.
+
+새로운 access token을 발급받다가 실패한 경우 local sotrage를 clear 함으로써 access token을 삭제하고, 메인 페이지로 이동시킨다.
+
+만약 에러가 'T-001'이 아니어서 access token만료로 인한 에러가 아닌 경우 에러를 그대로 반환한다.
+
+이렇게 response interceptors를 사용해서 access token이 만료된 경우 refresh token을 사용하여 새로운 access token을 발급받고, 기존의 요청을 다시 실행시켜 **axios를 사용하는 개발자는 access token만료를 신경 쓰지 않고 개발을 할 수 있다.**
+
+## 중복된 access token 발급 요청
+
+페이지 접속 시 단 하나의 요청만 전송하는 경우도 있지만, 여러 개의 요청을 보내야 하는 경우도 더러 있다. 여러 요청을 동시에 보내는 상황에서 access token이 만료된 경우 위의 interceptors를 사용하면 어떤 일이 발생할까?
+
+
+
+**interceptor가 이렇게 여러 번 실행된다.**
+
+위에 정의한 interceptor는 단순히 access token만 새로 발급하기만 하지 않고, 기존 요청을 새로운 access token으로 다시 실행시킨다. 서버에서 refresh token을 사용해 새로운 access token을 발급받은 경우 기존의 access token을 만료 처리를 한다면, 그림에서 **access token 1은 발급을 받자마자 만료**가 된다.
+
+A 요청과 B 요청을 동시에 전송하는 상황에서 access token이 만료되면 아래와 같은 흐름이 발생할 수도 있다.
+A 요청 실패로 인한 access token 발급 요청 -> B 요청 실패로 인한 access token 발급 요청 -> A 요청에 대해 new access token 1을 발급하고 기존 access token을 만료 처리 -> B 요청에 대해 new access token 2를 발급하고 new access token 1을 만료 처리 -> 실패한 A 요청이 new access token1을 통해 인가 요청
+
+이렇게 새로운 access token을 발급받아 요청을 보내려 해도, 이미 만료가 되어 버려 새로운 access token을 또 발급받아야 하고, 이는 무한 재귀로까지 빠질 수도 있다.
+
+즉 interceptor가 여러 번 실행되더라도, **새로운 access token을 받는 요청은 단 한 번만 실행되어야 한다.**
+
+어떻게 이 문제를 해결을 할 수 있을까 고민하고, 검색을 해본 결과 아래 medium 블로그에 개시된 글을 통해 방법을 찾을 수 있었다.
+
+https://medium.com/@sina.alizadeh120/repeating-failed-requests-after-token-refresh-in-axios-interceptors-for-react-js-apps-50feb54ddcbc
+
+해당 블로그에서는 queue를 사용해서 이 문제를 해결했다.
+
+```ts
+interface RetryQueueItem {
+ resolve: (value?: any) => void;
+ reject: (error?: any) => void;
+ config: AxiosRequestConfig;
+}
+
+const refreshAndRetryQueue: RetryQueueItem[] = [];
+```
+
+refreshAndRetryQueue는 access token이 만료된 요청들을 저장하는 리스트이다. 리스트 원소의 타입은 resolve, resject 함수와 config를 가지고 있다. refreshAndRetryQueue를 이용해서 access token이 만료된 응답을 받을 때마다 **새로운 access token을 발급하는 요청을 보내지 않고, 요청을 대기**시켜 놓을 수 있게 된다.
+
+```ts
+const refreshAndRetryQueue: RetryQueueItem[] = [];
+let isRefreshing = false;
+
+axios.interceptors.response.use(
+ (res) => res,
+ async (error) => {
+ const { config, response } = error;
+
+ if (response.data.code === 'T-001') {
+ // 아직 아무 요청도 새로운 access token을 발급받지 않았다면
+ if (!isRefreshing) {
+ isRefreshing = true;
+
+ try {
+ const response = await api.post('/v1/api/auth/reissue');
+ config.headers.Authorization = `Bearer ${response.data.accessToken}`;
+ localStorage.setItem('token', response.data.accessToken);
+ }
+ }
+ }
+
+ return Promise.reject(error);
+ },
+);
+```
+
+코드를 부분적으로 살펴보겠다. isRereshing 변수는 여러 요청 중 어떤 요청이 access token이 만료된 응답을 받고 **새로운 access token을 발급받고 있는 중임을 표시하는 변수**이다. isRereshing이 true라면 다른 요청이 이미 새로운 access token을 발급 받고 있는 중이라는 의미이고, false이면 아직 아무 요청도 새로운 access token을 발급받지 않았다는 의미이다.
+
+isRefresing은 초기 값으로 false로 설정되어 있고, access token이 만료된 첫 응답을 받은 요청은 위의 코드대로 실행이 될 것이다. 그러면 해당 요청은 새로운 access token을 발급받으니 **isRefresing 변수를 true로 설정하고 새로운 access token을 발급받는다.**
+
+```ts
+axios.interceptors.response.use(
+ (res) => res,
+ async (error) => {
+ const { config, response } = error;
+
+ if (response.data.code === 'T-001') {
+ if (!isRefreshing) {
+ isRefreshing = true;
+
+ try {
+ const response = await api.post('/v1/api/auth/reissue');
+ config.headers.Authorization = `Bearer ${response.data.accessToken}`;
+ localStorage.setItem('token', response.data.accessToken);
+ }
+ }
+
+ // 어떤 요청이 이미 새로운 access token발급을 하고 있는 경우
+ // queue에 요처을 대기
+ return new Promise((resolve, reject) => {
+ refreshAndRetryQueue.push({ config, resolve, reject });
+ });
+ }
+
+ return Promise.reject(error);
+ },
+);
+```
+
+다른 요청이 이미 새로운 access token을 발급받고 있는 중이면, **rereshAndRetryQueue에 요청을 대기**시킨다. 이를 통해 새로운 동시에 여러 요청이 새로운 access token을 발급받는 요청을 보내지 않아 위에서 언급한 무한 재귀 발생 문제는 방지를 할 수가 있다.
+
+```ts
+axios.interceptors.response.use(
+ (res) => res,
+ async (error) => {
+ const { config, response } = error;
+
+ if (response.data.code === "T-001") {
+ if (!isRefreshing) {
+ isRefreshing = true;
+
+ try {
+ const response = await api.post("/v1/api/auth/reissue");
+ config.headers.Authorization = `Bearer ${response.data.accessToken}`;
+ localStorage.setItem("token", response.data.accessToken);
+
+ // 새로운 access token을 발급 받으면 queue에서 대기하던 요청들을 실행
+ refreshAndRetryQueue.forEach(({ config, resolve, reject }) => {
+ axios
+ .request(config)
+ .then((response) => resolve(response))
+ .catch((err) => reject(err));
+ });
+ refreshAndRetryQueue.length = 0;
+
+ // 새로운 access token을 발급 받은 요청을 다시 실행
+ return axios(config);
+ } catch (err) {
+ localStorage.clear();
+ window.location.replace("/");
+ } finally {
+ isRefreshing = false;
+ }
+ }
+
+ return new Promise((resolve, reject) => {
+ refreshAndRetryQueue.push({ config, resolve, reject });
+ });
+ }
+
+ return Promise.reject(error);
+ }
+);
+```
+
+처음에 만료된 access token 응답을 받은 요청이 새로운 access token을 발급받으면 **isRefreshAndRetryQueue에서 대기 중인 요청들을 처리힌다.** 그리고 새로운 access token을 발급받은 요청은 queue에 들어있지 않았으니 이에 대한 요청도 실행시킨다. 그리고 finally에서 isRefreshing을 false로 설정해서 다음에 refresh 로직이 가능하도록 준비를 해둔다.
+
+전체 코드는 다음과 같다. 실제 코드는 axios에 interceptors를 설정하는 대신에, api이름으로 axios 인스턴스를 생성하여 사용한다.
+
+```ts
+import axios, { AxiosRequestConfig } from "axios";
+
+const api = axios.create({
+ baseURL: process.env.REACT_APP_BASE_URL,
+ withCredentials: true,
+});
+
+api.interceptors.request.use((config) => {
+ const token = localStorage.getItem("token");
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+});
+
+interface RetryQueueItem {
+ resolve: (value?: any) => void;
+ reject: (error?: any) => void;
+ config: AxiosRequestConfig;
+}
+
+const refreshAndRetryQueue: RetryQueueItem[] = [];
+let isRefreshing = false;
+
+api.interceptors.response.use(
+ (res) => res,
+ async (error) => {
+ const { config, response } = error;
+
+ if (response.data.code === "T-001") {
+ if (!isRefreshing) {
+ isRefreshing = true;
+
+ try {
+ const response = await api.post("/v1/api/auth/reissue");
+ config.headers.Authorization = `Bearer ${response.data.accessToken}`;
+ localStorage.setItem("token", response.data.accessToken);
+
+ refreshAndRetryQueue.forEach(({ config, resolve, reject }) => {
+ api
+ .request(config)
+ .then((response) => resolve(response))
+ .catch((err) => reject(err));
+ });
+ refreshAndRetryQueue.length = 0;
+
+ return api(config);
+ } catch (err) {
+ localStorage.clear();
+ window.location.replace("/");
+ } finally {
+ isRefreshing = false;
+ }
+ }
+
+ return new Promise((resolve, reject) => {
+ refreshAndRetryQueue.push({ config, resolve, reject });
+ });
+ }
+
+ return Promise.reject(error);
+ }
+);
+
+export default api;
+```
+
+JWT 인증 방식을 채택하면서 refresh token을 사용해 새로운 access token을 발급받는 로직은 널리 알려진 방법인 거 같다. 처음에는 단순히 interceptors에서 refresh 로직을 설정했지만, 한 페이지에서 여러 요청이 동시에 발생하는 경우 해결 방법은 쉽게 찾지는 못했었다. 발견한 문제점은 해결했지만, 아직 이 방법이 맞는 방법일까?라는 의문이 아직 해결하지 못했다. (만약 더 좋은 방법이 있다면 알려 주세요!!)
+
+하지만 해당 방식대로 직접 구현하면서 axios interceptors의 사용 방법을 알게 되었다. 그리고 글이 너무 길어질까 봐 언급하지는 않았지만 Promise 객체를 처음으로 직접 생성하며 사용해 보았다.
+
+비록 해당 방법이 최선의 방법인지는 모르겠지만, 이를 직접 구현하면서 **axios와 Promise 객체에 대해 이해**를 할 수 있는 기회가 되었다.
diff --git "a/WONYOUNG-HC/Week05/\354\275\224\355\205\214\354\235\264\355\206\240 9\352\270\260 \352\265\220\354\234\241\355\214\200 \355\231\234\353\217\231 \355\232\214\352\263\240\353\241\235.md" "b/WONYOUNG-HC/Week05/\354\275\224\355\205\214\354\235\264\355\206\240 9\352\270\260 \352\265\220\354\234\241\355\214\200 \355\231\234\353\217\231 \355\232\214\352\263\240\353\241\235.md"
new file mode 100644
index 0000000..ed40c37
--- /dev/null
+++ "b/WONYOUNG-HC/Week05/\354\275\224\355\205\214\354\235\264\355\206\240 9\352\270\260 \352\265\220\354\234\241\355\214\200 \355\231\234\353\217\231 \355\232\214\352\263\240\353\241\235.md"
@@ -0,0 +1,61 @@
+24년 3월부터 시작해 8월을 끝으로 IT연합 코테이토 9기 활동이 종료됐다.
+
+필자는 코테이토 9기에서 교육팀장으로 활동을 했다. **교육팀**은 코테이토 정기 세션에서 **CS교육**을 담당하는 부서이다. 주로 전공이나 개발과 관련된 기업에서 기술 면접에서 질문하는 내용들을 주제로 선별해 15 ~ 20분가량 발표를 진행한다.
+
+코테이토 8기에 처음 들어오자마자 교육팀원으로 활동을 했고, 9기에 교육팀장을 하면서 그간 진행한 얘기들에 대한 글을 정리해보고자 한다.
+
+## 코테이토 8기와 교육팀
+
+프로젝트 경험과 개발 커뮤니티를 얻기 위해 IT 연합동아리인 코테이토에 지원을 했고, 8기에 합격을 했다. 처음 동아리에 왔을 때는 어떤 운영팀들이 있는지 몰랐지만, OT활동과 홍보글을 보고 교육팀에 지원해야겠다고 결심이 들었다.
+
+
+
+CS와 관련된 주제들을 공부할 수 있고, 이를 통해 발표를 하는 부분에서 이 팀에는 꼭 들어가고 싶다는 생각이 들었다.
+
+처음에 동아리 들어왔을때는 CS와 관련된 지식이 많이 없었지만, 동아리 활동을 하면서 많은 점을 배우고 교육팀 활동을 통해서 CS 지식을 얻을 수 있었다. 8기에서는 IP주소, 브라우저 렌더랑, CSR과 SSR을 주제로 발표를 진행했다.
+
+## 9기 교육팀장
+
+8기 교육팀활동이 끝나고 9기에서는 교육팀장으로 교육팀을 이끌었다. 8기 교육팀장이 많은 부분을 변화했지만 내가 팀장을 맡으면서 몇 가지 부분을 변화시켰다.
+
+**1\. 회의 시간 조정**
+
+교육팀 발표는 금요일 오후에 열리는 정기 세션에서 진행된다. 8기까지는 목요일 23시에 진행했다. 회의때는 발표 자료 검수와, 발표 이후 퀴즈로 출제하는 문제들을 선별하는 시간을 가졌다. 회의는 보통 한 시간 정도 진행되는데, 최종 발표 자료는 금요일 점심까지는 완성을 해야 한다. 즉 자료 검수와 퀴즈 문제 출제를 진행하고 최종 발표 자료를 완성하기까지 시간이 촉박했던 문제가 있었다. 그러다 보니 발표 자료에서 실수하는 부분이 몇 번 발생하기도 했다
+
+9기에서는 회의 이후 여유로운 자료 완성과 검수를 위해 회의 시간을 목요일에서 수요일로 변경했다. 변경 결과는 꽤나 긍정적이였다. 비록 발표자는 기존보다 하루 일찍 발표 자료를 준비해야 했지만, 그만큼 더욱 발표 자료에 많은 시간을 할애할 수 있게 되어 지난 기수보다는 발표 자료에서 실수하는 부분이 줄어들었다.
+
+**2\. 기획, 디자인 파트 퀴즈 가점 부여**
+
+코테이토 동아리는 개발 파트 부원들만 있는것이 아니라 기획, 디자인 파트도 부원들로 같이 이루어져 있다. 개발 파트는 당연히 컴퓨터 관련 전공생들로 이루어져 있고, 기획 파트는 개발을 하지 않는 컴퓨터 전공생 또는 경영, 인문학과 학생들로, 디자인은 미대 학생들이 위주이다. 그래서 CS교육은 개발 파트 부원들에게는 익숙할지 몰라도 기획, 특히 디자인 파트 부원들은 자신의 분야와 거리가 있는 내용을 듣는 것이 쉽지는 않았다.
+
+아무리 IT 동아리이지만, 기획 파트와 디자인 파트 또한 같은 동아리 부원인데 이 시간을 잘 활용하지 못하고 있다고 생각이 들어 모든 동아리원들이 교육에 참여할 수 있도록 만들고 싶었다. CS교육의 성격상 내용 자체를 변경하기는 어려웠고, 대신 선택한 방안은 기획, 디자인 파트에게 퀴즈에서 가점을 부여하는 것이였다. CS교육은 발표 이후 10문제를 퀴즈로 선별해 교육 내용을 복습하고, 퀴즈에서 1등 한 부원에게는 상품을 제공했다. 이 퀴즈에서 기존에 문제를 맞히면 1점을 하지만 기획 파트가 득점하면 가점을 0.5점, 디자인 파트가 득점하면 1점 가점을 부여해 더 높은 교육 참여를 유도했다.
+
+결과는 회의 시간 조정처럼 긍정적이진 않았다. 가점을 부여했지만 여전히 기획, 디자인 파트들이 교육에 참여하기는 쉽지 않았다. 주제 자체가 컴퓨터 전공자들을 위한 교육이나 보니 퀴즈에서 가점을 부여해도 내용 자체가 이해하기 힘들어 흥미를 이끌지 못했다. 이 문제는 앞으로 다음 기수에서도 같이 고민을 해 보면서 다른 방안을 찾아야할 부분이다.
+
+9기 교육팀에서 Git, 인증과 인가, 웹 해킹, 함수형 프로그래밍 이렇게 네 가지의 주제로 교육을 진행했다. 8기때와 달리 많은 도전을 했다. 인증과 인가는 혼자서 30분이 넘는 시간을 넘게 발표를 진행해 보고, 함수형 프로그래밍은 중요한 내용은 아니지만 개인적인 흥미로 다른 사람들도 이 분야에 관심을 가졌으면 하는 바람으로도 교육을 진행했었다.
+
+9기에서 교육팀장을 하면서 개인적인 성장은 크게 세 가지로 정리를 할 수 있을거 같다.
+
+**1\. CS 지식 습득**
+
+CS와 관련된 주제로 교육을 준비하니 이 부분은 자연스럽게 따라오는 성장이였다. 항상 느끼는 내용이지만 발표자가 10만큼의 내용을 전달하기 위해서는 최소 15 이상의 내용은 준비를 해야 한다. 그러다 보니 당연히 내가 발표를 한 주제에 대해서는 어떤 질문이 들어와도 답변을 할 수 있는 상태가 될 수 있었다.
+
+또한 내가 발표한 내용이 아니더라도, 다른 사람의 발표 주제도 공부할 수 있었다. 교육팀의 교육 준비 방식은 발표자가 발표 자료를 준비하면, 그 자료를 토대로 각자 퀴즈에 사용될 문제를 출제한다. 내가 발표를 하는 내용이 아니더라도 퀴즈를 출제하기 위해 다른 사람의 발표 내용 또한 공부를 해야 했다. 솔직히 8기에서는 팀원으로만 활동을 했어 발표 내용에 대해 유의 깊게 살피지는 않고, 자료의 내용을 단순히 짜깁기 하여 간단하게 문제를 출제하는 경우가 종종 있었다. 하지만 9기부터는 팀장이라는 책임감을 가지고 모든 자료 내용들을 보다 꼼꼼히 공부를 했다. 교육팀 회의에서 퀴즈 문제를 선별하는 과정은 팀장을 주도로 진행되기 때문에 적어도 팀장은 본인이 출제한 문제뿐만 아니라 모든 팀원이 출제한 문제에 대해서 정답과 풀이를 전부 이해하고 회의를 진행해야 했다. 그리고 발표 내용에 오류가 발생하는 것을 최대한 줄이기 위해 꼼꼼한 발표 내용 검수 또한 필요했다.
+
+**2\. 내가 알고있는 내용을 발표하는 방법**
+
+8, 9기 교육팀으로 활동을 하면서 총 8번의 발표를 진행했다. 교육팀에 들어오기 전부터 발표에 대한 두려움은 크게 있지는 않았다. 팀플을 할 때 역할 분담을 하면 대부분의 경우 내가 발표자를 진행할 만큼 다른 사람들보다는 발표에 자신이 있었다. (사실 피피티 제작이나 자료조사가 하기 싫은 것도 있긴 했지만...) 하지만 교육팀에서 내가 공부한 내용을 발표를 통해 다른 사람들에게 전달하는 과정은 단순히 준비된 자료를 읽고 전달하는 과정과는 다소 다른 점이 있었다. 청자들에게 내용을 전달하는 것과 이해시키는 것의 차이점이었다.
+
+어떻게 하면 내가 공부한 내용을 사람들이 쉽게 이해를 할 수 있을까 많은 고민을 했다. 주제 자체를 놓고 보면 15분가량의 발표로는 듣는 사람이 완전히 이해하기는 거의 불가능의 가까운 시간이었던 거 같다. 그래서 부원들이 발표를 듣고 내용을 이해하기보다는 나중에 공부할 때 "아! 저 내용 CS교육 때 들었던 내용이다." 정도만 가져갈 수 있게 목표를 설정했다. 그래서 사람들이 교육을 들으면서 이 내용을 기억하기보다는 이야기 듣듯이 쉽고 편하게 접근할 수 있는 발표를 준비하고자 노력했다.
+
+
+
+우선 피피티를 제작하면서 글은 최대한 배제하고, 그림을 위주로 자료를 준비했다. CSRF에서 해커가 사용자 인증 정보를 통해 악의적인 요청을 보내는 방식을 다음과 같이 최대한 그림을 위주로 설명을 했다. 이런 방식으로 그림을 단계별로 여러 장 준비하다 보니 발표 시간은 15분이지만 피피티 페이지는 30, 40장을 넘기도 했다. 글보다는 그림 위주의 피피티를 통해서 듣는 사람이 어떤 개념의 정의나 설명을 읽는 시간을 줄이고, 발표자가 말하는 내용에 더욱 귀를 기울이도록 했다.
+
+**3\. 리더로서 성장**
+
+학교에서 팀플이나 스터디 활동에서 팀장을 해본 경험은 있었지만, 이렇게 동아리에서 팀장이란 역할은 이번이 처음이었다. 그래서 교육팀을 운영하면서 미숙했던 부분도 많아 아쉬움도 있었다. 9기에서 6개월 동안 교육은 총 14번 이루어졌다. 모든 교육 일정을 조절하고, 회의를 진행하면서 어떻게 팀을 리드해야 하는가를 배울 수 있었다.
+
+회의에서 이상적인 방향은 모든 팀원들이 각자의 의견을 건설적으로 나누고, 그 과정에서 타협점을 도달하는 방향일 것이다. 하지만 교육팀 회의에서는 단순히 퀴즈 문제를 선별하는 과정이기에 그 정도로 개인의 의견이 발생하지는 않는다. 이런 상황에서 회의를 진행하기 위해서는 리더가 주도적으로 의견을 제시해야 했다. 꼭 회의뿐만이 아닌 팀에서 의사결정이 필요할 때 리더로서 어떻게 행동해야 하는지를 배울 수 있었다.
+
+코테이토 8, 9기를 활동하면서 항상 교육팀으로 활동을 했다. 이제 1년 동안 코테이토에 있으면서 교육팀 활동은 그만하고, 10기에서는 새로운 도전인 코테이토 회장으로 활동을 시작한다. 교육팀장을 하면서 성공적이었다 생각한 것도 있지만, 아쉬움이 남는 부분이 많이 있었다. 6개월 뒤에 코테이토 10기 회장 회고록을 작성할 때 지금보다 아쉬운 점이 더 적게 남을 수 있는 활동이 되면 좋겠다는 바람을 가져본다.