Prompt dan konteks
Sebuah layanan pesanan melakukan validasi input, reservasi inventaris, dan penagihan. Stok habis, pembayaran ditolak, dan dependensi batas waktu habis (timeout) adalah kegagalan yang diharapkan; kesalahan pemrograman, invarian yang rusak, dan kegagalan sumber daya yang tidak dapat dipulihkan memerlukan jalur terpisah. Pewawancara meminta Anda merancang tipe pengembalian, komposisi, dan pencatatan galat dengan C++23 std::expected.
Ini menguji kontrak galat dan kode yang dapat dikomposisikan. std::expected<T, E> berisi sebuah nilai T atau sebuah galat E; tipe ini tidak melakukan percobaan ulang, logging, atau membatalkan efek samping untuk Anda. Tentukan domain galat terlebih dahulu, kemudian tunjukkan bagaimana pemanggil memeriksa hasil, dan terakhir nyatakan batas pengecualian dan kompensasi.
Hal yang dievaluasi pewawancara
- Apakah Anda membedakan kegagalan bisnis, kegagalan dependensi, dan invarian yang rusak.
- Apakah Anda menggunakan
has_value,value,error,unexpected, dan[[nodiscard]]dengan benar. - Apakah Anda dapat mengomposisikan kegagalan non-throwing dengan
and_then,transform, danor_else. - Apakah Anda menghindari memperlakukan
std::expectedsebagai pengganti pengecualian global atau sekadar string galat mentah. - Apakah Anda mencakup pemetaan galat, idempotensi, logging, dan kompensasi.
Pertanyaan untuk diklarifikasi terlebih dahulu
- Apakah basis kode dikompilasi sebagai C++23, dan apakah pustaka standarnya mengimplementasikan API target?
- Apakah galat ditangani secara lokal atau diserialisasikan melintasi batas layanan? Galat lintas-layanan membutuhkan kode yang stabil.
- Apakah suatu fungsi memiliki lock, handle file, atau reservasi? Nyatakan pembersihan status dan kompensasi sebelum mengembalikan kegagalan.
- Apakah langkah-langkah pemesanan bersifat idempoten? Mencoba ulang penagihan berbeda dengan melepaskan inventaris dua kali.
- Bidang diagnostik mana yang harus dipertahankan? Pesan pengguna dan log internal tidak boleh dicampuradukkan.
Kerangka jawaban 30 detik
“Saya akan memodelkan kegagalan bisnis yang diharapkan sebagai std::expected<T, OrderError> sehingga pemanggil dapat menanganinya secara eksplisit. Saya akan menandai hasil sebagai [[nodiscard]], mengomposisikan validasi, reservasi, dan penagihan dengan and_then, serta menggunakan or_else untuk pemetaan dan metrik yang konsisten. Pengecualian tetap dicadangkan untuk invarian yang rusak, kegagalan inisialisasi, atau batas yang tidak dapat dipulihkan secara aman. Setiap efek samping membutuhkan kunci idempotensi, rencana kompensasi, dan logging non-sensitif.”
Jawaban mendalam
Mendefinisikan domain galat
OrderError harus berisi kategori yang dapat ditindaklanjuti oleh pemanggil, seperti invalidrequest, outofstock, paymentdeclined, dan dependency_timeout, ditambah konteks internal yang terkontrol. Jangan menaruh teks pesan pengguna, stack trace, dan payload vendor ke dalam satu string. Kesalahan pemrograman dan invarian yang rusak tidak boleh secara diam-diam menjadi kegagalan bisnis biasa.
Memilih tipe nilai dan galat
Validasi dapat mengembalikan std::expected<ValidatedOrder, OrderError>, reservasi dapat mengembalikan std::expected<Reservation, OrderError>, dan penagihan dapat mengembalikan std::expected<Receipt, OrderError>. Buat tipe galat tetap dapat dipindahkan (movable), berukuran cukup kecil, dan eksplisit tentang kepemilikan. Tandai fungsi penghasil hasil dengan [[nodiscard]] sehingga pemanggil tidak dapat mengabaikan kegagalan secara diam-diam.
Menggunakan pemeriksaan eksplisit pada batas yang jelas
Ketika langkah-langkahnya pendek atau memiliki tindakan berbeda per galat, pemeriksaan eksplisit mudah diaudit:
[[nodiscard]] auto reserve(const ValidatedOrder& order)
-> std::expected<Reservation, OrderError>;
auto create_order(Input input) -> std::expected<Receipt, OrderError> {
auto valid = validate(std::move(input));
if (!valid) return std::unexpected(valid.error());
auto held = reserve(*valid);
if (!held) return std::unexpected(held.error());
return charge(*valid, *held);
}Gunakan operator* dan value() hanya setelah memastikan keberhasilan. Pertahankan kategori asli pada jalur kegagalan; jangan menyamarkan stok habis sebagai galat generik yang tidak jelas.
Mengomposisikan rantai homogen dengan operasi monadik
Ketika setiap langkah mengembalikan std::expected, and_then berlanjut hanya jika ada nilai dan memutus sirkuit (short-circuit) jika terjadi galat. transform memetakan nilai yang sukses, or_else mencatat atau mengonversi galat, dan C++23 juga menyediakan transform_error untuk konversi batas. Jaga agar urutan efek samping tetap terlihat; jangan menyembunyikan penagihan, percobaan ulang, dan kompensasi di dalam rantai yang tidak dapat diaudit.
Menetapkan batas pengecualian
Batas waktu habis, stok habis, dan pembayaran ditolak adalah hasil yang diharapkan, sehingga expected memungkinkan lapisan bisnis memilih antara percobaan ulang, umpan balik pengguna, atau penanganan manual. Invarian yang rusak, kegagalan inisialisasi, atau batas yang tidak dapat dipulihkan secara aman dapat menggunakan pengecualian. Jaga agar batas tetap konsisten: kategori galat yang sama tidak boleh terkadang dikembalikan dan terkadang dilempar dari fungsi yang sama.
Menangani efek samping, idempotensi, dan kompensasi
expected membawa suatu hasil; ia tidak membatalkan efek samping yang telah selesai. Jika reservasi berhasil dan penagihan gagal, lepaskan reservasi atau masuk ke status kompensasi. Percobaan ulang penagihan membutuhkan kunci idempotensi. Objek galat dapat membawa ID pesanan, langkah, dan saran percobaan ulang tanpa memuat rahasia; logging dapat mengaitkan jejak (trace) tanpa menyimpan kredensial pembayaran.
Membuat kontrak dapat diuji
Uji setiap cabang galat, short-circuit, pemetaan, dan urutan kompensasi. Uji bahwa pengecualian yang tidak terduga melintasi batas dan ditangani secara konsisten. Kunci versi kompilator, makro fitur pustaka standar, dan flag build di CI. Di seluruh layanan, petakan OrderError ke kode protokol yang stabil alih-alih mengekspos nama tipe C++ sebagai kontrak API.
Contoh jawaban berkualitas tinggi
“Saya akan mendefinisikan OrderError dengan kategori yang stabil dan diagnostik internal yang terkontrol. Validasi, reservasi, dan penagihan mengembalikan nilai [[nodiscard]] std::expected. Stok habis, pembayaran ditolak, dan batas waktu habis adalah kegagalan bisnis yang ditangani secara eksplisit oleh pemanggil; semuanya bukan merupakan pengecualian.
Untuk rantai yang pendek, saya akan menggunakan if (!result) dan mempropagasi std::unexpected(result.error()), menjaga agar setiap batas efek samping tetap dapat diaudit. Transformasi homogen murni dapat menggunakan and_then dan transform, sedangkan or_else mencatat dan memetakan galat. Saya hanya memanggil value() setelah keberhasilan dipastikan.
Jika reservasi berhasil dan penagihan gagal, expected tidak membatalkannya, jadi saya mencatat status idempoten dan menjalankan pelepasan atau kompensasi. Pengecualian dicadangkan untuk invarian yang rusak dan kegagalan inisialisasi yang tidak dapat dipulihkan secara aman oleh batas saat ini. CI mengunci toolchain C++23 dan menguji setiap jalur galat, short-circuit, dan kompensasi sebelum memetakan galat ke kode layanan yang stabil.”
Kesalahan umum
- Memperlakukan
std::expectedsebagai mesin percobaan ulang: tipe pengembalian tidak memiliki semantik percobaan ulang → putuskan di lapisan bisnis menggunakan kategori galat dan idempotensi. - Menggunakan
std::optionaldan kehilangan alasannya: pemanggil tidak dapat membedakan kegagalan stok dari batas waktu habis → gunakan tipe galat yang stabil. - Mengabaikan
[[nodiscard]]: pemanggil dapat membuang kegagalan begitu saja → anotasikan fungsi hasil dan ubah peringatan menjadi kegagalan build di CI. - Memanggil
value()tanpa memeriksa: kegagalan dapat melemparbad_expected_access→ lakukan percabangan secara eksplisit atau periksa terlebih dahulu. - Mengonversi setiap pengecualian menjadi kode galat: invarian yang rusak dapat tertelan tanpa disadari → pertahankan batas pengecualian yang jelas.
- Menaruh rahasia dalam objek galat: log atau serialisasi dapat membocorkan kredensial → pisahkan pesan pengguna, kode stabil, dan diagnostik internal.
- Mengabaikan efek samping yang telah selesai: inventaris tetap direservasi setelah penagihan gagal → rancang status idempoten dan kompensasi.
- Mengirim tipe C++ lintas layanan: peningkatan versi merusak protokol → petakan ke kode dan bidang versi yang stabil.
Pertanyaan lanjutan dan jawaban
Pertanyaan lanjutan 1: Bagaimana Anda memilih antara std::expected dan pengecualian?
Gunakan expected untuk hasil bisnis yang diharapkan yang dapat ditangani oleh pemanggil. Gunakan pengecualian untuk invarian yang rusak, kegagalan inisialisasi, atau batas yang tidak dapat dipulihkan secara aman. Konsistensi lebih penting daripada aturan universal: pemanggil seharusnya tidak perlu menebak alur kendali untuk satu kategori galat.
Pertanyaan lanjutan 2: Mengapa bukan std::optional<T>?
optional merepresentasikan keberadaan atau ketiadaan tetapi tidak membawa alasan. Alur pesanan membutuhkan tindakan berbeda untuk kegagalan inventaris, pembayaran, dan dependensi, sehingga memerlukan expected<T, E>.
Pertanyaan lanjutan 3: Bisakah penagihan berjalan di dalam and_then?
Bisa, jika batas efek samping tetap terlihat dan setiap langkah dapat dicoba ulang atau dikompensasi. Untuk rantai panjang dengan kebijakan berbeda, if yang eksplisit mungkin lebih mudah diaudit; jangan menyembunyikan perubahan status demi gaya penulisan fungsional.
Pertanyaan lanjutan 4: Bagaimana Anda menyatukan galat dari beberapa dependensi?
Definisikan galat domain yang stabil pada batas layanan. Petakan kegagalan vendor ke serangkaian kategori terbatas sambil mempertahankan penyebab internal. Gunakan transform_error atau or_else untuk pemetaan, catat kode vendor dan jejak secara internal, serta jauhkan kata-kata vendor dari pesan pengguna.
Pertanyaan lanjutan 5: Apakah mengembalikan galat lebih lambat daripada melempar pengecualian?
Ukur jalur yang sebenarnya. expected membuat kegagalan umum menjadi eksplisit tetapi dapat menyalin objek galat atau menambahkan percabangan; pengecualian memusatkan biaya pada jalur pelemparan (throw). Pilihlah berdasarkan target latensi, perilaku kompilator, dan tolok ukur frekuensi galat, bukan klaim mutlak.
Pertanyaan lanjutan 6: Bagaimana Anda mencegah pemanggil lupa melakukan pemeriksaan?
Anotasikan tipe hasil dan fungsi penting dengan [[nodiscard]], ubah peringatan kompilator menjadi kegagalan CI, dan tinjau setiap pemanggilan value(). Untuk API lintas bahasa, tambahkan pengujian status protokol dan kontrak untuk mencakup apa yang tidak dapat dipaksakan oleh kompilator.