Ketika sebuah pustaka Delphi menumbuhkan konfigurasi build tanpa framework visual, kelas-kelas pengganti adalah tempat bug bersemayat. Bukan platformnya, bukan kompiler: para stand-in-nya. PDFlibPas punya lapisan grafik yang menyediakan padanan bitmap, canvas, font, metafile, dan printer untuk build tanpa VCL, dan mem-portingnya ke Free Pascal memunculkan setiap mode kegagalan yang bisa dimiliki stand-in. Mereka terurut rapi berdasarkan biaya diagnosis, dan urutannya justru kebalikan dari yang disarankan intuisi
Stand-in yang melempar exception murah untuk ditemukan; exception itu menamai method-nya. Stand-in yang mengembalikan data kosong mahal, karena kegagalan muncul beberapa lapis dari penyebabnya. Stand-in yang mengembalikan sukses adalah yang terburuk, karena return code valid, error code nol, tidak ada exception yang dilempar, dan satu-satunya bukti bahwa ada yang keliru ada di byte yang keluar
Bentuk tiga: identifier gambar valid di atas XObject kosong
Konverter metafile vektor adalah badan procedure kosong di konfigurasi non-VCL. Segala yang di atasnya terus bekerja. Entry point impor EMF dan entry point canvas-capture berjalan sampai selesai dan mengembalikan identifier gambar yang sah, yang kemudian ditempatkan caller ke sebuah halaman. Yang mendarat di berkas adalah form XObject dengan content length nol. Halaman dirender putih
Tidak ada yang melaporkan masalah, dan itu termasuk program demonstrasi milik pustaka sendiri untuk fitur ini, yang menggambar halaman kosong dan tidak menyadarinya. Tidak ada return value yang gagal untuk diperiksa, karena rangkaian panggilan itu benar-benar semuanya berhasil; satu-satunya yang keliru adalah ukuran stream yang dihasilkan. Mendiagnosis kelas defek ini berarti mengajukan pertanyaan yang berbeda: bukan "apakah panggilan gagal" melainkan "apakah artefaknya masuk akal". Form XObject dengan panjang nol, gambar dengan nol piksel, halaman dengan nol byte konten, itulah asersi-asersi yang menangkapnya
Perbaikannya punya dua bagian dan bagian kedua mudah terlupa. Pertama, buat implementasi kosong melempar exception, agar kegagalan punya saluran. Kedua, konversikan exception itu menjadi hasil null di image factory dan tambahkan pemeriksaan null di dua tempat yang mengonsumsi identifier gambar, karena sebaliknya "kegagalan yang bersih" langsung berubah menjadi access violation ketika page tree melakukan dereferensi terhadap ketiadaan. Stub yang melempar hanya menjadi perbaikan jika caller sudah siap menerima kegagalan yang sebelumnya tidak pernah bisa mereka terima
Bentuk dua: data kosong, tiga lapis dari crash
Stand-in canvas metafile tidak mengisi dimensi fisiknya. Nilai itu membagi ke dalam perhitungan geometri halaman, sehingga perhitungan menghasilkan nol, sehingga komputasi bounding-box membagi dengan nol. Exception handler telanjang menelannya, image factory mengembalikan hasil null, dan access violation akhirnya terjadi di page tree ketika null itu dipakai. Tiga lapis antara penyebab dan gejala, dengan exception handler di tengah menghapus buktinya
Unit yang sama punya dua contoh pola lagi. Kelas font punya badan Assign dan constructor yang kosong, yang lebih berarti daripada kelihatannya karena property font canvas bersifat read-only: meng-assign ke dalamnya adalah satu-satunya cara mengirimkan font, sehingga implementasi kosong membuat pemilihan font diam-diam tidak berefek dan teks keluar dengan apa pun yang menjadi default. Dan nilai piksel-per-inci nol membuat setiap caller yang menentukan ukuran canvas dari metrik font menghasilkan canvas nol kali nol, yang menghasilkan halaman kosong dan sukses sebagai return
// Bentuk yang dicari di unit stand-in: sebuah method yang tidak
// melempar exception dan tidak melakukan apa pun. Keduanya terkompilasi
// dan keduanya menghasilkan "sukses" tanpa output
procedure TMetafileCanvasStandIn.Create(...);
begin
// tanpa panggilan inherited, tanpa inisialisasi field
end;
function TBitmapStandIn.LoadFromStream(Stream: TStream): Boolean;
begin
Result := True; // dan bitmapnya masih kosong
end;
Struktur wide yang hanya menyimpan karakter pertama
Yang ini sama sekali bukan masalah stand-in, tetapi masuk ke dalam katalog yang sama karena gejalanya sama jauhnya dari penyebab. Struktur enumerasi printer dideklarasikan dengan seluruh dua belas member string-nya bertipe pointer ke karakter single-byte, padahal fungsi yang mengisinya adalah varian wide-character dari API enumerasi
Ukuran pointer identik, sehingga layout struktur benar dan tidak ada yang crash. Yang terjadi justru pembacaan string UTF-16 sebagai string single-byte berhenti di byte nol pertama, yang untuk nama printer ASCII mana pun adalah setengah tinggi karakter kedua. Setiap nama printer kembali tepat satu karakter. Di hilir, validasi nama gagal, pembuatan printer gagal, dan pencetakan gagal untuk setiap printer sungguhan di mesin itu, dan tidak ada satu pun gejala itu menunjuk ke deklarasi struktur
// Keliru: ukuran benar, tipe elemen salah. Tanpa compile error, tanpa
// crash, setiap string terpotong menjadi satu karakter
type
TPrinterInfo2Wrong = record
pServerName: PAnsiChar;
pPrinterName: PAnsiChar;
// ... sepuluh lagi
end;
// Benar: struktur *W memiliki member wide di seluruhnya
type
TPrinterInfo2W = record
pServerName: PWideChar;
pPrinterName: PWideChar;
// ... sepuluh lagi
end;
Aturan yang lahir dari sana bersifat mekanis dan layak diterapkan tanpa berpikir: untuk struktur Win32 apa pun yang namanya berakhiran W, verifikasi setiap member string adalah varian wide-nya, field demi field. Mencampur dunia ANSI dan wide tidak menghasilkan diagnostic kompiler maupun crash, hanya truncation senyap, dan hal yang sama berlaku sebaliknya untuk varian ANSI
Exception handler telanjang adalah musuh sesungguhnya
Setiap penyelidikan ini dilambatkan oleh konstruksi yang sama: handler yang menangkap semuanya dan mengonversinya menjadi return value false. Itu hal yang wajar untuk ditulis di sekitar image decoder, karena gambar yang korup tidak seharusnya merobohkan pekerjaan dokumen. Ia juga alat untuk menghapus satu potong informasi yang Anda butuhkan
Respons praktisnya adalah membuat handler itu berisik untuk sementara. Membuang class, message, dan backtrace exception dari dalam handler telanjang, di bawah conditional debug, mengubah return null yang tidak bisa dijelaskan menjadi exception bernama dengan lokasi. Dalam dua dari tiga kasus di atas, satu langkah itu mengakhiri penyelidikan, karena exception-nya adalah pembagian dengan nol atau access violation di method stand-in yang namanya sudah menceritakan semuanya
Checklist untuk mengadopsi jalur stand-in
Empat butir, dalam urutan mereka memberi imbal hasil. Sebelum memanggil ke kelas pengganti, baca method yang akan Anda pakai dan pastikan masing-masing punya badan yang nyata; badan kosong bukan detail implementasi, ia fitur yang hilang. Utamakan stand-in yang melempar exception daripada stand-in yang mengembalikan nilai netral, dan pasangkan itu dengan pemeriksaan null di tempat-tempat di mana factory kini sah-sah saja mengembalikan ketiadaan. Verifikasi fitur dengan memeriksa artefaknya, bukan return code-nya, karena keseluruhan mode kegagalan di sini adalah return code bersih di atas artefak kosong; rincian byte dari apa yang benar-benar dimuat dokumen adalah cara tercepat melihatnya, dan artikel audit ukuran berkas membahas tooling itu. Dan ketika sebuah fitur tidak punya implementasi pengganti yang layak, arahkan sampel yang terdampak ke jalur yang berfungsi dan tuliskan alasannya dalam komentar, alih-alih meninggalkan demonstrasi yang diam-diam menghasilkan output kosong
Poin yang lebih luas berlaku jauh melampaui satu pustaka. Codebase apa pun dengan implementasi kedua yang conditional, lapisan mock, mode headless, platform shim, terpapar bentuk tiga. Alasan ia bersembunyi begitu baik adalah setiap gerbang kualitas yang biasa diandalkan tim, return code, error code, exception, exit status, adalah saluran status, dan bentuk tiga menjaga semuanya tetap bersih. Hanya output yang membongkarnya. Itu juga alasan di balik memeriksa artefak alih-alih status ketika menangani input tidak tepercaya, seperti dijelaskan dalam artikel parsing PDF tidak tepercaya, dan di balik membandingkan output render antar engine alih-alih percaya pada satu, seperti dijelaskan dalam rendering multi-engine
PDFlibPas adalah pustaka PDF Object Pascal native untuk Delphi, C++Builder, dan Free Pascal, dan konfigurasi non-VCL itulah yang membuat build headless dan lintas toolchain mungkin; cakupan konfigurasi saat ini tercantum di halaman produk losLab PDF Developer Library