Skip to content

Instantly share code, notes, and snippets.

@Asenim
Created December 26, 2024 18:47
Show Gist options
  • Save Asenim/004435c6cffec52c00e98418f3730330 to your computer and use it in GitHub Desktop.
Save Asenim/004435c6cffec52c00e98418f3730330 to your computer and use it in GitHub Desktop.

https://github.com/TurtleOnaRock/Gallow

Ревью от Алексея @Raketa4000az
[Alexander]

Игра в ООП стиле. После первого рефакторинга.

Недостатки реализации:

  1. Команды не работают
Хотите сыграть в "Висилицу"? [д]а/[н]ет :Д
Хотите сыграть в "Висилицу"? [д]а/[н]ет :Н
Хотите сыграть в "Висилицу"? [д]а/[н]ет :
  1. Нет списка неправильно введенных букв.

  2. Ненужные неудобства для пользователя. Если программе нужны буквы определенного регистра, то она сама должна переводить буквы в этот регистр, а не создавать юзеру неудобства Ваша буква: Ф В игре используються только русские строчные буквы. Ваша буква:

Хорошо:

  1. Простой понятный алгоритм

Замечания:

  1. Нейминг -UPPER_SNAKE только для констант, а это нет
private final int DEFAULT_VALUE_STAGE = 0;

//ПРАВИЛЬНО ТАК:
private static final int DEFAULT_VALUE_STAGE = 0;

//ИЛИ ТАК:
private final int defaultValueStage = 0;

-КОНСТАНТЫ_ПИШУТСЯ_СТИЛЕМ_UPPER_SNAKE

private static final String[] frames = ...

//ПРАВИЛЬНО:
private static final String[] FRAMES = ...

-Название вводит в заблуждение, это не поиск символа, это ввод символа

class CharFinder

//ПРАВИЛЬНО:
class CharInput

-Название путает на ровном месте

private Scanner in;
//...
this.in = new Scanner(System.in);

//ПРАВИЛЬНО:
private Scanner scanner;
//...
this.scanner = new Scanner(System.in);
--
Oracle Java code conventions, part."Naming conventions"
Мартин, "Чистый код", гл.2
-- 
  1. Нарушение DRY, магические буквы, числа, слова. Вводи константы
{...} while (answer != 'д' && answer != 'н');
return (answer == 'д');

//ПРАВИЛЬНО:
private final static char YES = 'д';
private final static char NO = 'н';

{...} while (answer != YES && answer != NO);
return answer == YES;
  1. Нарушение конвенции кода
for (int i = 0; i < secret.length; i++)
  this.secret[i] = Constants.MASK_SIGN;

//ПРАВИЛЬНО:
for (int i = 0; i < secret.length; i++) {
  this.secret[i] = Constants.MASK_SIGN;
}
  1. Избыточно
if (this.word.charAt(i) == c) {
  result = true;  
  return result;
}

//ПРАВИЛЬНО:
if (this.word.charAt(i) == c) {
  return true;
}
  1. Создавай вспомогательные методы, делай программу более простой и понятной
while (puzzle.isSecret() && man.isAlive()) {...}

//ПРАВИЛЬНО:
while (isИдетИгра()) {...}

private boolean isИдетИгра()() {
  return !isПроиграл() && !isВыиграл();
}

private boolean isПроиграл()() {
  return !man.isAlive();
}

private boolean isВыиграл()() {
  return !puzzle.isSecret();
}
  1. Если в блоке if есть return(break, continue, throw, exit и т.д.), то else не пишется - это не имеет смысла
if (line.length() == 1) {
  return line.charAt(0);
} else {
  System.out.print(Constants.ONE_LETTER_REQUEST);
}

//ПРАВИЛЬНО:
if (line.length() == 1) {
  return line.charAt(0);
}
System.out.print(Constants.ONE_LETTER_REQUEST);
  1. class Puzzle, реализация сущности "замаскированное слово" +В целом норм

-Если в слове отсутствует буква, которую хотим открыть, то метод должен бросать исключение- открывать можно только те буквы, которые есть в слове C

void update(char c)

-Магическое: '_'

  1. class HangedMan, модель висельник +В целом норм

-limit тоже нужно показывать через геттер. Чтобы, например, можно было подсчитать количество оставшихся попыток

  1. class Constants, константы для всех классов -Нарушение low coupling, свалка констант- содержит константы всех классов, т.о. все классы имеют зависимость на этот класс. Класс аннигилировать, константы перенести в те классы, которые их используют.

Например, ONE_LETTER_REQUEST перенести в class CharFinder. Сейчас class CharFinder не может существовать без class Constants. И если например будешь делать игру "Поле чудес", то не сможешь взять туда CharFinder для ввода букв. Потому что без Constants он не работает, а в другой программе будут актуальны другие константы.

  1. class Dictionary +Все ок

  2. class CharFinder, ввод символа пользователем +Все ок, кроме названия

  3. class GallowRender, распечатка картинок висельника -Нарушение DRY, дублирует значение лимита висельника

/* class HangedMan */
private final int DEFAULT_VALUE_LIMIT = 6;

/* class GallowRender */
private static final String LIMIT_MSG = "/6\n";

-Этот класс не должен знать лимит висельника потому что он не должен распечатывать информацию про лимит. Этот класс должен распечатывать только картинки. Сопроводительный текст должен печатать кто-то другой, в данном случае- Game

System.out.print(MISTAKE_MSG + stage + LIMIT_MSG);  //в GallowRender не надо это делать

Либо, в крайнем случае, параметр limit метод должен принимать в аргументы

public class GallowRender {
  //...
  public static void showMistakes(int stage, int limit) {
    System.out.print(MISTAKE_MSG + stage + "/" + limit);
  }
}

-Если класс печатает картинки не только по номеру, но и по состоянию игры(выиграл, проиграл), то эти картинки нужно хранить в трех разных переменных

  public static void showGallow(int stage) {
    System.out.println(frames[stage]);
  }

  public static void showWin() {
    System.out.println(frames[Constants.WIN_STAGE]);
  }

  public static void showLose() {
    System.out.println(frames[Constants.LOSE_STAGE]);
  }

//ПРАВИЛЬНО:
  public static void showGallow(int stage) {
    System.out.println(FRAMES[stage]);
  }

  public static void showWin() {
    System.out.println(PICTURE_WIN_STAGE);
  }

  public static void showLose() {
    System.out.println(PICTURE_LOSE_STAGE);
  }
  1. class Launcher, содержит точку входа main -Никогда не возвращай null
private static Dictionary getDictionary(String pathToFile) {
  //...
  System.out.println(Constants.ERROR_FILE);
  return null;
  //...
}
Возврат null повышает риск возникновения NullPointerException в программе
--
Мартин, "Чистый код", гл.7.7-8
Ютуб, Немчинский "Почему нельзя возвращать NULL?"
--

-Проверяй команды независимо от регистра

{...} while (answer != 'д' && answer != 'н');

//ПРАВИЛЬНО:
private final static char YES = 'д';
//...
{...} while (Character.toLowerCase(answer) != YES ....);

//ЕЩЕ ЛУЧШЕ:
{...} while (!isCommand(YES, answer) && !isCommand(NO, answer));

private static boolean isCommand(char command, char symbol) {
  return Character.toLowerCase(symbol) == command;
}

//ЕЩЕ ЕЩЕ ЛУЧШЕ:
{...} while (!isCommand(answer));

private static boolean isCommand(char symbol) {
  return isCommand(YES, symbol) || isCommand(NO, symbol);
}

private static boolean isCommand(char command, char symbol) {
  return Character.toLowerCase(symbol) == command;
}

-Если кастомный класс может вернуть null, то нужно всегда проверять возвращаемое значение на null. Но однажды это можно забыть сделать и это вызовет NPE, см. "Почему нельзя возвращать NULL?" Поэтому кастомные классы не должны возвращть null. Здесь getDictionary(...) в случае невозможности создать словарь должен либо возвращать Optional, либо бросать исключение

dictionary = getDictionary(Constants.PATH_TO_DICTIONARY);
if (dictionary == null) {...}
  1. class Game +Хорошо, что принимает в конструктор текст слова, а не грузит его сам из Словаря- соблюдение DI
public Game (String word){...}

-Отрицательные условия воспринимаются хуже положительных

private static boolean isNotRussian(char letter) {
  return !(((letter >= 'а') && (letter <= 'я')) || letter == 'ё');
}

//ПРАВИЛЬНО:
private static boolean isRussian(char letter) {
  letter = Character.toLowerCase(letter);  //большие русские буквы- они тоже русские
  return (letter >= 'а' && letter <= 'я') || letter == 'ё';
}

Вывод: стало значительно лучше. Можно пробовать Симуляцию.

#ревью #виселица

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