「かってに通知メモ」v1.1.4 をリリースしました。今回も例の辛口の友人からの指摘で、「夜の7時半」が「19時0分」になってしまうバグを修正したものです。
修正自体はシンプルでしたが、調査の過程で「同じバグが2箇所にあった」という、ちょっと面白い発見があったので記録しておきます。
報告されたバグ
友人からの指摘はこうでした:
「夜の7時半」と入力すると 19:00 になる。
単に「19時半」なら 19:30 になるのに。
確かに不便。前回 v1.1.3 で「8時半」のバグを直したのに、「夜の」を頭につけるだけでまた「半」が消える…という現象です。
実際にテストしてみると、こんな結果でした:
| 入力 | 結果 | 期待値 |
|---|---|---|
| 夜の7時半 | 19:00 | 19:30 |
| 朝の8時半 | 8:00 | 8:30 |
| 夕方の5時半 | 17:00 | 17:30 |
| 今夜7時半 | 19:00 | 19:30 |
一方、なぜか OK なパターンもありました:
| 入力 | 結果 |
|---|---|
| お昼の12時半 | 12:30 ✅ |
| 午前9時半 | 9:30 ✅ |
| 午後3時半 | 15:30 ✅ |
| 明日の朝8時半 | 8:30 ✅ |
「OK パターン」と「NG パターン」を並べてみると、原因がだいたい見えてきました。
一回目の修正:実は届いていなかった
最初は _tryTomorrowTime というメソッドの中にバグを見つけました:
if (text.contains('夜') || text.contains('晩')) {
int? hour = _extractHour(text);
if (hour != null && hour < 12) hour += 12;
hour ??= nightHour;
return DateTime(tomorrow.year, tomorrow.month, tomorrow.day, hour, 0);
// ↑
// ハードコードされた 0
}
分の部分が 0 でハードコードされている!_extractMinute を呼んでいないので、「半」が無視されていたわけです。
ここを直して、実機テストしてみたら…結果が変わらない。
「あれ?コードは正しいはずなのに…」と思って、parse() メソッドの呼び出し順を確認したら:
final result =
_tryRelativeMinutes(...) ??
_tryRelativeHours(...) ??
_trySpecificDate(...) ??
_tryTodayTime(...) ??
_tryTomorrowTime(...) ?? // ← ここに来る前提だったけど…
_tryDayAfterTomorrow(...) ??
_tryFuzzyTime(...) ??
_tryVagueTime(...); // ← 実はここで処理されていた
「夜の7時半」には「明日」というキーワードが含まれていないので、_tryTomorrowTime の冒頭で:
if (!text.contains('明日') && !text.contains('あした')) return null;
と即座に弾かれていました。つまり、修正は正しいけど、到達しないコードでした。
真犯人は _tryVagueTime
最終的に処理を引き受けていたのは _tryVagueTime でした。ここに同じパターンのバグが4箇所あったんです:
if (text.contains('夜') || text.contains('晩')) {
...
return DateTime(base.year, base.month, base.day, hour, 0); // ← ここ
}
if (text.contains('朝') || text.contains('午前')) {
...
return DateTime(base.year, base.month, base.day, hour, 0); // ← ここ
}
if (text.contains('昼') || text.contains('正午')) {
return DateTime(base.year, base.month, base.day, noonHour, 0); // ← ここ
}
if (text.contains('夕方')) {
...
return DateTime(base.year, base.month, base.day, hour, 0); // ← ここ
}
_tryTomorrowTime と同じ構造のバグが、別のメソッドにもそっくり存在していた、というわけです。
修正は同じパターンで、各箇所に:
final minute = _extractMinute(text);
を1行追加して、最後の 0 を minute に置き換えるだけ。4箇所すべて同じ修正で解決しました。
今回の学び:同じバグは複数箇所にあるかも疑う
これは個人開発あるあるかもしれませんが、似たような処理が複数のメソッドに散らばっていると、同じバグが複数箇所に存在することがあります。
今回の場合、_tryTomorrowTime と _tryVagueTime で「夜」「夕方」「朝」「昼」を別々に処理していました。コピペで作ったわけではないと思いますが、結果として同じパターンのコードが2箇所にあり、同じバグも2箇所にあった、という構造でした。
修正する時の教訓:
- バグを直す前に、似た処理が他にないか grep してみる
- 直した後、実際に動作確認するまで安心しない(私は最初の修正で「直った!」と思いそうになりました)
- 「コードが正しいのに動作が変わらない」時は、そのコードに到達していない可能性を疑う
シンプルな話ですが、改めて意識すると良いポイントだと感じました。
1週間で3バージョンの怒涛のリリース
実は今回の v1.1.4 で、1週間で v1.1.2 → 1.1.3 → 1.1.4 と3バージョンを公開したことになります。
| バージョン | 内容 |
|---|---|
| v1.1.2 | ジャスト時刻通知、「そのうち」設定、通知動線の統一 |
| v1.1.3 | 「8時半」バグ修正、曖昧表現5つ追加 |
| v1.1.4 | 「夜の7時半」など時間帯+〜時半 バグ修正 |
これらすべて、辛口の友人からのフィードバックが起点でした。
普通なら「次の大型アップデートにまとめてリリース」と考えるところですが、個人開発の良いところは「気になったらすぐ直してすぐ届けられる」スピード感かもしれません。特に時刻認識のような 日常で頻繁に使う部分のバグ は、待たせる理由がないので、できるだけ早くリリースする方針でやっています。
小さい修正なら、審査も平均1日以内に通過してくれているので、フィードバック → 修正 → リリースのサイクルが本当に速い。
おわりに
今後は、引き続き「日本語に強いリマインダーアプリ」というポジションを大切にしながら、季節・祝日対応(ひな祭り、節分、バレンタイン、敬老の日など)を v1.2.0 として計画中です。
「これも認識してほしい」「こう書いたのにうまくいかない」というご報告、いつでも歓迎です。今回のように、些細に見える指摘が地味に改善につながります。


