Manh Quy
Trang Chủ
Giới Thiệu
Dự Án
Blog
Liên Hệ

Đang tải...

Manh Quy

AI Automation · LLM Integration · Fullstack — Hà Nội, Việt Nam.

Liên Kết Nhanh

  • Trang Chủ
  • Giới Thiệu
  • Dự Án
  • Blog
  • Liên Hệ

Tài Nguyên

  • Blog
  • Điều Khoản Dịch Vụ
  • Chính Sách Bảo Mật
  • Chính Sách Cookie
  • Tuyên Bố & AI
© 2026 Manh Quy.Được tạo bằng
Next.js
Quay lại Blog
Blog
AI automation
Chạy Larastan ở mọi level trên code do AI viết: 4 bug thật đều lộ ở level 0
AI automation

Chạy Larastan ở mọi level trên code do AI viết: 4 bug thật đều lộ ở level 0

Một codebase Laravel 25.000 dòng do Claude Code viết được chạy Larastan từ level 0 đến 10. Bốn bug thật đều lộ ở level 0; level cao hơn chỉ còn ba loại nhiễu gõ kiểu với cách sửa một dòng. Bài phân tích rút ra quy tắc chọn level và nguyên tắc sửa nullable cho code do agent viết.

22 tháng 10, 2026
9 phút
0 lượt xem
Chạy Larastan ở mọi level trên code do AI viết: 4 bug thật đều lộ ở level 0

Chạy Larastan ở mọi level trên code do AI viết: 4 bug thật đều lộ ở level 0

Một lập trình viên vừa làm điều mà hầu hết đội dùng AI viết code đều tránh: đem static analysis chạy hết mọi level, từ 0 đến 10, trên một codebase Laravel thật, 25.000 dòng, phần lớn do Claude Code viết trong 15 tháng. Kết quả đảo ngược trực giác của nhiều người: bốn lỗi thật duy nhất đều bị level 0, level nhẹ nhàng nhất, bắt gọn. Còn từ level 2 trở lên, gần như toàn bộ cảnh báo là nhiễu gõ kiểu, gom lại chỉ còn ba nguyên nhân, và hai trong ba nguyên nhân sửa bằng đúng một dòng cấu hình hoặc một dòng docblock.

Bài thí nghiệm này đáng đọc không phải vì Larastan, mà vì nó cho thấy code do agent viết hỏng theo những kiểu rất riêng, và cách sửa cũng rất riêng.

Thí nghiệm được chạy như thế nào

Codebase là một SaaS Laravel 12 chạy Filament 4, khoảng 250 file trong thư mục app/. Tác giả cài Larastan 3.13.0 (đi kèm PHPStan 2.3.1, phát hành đầu tháng 10/2026) vào thư mục test riêng để không động vào dự án thật, rồi chạy lần lượt từng level.

Ngay lần chạy đầu tiên đã sập, và lý do sập cũng là một bài học. Larastan khởi động toàn bộ ứng dụng để phân tích, và một Artisan command trong dự án khởi tạo Stripe client ngay trong constructor bằng config('services.stripe.secret'). Không có file .env, secret là null, SDK ném lỗi. Bài học rút ra: static analysis cần một app khởi động được, nghĩa là trong CI phải có .env với giá trị giả khác rỗng cho mọi secret mà constructor đọc. Cách sửa sạch hơn nằm ở code: service gọi API ngoài không nên dựng client cho đến khi có ai đó thực sự gọi nó.

Bốn bug thật, và cả bốn đều ở level 0

Level 0 được thiết kế là level hiền nhất, nhưng nó tìm ra những phát hiện duy nhất trong cả đợt chạy có thể làm sập production:

  1. Class Filament v3 trong app Filament v4. Một trang admin gọi Section::make() tám lần với import từ Filament\Infolists\Components\Section. Trong Filament 4, class này đã chuyển sang Filament\Schemas\Components và class cũ không còn tồn tại. Trang sẽ ném lỗi ngay khi có người mở. Đây đúng là kiểu import mà agent viết khi nó "nhớ" Filament v3 trong khi phần còn lại của file là code v4.
  2. env() nằm ngoài thư mục config. Hai chỗ đọc email admin bằng config('mail.admin_email', env('ADMIN_EMAIL', ...)), nhưng key config đó không tồn tại nên luôn rơi xuống env(). Khi config đã cache, file .env không được nạp và env() chỉ thấy biến môi trường thật của hệ thống, nên giá trị ADMIN_EMAIL trong production bị bỏ qua và chuỗi fallback cứng thắng.
  3. Enum case không còn tồn tại. Hai method của model so sánh thuộc tính với các case đã bị xóa khỏi enum. Chưa có gì gọi hai method này nên chưa nổ, nhưng caller đầu tiên sẽ nhận Error vì PHP ném lỗi khi truy cập enum case không xác định.
  4. Key trùng trong mảng. Một command import có map tra cứu với cùng một key hai lần, mục thứ hai lặng lẽ ghi đè mục thứ nhất. Không crash, chỉ là một ánh xạ không bao giờ xảy ra.

Không bug nào trong bốn bug này là kỳ lạ. Cả bốn đều bị vendor/bin/phpstan ở setting thấp nhất bắt được, vào bất kỳ ngày nào trong năm qua. Đó chính là điểm đáng suy nghĩ: code do agent viết không hỏng theo kiểu tinh vi, nó hỏng theo kiểu cũ kỹ mà con người lười không chạy tool nên bỏ sót.

Ba loại nhiễu, và cách sửa một dòng

Từ level 2 trở lên, số lượng cảnh báo tăng nhanh nhưng gần như không có bug. Ba nguyên nhân bao trùm phần lớn:

Quan hệ Eloquent thiếu generics. Gần một nửa cảnh báo ở level 2 phàn nàn về Illuminate\Database\Eloquent\Model trơn, ví dụ báo method valid() không tồn tại dù đó là scope hợp lệ của model liên quan. Ứng dụng có 34 method quan hệ, không một method nào có kiểu trả về generic nên Larastan không biết quan hệ trả về model nào. Một dòng docblock cho mỗi quan hệ sửa được vấn đề:

/** @return HasMany */
public function shareTokens(): HasMany
{
    return $this->hasMany(ShareToken::class);
}

Dòng này xóa năm lỗi level 2 trong các file dùng quan hệ. Viết 34 lần thì chán, và đó chính xác là việc nên giao lại cho agent, kẻ đã bỏ qua nó lúc đầu.

Larastan không đọc method casts(). Larastan báo so sánh strict với enum case "luôn false" trên model mà method casts() đã cast cột đó sang enum rõ ràng. Nguyên nhân: mặc định Larastan chỉ đọc kiểu khai báo của method casts(), và khai báo trả về array không nói gì về cast nào ứng với cột nào. Đọc thân method là tùy chọn bật thêm vì chậm hơn:

parameters:
    parseModelCastsMethod: true

Bật lên, cảnh báo level 4 giảm 37%, level 2 giảm khoảng một phần tư. Model nào dùng method casts() thay vì thuộc tính $casts cũ thì nên bật trước khi đọc bất kỳ kết quả nào.

mixed ở level 9 và 10. Mọi phát hiện mới ở level 9 đều về mixed, và 93% ở level 10. Nguồn phổ biến là config() trả về mixed. Laravel đã có getter gõ kiểu cho việc này: Config::string('app.url') vừa xóa cảnh báo vừa ném lỗi nếu giá trị không phải string, tốt hơn nối null vào URL.

Agent yêu nullable theo hai cách ngược nhau

Đầu tháng 10, Nuno Maduro nhận xét rằng agent AI rất thích nullable: mỗi nullable nhân đôi số trạng thái code có thể rơi vào. Thí nghiệm này kiểm chứng nhận xét đó, và thấy agent sai theo cả hai hướng.

Ở level 4, Larastan gắn cờ tám toán tử nullsafe không cần thiết: hai cái đặt trên giá trị không bao giờ null, sáu cái còn lại nằm bên trái ?? vốn đã xử lý null. Mỗi ?-> thừa bắt người đọc sau lo về một null không tồn tại.

Ngược lại, ở level 8, nơi PHPStan bắt đầu báo gọi method trên kiểu nullable, agent lại gọi method trên giá trị thật sự nullable mà không thu hẹp kiểu: $request->user() sau auth middleware (kiểu User|null), ->first() ngay sau rule validation exists:, hay $this->record của Filament. Cùng một gốc: agent xử lý null theo thói quen thay vì đọc kiểu. Cách sửa chung một ý tưởng, làm cho kiểu nói đúng điều code đã biết: attribute #[CurrentUser] của Laravel inject user non-null và xóa chín cảnh báo level 8 trong controller, còn firstOrFail() thay first() biến lỗi null tiềm ẩn thành 404 đúng nghĩa.

PHPStan giờ nói chuyện trực tiếp với agent của bạn

Chi tiết thú vị nhất có lẽ không nằm ở bug nào. Khi chạy PHPStan bên trong một Claude Code session, đầu output xuất hiện một khối hướng dẫn: trước khi sửa lỗi phải đọc trang docs của mã lỗi, lỗi thường là bug thật nên sửa nguyên nhân chứ không làm cho nó biến mất, cấm thêm @phpstan-ignore, cấm assert() hay @var để ghi đè kiểu suy luận, cấm cast chỉ để bịt miệng cảnh báo, cấm nới rộng kiểu tham số cho qua chuyện.

PHPStan thêm khối này từ phiên bản 2.1.41: nó phát hiện biến môi trường như CLAUDECODE, AMP_CURRENT_THREAD_ID, CURSOR_AGENT và chỉ in khi có lỗi. Cách nhanh nhất để agent làm một lỗi nullable biến mất là thêm ?->, @var hoặc cast, và PHPStan giờ cấm thẳng ba đường tắt đó bằng tên, ngay trong output liệt kê lỗi. Tool phân tích tĩnh đã học cách đối phó với tool sinh code.

Đưa vào CI: baseline và cache

Sửa hết mọi thứ ở level 8 một lần là không thực tế, và cũng không cần. Cách làm: sinh baseline, commit, từ đó chỉ code mới phải sạch. PHPStan không cho baseline thiu: khi một lỗi trong baseline được sửa thật, lần chạy sau báo lỗi vì pattern không còn khớp và bắt sinh lại baseline, nên lỗi đã sửa không thể lặng lẽ quay lại.

Về tốc độ, số đo trên máy M1 Pro: chạy lạnh level 6 mất 3 đến 4 giây; level 10 giới hạn hai process mất 4,2 đến 4,7 giây; cache ấm còn 1,1 giây. Với 25.000 dòng thì chạy lạnh đã rẻ; cache chỉ thực sự quan trọng với app lớn hơn.

Quy tắc chọn level, rút từ thí nghiệm

  • Chạy level 0 ngay hôm nay, không baseline. Nó nhanh, nó tìm ra mọi bug thật trong codebase này, và codebase do agent viết thường chứa API framework đã cũ.
  • Rồi thêm ba thứ trước khi đọc level cao: parseModelCastsMethod: true, generics cho mọi quan hệ, getter gõ kiểu cho config. Ba thứ này xóa phần lớn nhiễu làm level cao không đọc nổi.
  • Sau đó là level 8 kèm baseline. Đó là level kiểm tra xử lý nullable, đúng thói quen mà codebase này cần sửa nhất. Level 9 và 10 toàn chuyện mixed, để sau.

Và một nguyên tắc bao trùm cả ba bước: khi agent viết code, đừng hỏi "làm sao hết cảnh báo", hãy hỏi "kiểu đã nói đúng điều code biết chưa". Phần lớn nhiễu biến mất khi câu trả lời là có.


Nguồn tham khảo: I Ran Larastan at Every Level on Agent-Written Laravel Code: 4 Real Bugs, 3 Kinds of Noise — DEV Community (bản gốc đăng tại hafiz.dev). Số liệu và đoạn mã trong bài viết theo thí nghiệm của tác giả trên Larastan 3.13.0 / PHPStan 2.3.1.

Thẻ bài viết

#larastan
#phpstan
#laravel
#ai-agent
#code-quality

Bạn thấy bài viết này hữu ích?

Hãy để lại like và chia sẻ với cộng đồng!

Bài Viết Liên Quan

Bình luận

Để lại bình luận

0/2000 ký tự