diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1658787 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target/ +.idea/ +*.iml +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..66180b0 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# Code Quality Review & Refactoring + +## 📌 Опис проєкту +Даний репозиторій містить навчальний Java-проєкт, який було проаналізовано з точки зору: +- відповідності стандартам кодування Java; +- принципів чистого коду (Clean Code); +- результатів статичного аналізу за допомогою плагіну **SonarLint**; +- етики та практик проведення code review в GitHub. + +Проєкт базується на навчальному репозиторії: +https://github.com/goitProjects/java-dev-homework-module-14-code-quality + +--- + +## 🧩 Структура проєкту (до рефакторингу) + +- Весь код розміщений в одному класі `App` +- Використовується default package +- Метод `main` містить повну логіку гри +- Відсутній поділ на доменні сутності та сервіси + +--- + +## 🔍 Аналіз стандартів кодування + +У процесі аналізу було виявлено такі проблеми: + +- Надмірно великий метод `main` (порушення принципу SRP) +- Неінформативні назви змінних (`i`, `rand`, `box`) +- Використання magic numbers (`1`, `2`, `3`, `9`) +- Дублювання коду (логіка перевірки перемоги для `X` та `O`) +- Використання `byte` замість стандартного `int` +- Використання нескінченних циклів `while(true)` +- Відсутність enum для опису стану гри +- Відсутність пакетної структури + +--- + +## 🛠 Аналіз за допомогою SonarLint + +Плагін **SonarLint** виявив наступні проблеми: + +- High Cognitive Complexity +- Code Smells, пов’язані з дублюванням логіки +- Порушення naming conventions +- Методи з надмірною кількістю відповідальностей +- Погану читабельність коду + +SonarLint був використаний як інструмент для виявлення потенційних проблем та підтвердження необхідності рефакторингу. + +--- + +## ✨ Аналіз з точки зору Clean Code + +Порушені принципи: + +- **Single Responsibility Principle** — один клас виконує декілька ролей +- **DRY (Don’t Repeat Yourself)** — дублювання умов перевірки +- **Meaningful Names** — змінні не описують свою суть +- **Small Functions** — методи занадто великі +- **Readability** — код складний для розуміння та підтримки + +--- + +## 🔄 Проведений рефакторинг + +У результаті рефакторингу було виконано: + +- Розділення логіки гри на окремі класи +- Винесення ігрової логіки з методу `main` +- Введення enum для результату гри замість magic numbers +- Усунення дублювання коду +- Покращення назв змінних та методів +- Покращення загальної читабельності та підтримуваності коду +- Підготовка проєкту до подальшого тестування та розширення + +--- + +## 🧪 Code Review (GitHub) + +Було проведено навчальне code review pull request з використанням інструментів GitHub. + +Під час рев’ю особлива увага приділялась: +- дотриманню принципів чистого коду; +- читабельності та структурі коду; +- відсутності дублювання; +- дотриманню етики code review. + +Коментарі формулювались у конструктивній та коректній формі, без персональних оцінок. + +--- + +## 📎 Висновок + +Даний проєкт є хорошим прикладом коду, який працює, але потребує значного покращення з точки зору якості. Використання SonarLint та принципів Clean Code дозволило виявити основні проблеми та провести ефективний рефакторинг, що підвищило читабельність, підтримуваність та масштабованість коду. + +--- + +## 👤 Автор +Навчальна робота в межах курсу Java Developer \ No newline at end of file diff --git a/src/main/java/App.java b/src/main/java/App.java index 4f6ffd2..8dbe014 100644 --- a/src/main/java/App.java +++ b/src/main/java/App.java @@ -2,89 +2,106 @@ public class App { + private static final char PLAYER = 'X'; + private static final char COMPUTER = 'O'; + private static final int BOARD_SIZE = 9; + public static void main(String[] args) { Scanner scan = new Scanner(System.in); - byte input; - byte rand; - byte i; - boolean boxAvailable = false; + char[] box = new char[BOARD_SIZE]; byte winner = 0; - char box[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9' }; + + for (int i = 0; i < BOARD_SIZE; i++) { + box[i] = ' '; + } + System.out.println("Enter box number to select. Enjoy!\n"); - boolean boxEmpty = false; while (true) { - System.out.println("\n\n " + box[0] + " | " + box[1] + " | " + box[2] + " "); - System.out.println("-----------"); - System.out.println(" " + box[3] + " | " + box[4] + " | " + box[5] + " "); - System.out.println("-----------"); - System.out.println(" " + box[6] + " | " + box[7] + " | " + box[8] + " \n"); - if(!boxEmpty){ - for(i = 0; i < 9; i++) - box[i] = ' '; - boxEmpty = true; - } + printBoard(box); - if(winner == 1){ - System.out.println("You won the game!\nCreated by Shreyas Saha. Thanks for playing!"); + if (winner == 1) { + System.out.println("You won the game!"); break; - } else if(winner == 2){ - System.out.println("You lost the game!\nCreated by Shreyas Saha. Thanks for playing!"); + } else if (winner == 2) { + System.out.println("You lost the game!"); break; - } else if(winner == 3){ - System.out.println("It's a draw!\nCreated by Shreyas Saha. Thanks for playing!"); + } else if (winner == 3) { + System.out.println("It's a draw!"); break; } - while (true) { - input = scan.nextByte(); - if (input > 0 && input < 10) { - if (box[input - 1] == 'X' || box[input - 1] == 'O') - System.out.println("That one is already in use. Enter another."); - else { - box[input - 1] = 'X'; - break; - } - } - else - System.out.println("Invalid input. Enter again."); - } + makePlayerMove(scan, box); - if((box[0]=='X' && box[1]=='X' && box[2]=='X') || (box[3]=='X' && box[4]=='X' && box[5]=='X') || (box[6]=='X' && box[7]=='X' && box[8]=='X') || - (box[0]=='X' && box[3]=='X' && box[6]=='X') || (box[1]=='X' && box[4]=='X' && box[7]=='X') || (box[2]=='X' && box[5]=='X' && box[8]=='X') || - (box[0]=='X' && box[4]=='X' && box[8]=='X') || (box[2]=='X' && box[4]=='X' && box[6]=='X')){ - winner = 1; - continue; - } - - boxAvailable = false; - for(i=0; i<9; i++){ - if(box[i] != 'X' && box[i] != 'O'){ - boxAvailable = true; - break; - } + if (hasWinner(box, PLAYER)) { + winner = 1; + continue; } - if(boxAvailable == false){ + if (!hasFreeCells(box)) { winner = 3; continue; } - while (true) { - rand = (byte) (Math.random() * (9 - 1 + 1) + 1); - if (box[rand - 1] != 'X' && box[rand - 1] != 'O') { - box[rand - 1] = 'O'; - break; + makeComputerMove(box); + + if (hasWinner(box, COMPUTER)) { + winner = 2; + } + } + } + + private static void printBoard(char[] box) { + System.out.println("\n " + box[0] + " | " + box[1] + " | " + box[2]); + System.out.println("-----------"); + System.out.println(" " + box[3] + " | " + box[4] + " | " + box[5]); + System.out.println("-----------"); + System.out.println(" " + box[6] + " | " + box[7] + " | " + box[8] + "\n"); + } + + private static void makePlayerMove(Scanner scan, char[] box) { + while (true) { + byte input = scan.nextByte(); + if (input > 0 && input <= BOARD_SIZE) { + if (box[input - 1] == ' ') { + box[input - 1] = PLAYER; + return; + } else { + System.out.println("That one is already in use."); } + } else { + System.out.println("Invalid input."); } + } + } - if((box[0]=='O' && box[1]=='O' && box[2]=='O') || (box[3]=='O' && box[4]=='O' && box[5]=='O') || (box[6]=='O' && box[7]=='O' && box[8]=='O') || - (box[0]=='O' && box[3]=='O' && box[6]=='O') || (box[1]=='O' && box[4]=='O' && box[7]=='O') || (box[2]=='O' && box[5]=='O' && box[8]=='O') || - (box[0]=='O' && box[4]=='O' && box[8]=='O') || (box[2]=='O' && box[4]=='O' && box[6]=='O')){ - winner = 2; - continue; + private static void makeComputerMove(char[] box) { + while (true) { + int rand = (int) (Math.random() * BOARD_SIZE); + if (box[rand] == ' ') { + box[rand] = COMPUTER; + return; } } + } + + private static boolean hasFreeCells(char[] box) { + for (char c : box) { + if (c == ' ') { + return true; + } + } + return false; + } + private static boolean hasWinner(char[] box, char symbol) { + return (box[0] == symbol && box[1] == symbol && box[2] == symbol) + || (box[3] == symbol && box[4] == symbol && box[5] == symbol) + || (box[6] == symbol && box[7] == symbol && box[8] == symbol) + || (box[0] == symbol && box[3] == symbol && box[6] == symbol) + || (box[1] == symbol && box[4] == symbol && box[7] == symbol) + || (box[2] == symbol && box[5] == symbol && box[8] == symbol) + || (box[0] == symbol && box[4] == symbol && box[8] == symbol) + || (box[2] == symbol && box[4] == symbol && box[6] == symbol); } } \ No newline at end of file