У нас есть функция API, которая разбивает общую сумму на ежемесячные суммы на основе заданных дат начала и окончания.
// 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,
};
}
В последнее время потребитель этого API хотел указать диапазон дат другими способами: 1) путем указания количества месяцев вместо даты окончания или 2) путем предоставления ежемесячного платежа и расчета даты окончания. В ответ на это команда API изменила функцию на следующую:
// 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),
};
}
Я чувствую, что это изменение усложняет API. Теперь абонент должен беспокоиться о эвристиках спрятанных с реализацией функции в определении того, какие параметры принимают предпочтение в настоящее время используется для расчета диапазона дат (т.е. в порядке приоритета monthlyPayment
, numMonths
, endDate
). Если вызывающий не обращает внимания на сигнатуру функции, он может отправить несколько необязательных параметров и запутаться в том, почему endDate
игнорируется. Мы определяем это поведение в документации по функциям.
Кроме того, я чувствую, что это создает плохой прецедент и добавляет в API обязанности, которыми он не должен заниматься (то есть нарушает SRP). Пусть дополнительные потребители хотят функции для поддержки большего числа случаев использования, например, вычисления total
от numMonths
и monthlyPayment
параметров. Эта функция со временем будет становиться все более и более сложной.
Я предпочитаю оставить функцию такой, какой она была, и вместо этого требовать, чтобы вызывающая сторона вычисляла endDate
себя. Тем не менее, я могу ошибаться, и мне было интересно, были ли внесенные изменения приемлемым способом разработки функции API.
В качестве альтернативы, есть ли общий шаблон для обработки подобных сценариев? Мы могли бы предоставить дополнительные функции более высокого порядка в нашем API, которые обертывают исходную функцию, но это раздувает API. Возможно, мы могли бы добавить дополнительный параметр флага, определяющий, какой подход использовать внутри функции.
источник
Date
- вы можете поставить строку и может быть проанализирована , чтобы определить дату. Однако этот способ обработки параметров также может быть очень привередливым и может привести к ненадежным результатам. СмотриDate
снова. Это не невозможно сделать правильно - Момент справляется с этим лучше, но это очень раздражает в использовании, несмотря на это.monthlyPayment
задан, ноtotal
не является кратным ему целым числом. А также, как бороться с возможными ошибками округления с плавающей запятой, если значения не гарантируются как целые числа (например, попробуйте сtotal = 0.3
иmonthlyPayment = 0.1
).Ответы:
Видя реализацию, мне кажется, что вам действительно нужно 3 разные функции вместо одной:
Оригинальный:
Тот, который предоставляет количество месяцев вместо даты окончания:
и тот, который обеспечивает ежемесячный платеж и вычисляет дату окончания:
Теперь больше нет необязательных параметров, и должно быть достаточно ясно, какая функция называется как и для какой цели. Как упоминалось в комментариях, в строго типизированном языке можно также использовать перегрузку функций, различая 3 разные функции не обязательно по их имени, но по их сигнатуре, в случае, если это не скрывает их назначение.
Обратите внимание, что разные функции не означают, что вы должны дублировать какую-либо логику - внутренне, если эти функции используют общий алгоритм, его следует преобразовать в «приватную» функцию.
Я не думаю, что существует «шаблон» (в смысле шаблонов проектирования GoF), который описывает хороший дизайн API. Использование самоописываемых имен, функций с меньшим количеством параметров, функций с ортогональными (= независимыми) параметрами - это только основные принципы создания читаемого, поддерживаемого и развивающегося кода. Не каждая хорошая идея в программировании - это обязательно «шаблон дизайна».
источник
getPaymentBreakdown
(или на самом деле любая из этих 3), а две другие функции просто преобразуют аргументы и вызывают это. Зачем добавлять частную функцию, которая является идеальной копией одного из этих 3?innerNumMonths
,total
иstartDate
. Зачем хранить слишком сложную функцию с 5 параметрами, где 3 являются почти необязательными (за исключением того, что нужно установить один), когда функция с 3 параметрами также будет выполнять эту работу?getPaymentBreakdown(total, startDate, endDate)
функцию в качестве общей реализации, а другой инструмент просто вычислит подходящие итоговые / начальные / конечные даты и вызовет ее.getPaymentBreakdown
в вопросе.Вы совершенно правы.
Это также не идеально, потому что код вызывающего абонента будет загрязнен не связанной котельной плитой.
Введите новый тип, как
DateInterval
. Добавьте конструкторы, которые имеют смысл (дата начала + дата окончания, дата начала + число месяцев, что угодно.). Примите это как типы единой валюты для выражения интервалов дат / времени в вашей системе.источник
DateInterval
):calculatePayPeriod(startData, totalPayment, monthlyPayment)
Иногда беглые выражения помогают в этом:
Если у вас будет достаточно времени для разработки, вы сможете создать надежный API, который работает аналогично доменному языку.
Другим большим преимуществом является то, что IDE с автозаполнением делают почти интегрированные неуместным чтение документации API, а также интуитивно понятны благодаря своим самораскрываемым возможностям.
Есть ресурсы, такие как https://nikas.praninskas.com/javascript/2015/04/26/fluent-javascript/ или https://github.com/nikaspran/fluent.js. по этой теме.
Пример (взят из первой ссылки на ресурс):
источник
forTotalAmount(1234).breakIntoPayments().byPeriod(2).monthly().withPaymentsOf(12.34).byDateRange(saleStart, saleEnd);
forTotalAmountAndBreakIntoPaymentsByPeriodThenMonthlyWithPaymentsOfButByDateRange(1234, 2, 12.34, saleStart, saleEnd);
Ну, на других языках вы бы использовали именованные параметры . Это можно эмулировать в Javscript:
источник
getPaymentBreakdown(100, today, {endDate: whatever, noOfMonths: 4, monthlyPayment: 20})
.:
вместо=
?В качестве альтернативы вы также можете снять ответственность за указание номера месяца и оставить его вне своей функции:
И getpaymentBreakdown получит объект, который предоставит базовое количество месяцев
Это будет функция более высокого порядка, возвращающая, например, функцию.
источник
total
иstartDate
параметров?И если бы вы работали с системой с различимыми объединениями / алгебраическими типами данных, вы могли бы передать это как, скажем, a
TimePeriodSpecification
.и тогда ни одна из проблем не возникнет, если вы не сможете реализовать ее и так далее.
источник