ЛАБ 06

Кодын Чанар ба Цэвэр Код

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

Энэ лабораторид та цэвэр код бичих ур чадварыг бодит жишээнүүд дээр дадлагаар эзэмшинэ:

  1. Муу кодыг цэвэр код болгон сайжруулах
  2. Нэрлэлт, функц дизайн, алдаа зохицуулалтыг дадлагажуулах
  3. Кодын чанарын хэрэгсэл (CheckStyle) ашиглах
  4. Code Review хийх

Хэл: Java 17+ | IDE: Eclipse IDE


3.2 Лаб 1: Муу нэрлэлтийг засварлах

Даалгавар: Дараах кодын нэрлэлтийг цэвэр код зарчмаар сайжруулах

ӨМНӨ (Муу нэрлэлт):

public class Mgr {
    private List<int[]> lst = new ArrayList<>();

    public List<int[]> getThm() {
        List<int[]> r = new ArrayList<>();
        for (int[] x : lst) {
            if (x[0] == 4) {
                r.add(x);
            }
        }
        return r;
    }

    public double calc(List<Map<String, Object>> d) {
        double t = 0;
        int c = 0;
        for (Map<String, Object> m : d) {
            double v = (Double) m.get("v");
            t += v;
            c++;
        }
        return c > 0 ? t / c : 0;
    }

    public boolean chk(String s) {
        if (s == null) return false;
        if (s.length() < 8) return false;
        boolean h1 = false, h2 = false, h3 = false;
        for (char c : s.toCharArray()) {
            if (Character.isUpperCase(c)) h1 = true;
            if (Character.isLowerCase(c)) h2 = true;
            if (Character.isDigit(c)) h3 = true;
        }
        return h1 && h2 && h3;
    }
}

ДАРАА (Цэвэр нэрлэлт):

public class GameBoardManager {
    private List<int[]> cells = new ArrayList<>();

    public List<int[]> getFlaggedCells() {
        List<int[]> flaggedCells = new ArrayList<>();
        for (int[] cell : cells) {
            if (cell[STATUS_INDEX] == FLAGGED) {
                flaggedCells.add(cell);
            }
        }
        return flaggedCells;
    }

    public double calculateAverage(List<Map<String, Object>> dataPoints) {
        double totalValue = 0;
        int count = 0;
        for (Map<String, Object> dataPoint : dataPoints) {
            double value = (Double) dataPoint.get("value");
            totalValue += value;
            count++;
        }
        return count > 0 ? totalValue / count : 0;
    }

    public boolean isPasswordStrong(String password) {
        if (password == null) return false;
        if (password.length() < MIN_PASSWORD_LENGTH) return false;

        boolean hasUpperCase = false;
        boolean hasLowerCase = false;
        boolean hasDigit = false;

        for (char character : password.toCharArray()) {
            if (Character.isUpperCase(character)) hasUpperCase = true;
            if (Character.isLowerCase(character)) hasLowerCase = true;
            if (Character.isDigit(character)) hasDigit = true;
        }
        return hasUpperCase && hasLowerCase && hasDigit;
    }

    private static final int STATUS_INDEX = 0;
    private static final int FLAGGED = 4;
    private static final int MIN_PASSWORD_LENGTH = 8;
}

Сайжруулсан зүйлс:

#ӨмнөДарааАшигласан зарчим
1MgrGameBoardManagerУтга учиртай класс нэр
2lst, r, xcells, flaggedCells, cellУтга учиртай хувьсагч нэр
3getThm()getFlaggedCells()Метод зорилгоо илэрхийлэх
4calc()calculateAverage()Тодорхой метод нэр
5chk()isPasswordStrong()Boolean конвенци (is + тэмдэг нэр)
64, 8FLAGGED, MIN_PASSWORD_LENGTHMagic number → Тогтмол
7h1, h2, h3hasUpperCase, hasLowerCase, hasDigitBoolean хувьсагчийн нэрлэлт

3.3 Лаб 2: Функц дизайн — Нэг функц, нэг зүйл

Даалгавар: Дараах том функцыг жижиг, нэг зорилготой функцүүдэд хуваах

ӨМНӨ (Нэг том функц):

public class OrderProcessor {

    public String processOrder(String customerName, String email,
                               List<String> items, List<Double> prices,
                               String cardNumber, boolean isVIP) {
        // 1. Шалгах
        if (customerName == null || customerName.isEmpty()) {
            return "ERROR: Хэрэглэгчийн нэр хоосон";
        }
        if (email == null || !email.contains("@")) {
            return "ERROR: Имэйл буруу";
        }
        if (items.size() != prices.size()) {
            return "ERROR: Бүтээгдэхүүн ба үнэ тохирохгүй";
        }
        if (items.isEmpty()) {
            return "ERROR: Сагс хоосон";
        }

        // 2. Үнэ тооцоолох
        double total = 0;
        for (int i = 0; i < items.size(); i++) {
            total += prices.get(i);
        }

        // 3. Хөнгөлөлт
        double discount = 0;
        if (isVIP) {
            discount = total * 0.15;
        } else if (total > 100000) {
            discount = total * 0.05;
        }
        total = total - discount;

        // 4. Татвар
        double tax = total * 0.1;
        total = total + tax;

        // 5. Төлбөр
        if (cardNumber == null || cardNumber.length() != 16) {
            return "ERROR: Картын дугаар буруу";
        }
        System.out.println("Төлбөр боловсруулж байна: " + cardNumber);

        // 6. Баримт
        StringBuilder receipt = new StringBuilder();
        receipt.append("=== БАРИМТ ===\n");
        receipt.append("Хэрэглэгч: ").append(customerName).append("\n");
        for (int i = 0; i < items.size(); i++) {
            receipt.append(items.get(i)).append(": ")
                   .append(prices.get(i)).append("₮\n");
        }
        receipt.append("Хөнгөлөлт: -").append(discount).append("₮\n");
        receipt.append("Татвар: +").append(tax).append("₮\n");
        receipt.append("Нийт: ").append(total).append("₮\n");

        // 7. Имэйл илгээх
        System.out.println(email + " руу баримт илгээсэн");

        return receipt.toString();
    }
}

ДАРАА (Цэвэр функц дизайн):

public class OrderProcessor {

    private static final double VIP_DISCOUNT_RATE = 0.15;
    private static final double BULK_DISCOUNT_RATE = 0.05;
    private static final double BULK_THRESHOLD = 100_000;
    private static final double TAX_RATE = 0.10;
    private static final int CARD_NUMBER_LENGTH = 16;

    private final PaymentService paymentService;
    private final EmailService emailService;

    public OrderProcessor(PaymentService paymentService, EmailService emailService) {
        this.paymentService = paymentService;
        this.emailService = emailService;
    }

    public OrderResult processOrder(OrderRequest request) {
        validateOrder(request);
        OrderSummary summary = calculateOrderSummary(request);
        paymentService.charge(request.getCardNumber(), summary.getTotal());
        String receipt = generateReceipt(request, summary);
        emailService.sendReceipt(request.getEmail(), receipt);
        return new OrderResult(true, receipt);
    }

    private void validateOrder(OrderRequest request) {
        if (request.getCustomerName() == null || request.getCustomerName().isEmpty()) {
            throw new ValidationException("Хэрэглэгчийн нэр хоосон");
        }
        if (request.getEmail() == null || !request.getEmail().contains("@")) {
            throw new ValidationException("Имэйл буруу");
        }
        if (request.getItems().isEmpty()) {
            throw new ValidationException("Сагс хоосон");
        }
        if (request.getCardNumber() == null ||
            request.getCardNumber().length() != CARD_NUMBER_LENGTH) {
            throw new ValidationException("Картын дугаар буруу");
        }
    }

    private OrderSummary calculateOrderSummary(OrderRequest request) {
        double subtotal = calculateSubtotal(request.getItems());
        double discount = calculateDiscount(subtotal, request.isVIP());
        double taxableAmount = subtotal - discount;
        double tax = taxableAmount * TAX_RATE;
        double total = taxableAmount + tax;
        return new OrderSummary(subtotal, discount, tax, total);
    }

    private double calculateSubtotal(List<OrderItem> items) {
        return items.stream()
            .mapToDouble(OrderItem::getPrice)
            .sum();
    }

    private double calculateDiscount(double subtotal, boolean isVIP) {
        if (isVIP) return subtotal * VIP_DISCOUNT_RATE;
        if (subtotal > BULK_THRESHOLD) return subtotal * BULK_DISCOUNT_RATE;
        return 0;
    }

    private String generateReceipt(OrderRequest request, OrderSummary summary) {
        StringBuilder receipt = new StringBuilder();
        receipt.append("=== БАРИМТ ===\n");
        receipt.append("Хэрэглэгч: ").append(request.getCustomerName()).append("\n");
        for (OrderItem item : request.getItems()) {
            receipt.append(item.getName()).append(": ")
                   .append(String.format("%.0f₮", item.getPrice())).append("\n");
        }
        receipt.append(String.format("Хөнгөлөлт: -%.0f₮\n", summary.getDiscount()));
        receipt.append(String.format("Татвар: +%.0f₮\n", summary.getTax()));
        receipt.append(String.format("Нийт: %.0f₮\n", summary.getTotal()));
        return receipt.toString();
    }
}

Сайжруулсан зүйлс:

#ЗарчимТайлбар
1Нэг функц — Нэг зүйлprocessOrder → validate, calculate, pay, receipt, email
2Parameter Object6 параметр → OrderRequest объект
3Magic Number арилгасан0.15, 0.05, 100000 → Тогтмол
4Return code → Exception"ERROR:" string → ValidationException
5Dependency InjectionPaymentService, EmailService тусгаарлагдсан

3.4 Лаб 3: Алдаа зохицуулалт сайжруулах

Даалгавар: Null шалгалт, Exception зохицуулалтыг Clean Code зарчмаар сайжруулах

ӨМНӨ (Муу алдаа зохицуулалт):

public class StudentService {

    public String getStudentInfo(long id) {
        try {
            Student student = repository.findById(id);
            if (student == null) {
                return null;
            }
            String name = student.getName();
            if (name == null) {
                name = "Unknown";
            }
            Address addr = student.getAddress();
            String city = "";
            if (addr != null) {
                city = addr.getCity();
                if (city == null) {
                    city = "Unknown";
                }
            }
            return name + " - " + city;
        } catch (Exception e) {
            System.out.println("error");
            return null;
        }
    }

    public int divide(int a, int b) {
        return a / b;  // ArithmeticException if b == 0
    }
}

ДАРАА (Цэвэр алдаа зохицуулалт):

public class StudentService {

    private static final Logger log = LoggerFactory.getLogger(StudentService.class);

    public StudentInfo getStudentInfo(long id) {
        Student student = findStudentOrThrow(id);
        String name = student.getNameOrDefault("Нэргүй");
        String city = extractCity(student).orElse("Хотгүй");
        return new StudentInfo(name, city);
    }

    private Student findStudentOrThrow(long id) {
        return repository.findById(id)
            .orElseThrow(() -> {
                log.warn("Оюутан олдсонгүй: id={}", id);
                return new StudentNotFoundException("ID=" + id + " оюутан олдсонгүй");
            });
    }

    private Optional<String> extractCity(Student student) {
        return Optional.ofNullable(student.getAddress())
            .map(Address::getCity);
    }

    public int divideSafe(int dividend, int divisor) {
        if (divisor == 0) {
            throw new IllegalArgumentException(
                "Хуваагч 0 байж болохгүй. Хуваагдагч: " + dividend
            );
        }
        return dividend / divisor;
    }
}

Сайжруулсан зүйлс:

#ЗарчимТайлбар
1Optional ашиглахnull шалгалтыг Optional-оор солих
2Тодорхой ExceptionExceptionStudentNotFoundException
3Logger ашиглахSystem.out.println → Logger
4Guard ClausedivideSafe() — оролтыг эрт шалгах
5Утга учиртай мессеж"error" → "ID=1 оюутан олдсонгүй"

3.5 Лаб 4: Code Review дадлага

Даалгавар: Дараах кодыг Code Review хийж, асуудлуудыг олж, засварын санал бичих

Шалгах код:

public class u {
    String n;
    String e;
    int a;
    String p;

    public boolean l(String un, String pw) {
        if (un.equals(n) && pw.equals(p)) {
            System.out.println("ok");
            return true;
        } else {
            System.out.println("fail");
            return false;
        }
    }

    public void r(String n, String e, int a, String p) {
        this.n = n;
        this.e = e;
        this.a = a;
        this.p = p;
    }

    public String info() {
        return n + "," + e + "," + a;
    }
}

Code Review тайлбарууд:

#МөрАсуудалСанал
1class uМуу нэрлэлтclass User — PascalCase, тодорхой нэр
2String n, e, pНэргүй талбарname, email, password
3int aТодорхойгүйint age
4public талбаруудEncapsulation зөрчилprivate + getter/setter
5l() методМуу нэрlogin()
6r() методМуу нэрregister()
7pw.equals(p)Нууц үг шифрлэлтгүйХэзээ ч plain text нууц үг хадгалахгүй
8un.equals(n)NullPointerExceptionObjects.equals() эсвэл null шалгах
9System.out.printlnLogger ашиглахгүйlog.info() ашиглах
10Validation байхгүйОролт шалгаагүйEmail формат, нас зэрэг шалгах
11info() муу нэрТодорхойгүйgetFormattedInfo()
12"," hardcodedMagic stringТогтмол ашиглах

Засварласан код:

public class User {
    private String name;
    private String email;
    private int age;
    private String passwordHash;

    private static final Logger log = LoggerFactory.getLogger(User.class);

    public boolean login(String inputName, String inputPassword) {
        Objects.requireNonNull(inputName, "Нэр null байж болохгүй");
        Objects.requireNonNull(inputPassword, "Нууц үг null байж болохгүй");

        boolean isValid = inputName.equals(this.name)
            && PasswordEncoder.matches(inputPassword, this.passwordHash);

        if (isValid) {
            log.info("Амжилттай нэвтэрлэв: {}", inputName);
        } else {
            log.warn("Нэвтрэлт амжилтгүй: {}", inputName);
        }
        return isValid;
    }

    public void register(String name, String email, int age, String password) {
        validateRegistration(name, email, age, password);
        this.name = name;
        this.email = email;
        this.age = age;
        this.passwordHash = PasswordEncoder.encode(password);
        log.info("Шинэ хэрэглэгч бүртгэгдлээ: {}", name);
    }

    private void validateRegistration(String name, String email, int age, String password) {
        if (name == null || name.isBlank()) {
            throw new ValidationException("Нэр хоосон байж болохгүй");
        }
        if (email == null || !email.contains("@")) {
            throw new ValidationException("Имэйл буруу формат");
        }
        if (age < 1 || age > 150) {
            throw new ValidationException("Нас 1-150 хооронд байх ёстой");
        }
        if (password == null || password.length() < 8) {
            throw new ValidationException("Нууц үг 8+ тэмдэгттэй байх ёстой");
        }
    }

    public String getFormattedInfo() {
        return String.format("%s (%s, %d нас)", name, email, age);
    }

    // Getter методууд
    public String getName() { return name; }
    public String getEmail() { return email; }
    public int getAge() { return age; }
}