Java | 1 日 90 分 × 7 日アプリ学習 初級編:ファイル保存アプリ

Web APP Java
スポンサーリンク

7日目のゴール

7日目のテーマは
「ファイル保存アプリを“自分の設計ポリシーを持ったアプリ”にすること」 です。

ここまで6日間で、あなたはかなり多くのことを体で覚えています。

ファイルに文字を書いて永続化する
追記で履歴を溜める
1行=1レコードとして構造を持たせる
追加・一覧・削除・編集を一時ファイルで再構築する
Memo/MemoRepository/AppConfig/アプリ本体に役割を分ける
UTF-8 や保存先フォルダなど、現実世界の事情も考慮する

7日目では、これらを「バラバラなテクニック」としてではなく、
ひとつの“方針”として言葉にする ところまで行きます。

「自分なら、ファイル保存をこう設計する」
これを説明できるようになるのが、今日のゴールです。


これまで作ってきた「ファイル保存アプリ」の全体像を言葉で整理する

レイヤー構造で振り返る

今のファイル保存アプリは、ざっくりこういう層に分かれています。

一番下の層には「ファイルシステム」があります。
OS が提供する、フォルダやファイルそのものの世界です。

その上に「ファイル操作クラス」がいます。
MemoRepository がここにあたります。
Path や UTF-8 を意識しながら、
「メモを保存する」「メモを読み込む」「メモを更新する」といった
具体的なファイル操作を担当しています。

さらにその上に「ドメイン(意味の世界)」があります。
Memo クラスがここです。
ID・日付・本文という「メモという概念」を表現しています。

一番上には「アプリ本体」がいます。
コンソールでユーザーと会話し、
どの操作をするか選んでもらい、
MemoRepository に「これやって」と依頼し、
結果をユーザーに伝えます。

この構造を、今日は「設計の視点」で見直していきます。


7日目の題材:自分の「永続化ポリシー」をコードに刻む

小さな一文にしてみる

まず、あえて雑に言葉にしてみます。

「このアプリでは、メモは UTF-8 のテキストファイルに、1行1レコードで保存する。
1レコードは ID|日付|本文 の形式で、ID は連番、日付は文字列、本文はユーザー入力。
ファイルの更新は、必ず一時ファイルを使って再構築し、バックアップを取りながら置き換える。
ファイルの場所は data/memo.txt とし、フォルダがなければ起動時に作る。
ファイル操作の詳細は MemoRepository に閉じ込め、アプリ本体は Memo のリストとして扱う。」

これ、もう立派な「永続化ポリシー」です。
7日目は、これをコードと結びつけながら、
「なぜそうしているのか」を自分の言葉で説明できるようにしていきます。


ポリシー1:データの形を先に決める(1行=1レコード)

行の仕様を“契約”として扱う

3日目で決めた仕様を思い出しましょう。

1行は ID|日付|本文 という形にする。
ID は整数、日付は文字列、本文は自由なテキスト。

この仕様があるからこそ、
parseLineformatLine が書けました。

例えば formatLine は、こうでした。

private String formatLine(Memo memo) {
    return memo.getId() + "|" + memo.getDate() + "|" + memo.getBody();
}
Java

ここには、「行の形」がはっきり埋め込まれています。
逆に parseLine は、その「契約」を信じて分解します。

private Memo parseLine(String line) {
    String[] parts = line.split("\\|", 3);
    if (parts.length != 3) {
        return null;
    }
    try {
        int id = Integer.parseInt(parts[0]);
        String date = parts[1];
        String body = parts[2];
        return new Memo(id, date, body);
    } catch (NumberFormatException e) {
        return null;
    }
}
Java

ここで大事なのは、
「ファイルはただのテキスト」ではなく、
「自分が決めたルールに従ったテキスト」だと扱っている
ことです。

この「ルールを決める → ルールどおりに書く → ルールどおりに読む」という流れが、
永続化の一番根っこにあります。


ポリシー2:ファイル更新は「部分書き換え」ではなく「再構築」

削除も編集も、やっていることは同じ

4日目でやった削除・編集を、もう一度言葉で整理します。

削除は「そのIDの行だけ書き出さない」。
編集は「そのIDの行だけ書き換えて書き出す」。

どちらも、

元ファイルを1行ずつ読む
行を Memo に変換する(parseLine)
どう扱うか決める(残す/捨てる/書き換える)
一時ファイルに「新しい姿」を書き出す
最後に一時ファイルで元ファイルを置き換える

というパターンで実現しています。

ここでのポリシーは、こう言えます。

「ファイルの更新は、直接いじらず、必ず一時ファイルで再構築する。」

コードで見ると、例えば削除のときはこうでした。

Path tempPath = filePath.resolveSibling(filePath.getFileName() + ".tmp");
boolean deleted = false;

try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8);
     BufferedWriter writer = Files.newBufferedWriter(tempPath, StandardCharsets.UTF_8,
             StandardOpenOption.CREATE,
             StandardOpenOption.TRUNCATE_EXISTING)) {

    String line;
    while ((line = reader.readLine()) != null) {
        Memo memo = parseLine(line);
        if (memo != null && memo.getId() == targetId) {
            deleted = true;
            continue;
        }
        writer.write(line);
        writer.newLine();
    }
}
Java

この「一度全部読んで、新しいファイルを作る」という発想を、
「再構築ポリシー」 として自分の中に持っておくと、
どんなアプリでも応用が効きます。


ポリシー3:ファイルの場所と文字コードは“明示的に決める”

保存先は「設定」として切り出す

6日目で AppConfig を作りました。

public class AppConfig {
    private final Path dataDir;
    private final Path memoFile;

    public AppConfig() {
        this.dataDir = Paths.get("data");
        this.memoFile = dataDir.resolve("memo.txt");
    }

    public Path getMemoFile() throws IOException {
        if (!Files.exists(dataDir)) {
            Files.createDirectories(dataDir);
        }
        return memoFile;
    }
}
Java

ここには、こんなポリシーが隠れています。

「メモファイルは data フォルダ配下に置く。
フォルダがなければ、アプリが起動したときに自動で作る。」

これを main 側から見ると、こうなります。

AppConfig config = new AppConfig();
Path memoPath = config.getMemoFile();
MemoRepository repo = new MemoRepository(memoPath);
Java

アプリ本体は、
「どこに保存するか」を知らなくていい。
「AppConfig に聞けばいい」という形になっています。

これは、かなり大事な設計の一歩です。

保存先を変えたくなったとき、
AppConfig だけ直せばいい。
テスト用に別の場所に保存したいときも、
AppConfig を差し替えればいい。

「場所の決定」を一箇所に閉じ込める、というポリシーです。

文字コードは UTF-8 を明示する

MemoRepository では、
StandardCharsets.UTF_8 を使っていました。

BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8);
BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8, ...);
Java

ここには、こういうポリシーがあります。

「このアプリが扱うテキストファイルは、UTF-8 で統一する。」

これをやっておくと、

別の環境でも文字化けしにくい
他のツール(エディタや他言語のプログラム)からも扱いやすい

というメリットがあります。

「文字コードを決める」というのも、
立派な設計の一部です。


ポリシー4:ファイル操作の詳細は“専任クラス”に閉じ込める

MemoRepository の存在意義を言葉にする

5日目で作った MemoRepository は、
こんな役割を持っていました。

メモを全部読み込んで List<Memo> にする
メモを1件追加する
メモをID指定で削除する
メモをID指定で編集する
次のIDを決める

そして、アプリ本体はこうなりました。

ユーザーから入力をもらう
MemoRepository に「こうして」と依頼する
結果に応じてメッセージを出す

ここにあるポリシーは、こうです。

「ファイルの行を直接触るのは MemoRepository だけにする。
アプリ本体は Memo のリストとして扱い、ファイル形式を意識しない。」

これをもう少し噛み砕くと、

ファイル形式(ID|日付|本文)を変えたくなったら、
MemoRepository だけ直せばいい。
アプリ本体のコードは、そのまま動く。

という状態を目指している、ということです。

これは、現場でめちゃくちゃ効いてくる考え方です。
「どこを直せばいいか」が明確だから、
変更が怖くなくなります。


7日目の小さな仕上げ:自分の言葉でコメントを書いてみる

コードに「設計の意図」を残す

7日目の練習としておすすめなのは、
自分のコードに「設計の意図」をコメントで書いてみることです。

例えば MemoRepository の先頭に、こんなコメントをつけてみる。

/**
 * メモをテキストファイルに永続化するクラス。
 * ファイル形式は 1行1レコードで、"ID|日付|本文" の形に統一する。
 * ファイルの更新は必ず一時ファイルを使って再構築し、バックアップを取りながら置き換える。
 * アプリ本体はこのクラスを通じてのみメモを読み書きし、ファイル形式の詳細は知らない。
 */
public class MemoRepository {
    ...
}
Java

これは、未来の自分へのメッセージでもあります。

「このクラスは、こういう方針で作ってあるから、
変えるときもその方針を意識してね。」

こういうコメントを書けるようになっている時点で、
あなたはもう「設計者の目線」を持ち始めています。


7日目で“本当に持って帰ってほしいこと”

テクニックではなく「軸」を持つ

ここまで7日間で、
ファイル読み書きのテクニックはたくさん出てきました。

try-with-resources
追記モード
readLine のループ
一時ファイルでの再構築
Path/Files/UTF-8
クラス分割(Memo/MemoRepository/AppConfig/アプリ本体)

でも、7日目で一番大事なのは、
それらを「バラバラな技」として覚えることではありません。

「自分は、ファイル保存をこういう方針で設計する」
という を持つことです。

例えば、あなたの軸は、こんな感じかもしれません。

「データの形を先に決める。
ファイル更新は再構築でやる。
保存先と文字コードは明示する。
ファイル操作は専任クラスに閉じ込める。」

これだけでもう、
十分に“プロっぽい”設計の入口に立っています。

あとは、この軸を持ったまま、
ToDo アプリでも、家計簿アプリでも、日記アプリでも、
好きな小さなアプリにファイル保存を組み込んでみてください。

そのとき書いているコードは、
もう「なんとなく動くコード」ではなく、
「あなたの方針が通ったコード」 になっています。

タイトルとURLをコピーしました