docs: replace data source task with csv student io task

This commit is contained in:
2026-08-28 21:10:13 +04:00
parent 1fe77ef1b3
commit c8c9c05a65
3 changed files with 324 additions and 480 deletions
+324
View File
@@ -0,0 +1,324 @@
# Техническое задание: чтение и дозапись `Student` в CSV
## Задача
Реализовать два независимых класса для работы с объектами `Student` в формате CSV:
- `StudentCsvWriter` — дозапись студентов в файл;
- `StudentCsvReader` — последовательное чтение студентов из файла.
Для работы с форматом CSV использовать готовую библиотеку для работы с CSV. Самостоятельно реализовывать парсинг CSV запрещено.
Оба класса должны принимать **абсолютный путь к файлу**.
Исключения `Student` и `StudentBuilder` не обрабатывать.
---
# Формат CSV-файла
Первая строка файла должна содержать заголовок:
```text
groupNumber,averageGrade,recordBookNumber
```
Каждая следующая строка содержит данные одного студента:
```text
groupNumber,averageGrade,recordBookNumber
```
Пример:
```text
groupNumber,averageGrade,recordBookNumber
A12,4.5,10001
B07,3.8,10002
C25,4.9,10003
```
Для формирования и чтения CSV использовать стандартную библиотеку для работы с форматом CSV.
---
# `StudentCsvWriter`
## Назначение
Дозаписывать одного студента в указанный CSV-файл.
## Контракт
Конструктор:
```java
public StudentCsvWriter(String absoluteFilePath)
```
Метод:
```java
public void write(Student student) throws IOException
```
### Требования
1. Метод принимает одного `Student`.
2. Запись выполняется в режиме добавления.
3. Существующие записи не изменяются.
4. Если файл существует, заголовок повторно не записывается.
5. Если файл не существует, необходимо создать файл.
6. При создании нового файла имя должно соответствовать формату:
```text
students-yyyy-MM-dd-HH-mm.csv
```
Например:
```text
students-2026-08-28-20-45.csv
```
1. Новый файл создаётся в директории, указанной абсолютным путём.
2. При создании нового файла сначала записывается заголовок.
3. При успешной записи метод завершается без исключения.
4. Метод не возвращает значение.
5. Исключения, связанные с записью файла, не обрабатывать внутри метода — они должны передаваться вызывающему коду.
6. Исключения `Student` и `StudentBuilder` также не обрабатывать.
## Невозможность записи
Если студент не может быть записан, например:
- отсутствуют права на запись;
- невозможно создать файл;
- файл недоступен;
- произошла ошибка файловой системы;
метод должен выбросить соответствующее исключение, например `IOException`.
Запись не должна возвращать специальное значение вроде `0` для обозначения ошибки.
---
# `StudentCsvReader`
## Назначение
Последовательно читать студентов из CSV-файла.
## Контракт
Класс должен реализовывать:
```java
Iterator<Student>
```
Конструктор:
```java
public StudentCsvReader(String absoluteFilePath)
```
Методы:
```java
@Override
public boolean hasNext()
```
```java
@Override
public Student next()
```
### Требования к `hasNext()`
1. Проверяет наличие следующей записи `Student`.
2. Заголовок CSV не считается записью.
3. Повторный вызов `hasNext()` не должен пропускать следующую запись.
4. После окончания файла метод возвращает `false`.
### Требования к `next()`
1. Возвращает следующего `Student`.
2. `Student` создаётся через существующий `StudentBuilder`.
3. При отсутствии следующего элемента выбрасывается:
```text
NoSuchElementException
```
1. Весь файл не должен предварительно загружаться в коллекцию.
## Невозможность прочитать студента
Если следующую запись невозможно прочитать или преобразовать в `Student`, например:
- некорректный формат CSV;
- отсутствует обязательное поле;
- значение невозможно преобразовать в нужный тип;
- данные не проходят валидацию `Student`;
исключение должно передаваться вызывающему коду.
Исключения `Student` и `StudentBuilder` **не перехватывать и не обрабатывать**.
Некорректные записи не должны молча пропускаться.
---
# Общие требования
1. Оба класса принимают абсолютный путь к файлу.
2. Использовать готовую библиотеку для работы с CSV.
3. Не реализовывать собственный CSV-парсер.
4. Не использовать стандартные коллекции Java как основное хранилище студентов.
5. Не дублировать в этих классах валидацию `Student`.
6. Не дублировать логику создания `Student` вне `StudentBuilder`.
7. `StudentCsvWriter` отвечает только за запись.
8. `StudentCsvReader` отвечает только за чтение.
---
# Примеры тестов
## `StudentCsvWriter`
### Создание нового файла
Передать путь к несуществующему файлу и вызвать:
```java
writer.write(student);
```
Проверить:
- файл создан;
- имя соответствует `students-yyyy-MM-dd-HH-mm.csv`;
- присутствует заголовок;
- студент записан;
- метод завершился без исключения.
### Дозапись
Создать файл с одним студентом и выполнить:
```java
writer.write(student2);
```
Проверить, что:
- первая запись сохранилась;
- `student2` добавлен в конец;
- заголовок присутствует только один раз.
### Несколько последовательных записей
```java
writer.write(student1);
writer.write(student2);
writer.write(student3);
```
Проверить наличие трёх записей в правильном порядке.
### Невозможность записи
Передать путь, по которому невозможно создать или открыть файл для записи.
Проверить, что `write()` выбрасывает `IOException`.
Не проверять специальное возвращаемое значение — метод ничего не возвращает.
---
## `StudentCsvReader`
### Чтение корректного файла
Для файла:
```text
groupNumber,averageGrade,recordBookNumber
A12,4.5,10001
B07,3.8,10002
C25,4.9,10003
```
Проверить:
- `hasNext()` возвращает `true` до последнего студента;
- `next()` возвращает студентов в правильном порядке;
- после последнего студента `hasNext()` возвращает `false`.
### Повторный вызов `hasNext()`
```java
reader.hasNext();
reader.hasNext();
Student student = reader.next();
```
Проверить, что первый студент не был пропущен.
### `next()` после окончания файла
После чтения всех студентов вызвать:
```java
reader.next();
```
Проверить выброс:
```text
NoSuchElementException
```
### Некорректная запись
Передать файл:
```text
groupNumber,averageGrade,recordBookNumber
A12,4.5,10001
INVALID
C25,4.9,10003
```
Проверить, что ошибка при чтении некорректной записи передаётся вызывающему коду.
### Некорректные данные `Student`
Передать данные, нарушающие ограничения `Student`.
Проверить, что исключение от `Student` или `StudentBuilder` выходит наружу и не перехватывается `StudentCsvReader`.
---
# Названия файлов
```text
StudentCsvWriter.java
StudentCsvReader.java
```
# Git
Название ветки:
```text
feature/csv-student-io
```
Conventional Commit:
```text
feat: add csv student reader and writer
```
-399
View File
@@ -1,399 +0,0 @@
# Техническое задание: источники данных
## Задача
Реализовать три независимых источника данных для формирования `MyList<Student>`:
- `ManualDataSource`;
- `RandomDataSource`;
- `FileDataSource`.
Все источники должны формировать только валидные объекты `Student`.
Класс `Student` содержит:
```java
private final String groupNumber;
private final double averageGrade;
private final String recordBookNumber;
```
Создание объектов `Student` выполнять через `StudentBuilder`.
---
# Общий контракт
Создать интерфейс `DataSource`:
```java
public interface DataSource {
MyList<Student> create();
}
```
Источник данных не должен самостоятельно получать данные от пользователя.
Все необходимые входные значения должны передаваться извне.
Результатом работы каждого источника является `MyList<Student>`.
Стандартные `List`, `ArrayList` и `LinkedList` для хранения результата не использовать.
---
# `ManualDataSource`
## Назначение
Постепенно формировать коллекцию `Student` на основе значений, передаваемых извне для каждого отдельного объекта.
## Входные данные одного объекта
Для создания одного `Student` передаются:
```text
groupNumber
averageGrade
recordBookNumber
```
Также передаётся `index` — позиция в коллекции, на которой должен находиться создаваемый объект.
Размер результирующей коллекции задаётся заранее.
## Контракт
`ManualDataSource` должен:
1. Принимать требуемый размер коллекции.
2. Принимать значения одного `Student`:
- `groupNumber`;
- `averageGrade`;
- `recordBookNumber`.
3. Принимать индекс, на котором должен находиться этот объект.
4. Создавать новый `Student` через `StudentBuilder`.
5. Добавлять созданный объект в нужную позицию коллекции.
6. Позволять повторно передавать данные для существующего индекса, заменяя объект на новый.
7. Проверять допустимость индекса.
8. Не изменять уже созданный объект `Student`.
9. Позволять получить текущее состояние коллекции для просмотра.
10. После заполнения вернуть сформированную `MyList<Student>`.
Рекомендуемый метод для передачи одного объекта:
```java
setStudent(
int index,
String groupNumber,
double averageGrade,
String recordBookNumber
)
```
Таким образом, пользовательский код может последовательно передать данные для всех элементов:
```java
dataSource.setStudent(0, "101", 4.5, "10001");
dataSource.setStudent(1, "102", 3.8, "10002");
dataSource.setStudent(2, "101", 4.9, "10003");
```
После заполнения:
```java
MyList<Student> students = dataSource.create();
```
### Изменение объекта
Для изменения объекта повторно вызывается `setStudent(...)` с нужным индексом:
```java
dataSource.setStudent(1, "105", 4.2, "10025");
```
Объект на позиции `1` заменяется новым валидным `Student`.
### Размер коллекции
Если задан:
```text
size = 5
```
то для успешного создания коллекции должны быть заданы данные для всех пяти позиций:
```text
0
1
2
3
4
```
Порядок заполнения не обязан совпадать с порядком индексов:
```java
dataSource.setStudent(3, "103", 4.1, "10004");
dataSource.setStudent(0, "101", 4.5, "10001");
dataSource.setStudent(4, "102", 3.9, "10005");
dataSource.setStudent(1, "101", 4.8, "10002");
dataSource.setStudent(2, "104", 3.7, "10003");
```
После заполнения коллекция должна содержать ровно пять объектов в соответствии с указанными индексами.
Если хотя бы одна позиция не заполнена, `create()` должен сообщить об ошибке и не возвращать неполную коллекцию.
---
# `RandomDataSource`
## Назначение
Создавать заданное количество `Student` со случайными значениями в диапазонах, заданных извне.
## Входные данные
Передаются:
```text
size
```
и диапазоны для каждого поля:
```text
groupNumber:
minimum
maximum
averageGrade:
minimum
maximum
recordBookNumber:
minimum
maximum
```
## Контракт
`RandomDataSource` должен:
1. Принять размер создаваемой коллекции.
2. Принять минимальное и максимальное значение каждого поля.
3. Проверить корректность каждого диапазона.
4. Сгенерировать значения в заданных пределах.
5. Создать каждый объект через `StudentBuilder`.
6. Поместить объекты в `MyList<Student>`.
7. Вернуть коллекцию заданного размера.
Минимальное и максимальное значения включаются в диапазон генерации.
`RandomDataSource` не должен самостоятельно получать или отображать параметры генерации.
---
# `FileDataSource`
## Назначение
Создавать `MyList<Student>` на основании содержимого файла.
## Входные данные
Передаётся **абсолютный путь к файлу**.
Относительный путь не является входным параметром `FileDataSource`.
Формат одной записи:
```text
groupNumber;averageGrade;recordBookNumber
```
Пример:
```text
101;4.75;123456
102;3.80;123457
103;4.20;123458
```
## Контракт
`FileDataSource` должен:
1. Получить абсолютный путь к файлу.
2. Открыть файл.
3. Последовательно обработать его записи.
4. Разделить каждую запись на три значения.
5. Создать `Student` через `StudentBuilder`.
6. Добавить только валидные объекты в `MyList<Student>`.
7. Пропустить некорректную запись и продолжить обработку остальных.
8. При невозможности открыть файл сообщить об ошибке.
9. Вернуть сформированную `MyList<Student>`.
---
# Общие требования
1. Все источники должны создавать `Student` только через `StudentBuilder`.
2. Источник данных не должен самостоятельно вводить данные.
3. Источник данных не должен зависеть от интерфейса пользователя.
4. Источник данных не должен содержать логику сортировки.
5. Результат хранить только в `MyList<Student>`.
6. Невалидный `Student` не должен попасть в коллекцию.
7. Проверка корректности самого объекта выполняется средствами `Student`.
8. Для ручного заполнения изменение существующего `Student` выполнять заменой объекта, а не изменением его полей.
---
# Примеры тестов
## `ManualDataSource`
### Заполнение всей коллекции
Создать источник размера `3`:
```java
ManualDataSource dataSource = new ManualDataSource(3);
dataSource.setStudent(0, "101", 4.5, "10001");
dataSource.setStudent(1, "102", 3.8, "10002");
dataSource.setStudent(2, "103", 4.2, "10003");
MyList<Student> result = dataSource.create();
```
Проверить:
```text
result.size() == 3
```
и соответствие каждого объекта своему индексу.
### Заполнение в произвольном порядке
```java
dataSource.setStudent(2, "103", 4.2, "10003");
dataSource.setStudent(0, "101", 4.5, "10001");
dataSource.setStudent(1, "102", 3.8, "10002");
```
Проверить правильное расположение объектов.
### Изменение объекта
После заполнения:
```java
dataSource.setStudent(1, "105", 4.9, "10025");
```
Проверить, что изменился только объект с индексом `1`.
### Незаданный индекс
Для коллекции размера `3` вызвать:
```java
dataSource.setStudent(3, ...);
```
Проверить выброс исключения.
### Неполное заполнение
Заполнить только две позиции из трёх и вызвать `create()`.
Проверить, что неполная коллекция не возвращается.
---
## `RandomDataSource`
### Корректная генерация
Задать размер `100` и допустимые диапазоны для всех трёх полей.
Проверить:
```text
result.size() == 100
```
и нахождение каждого значения внутри заданного диапазона.
### Граничный диапазон
Задать одинаковые минимальное и максимальное значения.
Проверить, что все сгенерированные значения соответствуют этому значению.
### Некорректный диапазон
Задать:
```text
minimum > maximum
```
Проверить выброс исключения.
---
## `FileDataSource`
### Корректный файл
Для файла:
```text
101;4.75;123456
102;3.80;123457
103;4.20;123458
```
проверить получение трёх корректных объектов.
### Файл с некорректной записью
```text
101;4.75;123456
invalid;abc;123457
103;4.20;123458
```
Проверить, что в коллекцию попадают только две корректные записи.
### Пустой файл
Проверить получение пустой коллекции.
### Несуществующий файл
Передать путь к несуществующему файлу и проверить корректную обработку ошибки.
---
# Результат
Реализовать:
```text
DataSource.java
ManualDataSource.java
RandomDataSource.java
FileDataSource.java
```
Допускается создание вспомогательных классов для чтения файла и генерации случайных значений.
-81
View File
@@ -1,81 +0,0 @@
# Дополнение к техническому заданию: изменение типа `recordBookNumber`
Данное дополнение имеет **приоритет над основным ТЗ `DataSource.md`**.
## Изменение модели `Student`
Поле:
```java
private final String recordBookNumber;
```
заменено на:
```java
private final int recordBookNumber;
```
Метод:
```java
getRecordBookNumber()
```
возвращает `int`.
## Требования к `DataSource`
Во всех реализациях источников данных необходимо учитывать новый тип `recordBookNumber`.
### `ManualDataSource`
`recordBookNumber` должен передаваться как `int`:
```java
setStudent(
int index,
String groupNumber,
double averageGrade,
int recordBookNumber
);
```
Строковое значение номера зачётной книжки больше не используется.
### `RandomDataSource`
Для `recordBookNumber` необходимо задавать и генерировать **числовой диапазон**:
```text
минимальное значение — int
максимальное значение — int
```
Генерируемое значение должно быть целым числом и соответствовать ограничениям `Student`.
### `FileDataSource`
В файле `recordBookNumber` должен записываться как целое число.
Формат записи:
```text
groupNumber;averageGrade;recordBookNumber
```
Пример:
```text
A12;4.75;12345
B32;3.80;12346
C17;4.20;12347
```
При чтении значение необходимо преобразовать в `int` до создания `Student`.
## Приоритет
При противоречии между данным дополнением и основным `DataSource.md` использовать требования этого дополнения.
Все остальные требования основного технического задания остаются без изменений.