Dùng AI Tiết Kiệm Giờ Cho Dev: Giải Thích Chi Tiết Kèm Ví Dụ

Bài này có bản video ngắn:TikTok

1. Tổng quan

Bài này giải quyết một câu hỏi rất thực tế: làm sao dùng AI (cụ thể là LLM như GPT, Claude) để tiết kiệm thời gian trong công việc code hàng ngày, mà không biến bạn thành người “copy-paste mù”. Bạn sẽ thấy cách tích hợp LLM vào ứng dụng Spring Boot bằng Spring AI, cách dùng AI để sinh test, sinh Javadoc, và những cạm bẫy cần tránh.

Đọc xong, bạn sẽ tự viết được một ChatClient gọi LLM trong Spring Boot, biết cách viết prompt cho từng tác vụ (sinh code, review, viết test), và biết khi nào AI giúp được, khi nào AI chỉ làm bạn chậm hơn.

2. AI tiết kiệm giờ ở đâu trong công việc dev

Không phải chỗ nào AI cũng nhanh hơn bạn tự làm. Ba nhóm việc AI thực sự đáng dùng:

  • Việc lặp lại, có pattern rõ: sinh DTO từ entity, sinh getter/setter (dù IDE đã làm được), sinh boilerplate test case.
  • Việc cần tổng hợp thông tin nhanh: đọc stacktrace dài, tóm tắt PR, giải thích đoạn code legacy không có comment.
  • Việc cần bản nháp đầu tiên: viết Javadoc, viết README, viết câu truy vấn SQL phức tạp để bạn sửa lại.

Ngược lại, AI không tiết kiệm giờ khi: bạn chưa hiểu rõ vấn đề (prompt mơ hồ ra kết quả mơ hồ), logic nghiệp vụ phức tạp đặc thù công ty, hoặc code cần độ chính xác cao về bảo mật/tài chính mà bạn không review kỹ.

Lưu ý: AI sinh code nhanh nhưng review chậm thì tổng thời gian không giảm, đôi khi còn tăng vì bạn phải debug code “trông đúng nhưng sai logic”.

3. Tích hợp LLM vào Spring Boot với Spring AI

Thay vì gọi REST API thủ công bằng RestTemplate, bạn dùng Spring AI để có ChatClient tiện lợi.

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

Khai báo API key trong application.properties:

spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4o-mini

Tạo một service wrap ChatClient:

@Service
public class CodeAssistantService {

    private final ChatClient chatClient;

    public CodeAssistantService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public String explainStackTrace(String stackTrace) {
        String prompt = """
                Bạn là senior Java engineer. Giải thích ngắn gọn nguyên nhân
                của stacktrace sau và đề xuất cách fix bằng tiếng Việt:
                %s
                """.formatted(stackTrace);

        return chatClient.prompt()
                .user(prompt)
                .call()
                .content();
    }
}
  • ChatClient.Builder được Spring Boot auto-config sẵn nhờ starter ở trên, bạn chỉ cần inject.
  • prompt().user(...).call().content() gửi prompt và lấy về chuỗi text trả lời, không cần parse JSON thủ công.
  • Phương thức explainStackTrace nhận một đoạn stacktrace dài, trả về giải thích ngắn — việc này tiết kiệm 5–10 phút đọc log mỗi lần debug lỗi lạ.

Gọi thử trong controller:

@RestController
@RequestMapping("/api/assist")
public class CodeAssistantController {

    private final CodeAssistantService assistantService;

    public CodeAssistantController(CodeAssistantService assistantService) {
        this.assistantService = assistantService;
    }

    @PostMapping("/explain")
    public String explain(@RequestBody String stackTrace) {
        return assistantService.explainStackTrace(stackTrace);
    }
}

Kết quả gọi POST /api/assist/explain với body là stacktrace NullPointerException sẽ trả về đoạn text giải thích:

Lỗi NullPointerException xảy ra vì biến `customer` chưa được khởi tạo
trước khi gọi `customer.getEmail()`. Kiểm tra lại OrderService — có thể
repository trả về null khi không tìm thấy bản ghi. Nên dùng Optional
hoặc kiểm tra null trước khi truy cập thuộc tính.

4. Dùng AI sinh unit test tự động — ví dụ cụ thể

Một trong những việc tốn thời gian nhất là viết test case cho từng nhánh logic. Giả sử bạn có class DiscountCalculator:

public class DiscountCalculator {

    public BigDecimal calculate(BigDecimal orderAmount, CustomerTier tier) {
        if (orderAmount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Order amount must be positive");
        }
        BigDecimal rate = switch (tier) {
            case GOLD -> new BigDecimal("0.15");
            case SILVER -> new BigDecimal("0.10");
            case BASIC -> new BigDecimal("0.05");
        };
        return orderAmount.multiply(rate).setScale(2, RoundingMode.HALF_UP);
    }
}

Prompt gợi ý để yêu cầu LLM sinh test (bạn copy class trên vào prompt):

Viết unit test JUnit 5 + AssertJ cho class DiscountCalculator.
Bao gồm các case: GOLD, SILVER, BASIC, và case orderAmount <= 0 phải throw
IllegalArgumentException. Dùng @ParameterizedTest nếu hợp lý.

AI thường trả về bản nháp gần đúng, bạn chỉnh lại cho khớp style team:

class DiscountCalculatorTest {

    private final DiscountCalculator calculator = new DiscountCalculator();

    @ParameterizedTest
    @CsvSource({
            "GOLD, 100, 15.00",
            "SILVER, 100, 10.00",
            "BASIC, 100, 5.00"
    })
    void shouldCalculateDiscountByTier(CustomerTier tier, String amount, String expected) {
        BigDecimal result = calculator.calculate(new BigDecimal(amount), tier);

        assertThat(result).isEqualByComparingTo(new BigDecimal(expected));
    }

    @Test
    void shouldThrowWhenOrderAmountIsNotPositive() {
        assertThatThrownBy(() -> calculator.calculate(BigDecimal.ZERO, CustomerTier.GOLD))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessage("Order amount must be positive");
    }
}
  • @ParameterizedTest + @CsvSource gộp 3 case tier vào một test, giảm trùng lặp code test.
  • Dòng assertThat(result).isEqualByComparingTo(...) so sánh BigDecimal đúng cách (tránh lỗi equals phân biệt scale).
  • Case còn lại kiểm tra exception với message cụ thể.

Việc AI làm nhanh ở đây là liệt kê đủ case biên (bạn dễ quên case orderAmount <= 0), còn việc bạn phải làm là kiểm tra lại giá trị expected có đúng không — AI tính nhầm số là chuyện thường xảy ra.

5. Dùng AI sinh Javadoc và tóm tắt code legacy

Với code cũ không comment, AI tóm tắt nhanh hơn bạn đọc từng dòng. Ví dụ bạn có đoạn xử lý phức tạp:

public List<Order> filterEligibleOrders(List<Order> orders) {
    return orders.stream()
            .filter(o -> o.getStatus() == OrderStatus.PAID)
            .filter(o -> o.getCreatedAt().isAfter(LocalDate.now().minusDays(30)))
            .filter(o -> o.getItems().stream().anyMatch(Item::isRefundable))
            .toList();
}

Prompt: “Viết Javadoc tiếng Việt cho method này, nêu rõ 3 điều kiện lọc.” AI trả về:

/**
 * Lọc danh sách đơn hàng đủ điều kiện xử lý hoàn tiền.
 * Điều kiện: đơn phải ở trạng thái PAID, được tạo trong 30 ngày gần nhất,
 * và có ít nhất một item cho phép refund.
 *
 * @param orders danh sách đơn hàng đầu vào
 * @return danh sách đơn hàng thỏa cả ba điều kiện trên
 */

Việc này tiết kiệm thời gian onboard người mới vào codebase cũ — bạn dán hàng loạt method vào, AI trả về Javadoc nháp, bạn chỉ sửa chỗ sai thuật ngữ nghiệp vụ.

6. Tự động hóa review code với AI trong CI

Bạn có thể gắn bước gọi AI review diff trong pipeline để bắt lỗi sớm, trước khi con người review. Ví dụ bước đơn giản trong GitHub Actions dùng script gọi API:

name: ai-review
on: [pull_request]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Get diff
        run: git diff origin/main...HEAD > diff.txt
      - name: Call AI review
        run: |
          curl -s https://api.openai.com/v1/chat/completions \
            -H "Authorization: Bearer ${{ secrets.OPENAI_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d "$(jq -n --rawfile diff diff.txt \
              '{model:"gpt-4o-mini", messages:[{role:"user", content: ("Review đoạn diff Java sau, chỉ nêu vấn đề nghiêm trọng: " + $diff)}]}')" \
            > review.json
  • git diff origin/main...HEAD lấy đúng phần code thay đổi trong PR, tránh gửi cả repo (tốn token, chậm).
  • jq -n --rawfile dựng JSON body an toàn, tránh lỗi escape ký tự đặc biệt trong code.
  • Kết quả review.json bạn có thể post lại thành comment trên PR bằng một step khác.

Lưu ý: AI review không thay được senior review. Nó chỉ bắt các lỗi rõ ràng (null check thiếu, catch Exception trống, SQL injection cơ bản). Đừng merge PR chỉ vì AI “approve”.

7. Lỗi hay gặp và khi nào không nên dùng AI

Vài cạm bẫy thực tế khi dùng LLM trong dự án Java:

  • Hallucination API không tồn tại: AI có thể gợi ý gọi method Order.markAsRefundable() trong khi class thật không có method này. Luôn compile thử trước khi tin.
  • Sinh code đúng cú pháp nhưng sai version: AI hay trộn Java 8 style (new ArrayList<>()) với Java 21 (List.of(...)) không nhất quán — bạn cần chuẩn hóa lại theo coding convention team.
  • Leak thông tin nhạy cảm: đừng dán nguyên config chứa secret, connection string, hay dữ liệu khách hàng thật vào prompt gửi lên dịch vụ AI bên thứ ba.
  • Prompt mơ hồ → output mơ hồ: “viết test cho class này” cho kết quả hời hợt hơn nhiều so với prompt nêu rõ case biên cần cover.
  • Không nên dùng AI khi: logic tính lãi suất, tính thuế, hay bất kỳ phần liên quan compliance mà sai một ly là vấn đề pháp lý — AI chỉ nên hỗ trợ viết khung, phần tính toán core phải do người có chuyên môn nghiệp vụ kiểm tra kỹ bằng test case thực tế, không phải test case do AI tự nghĩ ra.

Một checklist nhỏ bạn nên áp dụng mỗi khi dùng AI sinh code:

1. Code có compile được không?
2. Test case AI sinh có cover đúng case biên thực tế không, hay chỉ cover case "nghe hợp lý"?
3. Có thông tin nhạy cảm nào lọt vào prompt không?
4. Style code có khớp convention hiện tại của team không?

8. Kết luận

AI giúp bạn tiết kiệm giờ rõ nhất ở các việc lặp lại và cần bản nháp nhanh: sinh test case, giải thích stacktrace, viết Javadoc, review diff sơ bộ. Spring AI giúp việc tích hợp LLM vào ứng dụng Java trở nên đơn giản chỉ với vài dòng cấu hình và ChatClient.

Điểm mấu chốt là bạn vẫn phải review kỹ mọi output — AI tiết kiệm thời gian viết, không tiết kiệm thời gian hiểu và kiểm chứng. Dùng đúng chỗ, đúng mức độ tin tưởng, bạn sẽ rút ngắn được nhiều giờ lặp đi lặp lại mỗi tuần mà không đánh đổi chất lượng code.

llmspring-aiaiproductivityjava