Day 80 のゴールと全体像
Day 80 では、ここまで育ててきたタスク管理アプリを 「きれいに片づける日」 にします。 コードそのものの機能はほとんど変えずに、
- フォルダ構成
- クラスの置き場所
- 名前空間の整理
を通して、読みやすく・育てやすいプロジェクト構成に整えていきます。
今回のターゲット構成は次のとおりです。
- Models
- Services
- Repositories
- Utilities
- Program.cs(ルート)
「家の中のものを、ちゃんと棚にしまう」ようなイメージで、 コードを気持ちよく並べ替えていきましょう。
なぜフォルダ構成を整理するのか
小さなアプリでも「整理整頓」は効いてくる
最初は、1ファイルに全部書いてしまっても動きます。 しかし、機能が増えてくると、
- どこに何が書いてあるのか分からない
- 似たような処理があちこちに散らばる
- 修正したいときに、探すだけで疲れる
という状態になりがちです。
そこで、役割ごとにフォルダを分けることで、
- 「データの形」は Models
- 「データの保存・取得」は Repositories
- 「アプリの振る舞い」は Services
- 「共通の小さな便利機能」は Utilities
- 「アプリの入口」は Program.cs
というように、頭の中の地図とプロジェクトの構造を一致させていきます。
フォルダ構成の全体像
目指すプロジェクト構成
最終的に、プロジェクトはこんな感じの見た目になります。
TaskApp/
├─ Models/
│ └─ TaskItem.cs
├─ Repositories/
│ └─ TaskRepository.cs
├─ Services/
│ └─ TaskService.cs
├─ Utilities/
│ └─ InputHelper.cs
└─ Program.cs
ここからは、フォルダごとに「何を置くか」「どう書くか」を、 順番に見ていきます。
Models:データの「形」を表す場所
TaskItem を Models に移動する
まずは、タスクを表す TaskItem クラスを Models フォルダに移します。
Models/TaskItem.cs
namespace TaskApp.Models
{
// タスクを表すモデルクラス
public class TaskItem
{
public int Id { get; set; } // 一意なID
public string Title { get; set; } // タスク名
public string Description { get; set; } // 詳細メモ
public int Priority { get; set; } // 優先度 (1:高, 2:中, 3:低)
public DateTime? DueDate { get; set; } // 期限(nullなら期限なし)
public bool IsCompleted { get; set; } // 完了フラグ
public DateTime CreatedAt { get; set; } // 作成日時
}
}
C#ここで意識したいこと
- Models には「データの形」だけを置きます。
- ロジック(保存・検索・表示など)は書きません。
- こうしておくと、 「タスクってどんな情報を持っているんだっけ?」 と確認したいときに、迷わず Models を見に行けます。
Repositories:データの保存・取得を担当する場所
TaskRepository を Repositories に移動する
次に、タスク一覧を管理する TaskRepository を Repositories フォルダに移します。
Repositories/TaskRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.IO;
using TaskApp.Models; // TaskItem を使うために Models を参照
namespace TaskApp.Repositories
{
// タスクの保存・取得を担当するクラス
public class TaskRepository
{
private readonly List<TaskItem> _tasks = new List<TaskItem>();
private int _nextId = 1;
// タスク追加
public TaskItem Add(string title, string description, int priority, DateTime? dueDate)
{
var task = new TaskItem
{
Id = _nextId++,
Title = title,
Description = description,
Priority = priority,
DueDate = dueDate,
IsCompleted = false,
CreatedAt = DateTime.Now
};
_tasks.Add(task);
return task;
}
// 全タスク取得
public IEnumerable<TaskItem> GetAll()
{
return _tasks;
}
// IDで検索
public TaskItem? FindById(int id)
{
return _tasks.FirstOrDefault(t => t.Id == id);
}
// 削除
public bool Remove(int id)
{
var task = FindById(id);
if (task == null)
{
return false;
}
_tasks.Remove(task);
return true;
}
// JSONファイルに保存
public void SaveToFile(string filePath)
{
try
{
var options = new JsonSerializerOptions
{
WriteIndented = true
};
string json = JsonSerializer.Serialize(_tasks, options);
File.WriteAllText(filePath, json);
}
catch (Exception ex)
{
Console.WriteLine("タスクの保存中にエラーが発生しました。");
Console.WriteLine($"詳細: {ex.Message}");
}
}
// JSONファイルから読み込み
public void LoadFromFile(string filePath)
{
try
{
if (!File.Exists(filePath))
{
return;
}
string json = File.ReadAllText(filePath);
var tasks = JsonSerializer.Deserialize<List<TaskItem>>(json);
if (tasks == null)
{
Console.WriteLine("タスクデータの読み込みに失敗しました。");
return;
}
_tasks.Clear();
_tasks.AddRange(tasks);
if (_tasks.Any())
{
_nextId = _tasks.Max(t => t.Id) + 1;
}
else
{
_nextId = 1;
}
}
catch (Exception ex)
{
Console.WriteLine("タスクの読み込み中にエラーが発生しました。");
Console.WriteLine($"詳細: {ex.Message}");
}
}
}
}
C#ここで意識したいこと
- Repositories は「どこに・どうやって保存するか」を担当します。
- コンソール表示やユーザー入力のことは考えません。
- 将来、JSONではなくデータベースに変えたくなったときも、 Repository を差し替えるだけで済むような構造を目指します。
Utilities:共通の小さな便利機能をまとめる場所
入力チェックを Utilities に切り出す
Day 79 で作った ReadInt や ReadDateOrNull のようなメソッドは、 いろいろな場面で使える「小さな便利機能」です。
これを Utilities/InputHelper.cs にまとめてみます。
Utilities/InputHelper.cs
namespace TaskApp.Utilities
{
// コンソール入力を安全に受け取るためのヘルパークラス
public static class InputHelper
{
// 数値入力を安全に受け取る
public static int ReadInt(string label)
{
while (true)
{
Console.Write($"{label}を入力してください: ");
string text = Console.ReadLine();
if (string.IsNullOrWhiteSpace(text))
{
Console.WriteLine("入力が空です。数値を入力してください。");
continue;
}
if (int.TryParse(text, out int value))
{
return value;
}
Console.WriteLine("数値として認識できませんでした。もう一度入力してください。");
}
}
// 日付入力を安全に受け取る(未入力ならnull)
public static DateTime? ReadDateOrNull(string label)
{
while (true)
{
Console.Write($"{label}を入力してください (yyyy-MM-dd、未入力なら期限なし): ");
string text = Console.ReadLine();
if (string.IsNullOrWhiteSpace(text))
{
return null;
}
if (DateTime.TryParse(text, out DateTime date))
{
return date;
}
Console.WriteLine("日付の形式が正しくありません。例: 2026-09-10 のように入力してください。");
}
}
}
}
C#ここで意識したいこと
- Utilities には「どの機能からも呼び出せる、共通の小さな部品」を置きます。
- static クラスにしておくと、「道具箱」っぽく使えて分かりやすいです。
Services:アプリの振る舞いをまとめる場所
TaskService で「アプリの操作」をまとめる
ここまで、Program が直接 Repository を呼んでいましたが、 少し整理して、アプリの操作をまとめるサービスクラスを作ってみます。
Services/TaskService.cs
using TaskApp.Models;
using TaskApp.Repositories;
using TaskApp.Utilities;
namespace TaskApp.Services
{
// タスク管理アプリの「振る舞い」をまとめるサービスクラス
public class TaskService
{
private readonly TaskRepository _repository;
public TaskService(TaskRepository repository)
{
_repository = repository;
}
// タスク追加の一連の流れ
public void CreateTask()
{
Console.WriteLine("=== タスク追加 ===");
Console.Write("タスク名を入力してください: ");
string? title = Console.ReadLine();
if (string.IsNullOrWhiteSpace(title))
{
Console.WriteLine("タスク名は必須です。空では登録できません。");
return;
}
Console.Write("詳細(メモ)を入力してください(空でも構いません): ");
string? description = Console.ReadLine();
int priority = InputHelper.ReadInt("優先度 (1: 高, 2: 中, 3: 低)");
DateTime? dueDate = InputHelper.ReadDateOrNull("期限");
var task = _repository.Add(
title,
description ?? string.Empty,
priority,
dueDate
);
Console.WriteLine($"タスクを追加しました。ID: {task.Id}");
}
// タスク一覧表示
public void ShowTasks()
{
Console.WriteLine("=== タスク一覧 ===");
var tasks = _repository.GetAll().ToList();
if (!tasks.Any())
{
Console.WriteLine("タスクが登録されていません。");
return;
}
foreach (var t in tasks)
{
string status = t.IsCompleted ? "[完了]" : "[未完了]";
string priorityText = t.Priority switch
{
1 => "高",
2 => "中",
3 => "低",
_ => "不明"
};
string dueText = t.DueDate.HasValue
? t.DueDate.Value.ToString("yyyy-MM-dd")
: "期限なし";
Console.WriteLine($"ID: {t.Id}");
Console.WriteLine($" 状態 : {status}");
Console.WriteLine($" 優先度 : {priorityText}");
Console.WriteLine($" 期限 : {dueText}");
Console.WriteLine($" タイトル: {t.Title}");
Console.WriteLine($" 詳細 : {t.Description}");
Console.WriteLine();
}
}
// ここに編集・削除・完了切り替えなども追加していける
}
}
C#ここで意識したいこと
- Services は「ユーザーが行う操作」をまとめる場所です。
- Repository に直接触るのではなく、 Service 経由で操作することで、 ロジックの変更や拡張がしやすくなります。
Program.cs:アプリの入口だけに集中させる
Main をすっきりさせる
最後に、Program.cs を「アプリの入口」に専念させます。
Program.cs
using TaskApp.Repositories;
using TaskApp.Services;
namespace TaskApp
{
internal class Program
{
const string TaskFilePath = "tasks.json";
static void Main()
{
var repository = new TaskRepository();
var service = new TaskService(repository);
try
{
// 起動時にタスクを読み込む
repository.LoadFromFile(TaskFilePath);
// メインループ
Run(service);
// 終了時にタスクを保存する
repository.SaveToFile(TaskFilePath);
}
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。アプリを終了します。");
Console.WriteLine($"詳細: {ex.Message}");
}
}
static void Run(TaskService service)
{
while (true)
{
Console.WriteLine("=== タスク管理アプリ ===");
Console.WriteLine("1: タスク追加");
Console.WriteLine("2: タスク一覧");
Console.WriteLine("0: 終了");
Console.Write("選択してください: ");
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
{
Console.WriteLine("入力が空です。メニュー番号を入力してください。");
Console.WriteLine();
continue;
}
switch (input)
{
case "1":
service.CreateTask();
break;
case "2":
service.ShowTasks();
break;
case "0":
Console.WriteLine("終了します。");
return;
default:
Console.WriteLine("不正な入力です。メニューに表示されている番号を入力してください。");
break;
}
Console.WriteLine();
}
}
}
}
C#ここで意識したいこと
- Program.cs は「アプリの入口」と「メニューのループ」だけに集中させます。
- 実際の処理は Service に任せることで、 Main がすっきりして読みやすくなります。
Day 80 のまとめ
Day 80 では、タスク管理アプリの フォルダ構成と責務の整理 を行いました。
- Models に「データの形」をまとめ
- Repositories に「保存・取得のロジック」を集め
- Utilities に「共通の小さな便利機能」を置き
- Services に「アプリの振る舞い」を整理し
- Program.cs を「入口とメニュー」に専念させました。
コードの中身は大きく変えていないのに、 プロジェクト全体の「見通しの良さ」はかなり変わってきたはずです。
「どこに何があるか分かる」 「どこを直せばいいかすぐに見つかる」
そんな状態は、プログラミングを続けていくうえで、 とても大きな安心感につながります。
ぜひ、フォルダを行ったり来たりしながら、 自分のアプリが「ちゃんとした構造」を持ち始めた感覚を味わってみてください。 その一歩一歩が、みなさんのコードを、 より美しく、より頼もしいものにしていきます。
