ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,24 @@
+import { EVENT, VALIDATION } from '../constants/event';
+
+const visitDate = (detail) => `유효하지 않은 날짜입니다. 다시 입력해 주세요. ${detail}`;
+const order = (detail) => `유효하지 않은 주문입니다. 다시 입력해 주세요. ${detail}`;
+
+export const VISIT_DATE_MESSAGE = Object.freeze({
+  invalidNumber: visitDate('날짜는 숫자여야 합니다.'),
+  invalidRange: visitDate(`12월 ${EVENT.startDate}일부터 ${EVENT.endDate}일까지만 입력 가능합니다.`),
+});
+
+export const ORDER_MESSAGE = Object.freeze({
+  duplicateMenu: order('주문에 중복된 메뉴가 존재합니다.'),
+  onlyDrinkMenu: order(`주문은 음료만 주문할 수 없습니다.`),
+  maxOrderCount: order(`메뉴는 총 ${VALIDATION.maxMenuCount}개 이하로 주문해야 합니다.`),
+});
+
+export const MENU_MESSAGE = Object.freeze({
+  isNotExistMenu: order('존재하지 않는 메뉴입니다.'),
+});
+
+export const MENU_COUNT_MESSAGE = Object.freeze({
+  invalidNumber: order('메뉴 개수는 숫자여야 합니다.'),
+  minMenuCount: order(`메뉴는 ${VALIDATION.minMenuCount}개 이상으로 주문해야 합니다.`),
+});

리뷰: 앗 맞아요! 그런데 추가적으로 코드짜는건 자유라고 생각해서, prefix로 다 유효하지 않은 날짜입니다. 다시 입력해 주세요.를 붙혀주고, 뒷부분에 해당 디테일한 이유를 추가해줬습니다!
@@ -0,0 +1,47 @@
+import Menu from './Menu';
+import { WEEKDAY_DISCOUNT, WEEKEND_DISCOUNT } from '../utils/constants/discount';
+import { DELIMITER } from '../utils/constants/event';
+import { validateDuplicateOrder, validateOnlyDrink, validateTotalMenuCount } from '../utils/validators/vaildateOrder';
+import { validateMenuCount } from '../utils/validators/validateMenu';
+
+class Order {
+  #order;
+
+  constructor(orderInput) {
+    this.#order = new Map();
+
+    orderInput.split(DELIMITER.orderDelimiter).forEach((menu) => {
+      this.#addMenu(menu);
+    });
+
+    validateOnlyDrink(this.#order);
+    validateTotalMenuCount(this.#order);
+  }
+
+  #addMenu(menu) {
+    const [name, count] = menu.split(DELIMITER.menuDelimiter);
+
+    if (validateDuplicateOrder(name, this.#order)) {
+      validateMenuCount(Number(count));
+      this.#order.set(new Menu(name), Number(count));
+    }
+  }
+
+  getWeekdayMenuCount() {
+    return Array.from(this.#order.entries())
+      .filter(([menu]) => WEEKDAY_DISCOUNT.menuType === menu.getType())
+      .reduce((totalCount, [_, count]) => totalCount + count, 0);
+  }
+
+  getWeekendMenuCount() {
+    return Array.from(this.#order.entries())
+      .filter(([menu]) => WEEKEND_DISCOUNT.menuType === menu.getType())
+      .reduce((totalCount, [_, count]) => totalCount + count, 0);
+  }
+
+  getOrder() {
+    return this.#order;
+  }
+}
+
+export default Order;

리뷰: 앗 그 부분을 생각하고 만든거에요! 보시면 getWeekdayMenuCount 같은 경우도 디저트, 메인메뉴를 가져오는게 아닌 평일 이벤트 메뉴를 가져오는 이름으로 지었고, WEEKDAY_DISCOUNT.menuType 도 평일할인의 메뉴타입을 가져오는데, 이부분은 constants부분에서 바로 type만 바꾸면 적용가능하게 했습니다!
constants부분을 보시면
```js
export const WEEKDAY_DISCOUNT = Object.freeze({
  menuType: MENU_TYPE.dessert,
  price: 2023,
});
```
이런식으로 되어있어서, 해당 `menuType`만 바꾸면 됩니다! 꼼꼼하십니다!👍

---
앗 말씀하신 부분이 `getWeekdayMenuCount`, `getWeekendMenuCount`가 메서드가 중복이니까, 굳이 따로 만들지 말고 get특정메뉴의MenuCount(메뉴 타입) 이런식으로 메서드 1개로도 충분히 처리 가능하다! 이 말씀이신가요? 이제 이해했네요.. 맞네요.. 굳이 2개를 나눌필요가 없었군요..! 이건 진짜 생각못한 부분인데 감사합니다!!
@@ -0,0 +1,47 @@
+import Menu from './Menu';
+import { WEEKDAY_DISCOUNT, WEEKEND_DISCOUNT } from '../utils/constants/discount';
+import { DELIMITER } from '../utils/constants/event';
+import { validateDuplicateOrder, validateOnlyDrink, validateTotalMenuCount } from '../utils/validators/vaildateOrder';
+import { validateMenuCount } from '../utils/validators/validateMenu';
+
+class Order {
+  #order;
+
+  constructor(orderInput) {
+    this.#order = new Map();
+
+    orderInput.split(DELIMITER.orderDelimiter).forEach((menu) => {
+      this.#addMenu(menu);
+    });
+
+    validateOnlyDrink(this.#order);
+    validateTotalMenuCount(this.#order);
+  }
+
+  #addMenu(menu) {
+    const [name, count] = menu.split(DELIMITER.menuDelimiter);
+
+    if (validateDuplicateOrder(name, this.#order)) {
+      validateMenuCount(Number(count));
+      this.#order.set(new Menu(name), Number(count));
+    }
+  }
+
+  getWeekdayMenuCount() {
+    return Array.from(this.#order.entries())
+      .filter(([menu]) => WEEKDAY_DISCOUNT.menuType === menu.getType())
+      .reduce((totalCount, [_, count]) => totalCount + count, 0);
+  }
+
+  getWeekendMenuCount() {
+    return Array.from(this.#order.entries())
+      .filter(([menu]) => WEEKEND_DISCOUNT.menuType === menu.getType())
+      .reduce((totalCount, [_, count]) => totalCount + count, 0);
+  }
+
+  getOrder() {
+    return this.#order;
+  }
+}
+
+export default Order;

리뷰: 그런데 여기서 고민했던 부분이, 현재 조건은 디저트 타입! 메뉴 타입! 으로 타입이 정해져있는데,

만약 평일 이벤트에 디저트 메뉴 + 메인 메뉴를 다같이 할인한다면, 이건 타입으로만 구별할 수 있는게 아니니까..
menuType으로 넣어야좋을지, 혹은 menus로 아예 할인 메뉴 배열을 넣어주는게 좋을지 고민했었습니다!

아무래도 문제 요구 사항에 맞춰서 menuType으로 가긴했는데, 말씀하신 것처럼 확장성있는 코드를 더 생각한다면 menus를 넣어주는것도 괜찮아 보이는 군요!
@@ -0,0 +1,59 @@
+import Discount from './Discount';
+import Order from './Order';
+import VisitDate from './VisitDate';
+import { BADGE, EVENT } from '../utils/constants/event';
+
+class Planner {
+  #visitDate;
+  #order;
+  #price;
+
+  setVisitDate(visitDate) {
+    this.#visitDate = new VisitDate(visitDate);
+  }
+
+  setOrder(order) {
+    this.#order = new Order(order);
+    this.#price = Array.from(this.#order.getOrder().entries()).reduce(
+      (price, [menu, count]) => price + menu.getPrice() * count,
+      0,
+    );
+  }
+
+  getVisitDate() {
+    return this.#visitDate.getVisitDate();
+  }
+
+  getOrder() {
+    return this.#order.getOrder();
+  }
+
+  getPrice() {
+    return this.#price;
+  }
+
+  caculateDiscount() {
+    if (this.#price < EVENT.discountTriggerPrice) {
+      return [{}, 0];
+    }
+    const discount = {
+      christmas: Discount.getChristmasDiscount(this.#visitDate),
+      weekday: Discount.getWeekdayDiscount(this.#visitDate, this.#order.getWeekdayMenuCount()),
+      weekend: Discount.getWeekendDiscount(this.#visitDate, this.#order.getWeekendMenuCount()),
+      special: Discount.getSpecialDiscount(this.#visitDate),
+      gift: Discount.getGiftDiscount(this.#price),
+    };
+    const discountPrice = Object.values(discount).reduce((total, discount) => total + discount, 0);
+    this.#price -= discountPrice - discount.gift;
+
+    return [discount, discountPrice];
+  }
+
+  getBadge(discountPrice) {
+    const badge = Object.values(BADGE).find(({ price }) => discountPrice >= price);
+
+    return badge?.name;
+  }
+}
+
+export default Planner;

리뷰: 고민했던 부분입니다!!! order 클래스 상에서 주문 메뉴의 총 가격을 반환한다면, price필드를 만들지 않아도 됬었습니다. order 클래스에서 총 주문금액을 반환하는 기능을 갖는게 더 맞다고 생각도 들었구요

또한 예상 할인금액과 예상 총액이기 때문에, price를 필드로 만드는 게 맞을까? 계속 고민했었습니다!
그렇다고 할인 전 price, 할인 price, 할인 후 price로 명확한 이름으로 필드를 3개 만드는건, 어차피 1번만 계산하는 예상 금액인데, 불필요한 필드라고 생각이 들었구요.

그런데 price를 필드로 만들지 않으면 order클래스에서 매번 가격을 계산해서 뽑아야하고, 코드 로직이 너무 복잡하게 되더라구요.. 또한 출력을 위해서 getMethod를 만들게 되서,, price 필드를 만들어줬습니다! 🥲

그런데 말씀하신 것 처럼 price부분이 많이 신경이 쓰입니다.. 리팩토링할때 진짜 많이 고민해보겠습니다! 감사합니다!!🙇
@@ -0,0 +1,59 @@
+import Discount from './Discount';
+import Order from './Order';
+import VisitDate from './VisitDate';
+import { BADGE, EVENT } from '../utils/constants/event';
+
+class Planner {
+  #visitDate;
+  #order;
+  #price;
+
+  setVisitDate(visitDate) {
+    this.#visitDate = new VisitDate(visitDate);
+  }
+
+  setOrder(order) {
+    this.#order = new Order(order);
+    this.#price = Array.from(this.#order.getOrder().entries()).reduce(
+      (price, [menu, count]) => price + menu.getPrice() * count,
+      0,
+    );
+  }
+
+  getVisitDate() {
+    return this.#visitDate.getVisitDate();
+  }
+
+  getOrder() {
+    return this.#order.getOrder();
+  }
+
+  getPrice() {
+    return this.#price;
+  }
+
+  caculateDiscount() {
+    if (this.#price < EVENT.discountTriggerPrice) {
+      return [{}, 0];
+    }
+    const discount = {
+      christmas: Discount.getChristmasDiscount(this.#visitDate),
+      weekday: Discount.getWeekdayDiscount(this.#visitDate, this.#order.getWeekdayMenuCount()),
+      weekend: Discount.getWeekendDiscount(this.#visitDate, this.#order.getWeekendMenuCount()),
+      special: Discount.getSpecialDiscount(this.#visitDate),
+      gift: Discount.getGiftDiscount(this.#price),
+    };
+    const discountPrice = Object.values(discount).reduce((total, discount) => total + discount, 0);
+    this.#price -= discountPrice - discount.gift;
+
+    return [discount, discountPrice];
+  }
+
+  getBadge(discountPrice) {
+    const badge = Object.values(BADGE).find(({ price }) => discountPrice >= price);
+
+    return badge?.name;
+  }
+}
+
+export default Planner;

리뷰: 옵셔널 체이닝입니다! 만약 뱃지가 없는경우 아무것도 반환이 안되기때문에, name이란 속성에 접근해버리면 에러가 뜹니다! 그래서 해당 ? 를 쓰면, 에러가 안뜨고 undefined이 반환됩니다! [참고링크](https://ko.javascript.info/optional-chaining)입니다!
@@ -0,0 +1,49 @@
+import {
+  CHRISTMAS_DISCOUNT,
+  WEEKDAY_DISCOUNT,
+  WEEKEND_DISCOUNT,
+  SPECIAL_DISCOUNT,
+  GIFT_DISCOUNT,
+} from '../utils/constants/discount';
+
+class Discount {
+  static getChristmasDiscount(visitDate) {
+    if (visitDate.isChristmasDay()) {
+      const date = visitDate.getVisitDate();
+      const incrementPrice = (date - CHRISTMAS_DISCOUNT.startDate) * CHRISTMAS_DISCOUNT.incrementPrice;
+
+      return CHRISTMAS_DISCOUNT.defaultPrice + incrementPrice;
+    }
+    return 0;
+  }
+
+  static getWeekdayDiscount(visitDate, count) {
+    if (visitDate.isWeekday()) {
+      return WEEKDAY_DISCOUNT.price * count;
+    }
+    return 0;
+  }
+
+  static getWeekendDiscount(visitDate, count) {
+    if (visitDate.isWeekend()) {
+      return WEEKEND_DISCOUNT.price * count;
+    }
+    return 0;
+  }
+
+  static getSpecialDiscount(visitDate) {
+    if (visitDate.isSpecialDay()) {
+      return SPECIAL_DISCOUNT.price;
+    }
+    return 0;
+  }
+
+  static getGiftDiscount(totalPrice) {
+    if (totalPrice >= GIFT_DISCOUNT.triggerPrice) {
+      return GIFT_DISCOUNT.price * GIFT_DISCOUNT.count;
+    }
+    return 0;
+  }
+}
+
+export default Discount;

리뷰: 아하 그렇네요!! 감사합니다!!👍👍👍
@@ -0,0 +1,20 @@
+import { VISIT_DATE_MESSAGE } from '../messages/errorMessages';
+import { EVENT } from '../constants/event';
+import CustomError from '../errors/CustomError';
+
+export const validateVisitDate = (visitDate) => {
+  validateType(visitDate);
+  validateRange(visitDate);
+};
+
+const validateType = (visitDate) => {
+  if (typeof visitDate !== 'number' || Number.isNaN(visitDate)) {
+    throw new CustomError(VISIT_DATE_MESSAGE.invalidNumber);
+  }
+};
+
+const validateRange = (visitDate) => {
+  if (visitDate < EVENT.startDate || visitDate > EVENT.endDate) {
+    throw new CustomError(VISIT_DATE_MESSAGE.invalidRange);
+  }
+};

리뷰: 앗 이건 생각 못한 예외네요... 필터링 안됩니다.. 추후 리팩토링할때 수정해야겠습니다! 감사합니다!!
@@ -0,0 +1,56 @@
+import Planner from '../domain/Planner';
+import InputView from '../view/InputView';
+import OutputView from '../view/OutputView';
+
+class ChristmasController {
+  #planner;
+
+  constructor() {
+    this.#planner = new Planner();
+  }
+
+  async play() {
+    await this.#setPlan();
+    this.#showBeforeDiscount();
+    this.#showAfterDiscount();
+  }
+
+  async #setPlan() {
+    await this.#retryInput(async () => {
+      const visitDate = await InputView.readVisitDate();
+      this.#planner.setVisitDate(visitDate);
+    });
+
+    await this.#retryInput(async () => {
+      const order = await InputView.readOrder();
+      this.#planner.setOrder(order);
+    });
+  }
+
+  #showBeforeDiscount() {
+    OutputView.printPreviewTitle(this.#planner.getVisitDate());
+    OutputView.printOrder(this.#planner.getOrder());
+    OutputView.printPriceBeforeDiscount(this.#planner.getPrice());
+  }
+
+  #showAfterDiscount() {
+    const [discount, discountTotal] = this.#planner.caculateDiscount();
+
+    OutputView.printGift(discount?.gift);
+    OutputView.printDiscountInfo(discount);
+    OutputView.printTotalDiscount(discountTotal);
+    OutputView.printPriceAfterDiscount(this.#planner.getPrice());
+    OutputView.printBadge(this.#planner.getBadge(discountTotal));
+  }
+
+  async #retryInput(func) {
+    try {
+      return await func();
+    } catch (error) {
+      OutputView.printError(error.message);
+      return this.#retryInput(func);
+    }
+  }
+}
+
+export default ChristmasController;

리뷰: 말씀하신것처럼 폴더나 파일 이름으로 알 수 있는데, 어느정도로 명확하게 이름을 지어야 할지 항상 고민입니다...🥲
@@ -0,0 +1,62 @@
+import { Console } from '@woowacourse/mission-utils';
+import { OUTPUT_MESSAGE, TITLE_MESSAGE, DISCOUNT_MESSAGE } from '../utils/messages/printMessages';
+
+const OutputView = {
+  printPreviewTitle(date) {
+    Console.print(OUTPUT_MESSAGE.preview(date));
+  },
+
+  printOrder(order) {
+    Console.print(TITLE_MESSAGE.order);
+
+    Array.from(order.entries()).forEach(([menu, menuCount]) => {
+      Console.print(OUTPUT_MESSAGE.menu(menu.getName(), menuCount));
+    });
+  },
+
+  printPriceBeforeDiscount(price) {
+    Console.print(TITLE_MESSAGE.priceBeforeDiscount);
+    Console.print(OUTPUT_MESSAGE.price(price));
+  },
+
+  printGift(gift) {
+    Console.print(TITLE_MESSAGE.gift);
+    gift ? Console.print(OUTPUT_MESSAGE.gift()) : Console.print(OUTPUT_MESSAGE.nothing);
+  },
+
+  printDiscountInfo(discounts) {
+    Console.print(TITLE_MESSAGE.discount);
+
+    if (Object.values(discounts).every((value) => value === 0)) {
+      Console.print(OUTPUT_MESSAGE.nothing);
+      return;
+    }
+
+    Object.entries(discounts).forEach(([discountName, discountPrice]) => {
+      if (discountPrice !== 0) {
+        Console.print(DISCOUNT_MESSAGE[discountName](discountPrice));
+      }
+    });
+  },
+
+  printTotalDiscount(price) {
+    Console.print(TITLE_MESSAGE.totalDiscount);
+    Console.print(price ? OUTPUT_MESSAGE.discountPrice(price) : OUTPUT_MESSAGE.price(price));
+  },
+
+  printPriceAfterDiscount(price) {
+    Console.print(TITLE_MESSAGE.priceAfterDiscount);
+    Console.print(OUTPUT_MESSAGE.price(price));
+  },
+
+  printBadge(badge) {
+    Console.print(TITLE_MESSAGE.badge);
+    badge ? Console.print(badge) : Console.print(OUTPUT_MESSAGE.nothing);
+  },
+
+  printError(errorMessage) {
+    Console.print(errorMessage);
+  },
+};
+
+export default OutputView;

리뷰: `Object.entries`를 사용할 수도 있었군요...!!
이 방법은 미처 생각하지 못했는데 배우고 갑니다 !!!
@@ -0,0 +1,34 @@
+import { MENU_MESSAGE, MENU_COUNT_MESSAGE } from '../messages/errorMessages';
+import { MENU } from '../constants/menu';
+import { VALIDATION } from '../constants/event';
+import CustomError from '../errors/CustomError';
+
+export const validateMenuName = (name) => {
+  validateExist(name);
+};
+
+export const validateMenuCount = (menuCount) => {
+  validateType(menuCount);
+  validateRange(menuCount);
+};
+
+const validateExist = (name) => {
+  for (const menuItems of Object.values(MENU)) {
+    if (menuItems.hasOwnProperty(name)) {
+      return;
+    }
+  }
+  throw new CustomError(MENU_MESSAGE.isNotExistMenu);
+};
+
+const validateType = (menuCount) => {
+  if (typeof menuCount !== 'number' || Number.isNaN(menuCount)) {
+    throw new CustomError(MENU_COUNT_MESSAGE.invalidNumber);
+  }
+};
+
+const validateRange = (menuCount) => {
+  if (menuCount < VALIDATION.minMenuCount) {
+    throw new CustomError(MENU_COUNT_MESSAGE.minMenuCount);
+  }
+};

리뷰: javascript airbnb에 따르면 추천하는 방법은 아니라고 합니다 !
`forEach`나 `map`을 사용하시는건 어떨까요??

참고 : [Javascript airbnb 11.1](https://github.com/tipjs/javascript-style-guide#11.1)
@@ -0,0 +1,4 @@
+export const priceFormatter = new Intl.NumberFormat('ko-KR', {
+  minimumFractionDigits: 0,
+  maximumFractionDigits: 0,
+});

리뷰: toLocaleString() 함수 말고 이렇게 하신 이유가 있을까요??
@@ -6,11 +6,24 @@
  *  smtp session 관리 시 충돌을 방지하기 위한 적절한 매커니즘을 적용하여 개발하여야 합니다.
  */
 
+//extern smtp_session_pool *sessions[MAX_POOL_NUM];
+
 void delSmtpSession(char *session_id) {
     /*  TODO 과제 1-1
      *   smtp 세션을 종료
      *   그동안 사용되었던 session을 session_id를 이용하여 session 보관소에서 제거하는 로직을 개발하여야 합니다.
      */
+	smtp_session_t *session = NULL;
+	int pool_num = 0;
+	for (pool_num = 0; pool_num < MAX_POOL_NUM; pool_num++) {
+		if (sessions[pool_num].occupied == 1) {
+			if (!strcmp(session_id, sessions[pool_num].sess_id)) {
+				sessions[pool_num].occupied = 0;
+				sessions[pool_num].used = 0;
+				return;
+			}
+		}
+	}
     return;
 }
 
@@ -19,6 +32,16 @@ smtp_session_t *addSmtpSession(smtp_session_t *session) {
      *   smtp 세션을 추가
      *   전달받은 smtp session정보를 활용하여 현재 관리하고 있는 smtp session 보관소에 추가하는 로직을 개발하여야 합니다.
      */
+	int pool_num = 0;
+	for (pool_num = 0; pool_num < MAX_POOL_NUM; pool_num++) {
+		if (sessions[pool_num].occupied == 0) {
+			sessions[pool_num].occupied = 1;
+			strcpy(sessions[pool_num].sess_id, session->session_id);
+			sessions[pool_num].session = (smtp_session_t *)session;
+			return session;
+		}
+	}
+
     return NULL;
 }
 
@@ -29,3 +52,41 @@ smtp_session_t *getSmtpSession(char *session_id) {
      */
     return NULL;
 }
+
+void initSessionPool()
+{
+	int pool_num = 0;
+	for (pool_num = 0; pool_num < MAX_POOL_NUM; pool_num++) {
+		sessions[pool_num].occupied = 0;
+		sessions[pool_num].used = 0;
+	}	
+}
+
+int getSmtpSessionIdxForPool() {
+   	int pool_num = 0;
+	for (pool_num = 0; pool_num < MAX_POOL_NUM; pool_num++) {
+		if (sessions[pool_num].occupied == 1) {
+			if (sessions[pool_num].used == 0) {
+				sessions[pool_num].used = 1;
+				return pool_num;
+			}
+		}
+	}	 
+	return -1;
+}
+
+void unusedSmtpSession(int pool_num) {
+	if (sessions[pool_num].used == 1) {
+		sessions[pool_num].used = 0;
+	}
+	return;
+}
+/*
+void unusedSmtpSession(char) {
+	int pool_num = 0;
+	for (pool_num = 0; pool_num < MAX_POOL_NUM; pool_num++) {
+		if (!strcmp(session_id, sessions[pool_num].sess_id)) {
+			sessions[pool_num].used = 0;
+		}
+	}
+} */

리뷰: 기능상 문제는 없어보입니다.
다만 배열 index 0부터 순환하여 대상 Session을 찾는 건 효율적이지 않는 로직 같습니다.
최초 할당 때와 달리, Session에 Index를 저장하는 방식 등 index를 저장 할 수 있는 공간이 있을 건데 그러지 않고 for loop + strcmp으로 같은 SessionID가 나올때까지 찾는 로직을 사용하였습니다. 시간 여건이 안되어 그랬을수도 있을것 같은데, 개발자가 이런 부분은 개선 되어야 함을 인지 하고 있었으면 좋겠습니다.
@@ -18,8 +18,25 @@ typedef struct smtp_session {
     char                smtp_mail_from[ MAX_MAIL_FROM ];
 } smtp_session_t;
 
+/* added by se00248 */
+#define MAX_POOL_NUM 10000
+
+typedef struct smtp_session_pool {
+	int occupied;
+	int used;
+    	char sess_id[ L_SESSION_ID ];
+	smtp_session_t *session;
+} smtp_session_pool;
+
+extern void initSessionPool();
+extern int getSmtpSessionIdxForPool();
+extern void unusedSmtpSession(int pool_num);
+//extern smtp_session_t *getSmtpSessionForPool();
+//extern void unusedSmtpSession(char *session_id);
+extern smtp_session_pool sessions[MAX_POOL_NUM];
+/* end added by se00248 */
+
 extern void delSmtpSession(char * session_id);
 extern smtp_session_t * addSmtpSession(smtp_session_t * session);
-
 #endif //CAREER_TEST3_SMTPSESSION_H
 

리뷰: 시험코드에 다음과 같이 point array형태로 가이드 코드를 넣어두었습니다.
extern smtp_session_pool *sessions[MAX_POOL_NUM]; 

하지만 해당 코드를 사용하지 않고 smtp_session_pool 을 만들어 smtp_session_t *session;을 감싸두었고, occupied / used 으로 구분 하였습니다.
이렇게 한 이유가 궁금합니다.
@@ -7,13 +7,25 @@
  *  smtpHandleInboundConnection로직을 통해 Connection을 맺는걸 권유 드립니다.(해당 로직을 사용안하셔도 무방합니다.)
  *
  */
+
+int count = 0;
+
 void smtpWaitAsync(int server_fd) {
         /* TODO 과제 2-1
         *  smtpSvrRecvAsync.c 파일은 비동기 처리를 이용하여 데이터를 제어 하는 로직이 작성 되어 있습니다.
         *  N개의 H_SERVER_WORK_TH 스레드와 하나의 smtpWaitAsync 스레드가 동시에 동작하고 있습니다.
         *  smtpWaitAsync Function에서 Connection을 맺고(smtpHandleInboundConnection로직 사용 권유)
         *  비동기 통신이 가능하게 개발한후 session정보를 WorkThread로 전달하는 로직을 개발하여야 합니다.
         */
+	while (1) {
+		smtp_session_t *session = NULL;
+		if ((session = smtpHandleInboundConnection(server_fd)) == NULL) {
+            		msleep(25);
+			continue;
+		}
+            	//msleep(25);
+	}
+	return;
 }
 
 void *H_SERVER_WORK_TH(void *args) {
@@ -26,12 +38,21 @@ void *H_SERVER_WORK_TH(void *args) {
         /* TODO 과제 2-2
         *  session정보를 해당 위치에 받아 올수있게 개발하여야 합니다.
         */
+	int idx = -1;
+	if ((idx = getSmtpSessionIdxForPool()) == -1) {
+            	msleep(25);
+		continue;
+	}
+	
+	session = sessions[idx].session;
 
         if (session == NULL) {
             msleep(25);
             continue;
         }
 
+	if (++count % 100 == 0) msleep(25);
+
         if ((nLine = smtpReadLine(session->sock_fd, buf, sizeof(buf))) <= 0) {
             LOG (LOG_INF, "%s : %sSMTP Connection closed%s : fd = %d, session_id=%s\n", __func__, C_YLLW, C_NRML,
                  session->sock_fd,
@@ -46,6 +67,8 @@ void *H_SERVER_WORK_TH(void *args) {
             }
             continue;
         }
+	
+	unusedSmtpSession(idx);
 
     }
     return NULL;
@@ -68,4 +91,4 @@ int smtpStartWorkThreads(int n_work_threads) {
     }
 
     return 0;
-}
\ No newline at end of file
+}

리뷰: 100단위로 sleep을 주고 있습니다. 
아마도 성능 테스트간에 이슈가 나와서 sleep을  주었던것으로 예상은 되는데, 정확한 의도가 궁금합니다.
또한 works Thread에서 적절한 lock 없이 전역변수 하나를 통해 제어를 하는점이 이상해 보이네요
@@ -6,11 +6,24 @@
  *  smtp session 관리 시 충돌을 방지하기 위한 적절한 매커니즘을 적용하여 개발하여야 합니다.
  */
 
+//extern smtp_session_pool *sessions[MAX_POOL_NUM];
+
 void delSmtpSession(char *session_id) {
     /*  TODO 과제 1-1
      *   smtp 세션을 종료
      *   그동안 사용되었던 session을 session_id를 이용하여 session 보관소에서 제거하는 로직을 개발하여야 합니다.
      */
+	smtp_session_t *session = NULL;
+	int pool_num = 0;
+	for (pool_num = 0; pool_num < MAX_POOL_NUM; pool_num++) {
+		if (sessions[pool_num].occupied == 1) {
+			if (!strcmp(session_id, sessions[pool_num].sess_id)) {
+				sessions[pool_num].occupied = 0;
+				sessions[pool_num].used = 0;
+				return;
+			}
+		}
+	}
     return;
 }
 
@@ -19,6 +32,16 @@ smtp_session_t *addSmtpSession(smtp_session_t *session) {
      *   smtp 세션을 추가
      *   전달받은 smtp session정보를 활용하여 현재 관리하고 있는 smtp session 보관소에 추가하는 로직을 개발하여야 합니다.
      */
+	int pool_num = 0;
+	for (pool_num = 0; pool_num < MAX_POOL_NUM; pool_num++) {
+		if (sessions[pool_num].occupied == 0) {
+			sessions[pool_num].occupied = 1;
+			strcpy(sessions[pool_num].sess_id, session->session_id);
+			sessions[pool_num].session = (smtp_session_t *)session;
+			return session;
+		}
+	}
+
     return NULL;
 }
 
@@ -29,3 +52,41 @@ smtp_session_t *getSmtpSession(char *session_id) {
      */
     return NULL;
 }
+
+void initSessionPool()
+{
+	int pool_num = 0;
+	for (pool_num = 0; pool_num < MAX_POOL_NUM; pool_num++) {
+		sessions[pool_num].occupied = 0;
+		sessions[pool_num].used = 0;
+	}	
+}
+
+int getSmtpSessionIdxForPool() {
+   	int pool_num = 0;
+	for (pool_num = 0; pool_num < MAX_POOL_NUM; pool_num++) {
+		if (sessions[pool_num].occupied == 1) {
+			if (sessions[pool_num].used == 0) {
+				sessions[pool_num].used = 1;
+				return pool_num;
+			}
+		}
+	}	 
+	return -1;
+}
+
+void unusedSmtpSession(int pool_num) {
+	if (sessions[pool_num].used == 1) {
+		sessions[pool_num].used = 0;
+	}
+	return;
+}
+/*
+void unusedSmtpSession(char) {
+	int pool_num = 0;
+	for (pool_num = 0; pool_num < MAX_POOL_NUM; pool_num++) {
+		if (!strcmp(session_id, sessions[pool_num].sess_id)) {
+			sessions[pool_num].used = 0;
+		}
+	}
+} */

리뷰: smtpSession.c에 있는 함수들 공통으로, 전역 변수 접근이 있습니다. 
이 때 적절한 lock을 사용하여야 하였는데 그런 부분이 보이지 않습니다.