Kami memiliki fungsi API yang memecah jumlah total menjadi jumlah bulanan berdasarkan tanggal mulai dan berakhir yang diberikan.
// JavaScript
function convertToMonths(timePeriod) {
// ... returns the given time period converted to months
}
function getPaymentBreakdown(total, startDate, endDate) {
const numMonths = convertToMonths(endDate - startDate);
return {
numMonths,
monthlyPayment: total / numMonths,
};
}
Baru-baru ini, konsumen untuk API ini ingin menentukan rentang tanggal dengan cara lain: 1) dengan memberikan jumlah bulan alih-alih tanggal akhir, atau 2) dengan memberikan pembayaran bulanan dan menghitung tanggal akhir. Menanggapi hal ini, tim API mengubah fungsi menjadi sebagai berikut:
// JavaScript
function addMonths(date, numMonths) {
// ... returns a new date numMonths after date
}
function getPaymentBreakdown(
total,
startDate,
endDate /* optional */,
numMonths /* optional */,
monthlyPayment /* optional */,
) {
let innerNumMonths;
if (monthlyPayment) {
innerNumMonths = total / monthlyPayment;
} else if (numMonths) {
innerNumMonths = numMonths;
} else {
innerNumMonths = convertToMonths(endDate - startDate);
}
return {
numMonths: innerNumMonths,
monthlyPayment: total / innerNumMonths,
endDate: addMonths(startDate, innerNumMonths),
};
}
Saya merasa perubahan ini menyulitkan API. Sekarang penelepon perlu khawatir tentang heuristik yang tersembunyi dengan pelaksanaan fungsi dalam menentukan parameter mengambil preferensi dalam yang digunakan untuk menghitung rentang tanggal (yaitu dengan urutan prioritas monthlyPayment
, numMonths
, endDate
). Jika seorang penelepon tidak memperhatikan tanda tangan fungsi, mereka mungkin mengirim beberapa parameter opsional dan bingung mengapa endDate
diabaikan. Kami menentukan perilaku ini dalam dokumentasi fungsi.
Selain itu saya merasa itu menetapkan preseden buruk dan menambahkan tanggung jawab ke API yang seharusnya tidak menjadi perhatiannya (yaitu melanggar SRP). Misalkan konsumen menginginkan fungsi untuk mendukung lebih banyak kasus penggunaan, seperti menghitung total
dari numMonths
dan monthlyPayment
parameter. Fungsi ini akan menjadi semakin rumit dari waktu ke waktu.
Preferensi saya adalah untuk menjaga fungsinya seperti semula dan sebaliknya meminta penelepon untuk menghitung endDate
sendiri. Namun, saya mungkin salah dan bertanya-tanya apakah perubahan yang mereka lakukan merupakan cara yang dapat diterima untuk merancang fungsi API.
Atau, apakah ada pola umum untuk menangani skenario seperti ini? Kami dapat menyediakan fungsi tingkat tinggi tambahan di API kami yang membungkus fungsi asli, tetapi ini membengkak API. Mungkin kita bisa menambahkan parameter flag tambahan yang menentukan pendekatan mana yang akan digunakan di dalam fungsi.
sumber
Date
- Anda dapat menyediakan string dan dapat diurai untuk menentukan tanggal. Namun, cara ini oh menangani parameter juga bisa sangat rewel dan mungkin menghasilkan hasil yang tidak dapat diandalkan. LihatDate
lagi. Ini tidak mungkin dilakukan dengan benar - Momen menanganinya dengan lebih baik tetapi sangat menjengkelkan untuk digunakan.monthlyPayment
diberikan tetapitotal
bukan kelipatan bilangan bulat dari itu. Dan juga bagaimana menghadapi kemungkinan kesalahan roundoff floating point jika nilainya tidak dijamin bilangan bulat (mis. Coba dengantotal = 0.3
danmonthlyPayment = 0.1
).Jawaban:
Melihat implementasinya, menurut saya apa yang sebenarnya Anda perlukan di sini adalah 3 fungsi berbeda, bukan satu:
Yang asli:
Yang memberikan jumlah bulan, bukan tanggal akhir:
dan yang memberikan pembayaran bulanan dan menghitung tanggal akhir:
Sekarang, tidak ada parameter opsional lagi, dan itu harus cukup jelas fungsi mana yang disebut bagaimana dan untuk tujuan apa. Seperti yang disebutkan dalam komentar, dalam bahasa yang diketik dengan ketat, orang juga dapat menggunakan fungsi yang berlebihan, membedakan 3 fungsi yang berbeda tidak harus dengan nama mereka, tetapi dengan tanda tangan mereka, jika hal ini tidak mengaburkan tujuan mereka.
Perhatikan fungsi-fungsi yang berbeda tidak berarti Anda harus menduplikasi logika apa pun - secara internal, jika fungsi-fungsi ini memiliki algoritma yang sama, itu harus di refactored ke fungsi "pribadi".
Saya tidak berpikir ada "pola" (dalam arti pola desain GoF) yang menggambarkan desain API yang baik. Menggunakan nama yang menggambarkan diri sendiri, fungsi dengan parameter lebih sedikit, fungsi dengan parameter ortogonal (= independen), hanyalah prinsip dasar untuk membuat kode yang dapat dibaca, dipelihara, dan dikembangkan. Tidak setiap ide bagus dalam pemrograman selalu merupakan "pola desain".
sumber
getPaymentBreakdown
(atau benar-benar salah satu dari 3) dan dua fungsi lainnya hanya mengkonversi argumen dan menyebutnya. Mengapa menambahkan fungsi pribadi yang merupakan salinan sempurna dari salah satu dari 3 ini?innerNumMonths
,total
danstartDate
. Mengapa menyimpan fungsi yang terlalu rumit dengan 5 parameter, di mana 3 hampir opsional (kecuali satu harus ditetapkan), ketika fungsi 3-parameter akan melakukan pekerjaan dengan baik?getPaymentBreakdown(total, startDate, endDate)
fungsi publik sebagai implementasi umum, alat lainnya hanya akan menghitung total / mulai / tanggal akhir yang sesuai dan menyebutnya.getPaymentBreakdown
dalam pertanyaan.Anda benar sekali.
Ini juga tidak ideal, karena kode pemanggil akan tercemar dengan pelat ketel yang tidak terkait.
Memperkenalkan tipe baru, seperti
DateInterval
. Tambahkan konstruktor apa pun yang masuk akal (tanggal mulai + tanggal akhir, tanggal mulai + num bulan, apa pun.). Adopsi ini sebagai jenis mata uang umum untuk mengekspresikan interval tanggal / waktu di seluruh sistem Anda.sumber
DateInterval
):calculatePayPeriod(startData, totalPayment, monthlyPayment)
Terkadang ekspresi lancar membantu dalam hal ini:
Diberi cukup waktu untuk mendesain, Anda dapat membuat API solid yang bertindak serupa dengan bahasa khusus domain.
Keuntungan besar lainnya adalah bahwa IDE dengan autocomplete membuat hampir tidak menarik untuk membaca dokumentasi API, seperti intuitif karena kemampuannya yang dapat ditemukan sendiri.
Ada sumber daya di luar sana seperti https://nikas.praninskas.com/javascript/2015/04/26/fluent-javascript/ atau https://github.com/nikaspran/fluent.js tentang topik ini.
Contoh (diambil dari tautan sumber daya pertama):
sumber
forTotalAmount(1234).breakIntoPayments().byPeriod(2).monthly().withPaymentsOf(12.34).byDateRange(saleStart, saleEnd);
forTotalAmountAndBreakIntoPaymentsByPeriodThenMonthlyWithPaymentsOfButByDateRange(1234, 2, 12.34, saleStart, saleEnd);
Nah, dalam bahasa lain, Anda akan menggunakan parameter bernama . Ini dapat ditiru dalam Javscript:
sumber
getPaymentBreakdown(100, today, {endDate: whatever, noOfMonths: 4, monthlyPayment: 20})
.:
bukan=
?Sebagai alternatif, Anda juga dapat memutuskan tanggung jawab menentukan jumlah bulan dan meninggalkannya di luar fungsi Anda:
Dan getpaymentBreakdown akan menerima objek yang akan memberikan jumlah bulan dasar
Mereka akan urutan fungsi yang lebih tinggi mengembalikan misalnya fungsi.
sumber
total
danstartDate
?Dan jika Anda bekerja dengan sistem dengan tipe data serikat / aljabar yang dibedakan, Anda dapat meneruskannya sebagai, katakanlah, a
TimePeriodSpecification
.dan kemudian tidak ada masalah akan terjadi di mana Anda bisa gagal untuk benar-benar mengimplementasikannya dan seterusnya.
sumber