Kiểu nghiêm ngặt ở mọi nơi
Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 Evidence: Code-backed PHPStan: Level 10, no src baseline
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”NextPDF chạy PHPStan ở Level 10 trên mã nguồn của engine mà không dùng baseline để chặn lỗi. Trang này giải thích vì sao “không có baseline” là một quyết định thiết kế, chứ không phải chi tiết công cụ, và sự nghiêm ngặt đó thực sự mang lại điều gì cho một pipeline có nhiệm vụ tránh âm thầm xử lý sai dữ liệu.
Vì sao điều này quan trọng
Phần tiêu đề “Vì sao điều này quan trọng”Trong hầu hết ứng dụng, dùng kiểu nghiêm ngặt là một thói quen tốt. Với một engine PDF, nó gần với cơ chế đảm bảo tính đúng đắn hơn. Định dạng này không khoan nhượng. Một reader được kỳ vọng sẽ định vị nội dung bằng cách đọc tệp từ cuối lên, thông qua trailer và bảng cross-reference, nên các byte offset ở phía ghi phải chính xác. Hãy hình dung một kiểu âm thầm nới rộng thành mixed, một int âm thầm trở thành string, hoặc một giá trị nullable bị truy xuất mà không kiểm tra. Bất kỳ trường hợp nào trong số này cũng có thể tạo ra một tệp mở được trong một trình xem nhưng lại không vượt qua kiểm tra hợp lệ trong một trình xem khác, nhiều tuần sau đó, mà không có stack trace nào chỉ ra nguyên nhân.
Những lỗi tốn kém trong lĩnh vực này thường là lỗi âm thầm. Dùng kiểu nghiêm ngặt cùng một analyzer nghiêm ngặt là cách engine biến cả một nhóm lỗi âm thầm lúc chạy thành lỗi rõ ràng lúc build.
Tóm tắt ngắn gọn
Phần tiêu đề “Tóm tắt ngắn gọn”- Mã nguồn của engine được phân tích ở PHPStan Level 10 — mức nghiêm ngặt nhất — và được xác minh trong
phpstan.neon.dist. - Hoàn toàn không có baseline nào để chặn lỗi cho mã nguồn. Cấu hình khóa kết quả phân tích mã nguồn ở trạng thái không có lỗi. Một lỗi tái xuất sẽ làm build thất bại, thay vì bị nuốt vào một tệp ignore ngày càng phình to.
- Một vài mục
ignoreErrorshiện có đều hẹp, giới hạn theo identifier và path, và được biện minh riêng cho từng mục trong cấu hình (các ranh giới soft-dependency giữa các package và các test seam nhắm tới reflection) — không phải một baseline hàng loạt. - Một profile nghiêm ngặt riêng chạy
level: maxvà cấm thêm bất kỳ mục ignore mới nào, nên mã mới được giữ ở tiêu chuẩn còn chặt chẽ hơn. - Tác dụng mong muốn là áp lực thiết kế: mã không thể biểu đạt trung thực về kiểu sẽ không vượt qua, nên phải được thiết kế lại thay vì bị che giấu.
Cách NextPDF tiếp cận vấn đề này
Phần tiêu đề “Cách NextPDF tiếp cận vấn đề này”Khác biệt giữa “chúng tôi dùng một analyzer nghiêm ngặt” và “chúng tôi dùng một analyzer nghiêm ngặt không có baseline” chính là điểm cốt lõi, nên cần nói thật chính xác.
Một baseline ghi lại mọi vi phạm hiện có và yêu cầu analyzer bỏ qua đúng những vi phạm đó. Đó là một cách thực dụng để đưa phân tích tĩnh vào một codebase cũ, nhưng nó có cái giá của nó. Baseline trở thành một sổ nợ âm thầm mà hệ thống kiểu đã đồng ý không nhìn tới. Những vi phạm mới cùng loại có thể lọt vào bên cạnh các vi phạm đã có. Lời cam kết của analyzer suy yếu từ “mã này sạch về kiểu” thành “mã này không tệ hơn trước.”
NextPDF không chấp nhận đánh đổi đó cho mã nguồn của engine. Cấu hình cố định kết quả phân tích mã nguồn ở trạng thái không có lỗi và bật reportUnmatchedIgnoredErrors, nên kể cả một mục chặn lỗi cũ kỹ — một mục không còn khớp với bất cứ thứ gì — cũng làm build thất bại. Những mục ignore hẹp còn lại được giới hạn theo một error identifier và một tệp cụ thể. Mỗi mục đều kèm theo lời giải thích inline về lý do ranh giới đó là có chủ ý (ví dụ, core lập trình theo một interface của Pro/Enterprise mà nó chủ ý không phụ thuộc cụ thể vào đó). Người review có thể đọc và đánh giá từng mục. Không có một danh sách mờ ám nào để rồi mất dấu.
Quy trình giữ cho điều này luôn trung thực:
- Change proposed New or modified engine code.
- Level 10 analysis Strictest PHPStan level over src/, treatPhpDocTypesAsCertain on.
- Zero-error gate No source baseline; unmatched ignores also fail.
- Strict profile level: max; no new ignore entries permitted.
- Redesign, not suppress If it cannot be expressed honestly, the design changes.
treatPhpDocTypesAsCertain là một phần của cơ chế này. Các chú thích PHPDoc được xem là sự thật nền tảng, nên một @param list<T> hay @return non-empty-string không phải là một comment để analyzer lịch sự bỏ qua. Đó là một lời cam kết được kiểm tra. Chú thích và kiểu lúc chạy buộc phải nhất quán với nhau.
Bằng chứng cho thấy điều gì
Phần tiêu đề “Bằng chứng cho thấy điều gì”Trang này là Evidence: Code-backed . Chính cấu hình là bằng chứng:
phpstan.neon.distđặtlevel: 10,phpVersion: 80400, phân tíchsrc, và không chứa khóabaseline:nào — không cóphpstan-baseline.neonnào dành cho phân tích mã nguồn.- Cùng tệp đó đặt
treatPhpDocTypesAsCertain: truevàreportUnmatchedIgnoredErrors: true, kèm một ghi chú inline rằng kết quả phân tích mã nguồn ở L10 bị khóa ở mức không có lỗi và mọi lỗi tái xuất đều phải làm CI thất bại. - Các
ignoreErrorscòn lại đều được giới hạn theoidentifiervà thường là cảpath, kèm các comment giải thích lý do về soft-dependency và reflection-target — chúng không phải một baseline sinh ra hàng loạt. phpstan-strict.neon.distkế thừa cấu hình đó, nâng level lênmax, và đóng băng danh sách ignore để không một mục mới nào có thể được thêm vào trong profile nghiêm ngặt.
Góc nhìn từ tiêu chuẩn rất rõ ràng. Engine phải tạo ra những tệp mà reader có thể điều hướng từ trailer và bảng cross-reference theo Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 . Các byte offset chính xác là một vấn đề về kiểu trước khi là một vấn đề về serialization. Một offset là một số nguyên không bao giờ được phép âm thầm trở thành bất cứ thứ gì khác. Một pipeline sạch về kiểu ở Level 10 đã loại bỏ phần lớn những cách mà phép tính số học có thể âm thầm sai lệch.
Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Việc dùng kiểu nghiêm ngặt thể hiện rõ nhất ở nơi một quy tắc nghiệp vụ được mã hóa thành một kiểu, thay vì một phép kiểm tra lúc chạy. Bộ phân biệt mức tuân thủ trả lời các câu hỏi ở cấp đặc tả bằng một match bao quát mọi trường hợp, nên một trường hợp chưa được xử lý là lỗi kiểu, chứ không phải một PDF sai:
declare(strict_types=1);
enum ConformanceMode: string{ case Plain = 'plain'; case PdfUa2 = 'pdfua2'; case PdfA4 = 'pdfa4';
/** @return 2|3|4|null */ public function pdfaPart(): ?int { return match ($this) { self::PdfA4 => 4, default => null, }; }}Khai báo @return 2|3|4|null không phải là tài liệu. Dưới
treatPhpDocTypesAsCertain, nó được kiểm tra. Một caller giả định kết quả luôn là một int sẽ được báo ngay lúc phân tích, trước khi dù chỉ một byte của một số part PDF/A không tuân thủ được ghi ra.
Hiểu lầm thường gặp
Phần tiêu đề “Hiểu lầm thường gặp”Cái bẫy là hiểu “không có baseline” thành “mã tình cờ không có vi phạm nào.” Cách hiểu đó bị đảo ngược. Việc không có baseline là nguyên nhân, chứ không phải một kết quả may mắn. Vì không có chỗ nào để gửi tạm một vi phạm, mã đáng lẽ tạo ra vi phạm đó buộc phải được viết theo cách khác. Level 10 không có baseline cho mã nguồn là một ràng buộc định hình thiết kế, chứ không phải một phiếu báo cáo mô tả thiết kế sau khi mọi việc đã xong.
Một hiểu lầm thứ hai: một số ít mục ignoreErrors chỉ là một baseline mang tên khác. Không phải vậy. Một baseline được sinh ra hàng loạt và mờ ám. Những mục này được viết riêng cho từng trường hợp, giới hạn theo identifier, có giải thích, và được bảo vệ bởi reportUnmatchedIgnoredErrors, nên chúng không thể mục ruỗng mà không ai hay biết.
Giới hạn và ranh giới
Phần tiêu đề “Giới hạn và ranh giới”Trang này nói về việc phân tích mã nguồn của engine. Bộ test được phân tích trong một phạm vi và cấu hình riêng, được cố tình tách biệt; “không có baseline” ở đây là một khẳng định về src/, chứ không phải một tuyên bố rằng mọi phân tích phụ trợ trong repository đều không có baseline. PHPStan chứng minh tính đúng đắn về kiểu, chứ không phải tính đúng đắn về hành vi. Nó không thay thế kim tự tháp kiểm thử, mà chỉ loại bỏ một nhóm lỗi mà nếu không thì các bài test sẽ phải truy đuổi. Mức level, các flag và tập hợp ignore chính xác là đúng tính đến ngày soát xét của trang này. Nguồn có thẩm quyền luôn là phpstan.neon.dist và phpstan-strict.neon.dist trong repository core.
Phiên bản không làm thay đổi nguyên tắc này. Mọi phiên bản đều được build từ cùng một mã nguồn Level 10:
| Edition | Availability |
|---|---|
| Core | Mã nguồn Core được phân tích ở Level 10 mà không có baseline cho mã nguồn. |
| Pro | Pro được xây dựng trên cùng nguyên tắc mã nguồn ở Level 10. |
| Enterprise | Enterprise được xây dựng trên cùng nguyên tắc mã nguồn ở Level 10. |
Tài liệu liên quan
Phần tiêu đề “Tài liệu liên quan”- Nền tảng PHP 8.4 — những tính năng ngôn ngữ làm nền cho hệ thống kiểu.
- Lỗi như một tính năng — điều gì xảy ra với các lỗi mà việc dùng kiểu nghiêm ngặt phát hiện ra.
- Mô hình pipeline — kiến trúc mà nguyên tắc này bảo vệ.
Thuật ngữ
Phần tiêu đề “Thuật ngữ”- PHPStan Level 10 — mức phân tích nghiêm ngặt nhất, coi các giá trị không có kiểu và có kiểu lỏng lẻo là lỗi chứ không phải cảnh báo.
- Baseline — một bản ghi được sinh ra về các vi phạm hiện có mà analyzer được yêu cầu bỏ qua. NextPDF không dùng baseline nào cho mã nguồn của engine.
treatPhpDocTypesAsCertain— một thiết lập của PHPStan coi các chú thích kiểu PHPDoc là những sự thật được kiểm tra, chứ không phải comment mang tính khuyến nghị.reportUnmatchedIgnoredErrors— một thiết lập làm build thất bại khi một mục ignore không còn khớp với bất cứ thứ gì, nhằm ngăn các mục chặn lỗi lỗi thời.- Áp lực thiết kế — tác dụng của một ràng buộc buộc mã phải được viết theo một cách nhất định, trái ngược với một phép kiểm tra chỉ đo lường nó.