1. Провести рефакторинг PersonService
Условие задачи:
Дан метод getAdultMaleUsersAverageBMI(), который:
самостоятельно открывает соединение с БД;
получает совершеннолетних мужчин;
создаёт для каждой записи объект
Person;вычисляет средний BMI;
выводит результат в консоль;
самостоятельно обрабатывает ошибки.
Необходимо провести рефакторинг кода: убрать лишние операции, корректно управлять JDBC-ресурсами, обработать граничные случаи и разделить ответственность метода.
Код:
public class PersonService {
public void getAdultMaleUsersAverageBMI() {
double totalIMT = 0.0;
long countOfPerson = 0;
double heightInMeters = 0d;
double imt = 0d;
List<Person> adultPersons = new ArrayList<>();
try {
Connection c = DriverManager.getConnection(
"jdbc:hsqldb:mem:test",
"admin",
"qwerty54"
);
Statement s = c.createStatement();
ResultSet rs = s.executeQuery(
"SELECT * FROM person " +
"WHERE sex = 'male' AND age >= 18"
);
while (rs.next()) {
Person p = new Person();
p.setId(rs.getLong("id"));
p.setSex(rs.getString("sex"));
p.setName(rs.getString("name"));
p.setAge(rs.getLong("age"));
p.setWeight(rs.getLong("weight"));
p.setHeight(rs.getLong("height"));
adultPersons.add(p);
}
for (Person p : adultPersons) {
heightInMeters = p.getHeight() / 100d;
imt = p.getWeight()
/ (heightInMeters * heightInMeters);
totalIMT += imt;
}
countOfPerson = adultPersons.size();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(
"Average imt = "
+ totalIMT / countOfPerson
);
}
}
Спойлеры к решению
Подсказки
weight и height, поэтому создавать объекты Person необязательно.💡 JDBC-ресурсы следует закрывать через
try-with-resources.💡 Строку подключения и credentials лучше передавать через
DataSource, а не хранить в сервисе.💡 Метод лучше возвращает результат, а не занимается выводом в консоль.
💡 Нужно обработать ситуацию, когда подходящих людей нет, а также некорректный
height = 0.Решение
Один из вариантов рефакторинга:
public class PersonService {
private static final String QUERY = """
SELECT weight, height
FROM person
WHERE sex = ?
AND age >= ?
""";
private final DataSource dataSource;
public PersonService(DataSource dataSource) {
this.dataSource = dataSource;
}
public OptionalDouble getAdultMaleUsersAverageBMI() {
double totalBmi = 0.0;
long count = 0;
try (Connection connection = dataSource.getConnection();
PreparedStatement statement =
connection.prepareStatement(QUERY)) {
statement.setString(1, "male");
statement.setInt(2, 18);
try (ResultSet rs = statement.executeQuery()) {
while (rs.next()) {
double weight = rs.getDouble("weight");
double heightCm = rs.getDouble("height");
if (heightCm <= 0) {
continue;
}
double heightMeters = heightCm / 100.0;
totalBmi += weight
/ (heightMeters * heightMeters);
count++;
}
}
} catch (SQLException e) {
throw new IllegalStateException(
"Не удалось вычислить средний BMI",
e
);
}
if (count == 0) {
return OptionalDouble.empty();
}
return OptionalDouble.of(totalBmi / count);
}
}
Использование:
personService.getAdultMaleUsersAverageBMI()
.ifPresentOrElse(
bmi -> System.out.printf(
"Average BMI = %.2f%n",
bmi
),
() -> System.out.println(
"Подходящие пользователи не найдены"
)
);
В исходном методе выполнялось:
БД
↓
ResultSet
↓
Person
↓
List<Person>
↓
повторный обход списка
↓
BMI
Для вычисления среднего значения объекты Person и промежуточный список не нужны. Достаточно считать два необходимых поля:
SELECT weight, height
FROM person
WHERE sex = ?
AND age >= ?
и сразу обновлять сумму:
totalBmi += weight
/ (heightMeters * heightMeters);
count++;
try-with-resources гарантирует закрытие:
Connection
PreparedStatement
ResultSet
в том числе при возникновении исключения.
Получение соединения вынесено в:
private final DataSource dataSource;
поэтому сервис больше не содержит внутри себя:
URL БД
логин
пароль
и его проще конфигурировать и тестировать.
Вместо:
catch (Exception e) {
e.printStackTrace();
}
перехватывается конкретный SQLException. Ошибка не скрывается, а передаётся вызывающему коду как исключение более высокого уровня.
Возвращается:
OptionalDouble
поскольку при отсутствии подходящих записей среднего значения не существует. Это также предотвращает деление:
0 / 0
и получение NaN.
Метод теперь отвечает за вычисление среднего BMI, а не за форматирование и вывод результата.
Основные улучшения:
убран ненужный
List<Person>;не создаются лишние объекты
Person;выбираются только необходимые столбцы;
JDBC-ресурсы гарантированно закрываются;
конфигурация БД отделена от бизнес-логики;
обработан случай отсутствия данных;
исключено деление на нулевой рост;
ошибка работы с БД не скрывается;
вычисление отделено от вывода результата.
Временная сложность — O(n), где n — количество найденных записей. Дополнительная память — O(1).