はじめに
Day 9では、時間記録のCSVフォーマット設計と月別ファイル分割の判断について書きました。
今回はファイル操作専用モジュールの設計について書きます。なぜ読み込み処理を独立モジュールとして切り出したのか、CSVとJSONLでメソッドを分けた理由、年月単位での管理設計の判断を記録します。
なぜ独立モジュールにしたか
ファイルの読み込み処理は、特定の機能にのみ使用されるものではありません。NN機能・画面表示・データ取り出しなど、複数の機能から同一の処理が呼ばれることが想定されます。
当初はgoals.pyの中に読み込み処理を実装していましたが、この設計では以下の問題がありました。
- 同じ読み込み処理を各機能が個別に実装することになり、修正時の影響範囲が広がる
- NN機能から読み込む際に、goals.pyに依存する形になり責務が不明確になる
これらの理由から、ファイル操作専用のモジュール(file_operations.py)にFileSearchクラスを新設し、goals.pyの読み込み処理を移植しました。
年月をインスタンス変数で保持する設計
FileSearchクラスは年月をインスタンス変数として保持する設計にしています。
class FileSearch:
def __init__(self, month, year):
self.month = month
self.year = year
def reard_to_jsonl(self):
return_data = []
json_file = create_path(f"json/{self.year}_{self.month}_goals.jsonl")
if not os.path.exists(json_file):
return "ファイルが見つかりません。"
with open(json_file, 'r', encoding='utf-8') as f:
for data in f.readlines():
try:
file_data = json.loads(data)
return_data.append(file_data)
except:
return "jsonファイルが見つかりません"
return return_data
def reard_to_csv(self):
csv_file = create_path(f"csv/time_{self.year}-{self.month}.csv")
return_data = []
print(csv_file)
if not os.path.exists(csv_file):
return "ファイルが見つかりません"
with open(csv_file, 'r', encoding='utf-8') as f:
try:
writer = csv.reader(f)
for data in writer:
return_data.append(data)
return return_data
except Exception as e:
return f"ファイル検索時に問題が発生-> {e}"
年月をインスタンス生成時に受け取る設計にした理由は、NN機能での読み込みを想定したためです。異なる月のデータが1つの推論に混入すると予測精度に影響します。年月単位でインスタンスを生成することで、参照するファイルの範囲を明示的に固定できます。
CSVとJSONLでメソッドを分けた理由
CSVとJSONLの読み込みはメソッドを分けて実装しています。
CSVはcsvモジュールでパースし行単位の配列として返します。JSONLは1行ずつjson.loads()でパースしてオブジェクトとして返します。パース処理の根本的な違いから、同一メソッドでの実装は条件分岐が複雑になると判断し、メソッドを分離しました。
戻り値の設計 - 全件返却の理由
読み込み後はファイルの内容を全件そのまま返す仕様にしています。
この設計にした理由は汎用性の確保です。NN機能と画面表示では使用するデータの範囲が異なります。読み込みの時点で整形・フィルタリングを行うと、片方の機能でデータが不足する可能性があります。読み込みモジュールは全件を返すことに責務を限定し、フィルタリングは呼び出し側で行う方針です。
Day 9で書いたimport_to_csvの当日分フィルタリングも、この方針に基づいた実装です。
def import_to_csv(self):
year = datetime.now().strftime('%Y')
month = datetime.now().strftime('%m')
data = FileSearch(year=year, month=month).reard_to_csv()
times = data[1:]
target = datetime.now().strftime("%Y-%m-%d")
return_data = [row for row in times if row[0] == target]
print(return_data)
return return_data
フロントエンド側の役割
フロントエンドでは、バックエンドから受け取った全件データを各モジュールで受け取り、目的に合わせてフィルタリングして表示する仕様を採用しています。
現在のtimer画面では当日分のみを表示していますが、将来的にNN機能が稼働した際は当月分・複数月分のデータを統計エリアに渡す想定です。バックエンドの戻り値が全件である設計はこの拡張を見据えたものです。
おわりに
今日はファイル読み込みモジュールの設計について書きました。
読み込み処理を独立させた判断は、現時点での保守性だけでなくNN機能との連携を見据えたものです。設計の意図を記録しておくことで、後から見返した際に判断の根拠が残せます。
Day 11ではswagger-typescript-apiの導入について書く予定です。手動fetchの問題点と、APIクライアント自動生成への切り替え経緯を記録します。
リポジトリはOSS公開準備中です。公開後にこの記事へリンクを追加します。
この記事は連載「クラウドに依存しないマイルストーン管理ツール開発記」のDay 10です。