Продвинутые темы методов
Переменное число параметров, fluent-стиль, private методы, тестирование и Javadoc
Добро пожаловать в финальный урок шестого модуля!
Вы проделали огромную работу! В предыдущих пяти уроках мы детально изучили основы методов в Java: что такое методы, как их создавать, как передавать параметры, как возвращать значения и как работать с перегрузкой. Теперь пришло время изучить более продвинутые темы, которые используются в реальных проектах. В этом уроке мы поговорим о переменном числе параметров, стильном программировании (fluent-стиль), приватных методах, тестировании, документировании кода и рефакторинге. Это последний урок модуля 6, поэтому мы также подведем итоги всего изученного.
Представьте, что вы изучаете язык. Сначала вы выучили алфавит, потом простые слова, потом предложения. Теперь вы готовы к более сложным конструкциям — метафорам, идиомам, стилистическим приемам. Точно так же и в программировании: после освоения базовых методов мы перейдем к более изящным и мощным техникам.
1. Переменное число параметров (Varargs)
Представьте, что вы пишете метод для суммирования чисел. Иногда вам нужно сложить два числа, иногда три, а иногда десять. Как быть? Создавать отдельные методы для каждого случая? Это было бы очень неудобно!
Вот тут на помощь приходит переменное число параметров (varargs). Это позволяет методу принимать любое количество аргументов одного типа. Синтаксис очень простой: после типа параметра ставится многоточие (...). Давайте посмотрим на пример:
Аналогия из жизни: Varargs — это как корзина для покупок. Вы можете положить в неё столько товаров, сколько хотите. Продавцу не нужно знать заранее, сколько вещей вы купите. Он просто складывает всё в одну корзину.
public class Calculator {
public static int sum(int... numbers) {
int result = 0;
for (int num : numbers) {
result += num;
}
return result;
}
public static void main(String[] args) {
System.out.println(sum(1, 2));
System.out.println(sum(1, 2, 3));
System.out.println(sum(1, 2, 3, 4, 5));
System.out.println(sum());
}
}
Как вы видите, метод sum может принимать от нуля до любого количества целых чисел. Внутри метода параметр numbers ведет себя как обычный массив. Вы можете использовать все знакомые вам операции с массивами: циклы, обращение по индексу, получение длины.
Важные правила использования varargs:
1. Параметр varargs должен быть последним в списке параметров.
2. В методе может быть только один параметр varargs.
3. Вы можете передавать массив вместо varargs (компилятор не будет против).
Давайте рассмотрим более практичный пример. Представьте, что вы пишете метод для вывода сообщений пользователю. Иногда нужно вывести просто текст, иногда текст и название файла, иногда текст, название файла и уровень важности:
public class Logger {
public static void log(String message, String... details) {
System.out.print(message);
if (details.length > 0) {
System.out.print(" (");
for (int i = 0; i < details.length; i++) {
System.out.print(details[i]);
if (i < details.length - 1) {
System.out.print(", ");
}
}
System.out.print(")");
}
System.out.println();
}
public static void main(String[] args) {
log("Программа запущена");
log("Файл не найден", "config.xml");
log("Ошибка базы данных", "users.sql", "критическая");
}
}
Этот пример показывает, как varargs делают API более гибким. Клиенты метода могут передавать разное количество информации, а метод адаптируется к этому.
Еще один важный момент: вы можете комбинировать varargs с обычными параметрами. Например:
public class MessageFormatter {
public static String format(String template, Object... values) {
String result = template;
for (int i = 0; i < values.length; i++) {
result = result.replace("{" + i + "}", values[i].toString());
}
return result;
}
public static void main(String[] args) {
String msg1 = format("Привет, {0}!", "Мир");
String msg2 = format("{0} купил {1} яблок", "Иван", 5);
String msg3 = format("{0} из {1} студентов сдали экзамен", 45, 50);
System.out.println(msg1);
System.out.println(msg2);
System.out.println(msg3);
}
}
Здесь мы видим, как varargs могут быть использованы для создания универсальных методов форматирования строк. Это очень популярный паттерн в реальных проектах.
2. Fluent-стиль (метод-чейнинг)
Fluent-стиль — это прием программирования, который делает код более читаемым и компактным. Вместо того чтобы вызывать методы отдельно, вы можете связать их в цепочку. Это достигается за счет того, что методы возвращают объект (обычно this), на котором можно вызвать следующий метод.
Аналогия: Fluent-стиль — это как собирать конструктор LEGO. Каждая деталь (метод) добавляет что-то к общей конструкции, и вы можете продолжать добавлять детали одну за другой, пока не получите готовый объект.
Давайте посмотрим на пример без fluent-стили:
QueryBuilder query = new QueryBuilder();
query.setTable("users");
query.addField("name");
query.addField("email");
query.where("age > 18");
query.orderBy("name");
String sql = query.build();
А теперь тот же код с fluent-стилем:
String sql = new QueryBuilder()
.setTable("users")
.addField("name")
.addField("email")
.where("age > 18")
.orderBy("name")
.build();
Как вы видите, второй вариант более компактный и читаемый. Код читается как одно целое описание: "создай запрос для таблицы users, добавь поля name и email, где возраст больше 18, отсортируй по имени, построй запрос".
Как реализовать fluent-стиль? Очень просто! Каждый метод должен возвращать объект того же класса (обычно это делается через возврат this):
public class QueryBuilder {
private String table;
private List<String> fields = new ArrayList<>();
private String condition;
private String orderField;
public QueryBuilder setTable(String table) {
this.table = table;
return this;
}
public QueryBuilder addField(String field) {
fields.add(field);
return this;
}
public QueryBuilder where(String condition) {
this.condition = condition;
return this;
}
public QueryBuilder orderBy(String field) {
this.orderField = field;
return this;
}
public String build() {
StringBuilder sql = new StringBuilder("SELECT ");
sql.append(String.join(", ", fields));
sql.append(" FROM ").append(table);
if (condition != null) {
sql.append(" WHERE ").append(condition);
}
if (orderField != null) {
sql.append(" ORDER BY ").append(orderField);
}
return sql.toString();
}
}
Обратите внимание на ключевой момент: каждый метод (кроме build()) возвращает this. Это и позволяет строить цепочки вызовов.
Builder-паттерн
Fluent-стиль тесно связан с Builder-паттерном — одним из самых популярных паттернов проектирования. Builder используется, когда у объекта много параметров, и вы хотите создавать его пошагово.
Зачем это нужно? Представьте, что вы создаете объект "Пицца". У неё может быть 10+ разных начинок. Создавать конструктор с 10 параметрами — nightmare. А через Builder вы можете выбрать только те начинки, которые вам нужны.
public class Pizza {
private final String size;
private final boolean cheese;
private final boolean mushrooms;
private final boolean pepperoni;
private final boolean olives;
private Pizza(Builder builder) {
this.size = builder.size;
this.cheese = builder.cheese;
this.mushrooms = builder.mushrooms;
this.pepperoni = builder.pepperoni;
this.olives = builder.olives;
}
public static class Builder {
private final String size;
private boolean cheese = false;
private boolean mushrooms = false;
private boolean pepperoni = false;
private boolean olives = false;
public Builder(String size) {
this.size = size;
}
public Builder cheese(boolean cheese) {
this.cheese = cheese;
return this;
}
public Builder mushrooms(boolean mushrooms) {
this.mushrooms = mushrooms;
return this;
}
public Builder pepperoni(boolean pepperoni) {
this.pepperoni = pepperoni;
return this;
}
public Builder olives(boolean olives) {
this.olives = olives;
return this;
}
public Pizza build() {
return new Pizza(this);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Пицца размера ").append(size);
if (cheese) sb.append(" с сыром");
if (mushrooms) sb.append(" с грибами");
if (pepperoni) sb.append(" с пепперони");
if (olives) sb.append(" с оливками");
return sb.toString();
}
public static void main(String[] args) {
Pizza pizza1 = new Pizza.Builder("Средняя")
.cheese(true)
.mushrooms(true)
.build();
Pizza pizza2 = new Pizza.Builder("Большая")
.cheese(true)
.pepperoni(true)
.olives(true)
.build();
System.out.println(pizza1);
System.out.println(pizza2);
}
}
В этом примере мы видим классический Builder-паттерн. Обратите внимание на несколько важных моментов:
1. Класс Pizza имеет приватный конструктор, принимающий Builder.
2. Builder — это внутренний статический класс.
3. У Builder есть метод build(), который создает объект Pizza.
4. Методы Builder возвращают this для fluent-стиля.
3. Private методы
В предыдущих уроках мы изучали методы с модификаторами доступа public и static. Сегодня мы поговорим о private методах — методах, которые доступны только внутри того класса, где они определены.
Аналогия: Private методы — это как внутренние инструкции на кухне ресторана. Повар знает их, но посетители не должны их видеть. Повар использует эти инструкции для приготовления блюда, но для клиента важен только результат — готовое блюдо.
Зачем нужны private методы? Вот несколько причин:
1. Инкапсуляция логики. Вы скрываете сложную внутреннюю логику, показывая пользователям только простой интерфейс.
public class ReportGenerator {
public String generateDailyReport(Date date) {
List<Data> rawData = fetchDataForDate(date);
List<Data> cleanedData = cleanData(rawData);
List<Data> aggregatedData = aggregateData(cleanedData);
String report = formatReport(aggregatedData);
return report;
}
private List<Data> fetchDataForDate(Date date) {
// Сложная логика получения данных
return new ArrayList<>();
}
private List<Data> cleanData(List<Data> data) {
// Логика очистки данных
return data;
}
private List<Data> aggregateData(List<Data> data) {
// Логика агрегации данных
return data;
}
private String formatReport(List<Data> data) {
// Логика форматирования отчета
return "Отчет";
}
}
В этом примере метод generateDailyReport — public. Это основной интерфейс для пользователя. Все остальные методы — private. Пользователь не должен знать, как именно данные получены, очищены и агрегированы. Ему важен только результат.
2. Разбиение больших методов. Когда метод становится слишком длинным, его можно разбить на несколько较小ных методов. Это делает код более читаемым и поддерживаемым.
public class UserValidator {
public boolean validateUser(User user) {
return isNameValid(user.getName())
&& isEmailValid(user.getEmail())
&& isAgeValid(user.getAge());
}
private boolean isNameValid(String name) {
return name != null && !name.isEmpty() && name.length() >= 2;
}
private boolean isEmailValid(String email) {
return email != null && email.contains("@") && email.contains(".");
}
private boolean isAgeValid(int age) {
return age >= 18 && age <= 120;
}
}
Вместо одного длинного метода validateUser с вложенными проверками, мы разбили его на три较小ших метода. Каждый делает одну конкретную вещь. Это делает код проще для понимания и тестирования.
3. Повторное использование кода. Если один и тот же код используется в нескольких местах класса, его лучше вынести в отдельный private метод.
public class EmailService {
public void sendWelcomeEmail(User user) {
String greeting = createGreeting(user.getName());
String body = "Добро пожаловать в наш сервис!";
sendEmail(user.getEmail(), greeting, body);
}
public void sendPasswordResetEmail(User user) {
String greeting = createGreeting(user.getName());
String body = "Сбросите ваш пароль по ссылке...";
sendEmail(user.getEmail(), greeting, body);
}
private String createGreeting(String name) {
return "Уважаемый(ая) " + name + ",";
}
private void sendEmail(String to, String subject, String body) {
// Логика отправки.email
System.out.println("Отправка email на " + to);
}
}
Метод createGreeting используется в двух разных public методах. Без private метода нам пришлось бы дублировать логику создания приветствия.
Важно: Private методы — это часть внутренней реализации класса. Вы можете свободно их изменять, добавлять или удалять, не опасаясь сломать код других классов. Это ключевое преимущество инкапсуляции.
4. Методы как параметры (лямбды — краткое упоминание)
В Java методы можно передавать как параметры других методов. Это мощная возможность, которая лежит в основе функционального программирования. Хотя полное изучение лямбд — тема для продвинутых курсов, давайте познакомимся с основами.
Представьте, что вы пишете метод для фильтрации списка. Вы хотите, чтобы пользователь мог сам определять, какие элементы оставлять, а какие удалять:
public class ListUtils {
public static <T> List<T> filter(List<T> list, Condition<T> condition) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (condition.check(item)) {
result.add(item);
}
}
return result;
}
public interface Condition<T> {
boolean check(T item);
}
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = filter(numbers, new Condition<Integer>() {
@Override
public boolean check(Integer item) {
return item % 2 == 0;
}
});
System.out.println("Четные числа: " + evenNumbers);
}
}
Здесь мы определили интерфейс Condition с методом check. Метод filter принимает список и условие фильтрации. Пользователь может передать любую реализацию условия.
С Java 8 появились лямбды, которые делают такой код гораздо компактнее:
List<Integer> evenNumbers = filter(numbers, item -> item % 2 == 0);
Примечание: Полное изучение лямбд и функционального программирования — это отдельный обширный курс. Здесь мы лишь кратко упомянули эту возможность, чтобы вы знали, что методы можно передавать как параметры.
5. Javadoc комментарии
Теперь давайте поговорим о документировании кода. Javadoc — это специальный формат комментариев, который используется для автоматической генерации документации к коду. Это очень важная часть разработки, потому что код читают люди, а не только компьютеры.
Аналогия: Javadoc — это как инструкция к бытовой технике. Без неё вы можете разобраться, что делает микроволновка (греет еду), но вы не узнаете, какие у неё функции, как настроить время, как разморозить продукты. Javadoc объясняет ВСЕ возможности метода.
Javadoc комментарии начинаются с /** и заканчиваются */. Внутри них используются специальные теги:
/**
* Calculates the sum of two numbers.
*
* This method adds two integers and returns the result.
* If the sum exceeds Integer.MAX_VALUE, the result will overflow.
*
* @param a the first number to add
* @param b the second number to add
* @return the sum of a and b
* @throws IllegalArgumentException if both parameters are negative
*/
public static int add(int a, int b) {
if (a < 0 && b < 0) {
throw new IllegalArgumentException("Both numbers cannot be negative");
}
return a + b;
}
Давайте разберем основные теги:
@param — описывает параметр метода. Формат: @param имяПараметра описание.
@return — описывает возвращаемое значение.
@throws — описывает исключения, которые может выбросить метод.
Вот более сложный пример для класса:
/**
* A utility class for performing mathematical operations.
*
* This class contains static methods for basic arithmetic
* and more complex mathematical calculations.
*
* @author Иван Иванов
* @version 1.2
* @since 2024-01-15
*/
public class MathUtils {
private static final double PI = 3.14159265359;
/**
* Calculates the area of a circle.
*
* @param radius the radius of the circle (must be positive)
* @return the area of the circle
* @throws IllegalArgumentException if radius is negative
*/
public static double circleArea(double radius) {
if (radius < 0) {
throw new IllegalArgumentException("Radius cannot be negative");
}
return PI * radius * radius;
}
/**
* Calculates the factorial of a number.
*
* This method uses iterative approach for efficiency.
*
* @param n the number to calculate factorial (must be non-negative)
* @return the factorial of n
* @throws IllegalArgumentException if n is negative
* @throws ArithmeticException if n is too large (greater than 20)
*/
public static long factorial(int n) {
if (n < 0) {
throw new IllegalArgumentException("n cannot be negative");
}
if (n > 20) {
throw new ArithmeticException("n is too large for long type");
}
long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
}
Обратите внимание на несколько важных моментов:
1. Javadoc размещается непосредственно перед методом или классом.
2. Описание должно быть кратким, но информативным.
3. Всегда документируйте параметры, возвращаемое значение и исключения.
4. Используйте теги @author, @version, @since для классов.
Специальные инструменты (javadoc) могут автоматически сгенерировать HTML-документацию из таких комментариев. Это очень удобно для создания справочников по API.
Совет: Пишите Javadoc так, как будто вы объясняете метод своему коллеге, который видит его впервые. Что делает метод? Зачем он нужен? Какие ограничения? Какие исключения могут быть?
6. Тестирование методов: структура теста (Arrange-Act-Assert)
Тестирование — это процесс проверки, что ваш код работает правильно. Хорошие тесты экономят время, помогают находить ошибки и делают код более надежным. Сегодня мы изучим базовую структуру теста: Arrange-Act-Assert (Подготовить-Действовать-Проверить).
Аналогия: Тестирование — это как проверка готовки блюда. Вы подготавливаете ингредиенты (Arrange), готовите блюдо (Act), а затем проверяете результат: пробуете на вкус, проверяете температуру, консистенцию (Assert).
Давайте рассмотрим простой метод и его тест:
public class Calculator {
public static int multiply(int a, int b) {
return a * b;
}
}
public class CalculatorTest {
public void testMultiply() {
// Arrange (Подготовка)
int a = 3;
int b = 4;
int expected = 12;
// Act (Действие)
int result = Calculator.multiply(a, b);
// Assert (Проверка)
if (result == expected) {
System.out.println("Тест пройден!");
} else {
System.out.println("Тест провален! Ожидалось: " + expected + ", получено: " + result);
}
}
}
Три фазы теста:
1. Arrange (Подготовка): Создаем все необходимые данные для теста. Здесь мы определяем входные параметры и ожидаемый результат.
2. Act (Действие): Вызываем тестируемый метод с подготовленными данными.
3. Assert (Проверка): Сравниваем результат с ожидаемым значением. Если они совпадают — тест пройден, если нет — тест провален.
Давайте напишем более полный набор тестов:
public class CalculatorTest {
public void testMultiplyPositiveNumbers() {
// Arrange
int a = 3;
int b = 4;
int expected = 12;
// Act
int result = Calculator.multiply(a, b);
// Assert
System.out.println("Тест 1: " + (result == expected ? "ПРОЙДЕН" : "ПРОВАЛЕН"));
}
public void testMultiplyByZero() {
// Arrange
int a = 5;
int b = 0;
int expected = 0;
// Act
int result = Calculator.multiply(a, b);
// Assert
System.out.println("Тест 2: " + (result == expected ? "ПРОЙДЕН" : "ПРОВАЛЕН"));
}
public void testMultiplyNegativeNumbers() {
// Arrange
int a = -3;
int b = -4;
int expected = 12;
// Act
int result = Calculator.multiply(a, b);
// Assert
System.out.println("Тест 3: " + (result == expected ? "ПРОЙДЕН" : "ПРОВАЛЕН"));
}
public void testMultiplyMixedNumbers() {
// Arrange
int a = -3;
int b = 4;
int expected = -12;
// Act
int result = Calculator.multiply(a, b);
// Assert
System.out.println("Тест 4: " + (result == expected ? "ПРОЙДЕН" : "ПРОВАЛЕН"));
}
}
Обратите внимание, что каждый тест проверяет один конкретный случай. Это очень важно! Каждый тест должен быть независимым и проверять одну конкретную функциональность.
Правило хорошего теста: Один тест — одна проверка. Не пытайтесь проверить всё в одном тесте. Если тест провалится, вы сразу будете знать, что именно сломалось.
7. Code review: что проверять в методах
Code review (обзор кода) — это процесс, когда другой разработчик проверяет ваш код перед тем, как он попадет в основной проект. Это важнейшая часть командной работы. Давайте разберем, на что обращать внимание при review методов.
Аналогия: Code review — это как проверка сочинения учителем. Учитель проверяет не только правильность ответа (работает ли код), но и стиль изложения (читаем ли код), структуру (хорошо ли организован код), соответствие требованиям (выполняет ли код то, что нужно).
Что проверять при review метода:
1. Название метода. Должно четко описывать, что делает метод. Плохо: process(), handle(), doStuff(). Хорошо: calculateTotalPrice(), sendEmailNotification(), validateUserInput().
2. Размер метода. Метод не должен быть слишком длинным. Если метод больше 30 строк, стоит задуматься о его разбиении. Есть правило: метод должен выполнять одну конкретную задачу.
3. Количество параметров. Метод не должен иметь слишком много параметров (больше 3-4 — уже подозрительно). Если параметров много, возможно, стоит создать объект-параметр или использовать Builder-паттерн.
4. Обработка ошибок. Проверьте, что метод корректно обрабатывает некорректные входные данные. Есть ли проверки на null? Проверяются ли границы значений?
5. Документация. Есть ли Javadoc? Описаны ли параметры, возвращаемое значение, исключения?
6. Тесты. Есть ли тесты для этого метода? Покрывают ли они основные случаи?
Вот пример метода, который вызовет вопросы при review:
public void process(User u, int t, String m, boolean f, int c) {
if (u != null) {
if (t == 1) {
if (f) {
if (c > 0) {
// Длинная логика обработки
// Еще код
// И еще
// ...
}
}
}
}
}
Проблемы этого метода:
- Непонятное название
- Неинформативные имена параметров
- Слишком много параметров
- Вложенные if-ы (should be flattened)
- Длинная логика без разбиения
- Отсутствие документации
А вот улучшенная версия:
/**
* Processes user subscription based on type and conditions.
*
* @param user the user to process
* @param subscriptionType type of subscription (1-monthly, 2-yearly)
* @param message notification message to send
* @param sendEmail whether to send email notification
* @param daysRemaining days remaining in current subscription
*/
public void processUserSubscription(User user, int subscriptionType,
String message, boolean sendEmail,
int daysRemaining) {
if (user == null) {
return;
}
if (subscriptionType == MONTHLY_SUBSCRIPTION && sendEmail && daysRemaining > 0) {
handleMonthlySubscription(user, message);
}
}
Обратите внимание на улучшения: понятное название, осмысленные имена параметров, документация, ранний выход при некорректных данных, уменьшенная вложенность.
8. Рефакторинг методов: длинные методы, extract method
Рефакторинг — это процесс улучшения структуры кода без изменения его поведения. Сегодня мы поговорим о рефакторинге методов, особенно о технике Extract Method (Извлечь метод).
Аналогия: Рефакторинг — это как перестановка мебели в квартире. Вы не меняете саму квартиру (она та же), но делаете её более удобной и функциональной.
Рассмотрим длинный метод:
public void generateReport() {
// Получение данных из базы
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
List<ReportItem> items = new ArrayList<>();
try {
conn = DriverManager.getConnection(DB_URL, USER, PASS);
String sql = "SELECT * FROM sales WHERE date BETWEEN ? AND ?";
stmt = conn.prepareStatement(sql);
stmt.setDate(1, startDate);
stmt.setDate(2, endDate);
rs = stmt.executeQuery();
while (rs.next()) {
ReportItem item = new ReportItem();
item.setId(rs.getInt("id"));
item.setAmount(rs.getDouble("amount"));
item.setDate(rs.getDate("date"));
items.add(item);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try { if (rs != null) rs.close(); } catch (SQLException e) {}
try { if (stmt != null) stmt.close(); } catch (SQLException e) {}
try { if (conn != null) conn.close(); } catch (SQLException e) {}
}
// Обработка данных
double total = 0;
for (ReportItem item : items) {
total += item.getAmount();
}
// Формирование отчета
StringBuilder report = new StringBuilder();
report.append("Отчет за период: ").append(startDate).append(" - ").append(endDate).append("\n");
report.append("Общая сумма: ").append(total).append("\n");
report.append("Количество записей: ").append(items.size()).append("\n");
// Сохранение отчета в файл
try (PrintWriter writer = new PrintWriter(new FileWriter("report.txt"))) {
writer.println(report.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
Этот метод делает слишком много: получает данные, обрабатывает их, формирует отчет и сохраняет его. Давайте применим Extract Method:
public void generateReport() {
List<ReportItem> items = fetchDataFromDatabase();
double total = calculateTotalAmount(items);
String report = buildReportString(items, total);
saveReportToFile(report);
}
private List<ReportItem> fetchDataFromDatabase() {
List<ReportItem> items = new ArrayList<>();
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(DB_URL, USER, PASS);
String sql = "SELECT * FROM sales WHERE date BETWEEN ? AND ?";
stmt = conn.prepareStatement(sql);
stmt.setDate(1, startDate);
stmt.setDate(2, endDate);
rs = stmt.executeQuery();
while (rs.next()) {
ReportItem item = new ReportItem();
item.setId(rs.getInt("id"));
item.setAmount(rs.getDouble("amount"));
item.setDate(rs.getDate("date"));
items.add(item);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try { if (rs != null) rs.close(); } catch (SQLException e) {}
try { if (stmt != null) stmt.close(); } catch (SQLException e) {}
try { if (conn != null) conn.close(); } catch (SQLException e) {}
}
return items;
}
private double calculateTotalAmount(List<ReportItem> items) {
double total = 0;
for (ReportItem item : items) {
total += item.getAmount();
}
return total;
}
private String buildReportString(List<ReportItem> items, double total) {
StringBuilder report = new StringBuilder();
report.append("Отчет за период: ").append(startDate).append(" - ").append(endDate).append("\n");
report.append("Общая сумма: ").append(total).append("\n");
report.append("Количество записей: ").append(items.size()).append("\n");
return report.toString();
}
private void saveReportToFile(String report) {
try (PrintWriter writer = new PrintWriter(new FileWriter("report.txt"))) {
writer.println(report);
} catch (IOException e) {
e.printStackTrace();
}
}
Теперь каждый метод делает одну конкретную вещь. Основной метод generateReport читается как план действий. Каждый较小ший метод понятен и может быть протестирован отдельно.
Преимущества рефакторинга:
1. Улучшается читаемость кода
2. Уменьшается дублирование кода
3. Упрощается тестирование
4. Уменьшается вероятность ошибок
5. Упрощается поддержка кода
9. Best practices: именование, размер метода, количество параметров
Давайте подведем итог最佳实践中 для написания хороших методов. Это правила, которые помогут вам писать качественный, поддерживаемый код.
Именование методов
Хорошее название метода — половина успеха. Вот правила именования:
1. Используйте глаголы: методы выполняют действия. calculateArea(), sendEmail(), validateInput().
2. Будьте конкретными: не process(), а processUserRegistration().
3. Избегайте сокращений: calc() вместо calculate() — плохо. Читаемость важнее экономии букв.
4. Используйте consistent casing: в Java принято использовать camelCase — getUserName(), не get_user_name().
5. Возвращаемое значение: для boolean методов используйте префиксы is, has, can, should. Например, isValid(), hasPermission().
Размер метода
Какой длины должен быть метод? Нет строгого правила, но есть рекомендации:
- Идеально: 5-15 строк
- Допустимо: до 30 строк
- Подозрительно: больше 30 строк
Если метод длиннее 30 строк, задайте себе вопросы:
1. Делает ли метод слишком много? (Single Responsibility Principle)
2. Можно ли вынести часть логики в较小шие методы?
3. Есть ли повторяющийся код, который можно вынести?
Количество параметров
Слишком много параметров — признак плохого дизайна:
- Идеально: 0-2 параметра
- Допустимо: 3 параметра
- Подозрительно: больше 3 параметров
Что делать, если параметров много:
1. Создать объект-параметр:sendEmail(Email email) вместо sendEmail(String to, String subject, String body, boolean isHtml)
2. Использовать Builder-паттерн (мы изучали в этом уроке)
3. Разбить метод на несколько较小ших методов
Золотое правило: Если вы не можете назвать метод одной фразой из 2-3 слов, возможно, он делает слишком много и должен быть разбит на несколько较小ших методов.
10. Итоговый обзор модуля 6
Мы завершили шестой модуль, посвященный методам в Java. Давайте подведем итоги всего изученного за шесть уроков.
Урок 1: Основы методов
Мы изучили, что такое методы, зачем они нужны, как их создавать. Разобрали синтаксис, модификаторы доступа, типы методов (void и возвращающие значение).
Урок 2: Параметры и аргументы
Научились передавать данные в методы через параметры. Разобрали разницу между параметрами и аргументами, изучили примитивные типы и ссылочные типы как параметры.
Урок 3: Возврат значений
Научились возвращать значения из методов с помощью return. Разобрали, как возвращать примитивные типы, объекты и массивы.
Урок 4: Перегрузка методов
Изучили перегрузку методов — возможность создавать методы с одинаковым именем, но разными параметрами. Это делает API более удобным.
Урок 5: Статические методы
Разобрались со статическими методами, которые принадлежат классу, а не экземпляру. Узнали, когда их стоит использовать.
Урок 6: Продвинутые темы (этот урок)
Изучили продвинутые темы: varargs, fluent-стиль, private методы, тестирование, Javadoc, code review и рефакторинг.
Теперь вы обладаете всеми необходимыми знаниями для работы с методами в Java. Вы умеете создавать методы, передавать в них данные, получать результаты, документировать код и поддерживать его в хорошем состоянии.
Итоги урока
- ✅ Переменное число параметров (varargs) позволяет методам принимать любое количество аргументов
- ✅ Fluent-стиль (метод-чейнинг) делает код более компактным и читаемым
- ✅ Builder-паттерн используется для пошагового создания объектов с помощью fluent-стиля
- ✅ Private методы инкапсулируют внутреннюю логику класса
- ✅ Методы можно передавать как параметры (лямбды — тема для продвинутого курса)
- ✅ Javadoc комментарии документируют код с помощью тегов @param, @return, @throws
- ✅ Тесты методов строятся по принципу Arrange-Act-Assert (Подготовить-Действовать-Проверить)
- ✅ Code review проверяет читаемость, размер метода, документацию и обработку ошибок
- ✅ Рефакторинг улучшает структуру кода без изменения поведения
- ✅ Best practices: осмысленные названия, маленький размер метода, минимум параметров
- ✅ Модуль 6 полностью завершен! Вы готовы к изучению классов и объектов
Что дальше?
Отлично! Вы завершили модуль 6 и теперь обладаете солидными знаниями о методах в Java. Это важнейшая основа для дальнейшего изучения.
В следующем модуле мы перейдем к классам и объектам — сердцу объектно-ориентированного программирования в Java. Вы узнаете, как создавать собственные типы данных, как инкапсулировать данные и поведение, и как строить сложные системы из простых строительных блоков.
Тест по уроку 6.6: Продвинутые темы методов
10 вопросов