Ketika saya mencoba membuat antarmuka untuk program tertentu, saya biasanya berusaha menghindari pengecualian yang bergantung pada input yang tidak divalidasi.
Jadi yang sering terjadi adalah saya sudah memikirkan sepotong kode seperti ini (ini hanya contoh demi contoh, jangan pedulikan fungsi yang dijalankannya, contoh di Jawa):
public static String padToEvenOriginal(int evenSize, String string) {
if (evenSize % 2 == 1) {
throw new IllegalArgumentException("evenSize argument is not even");
}
if (string.length() >= evenSize) {
return string;
}
StringBuilder sb = new StringBuilder(evenSize);
sb.append(string);
for (int i = string.length(); i < evenSize; i++) {
sb.append(' ');
}
return sb.toString();
}
OK, jadi katakan itu evenSize
sebenarnya berasal dari input pengguna. Jadi saya tidak yakin itu adil. Tapi saya tidak ingin memanggil metode ini dengan kemungkinan pengecualian dilemparkan. Jadi saya membuat fungsi berikut:
public static boolean isEven(int evenSize) {
return evenSize % 2 == 0;
}
tapi sekarang saya punya dua pemeriksaan yang melakukan validasi input yang sama: ekspresi dalam if
pernyataan dan cek eksplisit dalam isEven
. Kode duplikat, tidak baik, jadi mari kita refactor:
public static String padToEvenWithIsEven(int evenSize, String string) {
if (!isEven(evenSize)) { // to avoid duplicate code
throw new IllegalArgumentException("evenSize argument is not even");
}
if (string.length() >= evenSize) {
return string;
}
StringBuilder sb = new StringBuilder(evenSize);
sb.append(string);
for (int i = string.length(); i < evenSize; i++) {
sb.append(' ');
}
return sb.toString();
}
OK, itu menyelesaikannya, tapi sekarang kita masuk ke situasi berikut:
String test = "123";
int size;
do {
size = getSizeFromInput();
} while (!isEven(size)); // checks if it is even
String evenTest = padToEvenWithIsEven(size, test);
System.out.println(evenTest); // checks if it is even (redundant)
sekarang kita sudah mendapatkan redundant check: kita sudah tahu bahwa nilainya genap, tetapi padToEvenWithIsEven
masih melakukan pemeriksaan parameter, yang akan selalu mengembalikan true, seperti yang kita sudah memanggil fungsi ini.
Sekarang isEven
tentu saja tidak menimbulkan masalah, tetapi jika pemeriksaan parameter lebih rumit maka ini mungkin menimbulkan biaya terlalu banyak. Selain itu, melakukan panggilan redundan sama sekali tidak terasa benar.
Terkadang kita dapat mengatasi ini dengan memperkenalkan "tipe yang divalidasi" atau dengan membuat fungsi di mana masalah ini tidak dapat terjadi:
public static String padToEvenSmarter(int numberOfBigrams, String string) {
int size = numberOfBigrams * 2;
if (string.length() >= size) {
return string;
}
StringBuilder sb = new StringBuilder(size);
sb.append(string);
for (int i = string.length(); i < size; i++) {
sb.append('x');
}
return sb.toString();
}
tetapi ini membutuhkan pemikiran yang cerdas dan refactor yang cukup besar.
Apakah ada (lebih) cara umum di mana kita dapat menghindari panggilan yang berlebihan isEven
dan melakukan pengecekan parameter ganda? Saya ingin solusi untuk tidak benar-benar memanggil padToEven
dengan parameter yang tidak valid, memicu pengecualian.
Tanpa pengecualian saya tidak bermaksud pemrograman bebas pengecualian, maksud saya bahwa input pengguna tidak memicu pengecualian oleh desain, sedangkan fungsi generik itu sendiri masih berisi pemeriksaan parameter (jika hanya untuk melindungi terhadap kesalahan pemrograman).
sumber
padToEvenWithIsEven
tidak melakukan validasi input pengguna. Ia melakukan pemeriksaan validitas pada inputnya untuk melindungi dirinya dari kesalahan pemrograman dalam kode panggilan. Seberapa luas validasi ini perlu tergantung pada analisis biaya / risiko di mana Anda meletakkan biaya cek terhadap risiko yang dilewati orang yang menulis kode panggilan dalam parameter yang salah.Jawaban:
Dalam contoh Anda, solusi terbaik adalah dengan menggunakan fungsi padding yang lebih umum; jika penelepon ingin pad ke ukuran genap maka mereka dapat memeriksanya sendiri.
Jika Anda berulang kali melakukan validasi yang sama pada suatu nilai, atau hanya ingin membolehkan subset nilai dari suatu jenis, maka jenis mikro / kecil dapat membantu. Untuk keperluan umum utilitas seperti padding, ini bukan ide yang baik, tetapi jika nilai Anda memainkan peran tertentu dalam model domain Anda, menggunakan tipe khusus alih-alih nilai primitif dapat menjadi langkah maju yang baik. Di sini, Anda mungkin mendefinisikan:
Sekarang kamu bisa mendeklarasikan
dan tidak perlu melakukan validasi dari dalam. Untuk pengujian sederhana yang membungkus int di dalam suatu objek kemungkinan akan lebih mahal dalam hal kinerja run time, tetapi menggunakan sistem tipe untuk keuntungan Anda dapat mengurangi bug dan memperjelas desain Anda.
Menggunakan tipe kecil seperti itu bahkan dapat berguna bahkan ketika mereka tidak melakukan validasi, misalnya untuk mendisambiguasikan sebuah string yang mewakili a
FirstName
dariLastName
. Saya sering menggunakan pola ini dalam bahasa yang diketik secara statis.sumber
EvenInteger size; while (size == null) { try { size = new EvenInteger(getSizeFromInput()); } catch(...){}} String result = padStringToEven(size,...);
unsafePadStringToEven
operasi yang tidak melakukan pemeriksaan, tetapi sepertinya ide yang buruk hanya untuk menghindari validasi.Sebagai ekstensi untuk jawaban @ amon, kita dapat menggabungkannya
EvenInteger
dengan apa yang oleh komunitas pemrograman fungsional dapat disebut "Smart Constructor" - sebuah fungsi yang membungkus konstruktor bodoh dan memastikan bahwa objek berada dalam keadaan valid (kita membuat bodoh kelas konstruktor atau dalam bahasa non-kelas berbasis modul / paket pribadi untuk memastikan hanya konstruktor pintar yang digunakan). Caranya adalah dengan mengembalikanOptional
(atau setara) untuk membuat fungsi lebih komposable.Kita kemudian dapat dengan mudah menggunakan
Optional
metode standar untuk menulis logika input:Anda juga bisa menulis
keepAsking()
dalam gaya Java yang lebih idiomatis dengan loop do-while.Kemudian di sisa kode Anda, Anda dapat mengandalkan
EvenInteger
dengan pasti bahwa itu benar-benar akan genap dan cek genap kami hanya pernah ditulis sekali, dalamEvenInteger::of
.sumber
Melakukan validasi dua kali adalah masalah jika hasil validasi sama DAN validasi dilakukan di kelas yang sama. Itu bukan contoh Anda. Dalam kode refactored Anda, pemeriksaan isEven pertama yang dilakukan adalah validasi input, kegagalan menghasilkan input baru yang diminta. Pemeriksaan kedua sepenuhnya independen dari yang pertama, karena pada metode publik padToEvenWithEven yang dapat dipanggil dari luar kelas dan memiliki hasil yang berbeda (pengecualian).
Masalah Anda mirip dengan masalah kode identik yang tidak sengaja dikacaukan karena tidak kering. Anda membingungkan implementasinya, dengan desainnya. Mereka tidak sama, dan hanya karena Anda memiliki satu atau selusin baris yang sama, tidak berarti bahwa mereka melayani tujuan yang sama dan selamanya dapat dianggap dapat dipertukarkan. Juga, mungkin membuat kelas Anda melakukan terlalu banyak, tetapi lewatkan saja karena ini mungkin hanya contoh mainan ...
Jika ini adalah masalah kinerja, Anda dapat menyelesaikan masalah dengan membuat metode pribadi, yang tidak melakukan validasi apa pun, yang disebut padToEvenWithEven publik Anda setelah melakukan validasi itu dan yang akan dipanggil oleh metode lain Anda. Jika ini bukan masalah kinerja, biarkan metode Anda yang berbeda melakukan pengecekan yang mereka perlukan untuk melakukan tugas yang ditugaskan.
sumber