Topik wawancara representatif

Wawancara C++: bagaimana Anda mendesain kontrak galat yang dapat dikomposisikan dengan std::expected?

CodingSedang
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Layanan pesanan dapat gagal selama validasi, reservasi inventaris, dan penagihan. Rancang rantai propagasi galat dengan C++23 std::expected dan jelaskan kapan pengecualian tetap berada di batas sistem.

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, dan or_else.
  • Apakah Anda menghindari memperlakukan std::expected sebagai 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:

cpp
[[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::expected sebagai mesin percobaan ulang: tipe pengembalian tidak memiliki semantik percobaan ulang → putuskan di lapisan bisnis menggunakan kategori galat dan idempotensi.
  • Menggunakan std::optional dan 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 melempar bad_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.

Sumber publik

Pertanyaan terkait

Alat wawancara terkait

Gunakan Tangkapan Layar untuk perintah coding

Ambil tangkapan layar soal, lalu telusuri batasan, solusi, kode, edge case, dan kompleksitas secara berurutan.

Lihat alat