У меня был фрагмент кода, который решил действительно простую проблему — он сформировал две даты, чтобы лучше выглядеть на веб-странице. Выглядело это так:
function formatDateRange(
days: { firstDay: string, lastDay: string }
): string {
let first, last;
first = dayjs(days.firstDay); //dayjs parses string that formatted like this "YYYY-MM-DD"
last = dayjs(days.lastDay); //and returns a wrapper for Date object
if (first.isSame(last, "date")) {
first = first.format("DD MMMM, YYYY")
last = ""
} else if (first.isSame(last, "month")) {
first = first.format("DD") + " ... "
last = last.format("DD, MMMM YYYY")
} else if (first.isSame(last, "year")) {
first = first.format("DD MMM") + " ... "
last = last.format("DD MMM, YYYY")
} else {
first = first.format("DD MMM, YYYY") + " ... "
last = last.format("DD MMM, YYYY")
}
return first + last
}
Затем я решил его реорганизовать и получил следующее:
//adapter that allows to use objects and arrays in function-composition
//EDIT: was called "callable"
function pluckFrom<T extends {}>(obj: T) {
return (key: keyof T) => obj[key]
}
// a function for function-composition
// (same as compose from redux but order is reversed)
function pipe(...funcs: Function[]) {
return funcs.reduce((f1, f2) => (...args: any) => f2(f1(...args)))
}
//provides an interface to apply array of functions to array of data
function apply<A>(maps: Array<(a: A) => any>) {
return {to: (arr: A[]) => arr.map((item, index) => maps[index](item))}
}
//takes array of dates, wrapped in Dayjs object and returns
//true if they're all the same in terms of unit of time
function isSame([day1, ...rest]: Dayjs[]) {
return (unit: UnitType) => rest.every(day => day.isSame(day1, unit))
}
interface DateRange {
firstDay: string;
lastDay: string;
}
//EDIT: removed redundant "same_" prefix
const case_to_format_map = {
"date": ["DD MMMM, YYYY", " "],
"month": ["DD ...", "DD, MMMM YYYY"],
"year": ["DD MMM ... ", "DD MMM, YYYY"],
" ": ["DD MMM, YYYY ... ", "DD MMM, YYYY"]
}
type Case = keyof typeof case_to_format_map
//EDIT: slightly refactored
function formatDateRange({firstDay, lastDay}: DateRange): string {
const
getCase = (days: Dayjs[]) => (
(["date", "month", "year", " "] as UnitType[])
.filter(isSame(days))[0]
) as Case
,
makeFormatter = (f: string) => (d: Dayjs) => d.format(f)
,
getFormatters = pipe(
getCase,
pluckFrom(case_to_format_map),
formats => formats.map(makeFormatter)
)
,
days = [firstDay, lastDay].map(dayjs)
return apply(getFormatters(days)).to(days).join(" ")
}
В нем больше кода, но он мне нравится, потому что теперь у меня есть 4 дополнительные служебные функции, он более читабельный для меня. В остальном я не уверен, что мне стало лучше или хуже. Итак, у меня есть такие вопросы:
Вам это доступно для чтения?
Соответствует ли он функциональному стилю и лучшим практикам?
Я не уверен, что мне когда-нибудь понадобится расширять этот код, но как я могу сделать его более расширяемым?
1 ответ
Я лично предпочитаю первый, но это очень субъективно. От человека, впервые читающего код, я могу бегло просмотреть первую версию и получить хорошее общее представление о том, о чем этот код. Во второй версии я вижу кучу абстрактных функций, и мне требуется больше времени, чтобы увидеть, что на самом деле делает код.
Хотя у второй версии есть некоторые преимущества, я не думаю, что она того стоит в данном конкретном случае, если вы не знаете чего-то о будущем этого кода, чего я не знаю. Я думаю, что я пытаюсь сказать, что вам следует переформатировать свой код таким образом, только если у вас есть для этого веская причина. Например, вы хотите иметь возможность изменять некоторую часть поведения функции во время выполнения или когда она вызывается из разных частей кодовой базы.