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 は整数、日付は文字列、本文は自由なテキスト。
この仕様があるからこそ、parseLine と formatLine が書けました。
例えば 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 アプリでも、家計簿アプリでも、日記アプリでも、
好きな小さなアプリにファイル保存を組み込んでみてください。
そのとき書いているコードは、
もう「なんとなく動くコード」ではなく、
「あなたの方針が通ったコード」 になっています。

