Strict types ทุกที่
Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 Evidence: Code-backed PHPStan: Level 10, no src baseline
โดยสรุป
หัวข้อที่มีชื่อว่า “โดยสรุป”NextPDF รัน PHPStan ที่ Level 10 บนซอร์สของเอนจินโดยไม่มี baseline สำหรับระงับข้อผิดพลาด หน้านี้อธิบายว่าเหตุใด “ไม่มี baseline” จึงเป็นการตัดสินใจด้านการออกแบบมากกว่ารายละเอียดของเครื่องมือ และความเข้มงวดนี้ช่วยอะไรกับไปป์ไลน์ที่ต้องหลีกเลี่ยงการจัดการข้อมูลผิดพลาดแบบเงียบๆอย่างแท้จริง
เหตุใดเรื่องนี้จึงสำคัญ
หัวข้อที่มีชื่อว่า “เหตุใดเรื่องนี้จึงสำคัญ”ในแอปพลิเคชันส่วนใหญ่ การกำหนดชนิดแบบเข้มงวดเป็นเพียงสุขลักษณะของโค้ด แต่ในเอนจิน PDF การกำหนดชนิดแบบเข้มงวดใกล้เคียงกับกลไกสร้างความถูกต้องมากกว่า รูปแบบไฟล์นี้ไม่ผ่อนปรนต่อข้อผิดพลาด ตัวอ่านควรค้นหาเนื้อหาด้วยการอ่านไฟล์จากส่วนท้ายผ่าน trailer และ cross-reference table ดังนั้น byte offset ที่ฝั่งตัวเขียนกำหนดไว้จึงต้องแม่นยำ ลองพิจารณาชนิดข้อมูลที่ถูกขยายเป็น mixed แบบเงียบๆ ค่า int ที่กลายเป็น string แบบเงียบๆ หรือค่า nullable ที่ถูก dereference โดยไม่มีการตรวจสอบ สิ่งเหล่านี้ล้วนอาจสร้างไฟล์ที่เปิดได้ตามปกติในโปรแกรมดูหนึ่ง แต่ไม่ผ่านการตรวจสอบความถูกต้องในอีกโปรแกรมหนึ่งหลังจากนั้นหลายสัปดาห์ โดยไม่มี stack trace ที่ชี้กลับไปยังต้นเหตุ
ความล้มเหลวที่มีต้นทุนสูงในขอบเขตงานนี้คือความล้มเหลวแบบเงียบๆ การกำหนดชนิดแบบเข้มงวดร่วมกับตัววิเคราะห์แบบเข้มงวดคือวิธีที่เอนจินใช้แปลงความล้มเหลวขณะรันแบบเงียบๆประเภทหนึ่งให้กลายเป็นความล้มเหลวที่ชัดเจนในเวลาบิลด์
ฉบับย่อ
หัวข้อที่มีชื่อว่า “ฉบับย่อ”- ซอร์สของเอนจินถูกวิเคราะห์ที่ PHPStan Level 10 ซึ่งเป็นระดับที่เข้มงวดที่สุด ยืนยันได้จาก
phpstan.neon.dist - ในเอนจินนี้ ไม่มี baseline สำหรับการระงับข้อผิดพลาดของซอร์ส การกำหนดค่าล็อกการวิเคราะห์ซอร์สไว้ที่ศูนย์ข้อผิดพลาด การถดถอยจะทำให้บิลด์ล้มเหลวแทนที่จะถูกกลบไว้ในไฟล์ ignore ที่ขยายตัวขึ้นเรื่อยๆ
- รายการ
ignoreErrorsเพียงไม่กี่รายการที่มีอยู่นั้น แคบ จำกัดขอบเขตด้วย identifier และ path และมีเหตุผลรองรับเป็นรายตัว ในการกำหนดค่า (ขอบเขต soft-dependency ข้ามแพ็กเกจและรอยต่อของเทสต์ที่เป็น reflection-target) ไม่ใช่ baseline แบบเหมารวม - นอกจากนี้ โปรไฟล์เข้มงวด แยกต่างหากรันที่
level: maxและห้ามเพิ่มรายการ ignore ใหม่ใดๆ ดังนั้นโค้ดใหม่จึงถูกยึดตามมาตรฐานที่เข้มงวดยิ่งขึ้น - ผลที่ตั้งใจให้เกิดคือแรงกดดันด้านการออกแบบ โค้ดที่ไม่สามารถแสดงออกได้อย่างซื่อตรงต่อชนิดข้อมูลจะไม่ผ่าน จึงต้องถูกออกแบบใหม่แทนที่จะถูกระงับ
วิธีที่ NextPDF จัดการเรื่องนี้
หัวข้อที่มีชื่อว่า “วิธีที่ NextPDF จัดการเรื่องนี้”ความแตกต่างระหว่าง “เราใช้ตัววิเคราะห์แบบเข้มงวด” กับ “เราใช้ตัววิเคราะห์แบบเข้มงวดโดยไม่มี baseline” คือแก่นของเรื่องทั้งหมด จึงต้องกล่าวให้แม่นยำ
โดยทั่วไป baseline จะบันทึกทุกการละเมิดที่มีอยู่และสั่งให้ตัววิเคราะห์เพิกเฉยต่อรายการเหล่านั้นพอดี วิธีนี้ใช้ได้จริงเมื่อต้องนำการวิเคราะห์แบบสถิตมาใช้กับโค้ดเบสรุ่นเก่า แต่ก็มีต้นทุน baseline กลายเป็นบัญชีหนี้เงียบที่ระบบชนิดข้อมูลตกลงว่าจะไม่มอง การละเมิดรายการใหม่ประเภทเดียวกันอาจหลุดรอดเข้ามาอยู่ข้างรายการเดิมได้ คำมั่นของตัววิเคราะห์จึงอ่อนลงจาก “โค้ดนี้สะอาดด้านชนิดข้อมูล” เป็น “โค้ดนี้ไม่ได้แย่ลงกว่าเดิม”
NextPDF ไม่ยอมแลกแบบนั้นกับซอร์สของเอนจิน การกำหนดค่าตรึงการวิเคราะห์ซอร์สไว้ที่ศูนย์ข้อผิดพลาดและเปิดใช้ reportUnmatchedIgnoredErrors ดังนั้นแม้แต่การระงับที่ ค้างเก่า หรือรายการที่ไม่ตรงกับสิ่งใดอีกต่อไป ก็ทำให้บิลด์ล้มเหลว รายการ ignore แบบแคบที่ยังเหลืออยู่ถูกจำกัดขอบเขตไว้กับ identifier ของข้อผิดพลาดและไฟล์ที่เฉพาะเจาะจง แต่ละรายการมีคำอธิบายในบรรทัดว่าเหตุใดขอบเขตนั้นจึงตั้งใจไว้เช่นนี้ (ตัวอย่างเช่น core ที่เขียนให้ทำงานกับอินเทอร์เฟซ Pro/Enterprise ที่จงใจไม่พึ่งพาแบบรูปธรรม) ผู้รีวิวจึงอ่านและประเมินแต่ละรายการได้ ไม่มีรายการคลุมเครือที่จะหลุดจากการติดตาม
ขั้นตอนการทำงานที่รักษาความซื่อตรงนี้ไว้:
- 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 เป็นส่วนหนึ่งของเรื่องนี้ annotation ของ PHPDoc ถูกถือเป็นข้อเท็จจริงพื้นฐาน ดังนั้น @param list<T> หรือ @return non-empty-string จึงไม่ใช่คอมเมนต์ที่ตัววิเคราะห์เพียงรับทราบแล้วปล่อยผ่าน แต่เป็นคำมั่นที่ถูกตรวจสอบ annotation และชนิดข้อมูลขณะรันจึงถูกบังคับให้สอดคล้องกัน
หลักฐานบ่งชี้อะไร
หัวข้อที่มีชื่อว่า “หลักฐานบ่งชี้อะไร”หน้านี้เป็น Evidence: Code-backed การกำหนดค่าคือหลักฐาน:
phpstan.neon.distตั้งค่าlevel: 10,phpVersion: 80400วิเคราะห์srcและไม่มี คีย์baseline:ทั้งยังไม่มีphpstan-baseline.neonสำหรับการวิเคราะห์ซอร์ส- ไฟล์เดียวกันตั้งค่า
treatPhpDocTypesAsCertain: trueและreportUnmatchedIgnoredErrors: trueพร้อมหมายเหตุในบรรทัดว่าการวิเคราะห์ซอร์สแบบ L10 ถูกล็อกไว้ที่ศูนย์ข้อผิดพลาด และการถดถอยใดๆต้องทำให้ CI ล้มเหลว - รายการ
ignoreErrorsที่เหลือแต่ละรายการถูกจำกัดขอบเขตด้วยidentifierและมักจำกัดด้วยpathพร้อมคอมเมนต์อธิบายเหตุผลของ soft-dependency และ reflection-target รายการเหล่านี้ไม่ใช่ baseline ที่สร้างขึ้นแบบเหมารวม phpstan-strict.neon.distรับช่วงการกำหนดค่านั้น ยกระดับเป็นmaxและตรึงรายการ ignore ไว้เพื่อให้ ไม่สามารถเพิ่มรายการใหม่ ภายใต้โปรไฟล์เข้มงวดได้
มุมมองด้านมาตรฐานนั้นตรงไปตรงมา เอนจินต้องสร้างไฟล์ที่ตัวอ่านสามารถนำทางได้จาก trailer และ cross-reference table ตาม Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 byte offset ที่แม่นยำเป็น ปัญหาด้านชนิดข้อมูลก่อนจะเป็นปัญหาด้านการ serialize offset เพราะ offset เป็น จำนวนเต็มที่ต้องไม่กลายเป็นสิ่งอื่นใดแบบเงียบๆ ไปป์ไลน์ที่ สะอาดด้านชนิดข้อมูลที่ Level 10 ได้กำจัดวิธีส่วนใหญ่ที่การคำนวณทางคณิตศาสตร์ จะผิดพลาดแบบเงียบๆออกไปแล้ว
ตัวอย่างเชิงปฏิบัติ
หัวข้อที่มีชื่อว่า “ตัวอย่างเชิงปฏิบัติ”การกำหนดชนิดแบบเข้มงวดเห็นได้ชัดที่สุดเมื่อกฎของขอบเขตงานถูกเข้ารหัสเป็นชนิดข้อมูลแทนการตรวจสอบขณะรัน ตัวจำแนกความสอดคล้องตอบคำถามระดับสเปกด้วย match ที่ครอบคลุมครบทุกกรณี ดังนั้นกรณีที่ไม่ได้จัดการจึงเป็นข้อผิดพลาดด้านชนิดข้อมูล ไม่ใช่ PDF ที่ผิด:
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, }; }}บรรทัด @return 2|3|4|null ไม่ใช่เอกสารประกอบ ภายใต้
treatPhpDocTypesAsCertain annotation นี้จะถูกตรวจสอบ ผู้เรียกที่สมมติว่าผลลัพธ์เป็น int เสมอจะได้รับแจ้งในเวลาวิเคราะห์ ก่อนที่จะมีการเขียนหมายเลขส่วน PDF/A ที่ไม่สอดคล้องตามมาตรฐานแม้แต่ไบต์เดียว
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”กับดักคือการตีความ “ไม่มี baseline” ว่าเป็น “บังเอิญที่โค้ดไม่มีการละเมิด” นั่นเป็นความเข้าใจที่กลับด้าน การไม่มี baseline คือ สาเหตุ ไม่ใช่ผลลัพธ์ที่โชคดี เนื่องจากไม่มีที่ให้พักการละเมิด โค้ดที่อาจก่อให้เกิดการละเมิดจึงต้องถูกเขียนอีกแบบหนึ่ง Level 10 ที่ไม่มี baseline สำหรับซอร์สเป็นข้อจำกัดที่กำหนดรูปแบบการออกแบบ ไม่ใช่ใบรายงานผลที่อธิบายการออกแบบนั้นในภายหลัง
ความเข้าใจผิดที่สองคือรายการ ignoreErrors เพียงไม่กี่รายการเป็น baseline ในอีกชื่อหนึ่ง ไม่ใช่เช่นนั้น baseline ถูกสร้างขึ้นแบบเหมารวมและคลุมเครือ แต่รายการเหล่านี้ถูกเขียนเป็นรายตัว จำกัดขอบเขตด้วย identifier มีคำอธิบาย และมี reportUnmatchedIgnoredErrors คอยกำกับ จึงไม่สามารถเสื่อมสภาพไปโดยไม่มีใครสังเกตได้
ข้อจำกัดและขอบเขต
หัวข้อที่มีชื่อว่า “ข้อจำกัดและขอบเขต”หน้านี้เกี่ยวกับการวิเคราะห์ ซอร์สของเอนจิน ชุดเทสต์ถูกวิเคราะห์ภายใต้ขอบเขตและการกำหนดค่าที่แยกต่างหากซึ่งจงใจให้แตกต่าง “ไม่มี baseline” ในที่นี้เป็นคำกล่าวเกี่ยวกับ src/ ไม่ใช่การอ้างว่าการวิเคราะห์เสริมทุกชนิดในรีพอซิทอรีปราศจาก baseline PHPStan พิสูจน์ความถูกต้องด้านชนิดข้อมูล ไม่ใช่ความถูกต้องด้านพฤติกรรม PHPStan ไม่ได้แทนที่พีระมิดการทดสอบ เพียงแต่กำจัดความล้มเหลวประเภทหนึ่งที่ไม่เช่นนั้นการทดสอบจะต้องไล่ตาม ระดับ แฟล็ก และชุด ignore ที่แน่นอนเป็นจริง ณ วันที่รีวิวของหน้านี้ แหล่งข้อมูลที่เชื่อถือได้คือ phpstan.neon.dist และ phpstan-strict.neon.dist ในรีพอซิทอรี core เสมอ
เอดิชันไม่เปลี่ยนวินัยนี้ ทุกเอดิชันถูกสร้างจากซอร์ส Level 10 เดียวกัน:
| Edition | Availability |
|---|---|
| Core | ซอร์สของ Core ถูกวิเคราะห์ที่ Level 10 โดยไม่มี baseline สำหรับซอร์ส |
| Pro | Pro ถูกสร้างบนวินัยซอร์ส Level 10 เดียวกัน |
| Enterprise | Enterprise ถูกสร้างบนวินัยซอร์ส Level 10 เดียวกัน |
เอกสารที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เอกสารที่เกี่ยวข้อง”- รากฐานของ PHP 8.4 — ฟีเจอร์ของภาษาที่ระบบชนิดข้อมูลพึ่งพา
- ข้อผิดพลาดในฐานะฟีเจอร์ — สิ่งที่เกิดขึ้นกับความล้มเหลวที่การกำหนดชนิดแบบเข้มงวดเผยให้เห็น
- โมเดลของไปป์ไลน์ — สถาปัตยกรรมที่วินัยนี้ปกป้องไว้
อภิธานศัพท์
หัวข้อที่มีชื่อว่า “อภิธานศัพท์”- PHPStan Level 10 — ระดับการวิเคราะห์ที่เข้มงวดที่สุด ถือว่าค่าที่ไม่มีชนิดและค่าที่กำหนดชนิดแบบหลวมเป็นข้อผิดพลาดแทนที่จะเป็นคำเตือน
- Baseline — บันทึกที่สร้างขึ้นจากการละเมิดที่มีอยู่ซึ่งตัววิเคราะห์ถูกสั่งให้เพิกเฉย NextPDF ไม่ใช้ baseline เลยสำหรับซอร์สของเอนจิน
treatPhpDocTypesAsCertain— การตั้งค่าของ PHPStan ที่ถือว่า annotation ชนิดข้อมูลของ PHPDoc เป็นข้อเท็จจริงที่ถูกตรวจสอบ ไม่ใช่คอมเมนต์เชิงคำแนะนำreportUnmatchedIgnoredErrors— การตั้งค่าที่ทำให้บิลด์ล้มเหลวเมื่อรายการ ignore ไม่ตรงกับสิ่งใดอีกต่อไป เพื่อป้องกันการระงับที่ค้างเก่า- แรงกดดันด้านการออกแบบ — ผลของข้อจำกัดที่บังคับให้โค้ดถูกเขียนในแบบใดแบบหนึ่ง ตรงกันข้ามกับการตรวจสอบที่เพียงแค่วัดผลเท่านั้น