Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target/
.idea/
*.iml
.vscode/
100 changes: 100 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
135 changes: 76 additions & 59 deletions src/main/java/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}