Haruskah fungsi mengembalikan null atau objek kosong?

209

Apa praktik terbaik saat mengembalikan data dari fungsi. Apakah lebih baik mengembalikan Null atau objek kosong? Dan mengapa seseorang harus melakukan satu dari yang lain?

Pertimbangkan ini:

public UserEntity GetUserById(Guid userId)
{
     //Imagine some code here to access database.....

     //Check if data was returned and return a null if none found
     if (!DataExists)
        return null; 
        //Should I be doing this here instead? 
        //return new UserEntity();  
     else
        return existingUserEntity;
}

Mari kita berpura-pura bahwa akan ada kasus yang valid dalam program ini bahwa tidak akan ada informasi pengguna dalam database dengan GUID itu. Saya akan membayangkan bahwa tidak pantas untuk melemparkan pengecualian dalam kasus ini ?? Saya juga mendapat kesan bahwa penanganan pengecualian dapat merusak kinerja.

7wp
sumber
4
Saya pikir maksud Anda if (!DataExists).
Sarah Vessels
107
Ini adalah pertanyaan arsitektur dan sangat sesuai. Pertanyaan OP itu valid terlepas dari masalah bisnis yang ingin dipecahkannya.
Joseph Ferris
2
Pertanyaan ini sudah cukup dijawab. Saya pikir ini adalah pertanyaan yang sangat menarik.
John Scipione
'getUser ()' harus mengembalikan nol. 'getCurrentUserInfo ()' atau 'getCurrentPermissions ()', OTOH, akan lebih banyak mengungkapkan pertanyaan - mereka harus mengembalikan objek jawaban yang tidak nol, terlepas dari siapa / atau apakah ada orang yang login.
Thomas W
2
Tidak @Bergi yang lain adalah duplikat. Milik saya ditanyakan pertama, pada bulan Oktober, yang lain diminta 3 bulan kemudian pada bulan Desember. Ditambah yang lain berbicara tentang koleksi yang sedikit berbeda.
7wp

Jawaban:

207

Mengembalikan nol biasanya merupakan ide terbaik jika Anda bermaksud menunjukkan bahwa tidak ada data yang tersedia.

Objek kosong menyiratkan data telah dikembalikan, sedangkan pengembalian nol dengan jelas menunjukkan bahwa tidak ada yang dikembalikan.

Selain itu, mengembalikan nol akan menghasilkan pengecualian nol jika Anda mencoba mengakses anggota di objek, yang dapat berguna untuk menyoroti kode buggy - mencoba mengakses anggota yang tidak ada artinya tidak masuk akal. Mengakses anggota objek kosong tidak akan gagal berarti bug tidak dapat ditemukan.

ls
sumber
21
Anda harus melempar pengecualian, tidak menelan masalah dan mengembalikan nol. Minimal, Anda harus mencatat ini dan kemudian melanjutkan.
Chris Ballance
130
@ Chris: Saya tidak setuju. Jika kode dengan jelas mendokumentasikan bahwa nilai kembali adalah nol, maka dapat diterima untuk mengembalikan nol jika tidak ditemukan hasil yang cocok dengan kriteria Anda. Melempar pengecualian harus menjadi pilihan TERAKHIR Anda, bukan yang PERTAMA.
Mike Hofer
12
@ Chris: Atas dasar apa Anda memutuskan ini? Menambahkan logging ke persamaan tentu tampak berlebihan. Biarkan kode konsumsi memutuskan apa - jika ada - yang harus dilakukan jika tidak ada pengguna. Seperti komentar saya sebelumnya, sama sekali tidak ada masalah dengan mengembalikan nilai yang secara universal didefinisikan sebagai "tidak ada data".
Adam Robinson
17
Saya sedikit bingung bahwa pengembang Microsoft percaya bahwa "mengembalikan nol" sama dengan "menelan masalah." Jika ingatanku, ada banyak metode dalam Kerangka di mana metode null dikembalikan jika tidak ada yang cocok dengan permintaan pemanggil. Apakah itu "menelan masalahnya?"
Mike Hofer
5
Terakhir namun tidak kalah pentingnya adalah bool GetUserById(Guid userId, out UserEntity result)- yang saya lebih suka nilai pengembalian "nol", dan yang tidak ekstrim seperti melempar pengecualian. Ini memungkinkan nullkode -gratis yang indah seperti if(GetUserById(x,u)) { ... }.
Marcel Jackwerth
44

Tergantung pada apa yang paling masuk akal untuk kasus Anda.

Apakah masuk akal untuk mengembalikan nol, misalnya "tidak ada pengguna seperti itu"?

Atau masuk akal untuk membuat pengguna default? Ini masuk akal ketika Anda dapat dengan aman berasumsi bahwa jika pengguna TIDAK ADA, kode panggilan berniat untuk ada ketika mereka memintanya.

Atau apakah masuk akal untuk melemparkan pengecualian (ala "FileNotFound") jika kode panggilan menuntut pengguna dengan ID yang tidak valid?

Namun - dari pemisahan keprihatinan / sudut pandang SRP, dua yang pertama lebih benar. Dan secara teknis yang pertama adalah yang paling benar (tetapi hanya dengan rambut) - GetUserById hanya bertanggung jawab untuk satu hal - mendapatkan pengguna. Menangani kasus "pengguna tidak ada" sendiri dengan mengembalikan sesuatu yang lain bisa menjadi pelanggaran SRP. Memisahkan menjadi cek yang berbeda - bool DoesUserExist(id)akan sesuai jika Anda memilih untuk melemparkan pengecualian.

Berdasarkan komentar ekstensif di bawah ini : jika ini adalah pertanyaan desain tingkat API, metode ini dapat dianalogikan dengan "OpenFile" atau "ReadEntireFile". Kami "membuka" pengguna dari beberapa repositori dan menghidrasi objek dari data yang dihasilkan. Pengecualian bisa sesuai dalam kasus ini. Mungkin tidak, tetapi bisa juga.

Semua pendekatan dapat diterima - itu hanya tergantung, berdasarkan konteks API / aplikasi yang lebih besar.

Rex M
sumber
Seseorang menolak Anda dan saya mendukung Anda, karena ini sepertinya bukan jawaban yang buruk untuk saya; kecuali: Saya tidak akan pernah melempar pengecualian ketika tidak menemukan pengguna dalam metode seperti yang diberikan poster. Jika tidak menemukan pengguna menyiratkan ID tidak valid atau beberapa masalah pengecualian-layak seperti itu, yang harus terjadi lebih tinggi - metode lempar kebutuhan untuk tahu lebih banyak tentang di mana ID yang datang dari, dll
Jacob Mattison
(Saya menduga downvote adalah keberatan dengan gagasan melemparkan pengecualian dalam keadaan seperti ini.)
Jacob Mattison
1
Setuju sampai poin terakhir Anda. Tidak ada pelanggaran SRP dengan mengembalikan nilai yang secara universal didefinisikan sebagai "tidak ada data". Itu seperti menyatakan bahwa database SQL harus mengembalikan kesalahan jika klausa di mana tidak menghasilkan hasil. Sementara Pengecualian adalah pilihan desain yang valid (meskipun yang akan membuat saya kesal sebagai konsumen), itu bukan "lebih benar" daripada mengembalikan nol. Dan tidak, saya bukan DV.
Adam Robinson
@ Jacobs kami memberikan pengecualian ketika kami menuntut path sistem file yang tidak ada, tidak mengembalikan null, tetapi tidak dari database. Jadi jelas keduanya sesuai, itulah yang saya maksudkan - itu tergantung.
Rex M
2
@ Charles: Anda menjawab pertanyaan "haruskah pengecualian dilemparkan di beberapa titik", tetapi pertanyaannya adalah "haruskah fungsi ini membuang pengecualian". Jawaban yang benar adalah "mungkin", bukan "ya".
Adam Robinson
30

Secara pribadi, saya menggunakan NULL. Itu membuat jelas bahwa tidak ada data untuk kembali. Tetapi ada beberapa kasus ketika Obyek Null mungkin berguna.

Fernando
sumber
Baru saja akan menambahkan ini sebagai jawaban. Pola NullObjectPattern atau kasus khusus. Maka Anda bisa menerapkan satu untuk setiap kasus, NoUserEntitiesFound, NullUserEntities dll.
David Swindells
27

Jika jenis pengembalian Anda adalah sebuah array maka kembalikan array kosong jika tidak mengembalikan null.

Darin Dimitrov
sumber
Apakah 0 item dalam daftar sama dengan daftar yang tidak ditugaskan saat ini?
AnthonyWJones
3
0 item dalam daftar tidak sama dengan null. Hal ini memungkinkan Anda untuk menggunakannya dalam foreachpernyataan dan permintaan LINQ tanpa khawatir NullReferenceException.
Darin Dimitrov
5
Saya terkejut ini belum ter-upgrade. Ini sepertinya pedoman yang cukup masuk akal bagi saya.
shashi
Nah, wadah kosong hanyalah contoh spesifik dari pola objek nol. Yang mungkin sesuai, kami tidak tahu.
Deduplicator
Mengembalikan array kosong ketika data tidak tersedia sama sekali salah . Ada perbedaan antara data yang tersedia dan tidak mengandung item dan data tidak tersedia. Mengembalikan array kosong dalam kedua kasus membuat tidak mungkin untuk mengetahui mana yang menjadi kasus. Melakukannya hanya agar Anda dapat menggunakan foreach tanpa memeriksa apakah data yang ada ada di luar konyol - penelepon harus memeriksa apakah data itu ada dan NullReferenceException jika penelepon tidak memeriksa baik karena mengekspos bug ..
Reinstate Monica
12

Anda harus melempar pengecualian (hanya) jika kontrak tertentu rusak.
Dalam contoh spesifik Anda, meminta UserEntity berdasarkan ID yang dikenal, itu akan tergantung pada kenyataan jika pengguna yang hilang (dihapus) adalah kasus yang diharapkan. Jika demikian, kembalilah nulltetapi jika itu bukan kasus yang diharapkan, maka lontarkan pengecualian.
Perhatikan bahwa jika fungsi dipanggil UserEntity GetUserByName(string name)mungkin tidak akan melempar tetapi mengembalikan null. Dalam kedua kasus mengembalikan UserEntity kosong akan tidak membantu.

Untuk string, array dan koleksi situasinya biasanya berbeda. Saya ingat beberapa bentuk pedoman MS bahwa metode harus menerima nullsebagai daftar 'kosong' tetapi mengembalikan koleksi panjang nol daripada null. Hal yang sama untuk string. Perhatikan bahwa Anda dapat mendeklarasikan array kosong:int[] arr = new int[0];

Henk Holterman
sumber
Senang Anda menyebutkan bahwa string berbeda, karena Google menunjukkan ini kepada saya ketika saya memutuskan apakah akan mengembalikan string kosong.
Noumenon
String, koleksi dan array tidak berbeda. Jika MS mengatakan demikian, MS salah. Ada perbedaan antara string kosong dan nol, dan antara koleksi kosong dan nol. Dalam kedua kasus yang pertama mewakili data yang ada (ukuran 0) dan yang terakhir mewakili kurangnya data. Dalam beberapa kasus perbedaannya sangat penting. Misalnya, jika Anda mencari entri dalam cache, Anda ingin tahu perbedaan antara data yang di-cache tetapi kosong dan data yang tidak di-cache sehingga Anda harus mengambilnya dari sumber data yang mendasarinya, di mana mungkin tidak kosong.
Pasang kembali Monica
1
Anda sepertinya melewatkan pokok dan konteksnya. .Wher(p => p.Lastname == "qwerty")harus mengembalikan koleksi kosong, bukan null.
Henk Holterman
@HenkHolterman Jika Anda dapat mengakses koleksi lengkap dan menerapkan filter yang tidak menerima item dalam koleksi, koleksi kosong adalah hasil yang benar. Tetapi jika koleksi lengkap tidak ada, koleksi kosong sangat menyesatkan - null atau melempar akan benar tergantung pada apakah situasinya normal atau luar biasa. Karena pos Anda tidak memenuhi syarat untuk situasi mana yang Anda bicarakan (dan sekarang Anda mengklarifikasi bahwa Anda berbicara tentang situasi sebelumnya) dan karena OP berbicara tentang situasi yang terakhir, saya harus tidak setuju dengan Anda.
Reinstate Monica
11

Ini adalah pertanyaan bisnis, tergantung pada apakah keberadaan pengguna dengan ID Panduan spesifik merupakan kasus penggunaan normal yang diharapkan untuk fungsi ini, atau apakah ini merupakan anomali yang akan mencegah aplikasi untuk berhasil menyelesaikan fungsi apa pun yang diberikan metode ini kepada pengguna objek untuk...

Jika ini merupakan "pengecualian", dalam ketiadaan pengguna dengan Id itu akan mencegah aplikasi untuk berhasil menyelesaikan fungsi apa pun yang dilakukannya, (Katakanlah kami membuat faktur untuk pelanggan yang telah kami kirimi produk ke ... ), maka situasi ini harus melempar ArgumentException (atau pengecualian khusus lainnya).

Jika pengguna yang hilang ok, (salah satu hasil normal potensial memanggil fungsi ini) maka kembalikan nol ....

EDIT: (untuk menanggapi komentar dari Adam di jawaban lain)

Jika aplikasi berisi beberapa proses bisnis, satu atau lebih yang membutuhkan Pengguna untuk menyelesaikan dengan sukses, dan satu atau lebih yang dapat diselesaikan dengan sukses tanpa pengguna, maka pengecualian harus dibuang lebih jauh ke tumpukan panggilan, lebih dekat ke tempat proses bisnis yang membutuhkan Pengguna memanggil utas eksekusi ini. Metode antara metode ini dan titik itu (di mana pengecualian dilemparkan) harus hanya berkomunikasi bahwa tidak ada pengguna (null, boolean, apa pun - ini adalah detail implementasi).

Tetapi jika semua proses dalam aplikasi membutuhkan pengguna, saya masih akan membuang pengecualian dalam metode ini ...

Charles Bretana
sumber
-1 ke downvoter, +1 ke Charles - ini sepenuhnya merupakan pertanyaan bisnis dan tidak ada praktik terbaik untuk ini.
Austin Salonen
Itu sedang melintasi sungai. Apakah atau tidak itu adalah "kondisi kesalahan" didorong oleh logika bisnis. Cara mengatasinya adalah keputusan arsitektur aplikasi. Logika bisnis tidak akan menentukan bahwa null akan dikembalikan, hanya saja persyaratannya dipenuhi. Jika bisnis menentukan jenis metode pengembalian, maka mereka terlalu terlibat dalam aspek teknis implementasi.
Joseph Ferris
@ joseph, prinsip inti di balik "penanganan pengecualian terstruktur" adalah bahwa pengecualian harus dilemparkan ketika metode tidak dapat menyelesaikan fungsi apa pun yang mereka kodekan untuk implementasikan. Anda benar, bahwa jika fungsi bisnis metode ini telah dikodekan untuk diterapkan dapat "berhasil diselesaikan", (apa pun artinya dalam Model Domain), maka Anda tidak perlu melempar pengecualian, Anda dapat mengembalikan nol , atau variabel "FoundUser" boolean, atau apa pun ... Bagaimana Anda berkomunikasi dengan metode panggilan yang tidak ditemukan pengguna kemudian menjadi detail implementasi teknis.
Charles Bretana
10

Saya pribadi akan mengembalikan nol, karena itulah yang saya harapkan lapisan DAL / Repositori untuk bertindak.

Jika tidak ada, jangan kembalikan apa pun yang dapat ditafsirkan sebagai berhasil mengambil objek, nullberfungsi dengan baik di sini.

Yang paling penting adalah konsisten di seluruh Lapisan DAL / Repos Anda, sehingga Anda tidak bingung bagaimana menggunakannya.

Alex Moore
sumber
7

saya cenderung

  • return nulljika id objek tidak ada saat itu tidak diketahui sebelumnya apakah itu harus ada.
  • throwjika id objek tidak ada saat itu seharusnya ada.

Saya membedakan dua skenario ini dengan tiga jenis metode ini. Pertama:

Boolean TryGetSomeObjectById(Int32 id, out SomeObject o)
{
    if (InternalIdExists(id))
    {
        o = InternalGetSomeObject(id);

        return true;
    }
    else
    {
        return false;
    }
}

Kedua:

SomeObject FindSomeObjectById(Int32 id)
{
    SomeObject o;

    return TryGetObjectById(id, out o) ? o : null;
}

Ketiga:

SomeObject GetSomeObjectById(Int32 id)
{
    SomeObject o;

    if (!TryGetObjectById(id, out o))
    {
        throw new SomeAppropriateException();
    }

    return o;
}
Johann Gerell
sumber
Maksud Anda outtidakref
Matt Ellen
@ Matt: Ya, Pak, saya pasti melakukannya! Tetap.
Johann Gerell
2
Sungguh, ini adalah satu-satunya jawaban yang cocok untuk semua, dan karenanya kebenaran sepenuhnya dan semata-mata! :) Ya, itu tergantung pada asumsi yang didasarkan pada metode yang dipanggil ... Jadi pertama-tama jelaskan asumsi ini dan kemudian pilih kombinasi yang tepat dari yang di atas. Harus menggulir terlalu banyak untuk sampai ke sini :) +100
yair
Ini tampaknya merupakan pola yang digunakan, kecuali itu tidak mendukung metode async. Saya mereferensikan jawaban ini dan menambahkan solusi async dengan Tuple Literals -> di sini
ttugates
6

Namun pendekatan lain melibatkan lewat objek callback atau delegasi yang akan beroperasi pada nilai. Jika nilai tidak ditemukan, panggilan balik tidak disebut.

public void GetUserById(Guid id, UserCallback callback)
{
    // Lookup user
    if (userFound)
        callback(userEntity);  // or callback.Call(userEntity);
}

Ini berfungsi dengan baik ketika Anda ingin menghindari cek nol di seluruh kode Anda, dan ketika tidak menemukan nilai bukan kesalahan. Anda juga dapat memberikan panggilan balik ketika tidak ada objek yang ditemukan jika Anda memerlukan pemrosesan khusus.

public void GetUserById(Guid id, UserCallback callback, NotFoundCallback notFound)
{
    // Lookup user
    if (userFound)
        callback(userEntity);  // or callback.Call(userEntity);
    else
        notFound(); // or notFound.Call();
}

Pendekatan yang sama menggunakan objek tunggal mungkin terlihat seperti:

public void GetUserById(Guid id, UserCallback callback)
{
    // Lookup user
    if (userFound)
        callback.Found(userEntity);
    else
        callback.NotFound();
}

Dari perspektif desain, saya sangat suka pendekatan ini, tetapi memiliki kelemahan membuat situs panggilan lebih besar dalam bahasa yang tidak dengan mudah mendukung fungsi kelas satu.

Marc
sumber
Menarik. Ketika Anda mulai berbicara tentang delegasi, saya langsung mulai bertanya-tanya apakah ekspresi Lambda dapat digunakan di sini.
7wp
Ya! Seperti yang saya pahami, sintaksis C # 3.0 dan yang lebih baru pada dasarnya adalah gula sintaksis untuk delegasi anonim. Demikian pula di Jawa, tanpa lambda yang bagus atau sintaks delegasi anonim, Anda bisa membuat kelas anonim. Agak jelek, tapi bisa sangat berguna. Saya kira hari ini, contoh C # saya bisa menggunakan Func <UserEntity> atau sesuatu seperti itu alih-alih delegasi bernama, tetapi proyek C # terakhir saya masih menggunakan versi 2.
Marc
+1 Saya suka pendekatan ini. Masalahnya adalah bahwa itu tidak konvensional dan sedikit meningkatkan penghalang untuk masuk ke basis kode.
timoxley
4

Kami menggunakan CSLA.NET, dan dibutuhkan pandangan bahwa pengambilan data yang gagal harus mengembalikan objek "kosong". Ini sebenarnya cukup menjengkelkan, karena menuntut konvensi memeriksa apakah obj.IsNewbukan obj == null.

Seperti poster sebelumnya yang disebutkan, nilai pengembalian nol akan menyebabkan kode gagal segera, mengurangi kemungkinan masalah siluman yang disebabkan oleh objek kosong.

Secara pribadi, saya pikir nulllebih elegan.

Ini adalah kasus yang sangat umum, dan saya terkejut bahwa orang-orang di sini tampaknya terkejut dengan hal itu: pada aplikasi web apa pun, data sering diambil menggunakan parameter querystring, yang jelas-jelas dapat dikacaukan, sehingga mengharuskan pengembang menangani insiden "tidak ditemukan" ".

Anda dapat menangani ini dengan:

if (User.Exists (id)) {
  this.User = User.Fetch (id);
} lain {
  Response.Redirect ("~ / notfound.aspx");
}

... tapi itu panggilan tambahan ke database setiap saat, yang mungkin menjadi masalah pada halaman dengan lalu lintas tinggi. Sedangkan:

this.User = User.Fetch (id);

if (this.User == null) {
  Response.Redirect ("~ / notfound.aspx");
}

... hanya membutuhkan satu panggilan.

Keith Williams
sumber
4

Saya lebih suka null, karena kompatibel dengan operator null-coalescing ( ??).

tak seorangpun
sumber
4

Saya akan mengatakan return null bukan objek kosong.

Tetapi contoh spesifik yang telah Anda sebutkan di sini, Anda sedang mencari pengguna dengan id pengguna, yang merupakan semacam kunci untuk pengguna itu, dalam hal ini saya mungkin ingin melemparkan pengecualian jika tidak ada instance instance pengguna ditemukan .

Ini adalah aturan yang biasanya saya ikuti:

  • Jika tidak ada hasil yang ditemukan pada pencarian dengan operasi kunci utama, lempar ObjectNotFoundException.
  • Jika tidak ada hasil yang ditemukan pada pencarian dengan kriteria lain, kembalikan nol.
  • Jika tidak ada hasil ditemukan pada penemuan dengan kriteria non-kunci yang dapat mengembalikan beberapa objek mengembalikan koleksi kosong.
Partha Choudhury
sumber
Mengapa Anda melemparkan pengecualian dalam kasus-kasus ini? Terkadang pengguna tidak ada dalam database, dan kami berharap itu tidak terjadi. Itu bukan perilaku yang luar biasa.
siride
3

Ini akan bervariasi berdasarkan konteks, tetapi saya umumnya akan mengembalikan nol jika saya mencari satu objek tertentu (seperti dalam contoh Anda) dan mengembalikan koleksi kosong jika saya mencari satu set objek tetapi tidak ada.

Jika Anda membuat kesalahan dalam kode Anda dan mengembalikan null mengarah ke pengecualian null pointer, maka semakin cepat Anda menangkap bahwa semakin baik. Jika Anda mengembalikan objek kosong, penggunaan awal mungkin berhasil, tetapi Anda mungkin mendapatkan kesalahan nanti.

Jacob Mattison
sumber
+1 Saya mempertanyakan logika yang sama dengan yang Anda katakan di sini, itulah sebabnya saya memposting pertanyaan untuk melihat pendapat orang lain tentang ini
7wp
3

Yang terbaik dalam hal ini mengembalikan "null" dalam kasus tidak ada pengguna tersebut. Jadikan metode Anda statis.

Edit:

Biasanya metode seperti ini adalah anggota beberapa kelas "Pengguna" dan tidak memiliki akses ke anggota contohnya. Dalam hal ini metode harus statis, jika tidak, Anda harus membuat turunan dari "Pengguna" dan kemudian memanggil metode GetUserById yang akan mengembalikan contoh "Pengguna" yang lain. Setuju ini membingungkan. Tetapi jika metode GetUserById adalah anggota dari beberapa kelas "DatabaseFactory" - tidak ada masalah untuk meninggalkannya sebagai anggota instance.

Kamarey
sumber
Bisakah saya bertanya mengapa saya ingin membuat metode saya statis? Bagaimana jika saya ingin menggunakan Injeksi Ketergantungan?
7wp
Ok sekarang saya mengerti logikanya. Tapi saya tetap dengan pola Repositori, dan saya suka menggunakan injeksi dependensi untuk repositori saya karena itu saya tidak bisa menggunakan metode statis. Tetapi +1 memberi saran untuk mengembalikan null :)
7wp
3

Saya pribadi mengembalikan instance default objek. Alasannya adalah bahwa saya mengharapkan metode untuk mengembalikan nol ke banyak atau nol ke satu (tergantung pada tujuan metode). Satu-satunya alasan bahwa itu akan menjadi keadaan kesalahan dalam bentuk apa pun, menggunakan pendekatan ini, adalah jika metode tidak mengembalikan objek (s) dan selalu diharapkan (dalam hal pengembalian satu ke banyak atau tunggal).

Adapun asumsi bahwa ini adalah pertanyaan domain bisnis - saya hanya tidak melihatnya dari sisi persamaan. Normalisasi tipe pengembalian adalah pertanyaan arsitektur aplikasi yang valid. Paling tidak, itu tunduk pada standardisasi dalam praktik pengkodean. Saya ragu bahwa ada pengguna bisnis yang akan mengatakan "dalam skenario X, beri mereka nol".

Joseph Ferris
sumber
+1 Saya suka tampilan alternatif masalah. Jadi pada dasarnya Anda mengatakan pendekatan mana yang saya pilih harus baik-baik saja asalkan metode ini konsisten di seluruh aplikasi?
7wp
1
Itu keyakinan saya. Saya pikir konsistensi sangat penting. Jika Anda melakukan banyak hal di banyak tempat, itu menimbulkan risiko lebih tinggi untuk bug baru. Kami secara pribadi telah menggunakan pendekatan objek default, karena ini bekerja dengan baik dengan pola Essence yang kami gunakan dalam model domain kami. Kami memiliki metode ekstensi generik tunggal yang dapat kami uji terhadap semua objek domain untuk memberi tahu kami apakah populasinya atau tidak, sehingga kami tahu bahwa DO apa pun dapat diuji dengan panggilan objek.IsDefault () - menghindari pemeriksaan kesetaraan secara langsung .
Joseph Ferris
3

Di Objek Bisnis kami, kami memiliki 2 metode Get utama:

Untuk menjaga hal-hal sederhana dalam konteks atau Anda mempertanyakan mereka akan menjadi:

// Returns null if user does not exist
public UserEntity GetUserById(Guid userId)
{
}

// Returns a New User if user does not exist
public UserEntity GetNewOrExistingUserById(Guid userId)
{
}

Metode pertama digunakan ketika mendapatkan entitas tertentu, metode kedua digunakan secara khusus saat menambahkan atau mengedit entitas di halaman web.

Ini memungkinkan kita untuk memiliki yang terbaik dari kedua dunia dalam konteks di mana mereka digunakan.

Mark Redman
sumber
3

Saya seorang mahasiswa IT Prancis, jadi permisi bahasa Inggris saya yang buruk. Di kelas kami, kami diberitahu bahwa metode seperti itu tidak boleh mengembalikan null, atau objek kosong. Pengguna metode ini seharusnya memeriksa dulu bahwa objek yang ia cari ada sebelum mencoba mendapatkannya.

Menggunakan Java, kami diminta untuk menambahkan assert exists(object) : "You shouldn't try to access an object that doesn't exist";di awal metode apa pun yang dapat mengembalikan nol, untuk mengekspresikan "prasyarat" (Saya tidak tahu apa kata dalam bahasa Inggris).

IMO ini benar-benar tidak mudah digunakan tetapi itulah yang saya gunakan, menunggu sesuatu yang lebih baik.

Saend
sumber
1
Terima kasih atas jawaban anda Tetapi saya tidak menyukai gagasan untuk memeriksa terlebih dahulu jika ada. Alasannya adalah bahwa menghasilkan permintaan tambahan ke database. Dalam aplikasi yang sedang diakses oleh jutaan orang dalam sehari ini dapat mengakibatkan hilangnya kinerja yang dramatis.
7wp
1
Salah satu manfaatnya adalah bahwa pemeriksaan keberadaannya abstrak dengan tepat: if (userExists) sedikit lebih mudah dibaca, lebih dekat ke domain masalah, & lebih sedikit 'komputer' daripada: jika (pengguna == null)
timoxley
Dan saya berpendapat bahwa 'jika (x == null)' adalah pola yang sudah ada beberapa dekade yang jika Anda belum pernah melihatnya sebelumnya, Anda belum menulis kode terlalu lama (dan Anda harus terbiasa seperti itu di jutaan baris kode). "Komputer"? Kita berbicara tentang akses basis data ...
Lloyd Sargent
3

Jika kasus pengguna tidak ditemukan cukup sering muncul, dan Anda ingin mengatasinya dengan berbagai cara tergantung pada keadaan (kadang-kadang melemparkan pengecualian, kadang-kadang mengganti pengguna kosong) Anda juga bisa menggunakan sesuatu yang dekat dengan tipe F # Optionatau Haskell Maybe, yang secara eksplisit memisahkan case 'no value' dari 'found something!'. Kode akses basis data dapat terlihat seperti ini:

public Option<UserEntity> GetUserById(Guid userId)
{
 //Imagine some code here to access database.....

 //Check if data was returned and return a null if none found
 if (!DataExists)
    return Option<UserEntity>.Nothing; 
 else
    return Option.Just(existingUserEntity);
}

Dan digunakan seperti ini:

Option<UserEntity> result = GetUserById(...);
if (result.IsNothing()) {
    // deal with it
} else {
    UserEntity value = result.GetValue();
}

Sayangnya, semua orang tampaknya menggulung tipe seperti ini sendiri.

yatima2975
sumber
2

Saya biasanya mengembalikan nol. Ini menyediakan mekanisme cepat dan mudah untuk mendeteksi jika ada sesuatu yang kacau tanpa membuang pengecualian dan menggunakan banyak mencoba / menangkap semua tempat.

Apa namanya
sumber
2

Untuk tipe koleksi saya akan mengembalikan Koleksi Kosong, untuk semua tipe lainnya saya lebih suka menggunakan pola NullObject untuk mengembalikan objek yang mengimplementasikan antarmuka yang sama dengan tipe yang kembali. untuk perincian tentang pola, periksa tautan teks

Menggunakan pola NullObject ini akan menjadi: -

public UserEntity GetUserById(Guid userId)

{// Bayangkan beberapa kode di sini untuk mengakses basis data .....

 //Check if data was returned and return a null if none found
 if (!DataExists)
    return new NullUserEntity(); //Should I be doing this here instead? return new UserEntity();  
 else
    return existingUserEntity;

}

class NullUserEntity: IUserEntity { public string getFirstName(){ return ""; } ...} 
vikram nayak
sumber
2

Untuk menempatkan apa yang orang lain katakan dengan cara empuk ...

Pengecualian untuk keadaan Luar Biasa

Jika metode ini adalah lapisan akses data murni, saya akan mengatakan bahwa mengingat beberapa parameter yang akan dimasukkan dalam pernyataan pilih, itu akan berharap bahwa saya mungkin tidak menemukan baris untuk membangun objek, dan karena itu mengembalikan nol akan dapat diterima karena ini adalah logika akses data.

Di sisi lain, jika saya mengharapkan parameter saya untuk mencerminkan kunci utama dan saya hanya akan mendapatkan satu baris kembali, jika saya mendapatkan lebih dari satu kembali saya akan melemparkan pengecualian. 0 ok untuk mengembalikan nol, 2 tidak.

Sekarang, jika saya memiliki beberapa kode masuk yang diperiksa terhadap penyedia LDAP kemudian diperiksa terhadap DB untuk mendapatkan detail lebih lanjut dan saya berharap itu harus disinkronkan setiap saat, saya mungkin akan melemparkan pengecualian itu. Seperti yang orang lain katakan, itu aturan bisnis.

Sekarang saya akan mengatakan itu adalah aturan umum . Ada saat-saat di mana Anda mungkin ingin memecahkannya. Namun, pengalaman dan percobaan saya dengan C # (banyak itu) dan Java (sedikit dari itu) telah mengajarkan saya bahwa jauh lebih mahal kinerja bijaksana untuk menangani pengecualian daripada menangani masalah yang dapat diprediksi melalui logika kondisional. Saya berbicara dengan nada 2 atau 3 kali lipat lebih mahal dalam beberapa kasus. Jadi, jika mungkin kode Anda bisa berakhir dalam satu lingkaran, maka saya akan menyarankan mengembalikan nol dan mengujinya.

Jim L.
sumber
2

Maafkan pseudo-php / code saya.

Saya pikir itu benar-benar tergantung pada tujuan penggunaan hasil.

Jika Anda bermaksud mengedit / memodifikasi nilai balik dan menyimpannya, maka kembalikan objek kosong. Dengan begitu, Anda bisa menggunakan fungsi yang sama untuk mengisi data pada objek baru atau yang sudah ada.

Katakanlah saya memiliki fungsi yang mengambil kunci utama dan array data, mengisi baris dengan data, lalu menyimpan catatan yang dihasilkan ke db. Karena saya bermaksud mengisi objek dengan data saya, itu bisa menjadi keuntungan besar untuk mendapatkan objek kosong kembali dari pengambil. Dengan begitu, saya bisa melakukan operasi yang identik dalam kedua kasus. Anda menggunakan hasil dari fungsi pengambil tidak peduli apa.

Contoh:

function saveTheRow($prim_key, $data) {
    $row = getRowByPrimKey($prim_key);

    // Populate the data here

    $row->save();
}

Di sini kita dapat melihat bahwa rangkaian operasi yang sama memanipulasi semua catatan jenis ini.

Namun, jika tujuan akhir dari nilai pengembalian adalah untuk membaca dan melakukan sesuatu dengan data, maka saya akan mengembalikan nol. Dengan cara ini, saya dapat dengan cepat menentukan apakah tidak ada data yang dikembalikan dan menampilkan pesan yang sesuai kepada pengguna.

Biasanya, saya akan menangkap pengecualian dalam fungsi saya yang mengambil data (jadi saya bisa mencatat pesan kesalahan, dll ...) lalu mengembalikan null langsung dari tangkapan. Secara umum tidak masalah bagi pengguna akhir apa masalahnya, jadi saya menemukan yang terbaik untuk merangkum kesalahan saya logging / pemrosesan secara langsung dalam fungsi yang mendapatkan data. Jika Anda mempertahankan basis kode bersama di perusahaan besar ini, ini sangat menguntungkan karena Anda dapat memaksa kesalahan logging / penanganan bahkan pada pemrogram yang paling malas sekalipun.

Contoh:

function displayData($row_id) {
    // Logging of the error would happen in this function
    $row = getRow($row_id);
    if($row === null) {
        // Handle the error here
    }

    // Do stuff here with data
}

function getRow($row_id) {
 $row = null;
 try{
     if(!$db->connected()) {
   throw excpetion("Couldn't Connect");
  }

  $result = $db->query($some_query_using_row_id);

  if(count($result) == 0 ) {
   throw new exception("Couldn't find a record!");
  }

  $row = $db->nextRow();

 } catch (db_exception) {
  //Log db conn error, alert admin, etc...
  return null; // This way I know that null means an error occurred
 }
 return $row;
}

Itu aturan umum saya. Sejauh ini sudah bekerja dengan baik.


sumber
2

Pertanyaan yang menarik dan saya pikir tidak ada jawaban "benar", karena selalu tergantung pada tanggung jawab kode Anda. Apakah metode Anda tahu apakah tidak ada data yang ditemukan bermasalah atau tidak? Dalam kebanyakan kasus, jawabannya adalah "tidak" dan itulah sebabnya mengembalikan nol dan membiarkan penelepon yang menangani situasinya sempurna.

Mungkin pendekatan yang baik untuk membedakan metode melempar dari metode null-return adalah dengan menemukan konvensi di tim Anda: Metode yang mengatakan mereka "mendapatkan" sesuatu harus melempar pengecualian jika tidak ada yang didapat. Metode yang mengembalikan null dapat dinamai berbeda, mungkin "Cari ..." sebagai gantinya.

Marc Wittke
sumber
+1 Saya menyukai gagasan untuk menggunakan konvensi penamaan yang seragam untuk memberi isyarat kepada programmer bagaimana fungsi itu dikonsumsi.
7wp
1
Tiba-tiba saya menyadari bahwa inilah yang dilakukan LINQ: pertimbangkan First (...) vs FirstOrDefault (...)
Marc Wittke
2

Jika objek yang dikembalikan adalah sesuatu yang dapat diulangi, saya akan mengembalikan objek yang kosong, sehingga saya tidak perlu menguji nol terlebih dahulu.

Contoh:

bool IsAdministrator(User user)
{
    var groupsOfUser = GetGroupsOfUser(user);

    // This foreach would cause a run time exception if groupsOfUser is null.
    foreach (var groupOfUser in groupsOfUser) 
    {
        if (groupOfUser.Name == "Administrators")
        {
            return true;
        }
    }

    return false;
}
Jan Aagaard
sumber
2

Saya suka tidak mengembalikan null dari metode apa pun, tetapi menggunakan tipe fungsional Option. Metode yang dapat mengembalikan tanpa hasil mengembalikan Opsi kosong, bukan nol.

Juga, metode seperti itu yang dapat mengembalikan tanpa hasil harus menunjukkan bahwa melalui nama mereka. Saya biasanya meletakkan Try atau TryGet atau TryFind di awal nama metode untuk menunjukkan bahwa ia dapat mengembalikan hasil yang kosong (misalnya TryFindCustomer, TryLoadFile, dll.).

Itu memungkinkan penelepon menerapkan teknik yang berbeda, seperti pipelining koleksi (lihat Koleksi Pipa Martin Fowler ) pada hasilnya.

Berikut adalah contoh lain di mana Opsi kembali bukan nol digunakan untuk mengurangi kompleksitas kode: Cara Mengurangi Kompleksitas Siklomatik: Opsi Jenis Fungsional

Zoran Horvat
sumber
1
Saya menulis jawaban, saya bisa melihatnya mirip dengan Anda ketika saya menggulir ke atas, dan saya setuju, Anda dapat menerapkan jenis opsi dengan koleksi generik dengan 0 atau 1 elemen. Terima kasih atas tautan tambahannya.
Gabriel P.
1

Lebih banyak daging untuk digiling: katakanlah DAL saya mengembalikan NULL untuk GetPersonByID seperti yang disarankan oleh beberapa orang. Apa yang harus saya lakukan (agak tipis) BLL jika menerima NULL? Lewati NULL itu dan biarkan konsumen akhir mengkhawatirkannya (dalam hal ini, halaman ASP.Net)? Bagaimana kalau BLL melempar pengecualian?

BLL dapat digunakan oleh ASP.Net dan Win App, atau perpustakaan kelas lainnya - Saya pikir tidak adil untuk mengharapkan konsumen akhir untuk secara intrinsik "tahu" bahwa metode GetPersonByID mengembalikan nol (kecuali jika jenis null digunakan, saya kira ).

Pandangan saya (untuk apa nilainya) adalah bahwa DAL saya mengembalikan NULL jika tidak ada yang ditemukan. UNTUK BEBERAPA BENDA, tidak apa-apa - bisa jadi 0: banyak daftar hal, jadi tidak punya barang apa-apa baik-baik saja (misalnya daftar buku favorit). Dalam hal ini, BLL saya mengembalikan daftar kosong. Untuk sebagian besar hal entitas tunggal (misalnya pengguna, akun, faktur) jika saya tidak memilikinya, maka itu jelas merupakan masalah dan membuang pengecualian yang mahal. Namun, melihat mengambil pengguna dengan pengidentifikasi unik yang sebelumnya diberikan oleh aplikasi harus selalu mengembalikan pengguna, pengecualian adalah pengecualian "tepat", karena di dalamnya luar biasa. Konsumen akhir BLL (ASP.Net, f'rinstance) hanya berharap hal-hal menjadi keren, sehingga Handler Exception Handler akan digunakan alih-alih membungkus setiap panggilan tunggal ke GetPersonByID dalam blok coba-tangkap.

Jika ada masalah mencolok dalam pendekatan saya, harap beri tahu saya karena saya selalu ingin belajar. Seperti yang dikatakan poster lainnya, pengecualian adalah hal yang mahal, dan pendekatan "memeriksa dulu" itu baik, tetapi pengecualian harus hanya itu - luar biasa.

Saya menikmati posting ini, banyak saran bagus untuk skenario "tergantung" :-)

Mike Kingscott
sumber
Dan tentu saja, hari ini saya telah menemukan skenario di mana saya akan mengembalikan NULL dari BLL saya ;-) Yang mengatakan, saya masih bisa melempar pengecualian dan menggunakan try / catch di kelas konsumsi TETAPI saya masih punya masalah : bagaimana kelas konsumsi saya tahu untuk menggunakan try / catch, serupa bagaimana mereka tahu untuk memeriksa NULL?
Mike Kingscott
Anda dapat mendokumentasikan bahwa suatu metode melempar pengecualian melalui doctag @throws dan Anda akan mendokumentasikan fakta bahwa ia dapat mengembalikan nol dalam doctag @return.
timoxley
1

Saya pikir fungsi tidak boleh mengembalikan nol, untuk kesehatan basis kode Anda. Saya dapat memikirkan beberapa alasan:

Akan ada sejumlah besar klausa penjaga yang memperlakukan referensi nol if (f() != null).

Apa itu null, apakah itu jawaban yang diterima atau masalah? Apakah null keadaan yang valid untuk objek tertentu? (bayangkan Anda adalah klien untuk kode). Maksud saya semua jenis referensi bisa nol, tetapi haruskah mereka?

Memiliki nullberkeliaran akan hampir selalu memberikan beberapa pengecualian NullRef tak terduga dari waktu ke waktu sebagai kode-dasar Anda tumbuh.

Ada beberapa solusi, tester-doer patternatau menerapkan option typepemrograman fungsional.

Gabriel P.
sumber
0

Saya bingung dengan jumlah jawaban (di seluruh web) yang mengatakan bahwa Anda memerlukan dua metode: metode "IsItThere ()" dan metode "GetItForMe ()" dan ini menyebabkan kondisi perlombaan. Apa yang salah dengan fungsi yang mengembalikan null, menugaskannya ke variabel, dan memeriksa variabel untuk Null semua dalam satu tes? Kode C lama saya dibumbui dengan

if (NULL! = (variabel = fungsi (argumen ...))) {

Jadi, Anda mendapatkan nilai (atau nol) dalam suatu variabel, dan hasilnya sekaligus. Apakah idiom ini dilupakan? Mengapa?

tidak ada comprende
sumber
0

Saya setuju dengan sebagian besar posting di sini, yang cenderung ke arah null.

Alasan saya adalah bahwa menghasilkan objek kosong dengan properti yang tidak dapat dibatalkan dapat menyebabkan bug. Misalnya, entitas dengan int IDproperti akan memiliki nilai awal ID = 0, yang merupakan nilai yang sepenuhnya valid. Jika objek itu, dalam keadaan tertentu, disimpan ke database, itu akan menjadi hal yang buruk.

Untuk apa pun dengan iterator saya akan selalu menggunakan koleksi kosong. Sesuatu seperti

foreach (var eachValue in collection ?? new List<Type>(0))

adalah bau kode menurut saya. Properti koleksi tidak boleh nol, selamanya.

Kasing tepi adalah String. Banyak orang mengatakan, String.IsNullOrEmptytidak benar-benar diperlukan, tetapi Anda tidak selalu dapat membedakan antara string kosong dan null. Selain itu, beberapa sistem basis data (Oracle) tidak akan membedakan keduanya ( ''disimpan sebagai DBNULL), jadi Anda dipaksa untuk menanganinya secara merata. Alasannya adalah, sebagian besar nilai string berasal dari input pengguna atau dari sistem eksternal, sementara kotak teks atau sebagian besar format pertukaran tidak memiliki representasi berbeda untuk ''dan null. Jadi, bahkan jika pengguna ingin menghapus nilai, ia tidak bisa melakukan apa pun selain membersihkan kontrol input. Juga perbedaan nvarcharbidang database nullable dan non-nullable lebih dari dipertanyakan, jika DBMS Anda bukan oracle - bidang wajib yang memungkinkan''aneh, UI Anda tidak akan pernah mengizinkan ini, jadi kendala Anda tidak memetakan. Jadi jawabannya di sini, menurut saya adalah, menanganinya sama, selalu.

Mengenai pertanyaan Anda tentang pengecualian dan kinerja: Jika Anda melemparkan pengecualian yang tidak dapat Anda tangani sepenuhnya dalam logika program Anda, Anda harus membatalkan, pada titik tertentu, apa pun program Anda lakukan, dan meminta pengguna untuk mengulang apa pun yang baru saja ia lakukan. Dalam hal itu, penalti kinerja dari a catchadalah yang paling tidak perlu Anda khawatirkan - harus bertanya kepada pengguna adalah gajah di dalam ruangan (yang berarti merender ulang seluruh UI, atau mengirim beberapa HTML melalui internet). Jadi, jika Anda tidak mengikuti anti-pola " Aliran Program dengan Pengecualian ", jangan repot-repot, lemparkan saja jika itu masuk akal. Bahkan dalam kasus batas, seperti "Pengecualian Validasi", kinerja benar-benar tidak menjadi masalah, karena Anda harus bertanya kepada pengguna lagi, dalam hal apa pun.

Oliver Schimmer
sumber
0

Sebuah Pola Asynchronous TryGet:

Untuk metode sinkron, saya percaya @Johann Gerell ini jawabannya adalah yang pola untuk digunakan dalam semua kasus.

Namun pola TryGet dengan outparameter tidak berfungsi dengan metode Async.

Dengan Tuple Literals C # 7 sekarang Anda dapat melakukan ini:

async Task<(bool success, SomeObject o)> TryGetSomeObjectByIdAsync(Int32 id)
{
    if (InternalIdExists(id))
    {
        o = await InternalGetSomeObjectAsync(id);

        return (true, o);
    }
    else
    {
        return (false, default(SomeObject));
    }
}
ttugate
sumber