ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,50 @@
+package blackjack.domain.result;
+
+import blackjack.domain.participant.Dealer;
+import blackjack.domain.participant.Player;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.function.BiFunction;
+
+public enum Rule{
+    // 카드 값의 합이 21을 넘으면 승패 결정 (플레이어 기준으로 선언)
+    PLAYER_BUST(((player, dealer) -> player.isBust()), WinningResult.LOSE, 1),
+    DEALER_BUST(((player, dealer) -> dealer.isBust()), WinningResult.WIN, 2),
+
+    // 카드 값의 합이 21을 넘지 않으면서, 값이 더 큰쪽이 승패결정(이미 여기서 결정, BlackJack 체크 필요없음)
+    PLAYER_HIGHER(((player, dealer) -> player.getCardsSum() > dealer.getCardsSum()), WinningResult.WIN, 3),
+    DEALER_HIGHER(((player, dealer) -> player.getCardsSum() < dealer.getCardsSum()), WinningResult.LOSE, 4),
+
+    // 무승부 결정
+    TIES(((player, dealer) -> player.getCardsSum() == dealer.getCardsSum()), WinningResult.TIE, 5);
+
+    private BiFunction<Player, Dealer, Boolean> compare;
+    private WinningResult winningResult;
+    @Getter
+    private int verificationOrder;
+
+    Rule(BiFunction<Player, Dealer, Boolean> compare, WinningResult winningResult, int verificationOrder) {
+        this.compare = compare;
+        this.winningResult = winningResult;
+        this.verificationOrder = verificationOrder;
+    }
+
+    public Boolean findMatchingRule(Player player, Dealer dealer) {
+        return compare.apply(player, dealer);
+    }
+
+    public WinningResult getWinningResult() {
+        return winningResult;
+    }
+
+    public static WinningResult resultPlayerVersusDealer(Player player, Dealer dealer) {
+
+        return Arrays.stream(Rule.values())
+                .sorted(new RuleComparator())
+                .filter(rule -> rule.findMatchingRule(player, dealer))
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException("일치하는 결과를 찾을 수 없습니다."))
+                .getWinningResult();
+    }
+}

리뷰: 친절한 주석 좋네요. 해당 메소드는 participant에게 값을 비교하는 형태로 리팩토링할 수 있을 것 같아요
@@ -0,0 +1,50 @@
+package blackjack.domain.result;
+
+import blackjack.domain.participant.Dealer;
+import blackjack.domain.participant.Player;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.function.BiFunction;
+
+public enum Rule{
+    // 카드 값의 합이 21을 넘으면 승패 결정 (플레이어 기준으로 선언)
+    PLAYER_BUST(((player, dealer) -> player.isBust()), WinningResult.LOSE, 1),
+    DEALER_BUST(((player, dealer) -> dealer.isBust()), WinningResult.WIN, 2),
+
+    // 카드 값의 합이 21을 넘지 않으면서, 값이 더 큰쪽이 승패결정(이미 여기서 결정, BlackJack 체크 필요없음)
+    PLAYER_HIGHER(((player, dealer) -> player.getCardsSum() > dealer.getCardsSum()), WinningResult.WIN, 3),
+    DEALER_HIGHER(((player, dealer) -> player.getCardsSum() < dealer.getCardsSum()), WinningResult.LOSE, 4),
+
+    // 무승부 결정
+    TIES(((player, dealer) -> player.getCardsSum() == dealer.getCardsSum()), WinningResult.TIE, 5);
+
+    private BiFunction<Player, Dealer, Boolean> compare;
+    private WinningResult winningResult;
+    @Getter
+    private int verificationOrder;
+
+    Rule(BiFunction<Player, Dealer, Boolean> compare, WinningResult winningResult, int verificationOrder) {
+        this.compare = compare;
+        this.winningResult = winningResult;
+        this.verificationOrder = verificationOrder;
+    }
+
+    public Boolean findMatchingRule(Player player, Dealer dealer) {
+        return compare.apply(player, dealer);
+    }
+
+    public WinningResult getWinningResult() {
+        return winningResult;
+    }
+
+    public static WinningResult resultPlayerVersusDealer(Player player, Dealer dealer) {
+
+        return Arrays.stream(Rule.values())
+                .sorted(new RuleComparator())
+                .filter(rule -> rule.findMatchingRule(player, dealer))
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException("일치하는 결과를 찾을 수 없습니다."))
+                .getWinningResult();
+    }
+}

리뷰: 해당 메소드는 비교하는 메소드가 아니라, 어떤 Rule인지를 찾는거에 가깝네요. 요것도 메소드 이름 수정해주시면 좋을 것 같아요.
@@ -0,0 +1,27 @@
+package blackjack.domain.result;
+
+import lombok.Getter;
+
+@Getter
+public enum WinningResult {
+
+    WIN("승"),
+    LOSE("패"),
+    TIE("무승부");
+
+    private final String result;
+
+    WinningResult(String result) {
+        this.result = result;
+    }
+
+    public WinningResult reverse() {
+        if (this == WIN) {
+            return LOSE;
+        }
+        if (this == LOSE) {
+            return WIN;
+        }
+        return TIE;
+    }
+}

리뷰: 요거 제가 전에 할 때, 안 됐던건데 WinningResult가 WinningResult 멤버변수를 가지고, 반대로 선언하면 안되더라구요. 왜 안되는지 바로 이해가시면 Enum을 잘 이해하고 계신 것 같습니다ㅋㅋㅋ
```java
WIN("승", LOST) // 요런식으로여
```
@@ -0,0 +1,66 @@
+package blackjack.controller;
+
+import blackjack.domain.card.Deck;
+import blackjack.domain.participant.Dealer;
+import blackjack.domain.participant.Player;
+import blackjack.domain.participant.PlayersFactory;
+import blackjack.domain.result.GameResult;
+import blackjack.dto.DrawCardRequestDto;
+import blackjack.dto.PlayersNameInputDto;
+import blackjack.view.InputView;
+import blackjack.view.OutputView;
+
+import java.util.List;
+
+public class BlackJackController {
+    private final InputView inputView = new InputView();
+    private final OutputView outputView = new OutputView();
+
+    public void run() {
+        PlayersNameInputDto namesInput = inputView.getPlayersName();
+        Dealer dealer = new Dealer();
+        Deck deck = new Deck();
+        List<Player> players = PlayersFactory.createPlayers(namesInput.getPlayersName());
+
+        drawTowCards(dealer, players, deck);
+        drawCardToPlayers(players, deck);
+        drawCardToDealer(dealer, deck);
+        outputView.printCardsResult(dealer, players);
+        outputView.printGameResult(GameResult.of(dealer, players));
+    }
+
+    private void drawTowCards(Dealer dealer, List<Player> players, Deck deck) {
+        for (Player player : players) {
+            player.receiveCard(deck.drawCard());
+            player.receiveCard(deck.drawCard());
+        }
+        dealer.receiveCard(deck.drawCard());
+        dealer.receiveCard(deck.drawCard());
+        outputView.printFirstCardsGiven(players, dealer);
+        outputView.printDealerCard(dealer);
+        outputView.printPlayersCard(players);
+    }
+
+    private void drawCardToPlayers(List<Player> players, Deck deck) {
+        for (Player player : players) {
+            drawCardToPlayer(player, deck);
+        }
+    }
+
+    private void drawCardToPlayer(Player player, Deck deck) {
+        DrawCardRequestDto drawCardRequest = inputView.getPlayersResponse(player);
+        while (player.drawable() && drawCardRequest.isYes()) {
+            player.receiveCard(deck.drawCard());
+
+            outputView.printCards(player);
+            drawCardRequest = inputView.getPlayersResponse(player);
+        }
+    }
+
+    private void drawCardToDealer(Dealer dealer, Deck deck) {
+        while (dealer.drawable()) {
+            dealer.receiveCard(deck.drawCard());
+            outputView.printDealerCardGiven();
+        }
+    }
+}

리뷰: 여러명이 동시에 요청하는 경우를 고려하지 않았네요. 서비스 개발자로서 항상 생각해야 할 부분인 것 같습니다.
view를 제외한 클래스변수들을 로컬변수로 수정했습니다.
@@ -0,0 +1,66 @@
+package blackjack.controller;
+
+import blackjack.domain.card.Deck;
+import blackjack.domain.participant.Dealer;
+import blackjack.domain.participant.Player;
+import blackjack.domain.participant.PlayersFactory;
+import blackjack.domain.result.GameResult;
+import blackjack.dto.DrawCardRequestDto;
+import blackjack.dto.PlayersNameInputDto;
+import blackjack.view.InputView;
+import blackjack.view.OutputView;
+
+import java.util.List;
+
+public class BlackJackController {
+    private final InputView inputView = new InputView();
+    private final OutputView outputView = new OutputView();
+
+    public void run() {
+        PlayersNameInputDto namesInput = inputView.getPlayersName();
+        Dealer dealer = new Dealer();
+        Deck deck = new Deck();
+        List<Player> players = PlayersFactory.createPlayers(namesInput.getPlayersName());
+
+        drawTowCards(dealer, players, deck);
+        drawCardToPlayers(players, deck);
+        drawCardToDealer(dealer, deck);
+        outputView.printCardsResult(dealer, players);
+        outputView.printGameResult(GameResult.of(dealer, players));
+    }
+
+    private void drawTowCards(Dealer dealer, List<Player> players, Deck deck) {
+        for (Player player : players) {
+            player.receiveCard(deck.drawCard());
+            player.receiveCard(deck.drawCard());
+        }
+        dealer.receiveCard(deck.drawCard());
+        dealer.receiveCard(deck.drawCard());
+        outputView.printFirstCardsGiven(players, dealer);
+        outputView.printDealerCard(dealer);
+        outputView.printPlayersCard(players);
+    }
+
+    private void drawCardToPlayers(List<Player> players, Deck deck) {
+        for (Player player : players) {
+            drawCardToPlayer(player, deck);
+        }
+    }
+
+    private void drawCardToPlayer(Player player, Deck deck) {
+        DrawCardRequestDto drawCardRequest = inputView.getPlayersResponse(player);
+        while (player.drawable() && drawCardRequest.isYes()) {
+            player.receiveCard(deck.drawCard());
+
+            outputView.printCards(player);
+            drawCardRequest = inputView.getPlayersResponse(player);
+        }
+    }
+
+    private void drawCardToDealer(Dealer dealer, Deck deck) {
+        while (dealer.drawable()) {
+            dealer.receiveCard(deck.drawCard());
+            outputView.printDealerCardGiven();
+        }
+    }
+}

리뷰: Dto 이름을 DrawCardRequestDto로 변경하고 판단로직을 Dto로 이동했습니다.
Dto가 가진 데이터만을 이용한 간단한 로직은 Dto에서 처리해도 된다는 것을 알게 되었습니다.

Dto 이름을 받아온 것인지, 반환하는 것인지에 따라 통일하면 일관성이 있어 헷갈릴 일이 없어서 좋은 방법인 것 같습니다.
@@ -0,0 +1,17 @@
+package blackjack.utils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class StringUtil {
+    private static final String COMMA = ",";
+    private static final String RESPONSE_RESTRICT_MESSAGE = "y 혹은 n 만 입력할 수 있습니다.";
+
+    public static List<String> splitByComma(String input) {
+        List<String> names = Arrays.asList(input.split(COMMA));
+        return names.stream()
+                .map(String::trim)
+                .collect(Collectors.toList());
+    }
+}

리뷰: 로직을 DrawCardRequestDto로 이동했습니다.
@@ -0,0 +1,32 @@
+package blackjack.domain.card;
+
+import blackjack.domain.card.strategy.DrawStrategy;
+import blackjack.domain.card.strategy.RandomDrawStrategy;
+import lombok.Getter;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class Deck {
+
+    private static final String ALERT_NO_CARD_LEFT = "사용 가능한 카드를 모두 소진하였습니다.";
+
+    @Getter
+    private final List<Card> deck;
+
+    public Deck() {
+        this.deck = new LinkedList<>(DeckFactory.createDeck());
+    }
+
+    public Card drawCard() {
+        if (deck.isEmpty()) {
+            throw new RuntimeException(ALERT_NO_CARD_LEFT);
+        }
+        return deck.remove(generateNextDrawCardIndex(new RandomDrawStrategy()));
+    }
+
+    private int generateNextDrawCardIndex(DrawStrategy drawStrategy) {
+        return drawStrategy.getNextIndex(deck);
+    }
+
+}

리뷰: 넵 그렇습니다.
@@ -0,0 +1,32 @@
+package blackjack.domain.card;
+
+import blackjack.domain.card.strategy.DrawStrategy;
+import blackjack.domain.card.strategy.RandomDrawStrategy;
+import lombok.Getter;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class Deck {
+
+    private static final String ALERT_NO_CARD_LEFT = "사용 가능한 카드를 모두 소진하였습니다.";
+
+    @Getter
+    private final List<Card> deck;
+
+    public Deck() {
+        this.deck = new LinkedList<>(DeckFactory.createDeck());
+    }
+
+    public Card drawCard() {
+        if (deck.isEmpty()) {
+            throw new RuntimeException(ALERT_NO_CARD_LEFT);
+        }
+        return deck.remove(generateNextDrawCardIndex(new RandomDrawStrategy()));
+    }
+
+    private int generateNextDrawCardIndex(DrawStrategy drawStrategy) {
+        return drawStrategy.getNextIndex(deck);
+    }
+
+}

리뷰: DrawStrategy 인터페이스를 만들고 그것을 구현하는 RandomDrawStrategy 클래스를 만들었습니다.
@@ -0,0 +1,39 @@
+package blackjack.domain.card;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class DeckFactory {
+
+    private static final List<Card> cardsDeck;
+
+    static {
+        cardsDeck = createCardsDeck();
+    }
+
+    private static List<Card> createCardsDeck() {
+        List<Card> cards = new ArrayList<>();
+
+        for (Denomination denomination : Denomination.values()) {
+            cards.addAll(createCards(denomination));
+        }
+        return cards;
+    }
+
+    private static List<Card> createCards(Denomination denomination) {
+        List<Card> cards = new ArrayList<>();
+
+        for (Type type : Type.values()) {
+            cards.add(new Card(denomination, type));
+        }
+
+        return cards;
+    }
+
+    public static List<Card> createDeck() {
+        return Collections.unmodifiableList(cardsDeck);
+    }
+}
+
+

리뷰: 스태틱블록으로 변경했습니다.

기능은 등호를 사용한 코드와 정확히 동일한건가요?
@@ -0,0 +1,38 @@
+package blackjack.domain.card;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CardTest {
+
+    @DisplayName("카드 객체의 숫자와 종류값이 같으면 같은 객체로 판단한다")
+    @Test
+    void equalsTest() {
+        //given, when, then
+        assertThat(new Card(Denomination.TWO, Type.DIAMOND))
+                .isEqualTo(new Card(Denomination.TWO, Type.DIAMOND));
+
+    }
+
+    @DisplayName("카드 객체의 숫자와 종류를 출력한다")
+    @Test
+    void printCardTwoDiamond() {
+        //given
+        Card card = new Card(Denomination.TWO, Type.DIAMOND);
+
+        //when, then
+        assertThat(card).hasToString("2다이아몬드");
+    }
+
+    @DisplayName("카드 객체의 숫자와 종류를 출력한다")
+    @Test
+    void printCardAceHeart() {
+        //given
+        Card card = new Card(Denomination.ACE, Type.HEART);
+
+        //when, then
+        assertThat(card.toString()).isEqualTo("A하트");
+    }
+}

리뷰: assert문으로 문자열을 비교할 때는 hasToString메서드를 사용하겠습니다.
@@ -0,0 +1,27 @@
+package blackjack.domain.participant;
+
+import lombok.Getter;
+
+@Getter
+public class Player extends Participant {
+
+    private static final int PLAYER_DRAW_THRESHOLD = 21;
+    private static final String DEALER_NAME = "딜러";
+    private static final String CHECK_SAME_NAME_AS_DEALER = "이름은 딜러와 같을 수 없으니 다른 이름을 지정해주세요.";
+
+    public Player(String name) {
+        super(name);
+        validateDealerNameDuplicated(name);
+    }
+
+    private void validateDealerNameDuplicated(String name) {
+        if (name.equals(DEALER_NAME)) {
+            throw new IllegalArgumentException(CHECK_SAME_NAME_AS_DEALER);
+        }
+    }
+
+    @Override
+    public boolean drawable() {
+        return getCardsSum() < PLAYER_DRAW_THRESHOLD;
+    }
+}

리뷰: 말씀하신 부분이 맞다고 생각합니다. 문자열 생성 로직을 view로 이동했습니다.
@@ -0,0 +1,27 @@
+package blackjack.domain.participant;
+
+import lombok.Getter;
+
+@Getter
+public class Player extends Participant {
+
+    private static final int PLAYER_DRAW_THRESHOLD = 21;
+    private static final String DEALER_NAME = "딜러";
+    private static final String CHECK_SAME_NAME_AS_DEALER = "이름은 딜러와 같을 수 없으니 다른 이름을 지정해주세요.";
+
+    public Player(String name) {
+        super(name);
+        validateDealerNameDuplicated(name);
+    }
+
+    private void validateDealerNameDuplicated(String name) {
+        if (name.equals(DEALER_NAME)) {
+            throw new IllegalArgumentException(CHECK_SAME_NAME_AS_DEALER);
+        }
+    }
+
+    @Override
+    public boolean drawable() {
+        return getCardsSum() < PLAYER_DRAW_THRESHOLD;
+    }
+}

리뷰: orElseThrow로 수정하고, 메세지를 담은 예외를 던지도록 했습니다.
@@ -1,16 +1,28 @@
 package webserver;
 
-import java.io.DataOutputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import webserver.controller.*;
+import webserver.domain.HttpRequest;
+import webserver.domain.HttpResponse;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.util.HashMap;
+import java.util.Map;
 
 public class RequestHandler implements Runnable {
     private static final Logger logger = LoggerFactory.getLogger(RequestHandler.class);
+    private static final Map<String, Controller> controllers = new HashMap<>();
+
+    static {
+        controllers.put("/user/create", new CreateUserController());
+        controllers.put("/user/login", new LoginController());
+        controllers.put("/user/list", new ListUserController());
+        controllers.put("/user/list.html", new ListUserController());
+    }
 
     private Socket connection;
 
@@ -21,35 +33,12 @@ public RequestHandler(Socket connectionSocket) {
     public void run() {
         logger.debug("New Client Connect! Connected IP : {}, Port : {}", connection.getInetAddress(),
                 connection.getPort());
-
         try (InputStream in = connection.getInputStream(); OutputStream out = connection.getOutputStream()) {
-            // TODO 사용자 요청에 대한 처리는 이 곳에 구현하면 된다.
-            DataOutputStream dos = new DataOutputStream(out);
-            byte[] body = "Hello World".getBytes();
-            response200Header(dos, body.length);
-            responseBody(dos, body);
-        } catch (IOException e) {
-            logger.error(e.getMessage());
-        }
-    }
-
-    private void response200Header(DataOutputStream dos, int lengthOfBodyContent) {
-        try {
-            dos.writeBytes("HTTP/1.1 200 OK \r\n");
-            dos.writeBytes("Content-Type: text/html;charset=utf-8\r\n");
-            dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n");
-            dos.writeBytes("\r\n");
-        } catch (IOException e) {
-            logger.error(e.getMessage());
-        }
-    }
-
-    private void responseBody(DataOutputStream dos, byte[] body) {
-        try {
-            dos.write(body, 0, body.length);
-            dos.flush();
+            HttpRequest httpRequest = new HttpRequest(in);
+            HttpResponse httpResponse = new HttpResponse(out);
+            controllers.getOrDefault(httpRequest.getPath(), new DefaultController()).service(httpRequest, httpResponse);
         } catch (IOException e) {
             logger.error(e.getMessage());
         }
     }
-}
+}
\ No newline at end of file

리뷰: path에 맞는 Controller 정의 좋습니다 👍
@@ -0,0 +1,80 @@
+package webserver.controller;
+
+import webserver.domain.HttpRequest;
+import webserver.domain.HttpResponse;
+import webserver.domain.HttpStatusCode;
+import webserver.exceptions.InvalidRequestException;
+import webserver.exceptions.NotYetSentException;
+
+public abstract class AbstractController implements Controller {
+
+    public void service(HttpRequest request, HttpResponse response) {
+        try {
+            System.out.println(request.getMethod().name() + " " + request.getPath());
+            methodMapping(request, response);
+        } catch (Exception e) {
+            if(!response.isSent()){
+                System.out.println("자식 컨트롤러에서 처리 누락된 예외 상황 : " + e.getMessage());
+                e.printStackTrace();
+                response.send(HttpStatusCode.NOT_IMPLEMENTED, e.getMessage());
+            }
+        } finally {
+            if (!response.isSent()) {
+                response.send(HttpStatusCode.INTERNAL_SERVER_ERROR);
+                throw new NotYetSentException("response의 메서드 send()가 호출되지 않았습니다");
+            }
+        }
+    }
+
+    private void methodMapping(HttpRequest request, HttpResponse response) {
+        switch (request.getMethod()) {
+            case GET:
+                validateGetRequest(request, response);
+                doGet(request, response);
+                return;
+            case POST:
+                validatePostRequest(request, response);
+                doPost(request, response);
+                return;
+            case PUT:
+                validatePutRequest(request, response);
+                doPut(request, response);
+                return;
+            case DELETE:
+                validateDeleteRequest(request, response);
+                doDelete(request, response);
+                return;
+            default:
+                response.send(HttpStatusCode.METHOD_NOT_ALLOWED);
+        }
+    }
+
+    public void doPost(HttpRequest request, HttpResponse response) {
+        response.send(HttpStatusCode.METHOD_NOT_ALLOWED);
+    }
+
+    public void doGet(HttpRequest request, HttpResponse response) {
+        response.send(HttpStatusCode.METHOD_NOT_ALLOWED);
+    }
+
+    public void doPut(HttpRequest request, HttpResponse response) {
+        response.send(HttpStatusCode.METHOD_NOT_ALLOWED);
+    }
+
+    public void doDelete(HttpRequest request, HttpResponse response) {
+        response.send(HttpStatusCode.METHOD_NOT_ALLOWED);
+    }
+
+    // 필요시에 오버라이딩 하여 인풋값을 검증한다
+    public void validatePostRequest(HttpRequest request, HttpResponse response) throws InvalidRequestException {
+    }
+
+    public void validateGetRequest(HttpRequest request, HttpResponse response) throws InvalidRequestException {
+    }
+
+    public void validatePutRequest(HttpRequest request, HttpResponse response) throws InvalidRequestException {
+    }
+
+    public void validateDeleteRequest(HttpRequest request, HttpResponse response) throws InvalidRequestException {
+    }
+}

리뷰: 추상 클래스 service 메소드에 흐름을 정리하여 템플릿 메소드 패턴으로 적용해주셨네요 👍 💯
@@ -0,0 +1,21 @@
+package webserver.controller;
+
+import utils.FileIoUtils;
+import webserver.domain.HttpRequest;
+import webserver.domain.HttpResponse;
+import webserver.domain.HttpStatusCode;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+public class DefaultController extends AbstractController {
+    @Override
+    public void doGet(HttpRequest httpRequest, HttpResponse httpResponse) {
+        try {
+            httpResponse.send(HttpStatusCode.OK, FileIoUtils.readStaticHttpFile(httpRequest.getPath()));
+        } catch (URISyntaxException | IOException e) {
+            httpResponse.send(HttpStatusCode.NOT_FOUND);
+        }
+    }
+
+}

리뷰: http://localhost:8080/index.html 로 접속했을 때 정상적인 페이지 호출이 되지 않고 있어요.
(index 뿐만 아니라 전체적으로 웹페이지 응답이 잘못 된 것 같습니다!)
이 부분 확인 부탁드립니다. 그리고 해당 메서드는 Controller의 관심사가 아닌것 같아요! 
유틸성 클래스로 만들어지는 것이 좋을 것 같습니다!
@@ -0,0 +1,32 @@
+package webserver.controller;
+
+import webserver.domain.HttpHeader;
+import webserver.domain.HttpRequest;
+import webserver.domain.HttpResponse;
+import webserver.domain.HttpStatusCode;
+import webserver.exceptions.InvalidRequestException;
+
+import java.io.IOException;
+
+import static service.JwpService.getProfilePage;
+import static service.JwpService.isLogin;
+
+public class ListUserController extends AbstractController {
+    @Override
+    public void doGet(HttpRequest httpRequest, HttpResponse httpResponse) {
+        try {
+            httpResponse.send(HttpStatusCode.OK, getProfilePage());
+        } catch (IOException e) {
+            httpResponse.send(HttpStatusCode.INTERNAL_SERVER_ERROR, "읽어들이는데 문제가 발생하였습니다");
+        }
+    }
+
+    @Override
+    public void validateGetRequest(HttpRequest request, HttpResponse response) throws InvalidRequestException {
+        if (!isLogin(request.getHeaders().get(HttpHeader.COOKIE))) {
+            response.getHeaders().add(HttpHeader.LOCATION, "/user/login.html");
+            response.send(HttpStatusCode.FOUND);
+            throw new InvalidRequestException("잘못된 로그인 요청");
+        }
+    }
+}

리뷰: TemplateView를 만드는 부분도 Controller 보다는 다른 곳에서 사용할 수 있도록 유틸성 클래스로 분리하는게 어떨까요?