Skip to content

Alimov#4

Open
Phoenix190696 wants to merge 3 commits intodemologin:mainfrom
Phoenix190696:alimov
Open

Alimov#4
Phoenix190696 wants to merge 3 commits intodemologin:mainfrom
Phoenix190696:alimov

Conversation

@Phoenix190696
Copy link

No description provided.

@demologin
Copy link
Owner

Общий вывод по проекту

Проект представляет собой классическую реализацию веб-квеста на базе Java Servlets. Автор успешно применил Mockito для тестирования контроллеров и продемонстрировал понимание жизненного цикла HTTP-сессий. Структура проекта логична, а именование классов и переменных в целом соответствует стандартам Java.

Однако, в архитектуре наблюдается сильная связанность (tight coupling) из-за использования статических методов и прямой работы с состоянием в сервлете. Внедрение паттерна "Состояние" и выделение сервисного слоя значительно повысят гибкость и расширяемость приложения. Также стоит уделить внимание безопасности (валидация ввода) и современным практикам логирования.

Итоговая оценка: A (online review)


HttpSession session = req.getSession();
GameState state = (GameState) session.getAttribute("state");
if (state == null) state = GameState.START;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Использование жестко закодированного состояния по умолчанию напрямую в контроллере нарушает инкапсуляцию. Рекомендуется вынести логику инициализации в GameLogic.

if (state == null) state = GameState.START;

String answer = req.getParameter("answer");
GameResult result = GameLogic.next(state, answer);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Контроллер напрямую вызывает статический метод GameLogic.next. Это затрудняет тестирование и нарушает принцип инверсии зависимостей. Стоит использовать внедрение зависимостей.


if (result.state() == GameState.WIN || result.state() == GameState.LOSE) {
Integer gamesPlayed = (Integer) session.getAttribute("gamesPlayed");
session.setAttribute("gamesPlayed", gamesPlayed == null ? 1 : gamesPlayed + 1);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Логика инкремента счетчика игр смешана с логикой обработки HTTP-запроса. Данную операцию следует вынести в отдельный сервисный слой.

if (result.state() == GameState.WIN || result.state() == GameState.LOSE) {
Integer gamesPlayed = (Integer) session.getAttribute("gamesPlayed");
session.setAttribute("gamesPlayed", gamesPlayed == null ? 1 : gamesPlayed + 1);
req.getRequestDispatcher("/WEB-INF/result.jsp").forward(req, resp);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пути к JSP ресурсам ('/WEB-INF/game.jsp') дублируются в коде. Целесообразно определить их как константы, чтобы избежать ошибок при опечатках.


public class GameLogic {

public static GameResult next(GameState current, String answer) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Метод next() содержит сложную структуру switch-case, управляющую переходами состояний. Это классическое нарушение принципа открытости/закрытости (OCP). Рекомендуется использовать паттерн State.

case START -> new GameResult(GameState.UFO_CHALLENGE, null);

case UFO_CHALLENGE -> {
if ("accept".equals(answer)) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Строковые литералы для ответов (например, 'accept', 'go') разбросаны по всему коду. Рекомендуется использовать перечисления (Enum) для возможных действий игрока.

} else {
yield new GameResult(GameState.LOSE, "Ты отклонил вызов. Поражение.");
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Текстовые сообщения для пользователя жестко прописаны в логике. Для поддержки интернационализации (i18n) их следует вынести в ResourceBundle.

@@ -0,0 +1,10 @@
package com.javarush.alimov.quest;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Перечисление GameState содержит как промежуточные состояния, так и терминальные (WIN, LOSE). Стоит рассмотреть разделение логики навигации и статуса завершения.

class GameLogicTest {

@Test
void testVictoryPath() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тест 'testVictoryPath' проверяет сразу всю цепочку игры. В юнит-тестах предпочтительнее проверять каждый переход состояния изолированно.

result = GameLogic.next(result.state(), "truth");

assertEquals(GameState.WIN, result.state());
assertEquals("Тебя вернули домой. Победа!", result.message());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Проверка текстовых сообщений в тестах делает их хрупкими. При изменении формулировки сообщения тесты упадут, хотя логика останется верной.


@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Использование MockitoAnnotations.openMocks(this) считается устаревшим. Рекомендуется использовать аннотацию @ExtendWith(MockitoExtension.class).


@Test
void doPost_StartToUfoChallenge() throws ServletException, IOException {
when(session.getAttribute("state")).thenReturn(null);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тест завязан на внутреннее поведение сессии. Лучше тестировать контракт взаимодействия через сервис, скрывающий детали работы с HttpSession.


import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import jakarta.servlet.*;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Использование wildcard import (jakarta.servlet.http.*) не рекомендуется, так как это затрудняет понимание используемых зависимостей.

protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {

HttpSession session = req.getSession();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Отсутствует логирование действий пользователя или ошибок. Использование SLF4J помогло бы в диагностике проблем на сервере.

@@ -0,0 +1,37 @@
package com.javarush.alimov.quest;

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Класс GameLogic не имеет приватного конструктора, хотя содержит только статические методы. Следует предотвратить создание экземпляров утилитного класса.

GameState state = (GameState) session.getAttribute("state");
if (state == null) state = GameState.START;

String answer = req.getParameter("answer");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Параметр 'answer' получается напрямую из запроса без валидации. Это потенциальная уязвимость, если данные будут использоваться в выводе без экранирования.

session.setAttribute("message", result.message());

if (result.state() == GameState.WIN || result.state() == GameState.LOSE) {
Integer gamesPlayed = (Integer) session.getAttribute("gamesPlayed");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Счетчик игр хранится в сессии как Integer. При конкурентных запросах (хотя здесь это маловероятно) возможны проблемы. Стоит рассмотреть атомарные типы или сервисный слой.


import java.io.IOException;

import static org.mockito.Mockito.*;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Статические импорты Mockito (when, verify) — это хорошая практика, стоит продолжать их использовать для чистоты тестов.

import java.io.IOException;

@WebServlet("/game")
public class GameServlet extends HttpServlet {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Методы doGet и doPost содержат дублирующийся код вызова Dispatcher. Общую логику перехода можно вынести в защищенный метод.


public enum GameState {
START,
UFO_CHALLENGE,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Названия состояний (например, UFO_CHALLENGE) выбраны удачно и соответствуют предметной области квеста.


import static org.junit.jupiter.api.Assertions.*;

class GameLogicTest {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Класс теста не имеет модификатора доступа public, что хорошо (пакетная видимость достаточна для JUnit 5).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants