3.1 Лабораторийн зорилго
Энэ лабораторид та цэвэр код бичих ур чадварыг бодит жишээнүүд дээр дадлагаар эзэмшинэ:
- Муу кодыг цэвэр код болгон сайжруулах
- Нэрлэлт, функц дизайн, алдаа зохицуулалтыг дадлагажуулах
- Кодын чанарын хэрэгсэл (CheckStyle) ашиглах
- 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;
}
Сайжруулсан зүйлс:
| # | Өмнө | Дараа | Ашигласан зарчим |
|---|---|---|---|
| 1 | Mgr | GameBoardManager | Утга учиртай класс нэр |
| 2 | lst, r, x | cells, flaggedCells, cell | Утга учиртай хувьсагч нэр |
| 3 | getThm() | getFlaggedCells() | Метод зорилгоо илэрхийлэх |
| 4 | calc() | calculateAverage() | Тодорхой метод нэр |
| 5 | chk() | isPasswordStrong() | Boolean конвенци (is + тэмдэг нэр) |
| 6 | 4, 8 | FLAGGED, MIN_PASSWORD_LENGTH | Magic number → Тогтмол |
| 7 | h1, h2, h3 | hasUpperCase, hasLowerCase, hasDigit | Boolean хувьсагчийн нэрлэлт |
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 |
| 2 | Parameter Object | 6 параметр → OrderRequest объект |
| 3 | Magic Number арилгасан | 0.15, 0.05, 100000 → Тогтмол |
| 4 | Return code → Exception | "ERROR:" string → ValidationException |
| 5 | Dependency Injection | PaymentService, 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;
}
}
Сайжруулсан зүйлс:
| # | Зарчим | Тайлбар |
|---|---|---|
| 1 | Optional ашиглах | null шалгалтыг Optional-оор солих |
| 2 | Тодорхой Exception | Exception → StudentNotFoundException |
| 3 | Logger ашиглах | System.out.println → Logger |
| 4 | Guard Clause | divideSafe() — оролтыг эрт шалгах |
| 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 тайлбарууд:
| # | Мөр | Асуудал | Санал |
|---|---|---|---|
| 1 | class u | Муу нэрлэлт | class User — PascalCase, тодорхой нэр |
| 2 | String n, e, p | Нэргүй талбар | name, email, password |
| 3 | int a | Тодорхойгүй | int age |
| 4 | public талбарууд | Encapsulation зөрчил | private + getter/setter |
| 5 | l() метод | Муу нэр | login() |
| 6 | r() метод | Муу нэр | register() |
| 7 | pw.equals(p) | Нууц үг шифрлэлтгүй | Хэзээ ч plain text нууц үг хадгалахгүй |
| 8 | un.equals(n) | NullPointerException | Objects.equals() эсвэл null шалгах |
| 9 | System.out.println | Logger ашиглахгүй | log.info() ашиглах |
| 10 | Validation байхгүй | Оролт шалгаагүй | Email формат, нас зэрэг шалгах |
| 11 | info() муу нэр | Тодорхойгүй | getFormattedInfo() |
| 12 | "," hardcoded | Magic 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; }
}