はじめに

こんにちは。エンジニアの荒井です。
前回作成したアプリの改修を記事にまとめました。
今回はGeminiにコードレビューをしてもらった後に、ジェスチャを使用した機能を2つ追加しました。

前回の記事はこちら

開発環境

macOS 15.1.1
Xcode Version 16.1

Githubリポジトリ

この記事で解説するコードおよび出力ファイルは以下のリポジトリに格納しております。
https://github.com/milldea/blog-python-sound-generator/tree/c28e287

今回やること

  • Geminiにアプリのコードを渡し、レビューをしてもらう
  • レビュー指摘に対応する
  • 長押しでカウントをリセットする機能を追加する
  • 月単位でカウントをグラフ表示する機能を追加する

実践

コードレビュー

Gemini

Geminiってなに?という方はこちら

※前提・注意事項
今回作成しているアプリは個人の趣味の範囲で開発しているものです。
業務で開発・使用しているコードをGeminiやOpenAIに渡すのは絶対にやめましょう。

自分で分かっている特に直した方が良いだろうという問題点を挙げてもらうまで何回もリクエストしてみて、最終的には以下を採用しました。

質問

以下のコードの問題点をなるべく多く指摘してください。
回答は日本語にしてください。

import Swift

(以下略)

回答

## ContentView の問題点(日本語)

このコードは基本的なカレンダー機能を実装していますが、いくつかの改善ポイントがあります。

1. 可読性の改善

* 変数名の一部が短く、より意味を明確にする名前にしても良いでしょう (e.g. `currentYear` -> `yearForCurrentDate`)
* 一部の関数にコメントを追加すると、理解しやすくなります (e.g. `dayFor` 関数)

2. エラー処理の不足

* デコード/エンコード処理でエラーが発生した場合の挙動が定義されていません。エラーハンドリングを追加しましょう (e.g. `print` 出力、アラート表示など)

3. テストの欠如

* テストケースが用意されていないようです。テストを書くことで、コードの信頼性を高めることができます。

4. マジックナンバー

* 以下の箇所で直接的な数値が使用されており、可読性を下げています。
    * `weekdayHeader` の `ForEach` ループ (7)
    * `dateGrid` の `ForEach` ループ (6, 7)
    * `firstDayOfMonthWeekday` の計算 (-2)
* これらの数値は定数として定義し、意味のある名前を付けるのが望ましいです。

5. 改善の可能性

* `UserDefaults` ではなく `Keychain` を検討 (カウントデータを永続的に保存する場合)
* カウントのリセット機能 (現在、日付をタップしたときにカウントがインクリメントされます)
* `DateFormatter` の利用 (より読みやすい形式の日付文字列に変換できます)
* カスタム型 (複雑な計算ロジックをカプセル化するために、カスタム型を定義することも検討できます)

まとめ

このコードは基本的な構造自体は悪くありませんが、可読性と保守性を向上させる余地があります。上記の改善ポイントを参考に、よりメンテナンスしやすいコードを目指しましょう。

セルフレビュー(おまけ)

  • 全てContentViewに詰め込んでいるのを適切に分割し、ファイルも分けたい
    • 日付セルの処理のViewを分離する
    • カレンダー部分とカウント部分を分離する
    • スタイル設定を分離する

レビュー対応

今回はレビュー指摘の 1, 2, 4 + セルフレビュー に対応しました。
レビュー反映のみの差分はこちら
最終的にファイル分割までして、この状態です。

ファイル分割とコメントの追加、変数名の修正によってだいぶ自分でもわかりやすくなったような気がします。
同じターゲット(ビルド単位)内のメソッド・クラスはインポート不要というのがいいですね。
おかげで気軽にファイル分割ができました。

機能追加

少しコードも整理できたので、機能を追加していきます。

カウントのリセット機能

深い意味はなく、ただ長押し(ロングタップ)動作を実装してみたかったので追加します。

追加時の差分はこちら

新たに onLongPressGesture を使用するだけで、基本的な流れは onTapGesture と変わりません。
とても簡単に実装できるのになんだかちょっとiPhoneアプリを作っている気持ちが高まります。

グラフ表示・スワイプ

月単位でカウントをグラフ表示する機能を追加します。
そしてこちらもただ使ってみたかっただけですが、上方向にスワイプした時にグラフが表示されるようにしてみました。

この状態で上方向にスワイプすると

こうなります。

追加時の差分はこちら

GraphView というビューを追加しました。
Charts ライブラリを使用し、以下のような形にした月単位のカウントデータを渡します。

[
 "2024-11-01": 3,
 "2024-11-02": 0,
 "2024-11-03": 2,
 ...
 "2024-11-30": 1
]

これを ForEach で回しつつ、LineMark 関数のx軸に日付、y軸にカウント数を渡しました。
日付を全て表示すると見づらいので、7日ごとに日付と縦線を表示しています。

ビューの表示には DragGesture と sheet を使用し、ContentView 内で上方向にスワイプされた時に isGraphViewPresented という GraphView の表示フラグをオンにしています。
オンになると下からグラフビューがスッと出てきて、下方向にスワイプする or グラフビュー以外の領域をタップすることでスッと消えます。
これもまた、iPhoneのアプリを作っている感がすごいです。

おわりに

今回はちょっとした機能追加とコードの掃除くらいのつもりで取りかかりましたが、思った以上に時間がかかりました。。。
特にグラフ表示の部分で、Chartライブラリを使ったグラフ部分の細かいカスタマイズと、保存済みのカウントデータを表示するところで盛大にハマりました。
(でも実は一番ハマったところが "保存しているカウントデータの日付は0埋めじゃないのに、表示する時の条件判定では0埋めになっていて10日以降しか表示されない" という言語は全然関係ない問題だったのは内緒です。ここを直しました。)
ロングタップやスワイプなどのジェスチャに対応する処理を書くのが楽しいので、もう少しこのアプリをいじってみようと思います。
続編にご期待ください。