Prompt dan konteks
Perkhidmatan pesanan melaksanakan pengesahan input, tempahan inventori dan pengecasan. Kehabisan stok, pembayaran ditolak dan tamat masa kebergantungan ialah kegagalan yang dijangka; ralat pengaturcaraan, invariant yang rosak dan kegagalan sumber yang tidak boleh dipulihkan memerlukan laluan berasingan. Penemu duga meminta anda mereka bentuk jenis pulangan, penggubahan dan perekodan ralat dengan C++23 std::expected.
Ini menguji kontrak ralat dan kod boleh gubah. std::expected<T, E> mengandungi sama ada nilai T atau ralat E; ia tidak mencuba semula, melog atau mengundur balik kesan sampingan untuk anda. Tentukan domain ralat terlebih dahulu, kemudian tunjukkan cara pemanggil memeriksa hasil, dan akhirnya nyatakan sempadan pengecualian serta pampasan.
Perkara yang dinilai oleh penemu duga
- Sama ada anda membezakan kegagalan perniagaan, kegagalan kebergantungan dan invariant yang rosak.
- Sama ada anda menggunakan
has_value,value,error,unexpecteddan[[nodiscard]]dengan betul. - Sama ada anda boleh menggubah kegagalan tanpa lemparan (non-throwing) dengan
and_then,transformdanor_else. - Sama ada anda mengelak daripada menganggap
std::expectedsebagai pengganti pengecualian global atau ralat rentetan mentah. - Sama ada anda merangkumi pemetaan ralat, keidempotensian, pengelogan dan pampasan.
Soalan untuk dijelaskan terlebih dahulu
- Adakah pangkalan kod dikompil sebagai C++23, dan adakah pustaka piawaiannya melaksanakan API sasaran?
- Adakah ralat dikendalikan secara setempat atau disiri merentasi sempadan perkhidmatan? Ralat rentas perkhidmatan memerlukan kod yang stabil.
- Adakah fungsi memiliki kunci (locks), pemegang fail atau tempahan? Nyatakan pembersihan keadaan dan pampasan sebelum mengembalikan kegagalan.
- Adakah langkah pesanan bersifat idempoten? Mencuba semula caj adalah berbeza daripada melepaskan inventori dua kali.
- Medan diagnostik manakah yang mesti dikekalkan? Mesej pengguna dan log dalaman tidak boleh dicampuradukkan.
Rangka jawapan 30 saat
“Saya akan memodelkan kegagalan perniagaan yang dijangka sebagai std::expected<T, OrderError> supaya pemanggil mengendalikannya secara eksplisit. Saya akan menandakan hasil sebagai [[nodiscard]], menggubah pengesahan, tempahan dan pengecasan dengan and_then, serta menggunakan or_else untuk pemetaan dan metrik yang konsisten. Pengecualian kekal untuk invariant yang rosak, kegagalan permulaan atau sempadan yang tidak dapat dipulihkan dengan selamat. Setiap kesan sampingan memerlukan kunci keidempotensian, pelan pampasan dan pengelogan tidak sensitif.”
Jawapan mendalam
Tentukan domain ralat
OrderError harus mengandungi kategori yang boleh diambil tindakan oleh pemanggil, seperti invalidrequest, outofstock, paymentdeclined dan dependency_timeout, berserta konteks dalaman yang terkawal. Jangan masukkan teks pengguna, surihan tindanan (stack trace) dan muatan vendor ke dalam satu rentetan. Ralat pengaturcaraan dan invariant yang rosak tidak sepatutnya secara senyap menjadi kegagalan perniagaan biasa.
Pilih jenis nilai dan ralat
Pengesahan boleh mengembalikan std::expected<ValidatedOrder, OrderError>, tempahan boleh mengembalikan std::expected<Reservation, OrderError> dan pengecasan boleh mengembalikan std::expected<Receipt, OrderError>. Pastikan jenis ralat boleh dialihkan (movable), bersaiz munasabah dan eksplisit tentang pemilikan. Tandakan fungsi penghasil hasil dengan [[nodiscard]] supaya pemanggil tidak boleh membuang kegagalan secara senyap.
Gunakan semakan eksplisit pada sempadan yang jelas
Apabila langkah adalah pendek atau mempunyai tindakan berbeza bagi setiap ralat, semakan eksplisit adalah mudah untuk 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 selepas memastikan kejayaan. Kekalkan kategori asal pada laluan kegagalan; jangan samarkan kehabisan stok sebagai ralat generik yang legap.
Gubah rantaian homogen dengan operasi monadik
Apabila setiap langkah mengembalikan std::expected, and_then diteruskan hanya pada nilai dan memintas (short-circuit) pada ralat. transform memetakan nilai yang berjaya, or_else merekodkan atau menukar ralat, dan C++23 juga menyediakan transform_error untuk penukaran sempadan. Pastikan susunan kesan sampingan kelihatan; jangan sembunyikan pengecasan, percubaan semula dan pampasan di dalam rantaian yang tidak boleh diaudit.
Tetapkan sempadan pengecualian
Tamat masa, kehabisan stok dan pembayaran ditolak ialah hasil yang dijangka, jadi expected membolehkan lapisan perniagaan memilih percubaan semula, maklum balas pengguna atau pengendalian manual. Invariant yang rosak, kegagalan permulaan atau sempadan yang tidak dapat dipulihkan dengan selamat boleh menggunakan pengecualian. Pastikan sempadan konsisten: kategori ralat yang sama tidak sepatutnya kadangkala dikembalikan dan kadangkala dilemparkan daripada fungsi yang sama.
Kendalikan kesan sampingan, keidempotensian dan pampasan
expected membawa hasil; ia tidak membatalkan kesan sampingan yang telah selesai. Jika tempahan berjaya dan pengecasan gagal, lepaskan tempahan atau masuki keadaan pampasan. Percubaan semula caj memerlukan kunci keidempotensian. Objek ralat boleh membawa ID pesanan, langkah dan nasihat percubaan semula tanpa rahsia; pengelogan boleh mengaitkan surihan tanpa menyimpan kelayakan pembayaran.
Jadikan kontrak boleh diuji
Uji setiap cabang ralat, pintasan, pemetaan dan susunan pampasan. Uji bahawa pengecualian yang tidak dijangka merentasi sempadan dan dikendalikan secara konsisten. Tetapkan pengkompil, makro ciri pustaka piawaian dan bendera binaan dalam CI. Merentasi perkhidmatan, petakan OrderError kepada kod protokol yang stabil dan bukannya mendedahkan nama jenis C++ sebagai kontrak API.
Model jawapan berkualiti tinggi
“Saya akan mentakrifkan OrderError dengan kategori stabil dan diagnostik dalaman terkawal. Pengesahan, tempahan dan pengecasan mengembalikan nilai [[nodiscard]] std::expected. Kehabisan stok, pembayaran ditolak dan tamat masa ialah kegagalan perniagaan yang dikendalikan secara eksplisit oleh pemanggil; semuanya bukan pengecualian.
Bagi rantaian pendek, saya akan menggunakan if (!result) dan menyebarkan std::unexpected(result.error()), mengekalkan setiap sempadan kesan sampingan boleh diaudit. Transformasi homogen tulen boleh menggunakan and_then dan transform, manakala or_else merekodkan dan memetakan ralat. Saya hanya memanggil value() selepas kejayaan dipastikan.
Jika tempahan berjaya dan pengecasan gagal, expected tidak mengundurkannya, jadi saya merekodkan keadaan idempoten dan menjalankan pelepasan atau pampasan. Pengecualian dikhaskan untuk invariant yang rosak dan kegagalan permulaan yang tidak dapat dipulihkan dengan selamat oleh sempadan semasa. CI menetapkan rantai alat C++23 dan menguji setiap laluan ralat, pintasan dan pampasan sebelum memetakan ralat kepada kod perkhidmatan yang stabil.”
Kesilapan lazim
- Menganggap
std::expectedsebagai enjin percubaan semula: jenis pulangan tidak mempunyai semantik percubaan semula → buat keputusan dalam lapisan perniagaan menggunakan kategori ralat dan keidempotensian. - Menggunakan
std::optionaldan kehilangan sebab: pemanggil tidak dapat membezakan kegagalan stok daripada tamat masa → gunakan jenis ralat yang stabil. - Mengabaikan
[[nodiscard]]: pemanggil boleh membuang kegagalan begitu sahaja → anotasikan fungsi hasil dan jadikan amaran sebagai kegagalan dalam CI. - Memanggil
value()tanpa menyemak: kegagalan boleh melemparbad_expected_access→ cawangkan secara eksplisit atau semak terlebih dahulu. - Menukar setiap pengecualian kepada kod ralat: invariant yang rosak mungkin ditelan secara senyap → kekalkan sempadan pengecualian yang jelas.
- Meletakkan rahsia dalam objek ralat: log atau penyirian boleh membocorkan kelayakan → asingkan teks pengguna, kod stabil dan diagnostik dalaman.
- Mengabaikan kesan sampingan yang telah selesai: inventori kekal ditempah selepas caj gagal → reka bentuk keadaan idempoten dan pampasan.
- Menghantar jenis C++ merentasi perkhidmatan: peningkatan versi merosakkan protokol → petakan kepada kod dan medan versi yang stabil.
Soalan susulan dan jawapan
Soalan susulan 1: Bagaimanakah anda memilih antara std::expected dan pengecualian?
Gunakan expected untuk hasil perniagaan yang dijangka yang boleh dikendalikan oleh pemanggil. Gunakan pengecualian untuk invariant yang rosak, kegagalan permulaan atau sempadan yang tidak dapat dipulihkan dengan selamat. Konsistensi lebih penting daripada peraturan sejagat: pemanggil tidak sepatutnya meneka aliran kawalan untuk satu kategori ralat.
Soalan susulan 2: Mengapa bukan std::optional<T>?
optional mewakili kehadiran atau ketiadaan tetapi tidak membawa sebab. Aliran pesanan memerlukan tindakan berbeza untuk kegagalan inventori, pembayaran dan kebergantungan, jadi ia memerlukan expected<T, E>.
Soalan susulan 3: Bolehkah pengecasan dijalankan di dalam and_then?
Ia boleh, jika sempadan kesan sampingan kekal kelihatan dan setiap langkah boleh dicuba semula atau dipampas. Untuk rantaian panjang dengan dasar berbeza, if yang eksplisit mungkin lebih mudah diaudit; jangan sembunyikan perubahan keadaan semata-mata untuk gaya berpenampilan fungsi.
Soalan susulan 4: Bagaimanakah anda menyatukan ralat daripada beberapa kebergantungan?
Takrifkan ralat domain yang stabil pada sempadan perkhidmatan. Petakan kegagalan vendor kepada set kategori yang terhad sambil mengekalkan punca dalaman. Gunakan transform_error atau or_else untuk pemetaan, rekodkan kod vendor dan surihan secara dalaman, serta elakkan penggunaan perkataan vendor dalam mesej pengguna.
Soalan susulan 5: Adakah mengembalikan ralat lebih perlahan daripada melempar pengecualian?
Ukur laluan sebenar. expected menjadikan kegagalan biasa eksplisit tetapi mungkin menyalin objek ralat atau menambah cawangan; pengecualian menumpukan kos pada laluan lemparan (throw). Pilihlah berdasarkan sasaran kependaman, gelagat pengkompil dan tanda aras kekerapan ralat, bukan dakwaan mutlak.
Soalan susulan 6: Bagaimanakah anda menghalang pemanggil daripada terlupa untuk menyemak?
Anotasikan jenis hasil dan fungsi penting dengan [[nodiscard]], jadikan amaran pengkompil sebagai kegagalan CI, dan semak setiap panggilan value(). Untuk API rentas bahasa, tambahkan ujian keadaan protokol dan kontrak untuk merangkumi perkara yang tidak dapat dikuatkuasakan oleh pengkompil.