タスクを完了する関数を記述する

完了

complete_task 関数は、ファイルに格納されている to-do リストからタスクを削除する処理を担当します。 この関数により、次のアクションを完了する必要があります。

  • ファイルを読み取る。
  • 既存のタスク (存在する場合) を収集する。
  • 指定された位置にあるタスクを削除する (存在する場合)。
  • 更新されたベクトルのタスクをファイルに書き戻す。

complete_task 関数の最初の実装は次のコードのようになりますが、コードの重複の兆候が既に見られるため、リファクタリングを行う必要があります。

use std::io::{Error, ErrorKind, Result, Seek, SeekFrom};  // Include the `Error` type.


pub fn complete_task(journal_path: PathBuf, task_position: usize) -> Result<()> {
    // Open the file.
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .open(journal_path)?;

    // Consume the file's contents as a vector of tasks.
    let tasks = match serde_json::from_reader(file) {
        Ok(tasks) => tasks,
        Err(e) if e.is_eof() => Vec::new(),
        Err(e) => Err(e)?,
    };

    // Remove the task.
    if task_position == 0 || task_position > tasks.len() {
        return Err(Error::new(ErrorKind::InvalidInput, "Invalid Task ID"));
    }
    tasks.remove(task_position - 1);

    // Rewind and truncate the file.
    file.seek(SeekFrom::Start(0))?;
    file.set_len(0)?;

    // Write the modified task list back into the file.
    serde_json::to_writer(file, &tasks)?;
    Ok(())
}

この関数を記述する前に、add_task 関数で使用したファイルを読み取るために同じコードがここに必要であることがわかります。 また、list_tasks 関数を実装するときにも必要になります。 この重複の必要性は、コードをリファクタリングし、その動作を専用の関数にカプセル化する必要があることを示しています。 そうすれば、3 つのアクションに、このロジックのコードを再利用できます。

Task コレクションをリファクタリングする

ファイルの解析を処理する collect_tasks 関数を作成できます。

fn collect_tasks(mut file: &File) -> Result<Vec<Task>> {
    file.seek(SeekFrom::Start(0))?; // Rewind the file before.
    let tasks = match serde_json::from_reader(file) {
        Ok(tasks) => tasks,
        Err(e) if e.is_eof() => Vec::new(),
        Err(e) => Err(e)?,
    };
    file.seek(SeekFrom::Start(0))?; // Rewind the file after.
    Ok(tasks)
}

この関数を使用して、File への参照を受け取り、std::io::Result<Vec<Task>> を返します。 つまり、io::Error が発生することを想定しています。 おまけとして、内容を読み取る前と、呼び出し元に戻す前に、ファイルを巻き戻します。

これで、add_task 関数をリファクタリングして新しい関数を使用できるようになりました。

pub fn add_task(journal_path: PathBuf, task: Task) -> Result<()> {
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(journal_path)?;
    let mut tasks = collect_tasks(&file)?;
    tasks.push(task);
    serde_json::to_writer(file, &tasks)?;
    Ok(())
}

リファクタリング後は、add-task がはるかにわかりやすく、読みやすくなります。

complete_task の最終バージョン

ついに complete_task 関数でリファクタリングされたコードを使用できるようになりました。

pub fn complete_task(journal_path: PathBuf, task_position: usize) -> Result<()> {
    // Open the file.
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .open(journal_path)?;

    // Consume file's contents as a vector of tasks.
    let mut tasks = collect_tasks(&file)?;

    // Try to remove the task.
    if task_position == 0 || task_position > tasks.len() {
        return Err(Error::new(ErrorKind::InvalidInput, "Invalid Task ID"));
    }
    tasks.remove(task_position - 1);

    // Write the modified task list back into the file.
    file.set_len(0)?;
    serde_json::to_writer(file, &tasks)?;
    Ok(())
}

1 つ目と 2 つ目の部分、そして 4 つ目の部分の一部では、いくつかの例外はありますが、add_task 関数と同じ内容を実行しています。

  • ジャーナル ファイルは作成していません。 ので、存在しません。
  • 削除操作を実行しているため、ファイルに書き込む前にファイルを切り捨てています。 そのため、ファイルは元のファイルよりも小さくなります。 この手順を無視すると、巻き戻されたカーソルがファイルの以前に書き込まれたバイトの後ろで停止し、JSON ファイルの形式が正しくなくなります。 file.set_len(0) 操作を使用してファイルを切り捨てるときは、空白のページにバイトが書き込まれていることを確認します。

3 つ目のセクションでは、タスクの位置を指定して、ベクトルからタスクを削除します。 位置が有効でない場合は、問題を説明するカスタムメイドの io::Error を使用して早期に戻ります。