3.1 Лабораторийн зорилго
Энэ лабораторид та кодын code smell таних, refactoring техник хэрэглэх чадвар эзэмшинэ:
- Long Method → Extract Method
- Magic Number, Rename → Цэвэр код
- Duplicate Code → Нийтлэг метод гаргах
- Replace Conditional with Polymorphism
Шаардлагатай зүйлс: Java JDK 17+, Eclipse IDE
Даалгавар: Дараах "муу" кодыг refactor хийх.
Алхам 1: Refactoring хийхийн ӨМНӨХ код:
class StudentReportCard {
// ❌ Long Method — хэт урт, олон зүйл хийж байна
public void generateReport(String name, int[] scores) {
// GPA тооцоолох
double total = 0;
for (int score : scores) {
total += score;
}
double gpa = total / scores.length;
// Үсгэн дүн тодорхойлох
String grade;
if (gpa >= 90) grade = "A";
else if (gpa >= 80) grade = "B";
else if (gpa >= 70) grade = "C";
else if (gpa >= 60) grade = "D";
else grade = "F";
// Тэнцсэн эсэх
boolean passed = gpa >= 60;
// Тайлан хэвлэх
System.out.println("================================");
System.out.println("ОЮУТНЫ ТАЙЛАН");
System.out.println("================================");
System.out.println("Нэр: " + name);
System.out.println("Дундаж дүн: " + String.format("%.1f", gpa));
System.out.println("Үсгэн дүн: " + grade);
System.out.println("Тэнцсэн: " + (passed ? "Тийм" : "Үгүй"));
System.out.println("================================");
}
}
Алхам 2: Code Smell тодорхойлох:
| # | Code Smell | Тайлбар |
|---|
| 1 | Long Method | Нэг метод дотор GPA тооцох, дүн тодорхойлох, хэвлэх бүгд байна |
| 2 | Magic Number | 90, 80, 70, 60 — хатуу утгууд |
| 3 | Нэг метод олон үүрэгтэй | Тооцоолол + Дүн + Хэвлэх = 3 үүрэг |
Алхам 3: Refactoring хийсний ДАРААХ код:
class StudentReportCard {
private static final int GRADE_A_THRESHOLD = 90;
private static final int GRADE_B_THRESHOLD = 80;
private static final int GRADE_C_THRESHOLD = 70;
private static final int PASSING_THRESHOLD = 60;
public void generateReport(String name, int[] scores) {
double gpa = calculateGPA(scores);
String grade = determineGrade(gpa);
boolean passed = isPassing(gpa);
printReport(name, gpa, grade, passed);
}
private double calculateGPA(int[] scores) {
double total = 0;
for (int score : scores) {
total += score;
}
return total / scores.length;
}
private String determineGrade(double gpa) {
if (gpa >= GRADE_A_THRESHOLD) return "A";
if (gpa >= GRADE_B_THRESHOLD) return "B";
if (gpa >= GRADE_C_THRESHOLD) return "C";
if (gpa >= PASSING_THRESHOLD) return "D";
return "F";
}
private boolean isPassing(double gpa) {
return gpa >= PASSING_THRESHOLD;
}
private void printReport(String name, double gpa, String grade, boolean passed) {
System.out.println("================================");
System.out.println("ОЮУТНЫ ТАЙЛАН");
System.out.println("================================");
System.out.println("Нэр: " + name);
System.out.println("Дундаж дүн: " + String.format("%.1f", gpa));
System.out.println("Үсгэн дүн: " + grade);
System.out.println("Тэнцсэн: " + (passed ? "Тийм" : "Үгүй"));
System.out.println("================================");
}
}
Алхам 4: Хийсэн refactoring-ийг тодорхойлох:
| Refactoring | Хийсэн зүйл |
|---|
| Extract Method | calculateGPA(), determineGrade(), isPassing(), printReport() |
| Replace Magic Number | 90 → GRADE_A_THRESHOLD, 60 → PASSING_THRESHOLD |
3.3 Лаб 2: Duplicate Code арилгах — Хөнгөлөлт тооцоолох
Алхам 1: ӨМНӨХ код (Duplicate Code smell):
class DiscountCalculator {
// ❌ Duplicate Code — ижил логик 3 газар давтагдаж байна
public double calculateStudentDiscount(double price, int quantity) {
double subtotal = price * quantity;
double tax = subtotal * 0.1;
double discount = subtotal * 0.2; // Оюутан 20%
double total = subtotal + tax - discount;
if (total < 0) total = 0;
return total;
}
public double calculateTeacherDiscount(double price, int quantity) {
double subtotal = price * quantity;
double tax = subtotal * 0.1;
double discount = subtotal * 0.15; // Багш 15%
double total = subtotal + tax - discount;
if (total < 0) total = 0;
return total;
}
public double calculateVIPDiscount(double price, int quantity) {
double subtotal = price * quantity;
double tax = subtotal * 0.1;
double discount = subtotal * 0.25; // VIP 25%
double total = subtotal + tax - discount;
if (total < 0) total = 0;
return total;
}
}
Алхам 2: Code Smell тодорхойлох:
| # | Code Smell | Тайлбар |
|---|
| 1 | Duplicate Code | 3 метод бараг ИЖИЛ, зөвхөн discount rate өөр |
| 2 | Magic Number | 0.1, 0.2, 0.15, 0.25 хатуу утгууд |
Алхам 3: ДАРААХ код (Refactored):
class DiscountCalculator {
private static final double TAX_RATE = 0.1;
enum CustomerType {
STUDENT(0.2),
TEACHER(0.15),
VIP(0.25);
private final double discountRate;
CustomerType(double discountRate) { this.discountRate = discountRate; }
public double getDiscountRate() { return discountRate; }
}
public double calculateTotal(double price, int quantity, CustomerType customerType) {
double subtotal = calculateSubtotal(price, quantity);
double tax = calculateTax(subtotal);
double discount = calculateDiscount(subtotal, customerType.getDiscountRate());
return Math.max(0, subtotal + tax - discount);
}
private double calculateSubtotal(double price, int quantity) {
return price * quantity;
}
private double calculateTax(double amount) {
return amount * TAX_RATE;
}
private double calculateDiscount(double amount, double rate) {
return amount * rate;
}
}
Алхам 4: Хийсэн refactoring:
| Refactoring | Хийсэн зүйл |
|---|
| Extract Method | calculateSubtotal(), calculateTax(), calculateDiscount() |
| Replace Magic Number | 0.1 → TAX_RATE, discount rate-ууд → enum |
| Parameterize Method | 3 ижил метод → 1 параметрчилсэн метод |
| Replace Type Code with Enum | Discount rate → CustomerType enum |
3.4 Лаб 3: Replace Conditional with Polymorphism — Дүрс
Алхам 1: ӨМНӨХ код (Switch statement smell):
class ShapeCalculator {
// ❌ Switch smell — шинэ дүрс нэмэх бүрд энд засвар хийх шаардлагатай
public double calculateArea(String type, double... params) {
switch (type) {
case "circle":
return Math.PI * params[0] * params[0];
case "rectangle":
return params[0] * params[1];
case "triangle":
return 0.5 * params[0] * params[1];
default:
throw new IllegalArgumentException("Үл мэдэгдэх дүрс: " + type);
}
}
public double calculatePerimeter(String type, double... params) {
switch (type) {
case "circle":
return 2 * Math.PI * params[0];
case "rectangle":
return 2 * (params[0] + params[1]);
case "triangle":
return params[0] + params[1] + params[2];
default:
throw new IllegalArgumentException("Үл мэдэгдэх дүрс: " + type);
}
}
}
Алхам 2: ДАРААХ код (Polymorphism ашигласан):
// Интерфейс
interface Shape {
double calculateArea();
double calculatePerimeter();
String getName();
}
// Тойрог
class Circle implements Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
@Override
public double calculatePerimeter() {
return 2 * Math.PI * radius;
}
@Override
public String getName() { return "Тойрог"; }
}
// Тэгш өнцөгт
class Rectangle implements Shape {
private final double width;
private final double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double calculateArea() { return width * height; }
@Override
public double calculatePerimeter() { return 2 * (width + height); }
@Override
public String getName() { return "Тэгш өнцөгт"; }
}
// Гурвалжин
class Triangle implements Shape {
private final double base;
private final double height;
private final double sideA, sideB, sideC;
public Triangle(double base, double height, double sideA, double sideB, double sideC) {
this.base = base; this.height = height;
this.sideA = sideA; this.sideB = sideB; this.sideC = sideC;
}
@Override
public double calculateArea() { return 0.5 * base * height; }
@Override
public double calculatePerimeter() { return sideA + sideB + sideC; }
@Override
public String getName() { return "Гурвалжин"; }
}
// Хэрэглээ
class ShapeReport {
public void printReport(List<Shape> shapes) {
for (Shape shape : shapes) {
System.out.println(shape.getName() + ":");
System.out.printf(" Талбай: %.2f%n", shape.calculateArea());
System.out.printf(" Периметр: %.2f%n", shape.calculatePerimeter());
}
}
}
Алхам 3: Хийсэн refactoring:
| Refactoring | Хийсэн зүйл |
|---|
| Replace Conditional with Polymorphism | switch → Shape интерфейс + дэд класс |
| Extract Class | Circle, Rectangle, Triangle тусдаа класс |
| Open/Closed Principle | Шинэ дүрс нэмэхэд зөвхөн шинэ класс нэмнэ, хуучин код өөрчлөхгүй |
3.5 Лаб 4: Бүтэн Refactoring дасгал — Номын сан
Алхам 1: ӨМНӨХ код (олон smell):
class Library {
List<String[]> books = new ArrayList<>(); // [title, author, year, available]
public void doStuff(String action, String t, String a, String y) {
if (action.equals("add")) {
books.add(new String[]{t, a, y, "true"});
System.out.println("Нэмэгдлээ: " + t);
} else if (action.equals("find")) {
for (String[] b : books) {
if (b[0].toLowerCase().contains(t.toLowerCase())) {
System.out.println(b[0] + " - " + b[1] + " (" + b[2] + ") " +
(b[3].equals("true") ? "Бэлэн" : "Зээлсэн"));
}
}
} else if (action.equals("borrow")) {
for (String[] b : books) {
if (b[0].equals(t) && b[3].equals("true")) {
b[3] = "false";
System.out.println("Зээллээ: " + t);
return;
}
}
System.out.println("Олдсонгүй эсвэл зээлсэн байна");
} else if (action.equals("return")) {
for (String[] b : books) {
if (b[0].equals(t) && b[3].equals("false")) {
b[3] = "true";
System.out.println("Буцааллаа: " + t);
return;
}
}
}
}
}
Алхам 2: Code Smell жагсаалт:
| # | Code Smell | Тайлбар |
|---|
| 1 | Long Method | doStuff() нэг метод бүх зүйл хийж байна |
| 2 | Primitive Obsession | String[] ашиглаж, Book объект үүсгээгүй |
| 3 | Magic String | "add", "find", "borrow", "true", "false" |
| 4 | Нэрлэлт муу | doStuff, t, a, y, b |
| 5 | God Method | Нэг метод бүх логик агуулж байна |
Алхам 3: ДАРААХ код (бүрэн refactored):
class Book {
private String title;
private String author;
private int year;
private boolean available;
public Book(String title, String author, int year) {
this.title = title;
this.author = author;
this.year = year;
this.available = true;
}
public boolean isAvailable() { return available; }
public void markAsBorrowed() { this.available = false; }
public void markAsReturned() { this.available = true; }
public String getTitle() { return title; }
public String getAuthor() { return author; }
public int getYear() { return year; }
public boolean matchesTitle(String keyword) {
return title.toLowerCase().contains(keyword.toLowerCase());
}
@Override
public String toString() {
String status = available ? "Бэлэн" : "Зээлсэн";
return title + " - " + author + " (" + year + ") [" + status + "]";
}
}
class Library {
private final List<Book> books = new ArrayList<>();
public void addBook(String title, String author, int year) {
Book book = new Book(title, author, year);
books.add(book);
System.out.println("Нэмэгдлээ: " + book.getTitle());
}
public List<Book> findBooks(String keyword) {
List<Book> results = new ArrayList<>();
for (Book book : books) {
if (book.matchesTitle(keyword)) {
results.add(book);
}
}
return results;
}
public boolean borrowBook(String title) {
for (Book book : books) {
if (book.getTitle().equals(title) && book.isAvailable()) {
book.markAsBorrowed();
System.out.println("Зээллээ: " + title);
return true;
}
}
System.out.println("Олдсонгүй эсвэл зээлсэн байна: " + title);
return false;
}
public boolean returnBook(String title) {
for (Book book : books) {
if (book.getTitle().equals(title) && !book.isAvailable()) {
book.markAsReturned();
System.out.println("Буцааллаа: " + title);
return true;
}
}
System.out.println("Олдсонгүй: " + title);
return false;
}
public void printSearchResults(String keyword) {
List<Book> results = findBooks(keyword);
if (results.isEmpty()) {
System.out.println("Олдсонгүй: " + keyword);
} else {
results.forEach(System.out::println);
}
}
}
Алхам 4: Хийсэн refactoring жагсаалт:
| # | Refactoring | Тайлбар |
|---|
| 1 | Extract Class | String[] → Book класс |
| 2 | Extract Method | doStuff() → addBook(), findBooks(), borrowBook(), returnBook() |
| 3 | Rename | doStuff → тодорхой нэрүүд, t → title, b → book |
| 4 | Replace Magic String | "true"/"false" → boolean available |
| 5 | Encapsulate Field | List<String[]> books → private List<Book> books |
| 6 | Move Method | matchesTitle(), toString() → Book класс руу |