ЛАБ 04

Кодын Дахин Засварлалт

3.1 Лабораторийн зорилго

Энэ лабораторид та кодын code smell таних, refactoring техник хэрэглэх чадвар эзэмшинэ:

  1. Long Method → Extract Method
  2. Magic Number, Rename → Цэвэр код
  3. Duplicate Code → Нийтлэг метод гаргах
  4. Replace Conditional with Polymorphism

Шаардлагатай зүйлс: Java JDK 17+, Eclipse IDE

3.2 Лаб 1: Extract Method — Оюутны GPA тооцоолох

Даалгавар: Дараах "муу" кодыг 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Тайлбар
1Long MethodНэг метод дотор GPA тооцох, дүн тодорхойлох, хэвлэх бүгд байна
2Magic Number90, 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 MethodcalculateGPA(), determineGrade(), isPassing(), printReport()
Replace Magic Number90GRADE_A_THRESHOLD, 60PASSING_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Тайлбар
1Duplicate Code3 метод бараг ИЖИЛ, зөвхөн discount rate өөр
2Magic Number0.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 MethodcalculateSubtotal(), calculateTax(), calculateDiscount()
Replace Magic Number0.1TAX_RATE, discount rate-ууд → enum
Parameterize Method3 ижил метод → 1 параметрчилсэн метод
Replace Type Code with EnumDiscount 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 Polymorphismswitch → Shape интерфейс + дэд класс
Extract ClassCircle, 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Тайлбар
1Long MethoddoStuff() нэг метод бүх зүйл хийж байна
2Primitive ObsessionString[] ашиглаж, Book объект үүсгээгүй
3Magic String"add", "find", "borrow", "true", "false"
4Нэрлэлт мууdoStuff, t, a, y, b
5God 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Тайлбар
1Extract ClassString[]Book класс
2Extract MethoddoStuff()addBook(), findBooks(), borrowBook(), returnBook()
3RenamedoStuff → тодорхой нэрүүд, ttitle, bbook
4Replace Magic String"true"/"false"boolean available
5Encapsulate FieldList<String[]> booksprivate List<Book> books
6Move MethodmatchesTitle(), toString()Book класс руу