s.equalsIgnoreCase(weekDayNames[day.index])) {
return day;
}
}
throw new IllegalArgumentException(
String.format("%s is not a valid weekday string", s));
}
public String toString() {
return dateSymbols.getWeekdays()[index];
}
}
В программе две функции getMonths (строки 288–316); первая функция вызывает вторую. Вторая функция не вызывается никем, кроме первой функцией. Я свернул две функции в одну, что привело к значительному упрощению кода [G9],[G12],[F4]. В завершение я переименовал итоговую функцию, присвоив ей более содержательное имя [N1].
public static String[] getMonthNames() {
return dateFormatSymbols.getMonths();
}
Функция isValidMonthCode (строки 326–346) потеряла актуальность после введения перечисления Month, поэтому я ее удалил [G9].
Функция monthCodeToQuarter (строки 356–375) отдает ФУНКЦИОНАЛЬНОЙ ЗАВИСТЬЮ [Refactoring]; вероятно, ее логичнее включить в перечисление Month в виде метода с именем quarter. Я выполнил замену.
public int quarter() {
return 1 + (index-1)/3;
}
В результате перечисление Month стало достаточно большим для выделения в отдельный класс. Я убрал его из DayDate по образцу перечисления Day [G11],[G13].
Следующие два метода называются monthCodeToString (строки 377–426). И снова мы видим, как один метод вызывает своего «двойника» с передачей флага. Обычно передавать флаг в аргументе не рекомендуется, особенно если он просто выбирает формат вывода [G15]. Я переименовал, упростил и реструктурировал эти функции и переместил их в перечисление Month [N1],[N3],[C3],[G14].
public String toString() {
return dateFormatSymbols.getMonths()[index - 1];
}
public String toShortString() {
return dateFormatSymbols.getShortMonths()[index - 1];
}
Далее в листинге идет метод stringToMonthCode (строки 428–472). Я переименовал его, переместил в перечисление Month и упростил [N1],[N3],[C3],[G14],[G12].
public static Month parse(String s) {
s = s.trim();
for (Month m : Month.values())
if (m.matches(s))
return m;
try {
return make(Integer.parseInt(s));
}
catch (NumberFormatException e) {}
throw new IllegalArgumentException("Invalid month " + s);
}
private boolean matches(String s) {
return s.equalsIgnoreCase(toString()) ||
s.equalsIgnoreCase(toShortString());
}
Метод isLeapYear (строки 495–517) можно сделать более выразительным [G16].
public static boolean isLeapYear(int year) {
boolean fourth = year % 4 == 0;
boolean hundredth = year % 100 == 0;
boolean fourHundredth = year % 400 == 0;
return fourth && (!hundredth || fourHundredth);
}
Следующая функция, leapYearCount (строки 519–536), не принадлежит DayDate. Она не вызывается никем, кроме двух методов SpreadsheetDate. Я переместил ее в производный класс [G6].
Функция lastDayOfMonth (строки 538–560) использует массив LAST_DAY_OF_MONTH. Этот массив принадлежит перечислению Month [G17], поэтому функция была перемещена. Заодно я упростил ее код и сделал его более выразительным [G16].
public static int lastDayOfMonth(Month month, int year) {
if (month == Month.FEBRUARY && isLeapYear(year))
return month.lastDay() + 1;
else
return month.lastDay();
}
Начинается самое интересное. Далее в листинге идет функция addDays (строки 562–576). Прежде всего, поскольку эта функция работает с переменными DayDate, она не должна быть статической [G18]. Соответственно, я преобразовал ее в метод экземпляра. Также она вызывает функцию toSerial, которую правильнее называть toOrdinal [N1]. Наконец, метод можно несколько упростить.
public DayDate addDays(int days) {
return DayDateFactory.makeDate(toOrdinal() + days);
}
Сказанное относится и к функции addMonths (строки 578–602). Она должна быть оформлена в виде метода экземпляра [G18]. Алгоритм относительно сложен, поэтому я воспользовался ПОЯСНИТЕЛЬНЫМИ ВРЕМЕННЫМИ ПЕРЕМЕННЫМИ [Beck97] [G19], чтобы сделать его смысл более прозрачным. Заодно метод getYYY был переименован в getYear [N1].
public DayDate addMonths(int months) {
int thisMonthAsOrdinal = 12 * getYear() + getMonth().index - 1;
int resultMonthAsOrdinal = thisMonthAsOrdinal + months;
int resultYear = resultMonthAsOrdinal / 12;
Month resultMonth = Month.make(resultMonthAsOrdinal % 12 + 1);
int lastDayOfResultMonth = lastDayOfMonth(resultMonth, resultYear);
int resultDay = Math.min(getDayOfMonth(), lastDayOfResultMonth);
return DayDateFactory.makeDate(resultDay, resultMonth, resultYear);
}
Функция addYears (строки 604–626) преобразуется по тем же принципам, что и ее аналоги.
public DayDate plusYears(int years) {
int resultYear = getYear() + years;
int lastDayOfMonthInResultYear = lastDayOfMonth(getMonth(), resultYear);
int resultDay = Math.min(getDayOfMonth(), lastDayOfMonthInResultYear);
return DayDateFactory.makeDate(resultDay, getMonth(), resultYear);
}
Преобразование статических методов в методы экземпляров вызвало у меня некоторое беспокойство. Поймет ли читатель при виде выражения date.addDays(5), что объект date не изменяется, а вместо этого возвращается новый экземпляр DayDate? Или он ошибочно решит, что к объекту date прибавляются пять дней? Казалось бы, проблема не столь серьезна, но конструкции вроде следующей могут оказаться очень коварными [G20].
DayDate date = DateFactory.makeDate(5, Month.DECEMBER, 1952);
date.addDays(7); // Смещение date на одну неделю.
Скорее всего, читатель кода предположит, что вызов addDays изменяет объект date. Значит, нам понадобится имя, разрушающее эту двусмысленность [N4]. Я переименовал методы в plusDays и plusMonths. Мне кажется, что предназначение данного метода отлично отражается конструкциями вида
DayDate date = oldDate.plusDays(5);
С другой стороны, следующая конструкция читается недостаточно бегло, чтобы читатель сразу предположил, что изменяется объект date:
date.plusDays(5);
Алгоритмы становятся все интереснее. Функция getPreviousDayOfWeek (строки 628–660) работает, но выглядит слишком сложно. После некоторых размышлений относительно того, что же в действительности происходит в этой функции [G21], мне удалось упростить ее и воспользоваться ПОЯСНИТЕЛЬНЫМИ ВРЕМЕННЫМИ ПЕРЕМЕННЫМИ [G19], чтобы сделать код более понятным. Я также преобразовал статический метод в метод экземпляра [G18] и избавился от дублирующего метода экземпляра [G5] (строки 997–1008).
public DayDate getPreviousDayOfWeek(Day targetDayOfWeek) {
int offsetToTarget = targetDayOfWeek.index - getDayOfWeek().index;
if (offsetToTarget >= 0)
offsetToTarget -= 7;
return plusDays(offsetToTarget);
}
Абсолютно такой же анализ с тем же результатом был проведен для метода getFollowingDayOfWeek (строки 662–693).
public DayDate getFollowingDayOfWeek(Day targetDayOfWeek) {
int offsetToTarget = targetDayOfWeek.index - getDayOfWeek().index;
if (offsetToTarget <= 0)
offsetToTarget += 7;