コード責務・重複・依存関係 調査レポート
調査日: 2026-07-03 / 対象コミット:
2881aaf系列 / ブランチ:claude/code-responsibility-analysis-vqkn3z対象規模: 非テストsrc428 ファイル・electron142 ファイル・合計 約 70,483 LOC(テスト src 140+) 目的: 各ファイルの責務分割が適切か、共通化できる重複実装がないか、ファイル依存関係を横断的に棚卸しする。
このドキュメントは点検時点のスナップショットです。指摘は「事実(file:line)」と「評価(✅ 良 / ⚠️ 要注意 / ❌ 問題)」を分けて記載します。改善の実施計画ではなく現状把握が主目的であり、リファクタ着手時の地図として使うことを想定しています。
目次
- エグゼクティブサマリー
- 調査方法とスコープ
- 全体アーキテクチャと依存関係
- レイヤ別 責務分析
- 責務の問題: god-file / デッドコード一覧
- 横断的な重複・共通化候補(最重要)
- 維持すべき良い設計
- 改善提案とロードマップ
- 付録: 依存エッジ表・評価インデックス
各節へは VitePress 右側のアウトライン、または見出し番号で移動してください。
1. エグゼクティブサマリー
総評
全体として 設計意図は明確で、多くのサブシステムが「container / view / hooks」分離や DI・ファサード分割を実践できている(特に player-dashboard, DB リポジトリ分割, StartupSequencer, SyncAdapter)。過去の God-object 分割(electron/database.ts 1370行 → 現行の modular repository)も完了しており重複は残っていない。
一方で、成長に伴う 2 種類の負債が全域に散在している:
- 機械的コピペ(構造的重複) — 共有ヘルパが存在するのに使われず、同じ定型が数十箇所に手書きされている。
- 並行実装(意味的重複) — 同じドメインロジックが 2〜4 系統に分裂し、一部は既にドリフト(不整合)してバグ源になっている。
最重要 findings トップ7(実地確認済み)
| # | 深刻度 | 内容 | 代表箇所 |
|---|---|---|---|
| 1 | 🔴 バグ源 | IPC 型契約の二重管理とドリフト。src/types/electron.d.ts(671) と electron/preload/types.ts(517) が window.electronAPI を手書き二重定義し、型リンクしないため不整合が検出されない。例: createProfile が renderer 側 (uid: string, playerName: string) vs preload 側 (uids: string[], playerNames: string[]) と単数/配列で真逆 | electron.d.ts:297 ⇔ preload/types.ts:89 |
| 2 | 🔴 バグ源 | BAN/PICK dedupe キーの三分裂。Aggregator.ts:20 は bannerId-characterId、aggregate.ts:81 は characterId-bannerId-bannerName。bannerId 空時に別人の同キャラ BAN を過剰削除しうる不整合(うち Aggregator 版はデッドコード化) | Aggregator.ts:20 ⇔ aggregate.ts:81 ⇔ StreamLogParserV2.ts:225 |
| 3 | 🔴 god-file | RecorderAutoStart.ts(1119) が最大の god-file。8 責務(監視統括/戦績フェッチ/ログインブラウザ/probe/バックフィル/勝率集計/CLIパース/状態公開)を単一クラスに凝集 | electron/services/recorder/RecorderAutoStart.ts |
| 4 | 🟠 デッドコード | workflowOrchestrator.ts(896) がほぼ死にコード。ブラウザ取得経路は shouldFallbackToBrowser = () => false(確認済) で無効化済み、directWorkflows.ts の完全二重実装。約 896 LOC 削減余地 | BrowserAutomationHandlers.ts:71 |
| 5 | 🟠 全域重複 | 勝率/百分率計算が共有 util 不在で 15+ 箇所に散在。(wins/total)*100 が 4 種のヘルパ + 多数インラインに分裂。0除算処理も 0/'-'/'0.0'/null とバラバラ、丸め規則も不統一 | 下記 §6-A |
| 6 | 🟠 全域重複 | window.electronAPI 直アクセスの散在。立派なファサード src/utils/electronAPI/** があるのに 61 ファイルが直叩き(採用率 ~36%)。イベント購読も共有 hook を無視した生 electronAPI.on コピペが多数 | 下記 §6-C |
| 7 | 🟠 並行実装 | browser-automation ワークフローの二重実装。useBrowserAutomation.ts(311) と useBrowserAutomationSession.ts(297) が status polling / match-completed / test・full workflow を ~250 行コピペ | 下記 §6-E |
良い設計(維持すべき点)
- DB 層: God-object 分割完了・単一
DatabaseConnection・リポジトリ間循環依存ゼロ・stats はコールバック注入で match 層への直依存を回避。 - ログ解析:
LogParser(batch)もLogWatcherService(realtime)も単一のStreamLogParserV2に収束(解析ロジックの重複なし)。logAnalyzerは components/store に一切依存せず自己完結。 - フロント:
ControlPanel.tsxは 13 hooks へ委譲するgod-component ではない合成ルート。usePlayerDashboardStateは 4 サブフックを束ねる模範的 orchestrator。DraggableAreaは overlay 全 10 エリアで共有。 - Electron:
preloadに Node 漏れなし・event allowlist あり・サブシステム間の循環依存なし。StartupSequencer/AppLifecycle/PathResolver・backfillWorkflow/MilestoneService(完全 DI)は好例。
2. 調査方法とスコープ
- 手法: リポジトリを 7 サブシステムに分割し、各領域を精読(本体まで読了)。加えて親側で横断的な grep 実測(import エッジ、重複パターン、ファサード採用率)を実施。最重要 3 findings は file:line で実地再確認。
- スコープ:
src/**(components / store / utils / hooks / screens / types / constants / theme)とelectron/**(main / core / ipc / preload / windows / database / services / utils)の非テストコード全域。テスト・stories・生成物・画像は原則対象外(重複の証跡としてのみ参照)。 - 観点: (1) 責務分割の適否(god-file・混在・過大)、(2) 重複・共通化候補(file:line 実証)、(3) 依存関係(レイヤリング・循環・境界越え)。
3. 全体アーキテクチャと依存関係
3.1 レイヤ構成
本アプリは Electron メインプロセスと、複数の renderer 画面(管理パネル / OBS オーバーレイ / BP相手 / Recorder戦績 / apiTester / telemetry など、8 HTML エントリ)で構成される。両者は src/utils/logAnalyzer / src/constants / src/types を共有ライブラリとして参照する。
3.2 サブシステム間の依存エッジ(grep 実測・主要のみ)
| 参照元 | → 参照先 | 件数 | 所見 |
|---|---|---|---|
electron/ipc | electron/services/* | 35 | ✅ IPC→サービスの正方向 |
tabs/player-dashboard | src/utils/logAnalyzer | 28 | 共有コアへの正当な依存 |
electron/ipc | electron/*(横断) | 24 | |
tabs/db-viewer | src/utils/logAnalyzer | 18 | |
screens/broadcast | src/types | 17 | |
electron/main+core | electron/services/* | 17 | 起動オーケストレーション |
electron/services/* | electron/database | 12 | ✅ サービス→DBの正方向 |
electron/ipc | electron/database | 11 | |
tabs/settings | src/store / tabs/common | 13/12 | tabs/common は実共有モジュール |
electron/services/recorder | src/utils/logAnalyzer | 6 | electron→src の共有参照 |
要点:
- レイヤリングは概ね健全(
main → ipc/services/windows、ipc → services → database)。サブシステム間の循環依存は検出されず。 src/utils/logAnalyzerが最も依存される共有コアで、renderer(tabs, screens)と main(services, ipc, utils)の両方から参照される。electron/がsrc/を逆参照(10 ファイル:CharacterService,logAnalyzer/types,constants/seasons,src/types等)。共有純粋ロジックだが、専用shared/パッケージが無くメインプロセスが renderer ソースツリーに手を伸ばす形。→ 中期的にshared/切り出し候補。
3.3 検出された依存上の注意点
- store の意図的循環(ファイルレベル・脆い) ⚠️:
store/index.ts → useOverlayStore.ts → overlay/sync.ts:5 (import * as storeIndex from '../index') → indexが閉じる。TDZ 回避のため巻き上げ関数宣言に依存しており、arrow const に変えると即クラッシュする構造的スメル。既存のadapterAccessor引数(sync.ts:84)で DI 注入すれば解消可能。 - view → container の逆流 ❌:
realtime-analysis/views/RealtimeSessionView.tsx:20がlog-analysis/containers/RealtimeAnalysisPanelを、:19がdb-viewer/containers/MatchDetailを import。当該 Panel は実質 presentational(配置ミス)。 - DB 型層 → services 層の逆依存 ⚠️:
electron/database/types.ts:6-11が../services/milestone/milestoneTypesを import(データ層が上位層に依存)。 - Law of Demeter 連鎖:
ipcRegistry.logWatcherHandlers.getSimulatorHandlers()(IPCRegistry.ts:188,recorderModeBootstrap.ts:374)。
4. レイヤ別 責務分析
各ファイルの評価は ✅ 良 / ⚠️ 要注意 / ❌ 問題。
4.1 Electron メイン / コア / IPC / preload
| ファイル | LOC | 評価 | 所見 |
|---|---|---|---|
core/StartupSequencer.ts | 170 | ✅ | critical/deferred 分割 + timeout + fault isolation。設計プリミティブとして綺麗 |
core/AppLifecycle.ts | 362 | ✅ | 単一インスタンスロック / エラーハンドラ / navigation guard |
core/PathResolver.ts | 121 | ✅ | 純粋パスユーティリティ |
core/ProcessManager.ts | 126 | ❌ | デッドコード。forceKillProcess/executeCommand は本番参照ゼロ |
main.ts | 596 | ⚠️ | モジュールレベル可変状態を多数保持。initServices(約100行) 等の巨大インラインクロージャ。database 直 import |
main/recorderModeBootstrap.ts | 438 | ⚠️ | サービス生成 + tray 配線 + 約160行の CLI trigger dispatcher(handleTrigger L267-427) が同居 |
ipc/IPCRegistry.ts | 279 | ✅ | 合成ルート。ただし BrowserAutomationHandlers への pass-through 委譲が漏出 |
ipc/BrowserAutomationHandlers.ts | 802 | ❌ | God ファイル(詳細 §5) |
ipc/SimulatorHandlers.ts | 397 | ⚠️ | handleSimulatorAction(L188-347) が約160行の if-else 連鎖 |
ipc/LogWatcherHandlers.ts | 381 | ⚠️ | startWatchersForDirs(約180行) に検証/watcher/DB保存/telemetry/通知/窓制御が同居 |
windows/OverlayWindowManager.ts | 249 | ⚠️ | 「標準サイズ強制復帰」try/catch が 4 回反復(L163-236) |
preload/*.ts (api×11) | – | ✅ | Node 漏れなし。import type のみで database/services を参照 |
preload/types.ts | 517 | ⚠️ | 単一巨大 ElectronAPI interface(約138メソッド)。§6-B のドリフト元 |
4.2 Electron データベース層
過去の God-object(1370行)→ modular 分割は完了。electron/database.ts(490) は SQL・業務ロジックを一切持たない現役 Facade(全メソッドが requireXxx().method() 委譲)。「legacy と repository のロジック重複」は存在しない。
| ファイル | LOC | 評価 | 所見 |
|---|---|---|---|
database.ts (Facade) | 490 | ⚠️ | requireXxx 9個 + 委譲58 + export68 の機械的重複 |
DatabaseConnection.ts | 468 | ⚠️ | createTables() 単一メソッドが約380行(DDL+migration 混在) |
MatchRepository.ts | 625 | ⚠️ | 肥大化 repo。CRUD + 一覧/詳細 + ウィジェット集計クエリ混入。if(!this.db) throw が18箇所(型的デッド) |
types.ts | 561 | ⚠️ | 全方位から import される結合中心。services への逆依存あり |
stats/MatchReconciler.ts | 422 | ⚠️ | battle-stats UPDATE の巨大列リスト重複保持 |
stats/ApiResponseWriter.ts | 284 | ⚠️ | 同上(§6-D) |
stats/StatsCalculator.ts | 260 | ✅ | 集計クエリ。コールバック注入で match 層に直依存しない |
match/matchDetailQueries.ts | 151 | ✅ | players/bans/picks 共有クエリ(良い抽出) |
MilestoneRepository.ts / SeasonFetchStateRepository.ts | 172/99 | ✅ | 共有 SELECT 定数 + mapRow、ON CONFLICT の好例 |
4.3 Electron サービス層
| ファイル | LOC | 評価 | 所見 |
|---|---|---|---|
recorder/RecorderAutoStart.ts | 1119 | ❌ | 最大の god-file(8 責務、§5) |
browserAutomation/workflowOrchestrator.ts | 896 | ⚠️ | 責務は単一だがほぼ死にコード(§5) |
BrowserAutomationService.ts | 466 | ✅ | 薄いファサード。Playwright ライフサイクルに専念、各モジュールへ委譲 |
LogWatcherService.ts | 403 | ✅ | chokidar 監視 + UTF-8 境界バッファリングは堅実。ただし変換ヘルパ4個が startupImport と重複 |
battleRecordApi/backfillWorkflow.ts | 276 | ✅ | DB 副作用を完全 DI 化した模範 |
milestone/MilestoneService.ts | 68 | ✅ | EvaluateDeps 注入の模範 |
battleRecordApi/BattleRecordApiClient.ts | 228 | ✅ | request() に共通処理集約 + FetchLike DI |
telemetry/* | – | ✅ | fail-safe と I/O・純関数分離が良好 |
utils/startupImport.ts | 327 | ⚠️ | 変換ヘルパ4個が LogWatcherService と重複 |
4.4 ログ解析 + 共有 utils
| ファイル | LOC | 評価 | 所見 |
|---|---|---|---|
logAnalyzer/core/line/LineParser.ts | 166 | ✅ | 正規表現の単一ソース |
logAnalyzer/core/StreamLogParserV2.ts | 428 | ⚠️ | FSM 駆動の解析エンジン。batch/realtime 両方の単一実体。大きいが凝集 |
logAnalyzer/LogParser.ts | – | ✅ | V2 を回す薄いラッパ(重複なし)。ただしコンストラクタが marker カウントで全行を二度舐め |
logAnalyzer/StreamLogParser.ts | – | ✅ | V1名 → V2 の re-export シム(実体重複なし。クリーン) |
logAnalyzer/core/aggregator/Aggregator.ts | 91 | ⚠️ | dedupe/sort が本番未使用のデッド層、かつ dedupe キーが誤り(§6-A/§5) |
logAnalyzer/CharacterService.ts | 159 | ⚠️ | DB照会 + 40行 fuzzy 画像マッチの 2 責務。nameAliases は空初期化のまま未配線 |
logAnalyzer/StatisticsCalculator.ts | 283 | ⚠️ | 集計は委譲するが match 反復+winRate をほぼ同一構造で 3 回 |
utils/electronAPI/database.ts | 396 | ⚠️ | electron/database のミラーではなく typed IPC プロキシ。22関数が同一定型ガード |
utils/formatters.ts / scoreMap.ts / configSync.ts / obsUtils.ts | – | ✅ | 単一責務 |
4.5 Store / 同期 / ControlPanel / hooks
| ファイル | LOC | 評価 | 所見 |
|---|---|---|---|
store/useOverlayStore.ts + overlay/* | – | ✅ | 薄い合成ルート + 純関数セレクタ。actions の90 setter は型安全上の本質(重複ではない) |
store/overlay/sync.ts | 212 | ⚠️ | 責務単一だがモジュールスコープ可変シングルトン。循環 import 回避コードが集中 |
store/sync/*Adapter.ts | 20-75 | ✅ | SyncAdapter 抽象は綺麗。isReceiving ガードは applyRemoteState に一元化(アダプタ間重複なし) |
components/ControlPanel.tsx | 306 | ✅ | god-component ではない。13 hooks に委譲。難点はセレクタ無し全ストア購読(L54) |
controlPanel/hooks/**(13個) | – | ✅ | 概ね単一責務。win/loss reset 2本と「一度だけ表示」系に重複(§6) |
hooks/useLogWatcher.ts | 397 | ✅ | fat hook を IPC/State/Settings 3 衛星に分解した好例 |
hooks/useLogWatcherIPC.ts | 274 | ⚠️ | 7本の生 electronAPI.on を手動配線(§6-C) |
4.6 タブ(src/components/tabs/**)
| タブ | パターン適用 | 代表的問題 |
|---|---|---|
| player-dashboard | ✅ 最も整備 | orchestrator + SectionView 分離が一貫。チャート集計フックに重複(§6-F) |
| debug | ✅ | 各 Card が自 hook を持ち Tab は Grid 合成のみ(理想) |
| character-stats / log-analysis | ✅ | フック分割良好 |
| db-viewer | ⚠️ | useMatchListState.ts(343) が god-hook(7 責務) |
| realtime-analysis | ⚠️ | RealtimeSessionView(372) に約55 props の prop drilling |
| browser-automation | ⚠️ | フック分割は良いが他タブとワークフロー重複(§6-E) |
| settings | ⚠️ | containers/ に presentational が混在(命名規約が緩い) |
| info / commemorative | ⚠️ | container/view パターン非準拠 |
4.7 画面(src/screens/**)+ theme / types
| ファイル | LOC | 評価 | 所見 |
|---|---|---|---|
overlay/views/OverlayView.tsx | 404 | ⚠️ | props interface が141行=約100個の神 props |
broadcast/Overlay.tsx | 190 | ⚠️ | 約100 props を手転記(L64-187) |
overlay/SeasonInfoArea.tsx | 334 | ✅ | SeasonBar/TitleSide 等に分解済み |
overlay/AnnouncementArea.tsx | 276 | ⚠️ | 5 state + 2 effect の状態機械が肥大 |
bp-opponent/BPOpponentView.tsx | 228 | ❌ | ヘッダ block を3状態で丸ごと重複(L99-109/136-146/169-179) |
common/DraggableArea.tsx | 133 | ✅ | overlay 全10エリアで共有(重複回避の核) |
constants/layoutDefaults.ts | 164 | ⚠️ | OverlayState と1:1で三重管理(§6-H) |
types/electron.d.ts | 671 | ❌ | preload と二重管理・実ドリフト(§6-B) |
types/index.ts | 334 | ⚠️ | overlay 型 + API 型が同居しやや雑多 |
画面 entry *.tsx ×8 | 20-61 | ⚠️ | 6個が bootstrap コピペ(§6-H) |
5. 責務の問題: god-file / デッドコード一覧
god-file / 責務過多
| ファイル | LOC | 抱える責務数 | 分割の方向性 |
|---|---|---|---|
recorder/RecorderAutoStart.ts | 1119 | 8 | 戦績フェッチ+ログイン管理を BattleRecordController へ、勝率DB集計を recentResultsSummary 側へ抽出しコアを ~400 LOC に |
ipc/BrowserAutomationHandlers.ts | 802 | 6 | 純粋ヘルパ / API スタック生成 / ログイン通知 / ブラウザ lifecycle / workflow / IPC登録 を分離。形骸定数 shouldTryDirect/shouldFallbackToBrowser を削除 |
db-viewer/hooks/useMatchListState.ts | 343 | 7 | useMatchDeletion / useSeasonFilter / getPlayerRelations(util) に分割 |
MatchRepository.ts | 625 | 3+ | ウィジェット集計クエリを stats 側へ、if(!this.db) ガードを基底へ |
DatabaseConnection.ts | 468 | 2 | 380行 createTables() を宣言的DDL配列 + migration runner に分離 |
RealtimeSessionView.tsx | 372 | – | 55 props を state オブジェクト or Context 渡しへ |
OverlayView.tsx | 404 | – | ~100 props を位置/サイズ束 + visibility map へ再編 |
デッドコード / 形骸
| 箇所 | 状態 | 備考 |
|---|---|---|
core/ProcessManager.ts(126 LOC 全体) | 本番参照ゼロ | 削除 or 結線の判断が必要 |
workflowOrchestrator.ts のブラウザ fetch 群(約896 LOC) | shouldFallbackToBrowser = () => false(BrowserAutomationHandlers.ts:71 確認済)で到達不能 | direct API に一本化済み。最大の削減余地 |
Aggregator.dedupeBans/dedupePicks/sortPlayers(Aggregator.ts:17-57) | テストのみ呼出 | 実 dedupe は StreamLogParserV2 / aggregate.ts にインライン。しかも誤ったキー(§6-A #2) |
shouldTryDirect/shouldFallbackToBrowser(BrowserAutomationHandlers.ts:63,71) | 常に true/false を返す形骸定数 | fetch mode 分岐の残骸 |
StreamFsm.canTransition() / CharacterService.nameAliases(:15) | 未使用 / 空のまま未配線 | |
MatchRepository.ts の if(!this.db) throw(18箇所) | db は非null注入のため型的に到達不能 | 他 repo は0箇所で不統一 |
6. 横断的な重複・共通化候補(最重要)
同種の重複をカテゴリ別に整理する。**「共有すべき正版が既に存在するのに使われていない」**ケースを優先的に示す。
A. 計算・フォーマットロジック
A-1. 勝率 / 百分率計算 🔴(最重要・全レイヤ横断) 共有ヘルパが不在で、(wins/total)*100 が 4 種の独立関数 + 約15箇所のインラインに分裂:
- 関数版:
store/overlay/state.ts:110 calculateWinRate(w,l):string/screens/recorder-stats/recorderStatsView.ts:25 winRatePercent():number|null/characterStats/buildCharacterRows.ts:60 rate():0..1/electron/services/recorder/recentResultsSummary.ts:62 formatWinRate(recorder ローカル) - インライン:
StatisticsCalculator.ts:160,209,261,dailySummary/formatDailySummaryText.ts:139,usePerformanceMetrics.ts:88,101,167,useOpponentDetails.ts:128,CharacterImagePieChart.tsx:172,OpponentAvailableCharactersPanelView.tsx:193,common/components/CharacterTile.tsx:43,db-viewer/WinRatePanel.tsx:47,MatchStatistics.tsx:53,70,screens/broadcast/overlay/hooks/useWinRateRecalc.ts:20,store/overlay/state.ts:113… - 不整合の実害: 0除算処理が
0/'-'/'0.0'/nullとバラバラ。MatchStatistics.tsxは同一画面内で.toFixed(1)(L53) とMath.round(*1000)/10(L70) の別方式を混用。StatsArea.tsx:43はuseWinRateRecalcが生成した文字列を/(\d+\.\d+)%/で再パースする往復実装。 - → 提案:
src/utils/format/winRate.tsにwinRatePercent(wins, total, opts?)/formatWinRate()/pctFrom01()を新設し全面置換。丸め・0除算規則を単一化。
A-2. formatDuration 三重定義 ⚠️: utils/formatters.ts:8(export 済)があるのに dailySummary/formatDailySummaryText.ts:54 と PerformanceSection.tsx:56 が local 再実装。
A-3. 時刻/日付フォーマット重複 ⚠️: ClockArea.tsx:52-94(12/24h・曜日配列)、recorderStatsView.ts:13-22、telemetry/types.ts:53-57、scoreMap.ts:37↔useSeasonAnalytics.ts:43(YYYYMMDD 分割)が各々独立。
B. IPC 型契約の二重管理 🔴(唯一「バグを生む」重複)
src/types/electron.d.ts(671, renderer 用 ambient global) と electron/preload/types.ts(517, export interface) が同一 window.electronAPI を手書きで二重管理。renderer は前者、preload は後者を見るため、型チェックでリンクせず不一致が検出されない。src/ が electron/ を import しない構造が根本原因で、実際に複数箇所がドリフト済み:
| メソッド | electron.d.ts(renderer) | preload/types.ts(実装) | 危険度 |
|---|---|---|---|
db.createProfile | (name, uid: string, playerName: string, logDir?) (L297) | (name, uids: string[], playerNames: string[], logDir?) (L89) | 🔴 単数/配列で真逆 |
getUserSettings/saveUserSettings | battleRecordFetchMode?/battleRecordBackfill? を含む (L341-342) | 含まない (L148-149) | 🟠 |
browserAutomation.executeMatchEndWorkflow | returns {detailPageUrl?} (L355) | returns {battleList?, battleDetail?} (L207) | 🟠 |
openUpdateLog | {ok, path?} (L197) | {success} (L158) | 🟠 |
db.logWatcherStatus | L291 と L292 に同一行コピペ事故 | – | 🟡 |
加えて row 型(SeasonInfoView, ApiResponseRow, Profile, SeasonSummaryRow 等)が electron.d.ts と electron/database/types.ts に二重定義。BattleTypeSlice は src/utils/logAnalyzer/battleType/roleListBattleType.ts:15 と electron/database/types.ts に手動同期義務付きの二重定義(コメントで自認)。
- → 提案:
ElectronAPIと row 型をsrc/・electron/双方が import する単一ソース(例src/types/ipc/)へ集約。まず表中のドリフトを実装に合わせ修正し、electron.d.ts:292の重複行を削除。
C. electronAPI アクセス / イベント購読 / localStorage
C-1. window.electronAPI 直アクセスの散在 🟠: 立派なファサード src/utils/electronAPI/**(database, events, telemetry, userSettings, window, system, …)があるのに、facade import は35ファイル / window.electronAPI?. 直叩きは61ファイル(採用率 ~36%)。さらに facade 内部(electronAPI/database.ts 等)も各関数で if (!window.electronAPI?.db?.X) { Logger.warn; return fallback } を毎回手書き。
- → 提案:
invoke('db.getMatches', args, fallback)的な高階ラッパで facade 内ボイラープレートを除去し、直接アクセス61ファイルを facade へ寄せる(lint ルールで強制)。
C-2. イベント購読抽象の不統一 🟠(最重要の一貫性欠如): 共有抽象 hooks/useElectronEvent.ts と utils/electronAPI/events.ts:16 subscribeToEvent の2つがあるのに、生 electronAPI.on(...) + if(typeof remove==='function') remove() が useGameLaunchWinLossReset.ts:20-35 / useAutoDeckApply.ts:51-78 / useSessionMatchDetailRefresh.ts:31-63 / useLogWatcherIPC.ts:73-273(7本)にコピペ。useStartupImport.ts:100 は共有 hook を使用済みで、他も置換可能な証拠。
C-3. localStorage 直アクセスの散在 🟠: 33ファイルが直接使用。共有 useLocalStorageState(+ safeGetItem/safeSetItem)があるのに useBrowserAutoStart.ts:16-35 / useTabRouter.ts:23-39 / useOnboardingGuide.ts:22,33 / useSupportDialog.ts:40,64 / useAutoDeckApply.ts:111 が各自 try/catch + JSON.parse を再実装。キーも定数(ONBOARDING_GUIDE_SEEN_KEY 等)と生文字列('myUid', 'logAnalysis_deckAutoApplyEnabled')が混在。
- → 提案: 型付き storage helper + キー定義の一元化(キーレジストリ)。「一度だけ表示 + 既読フラグ」は
useSeenOnce(key)化(現在 useOnboardingGuide / useSupportDialog / useStartupImport が各自実装)。
C-4. adapter envelope (de)serialization の 3〜4 コピー ⚠️: 送信 {type:'UPDATE', payload, sourceId, timestamp} が broadcastAdapter.ts:47/wsAdapter.ts:50/localStorageAdapter.ts:49、受信復元も各所、加えて electronAdapter.ts:15-30 toEnvelope が第4変種。フィールド追加が3〜4箇所同時修正になる。
C-5. extractIpcPayload バイト単位の完全重複 ⚠️: useLogWatcherIPC.ts:9-15 ≡ useAutoDeckApply.ts:13-17。
D. SQL / DB クエリ片(electron/database)
- D-1. battle-stats 12列リストの反復 🔴(最重要のSQL重複):
kill_count..avatar_urlの列並びが6箇所(ApiResponseWriter.ts:60-77,92-114,120-127/MatchReconciler.ts:187-203,208-230,235-240)。新スタット列追加のたび全箇所を手修正。name-fallback UPDATE(WHERE id=(SELECT... ORDER BY id ASC LIMIT 1))はほぼ完全一致。→ 12列を単一定数配列にし句を生成。 - D-2.
EXISTS(score_after)「詳細あり」判定の散在 ⚠️: 5箇所(MatchRepository.ts:316,366,399/MatchReconciler.ts:50-53,401-404/BattleListRepository.ts:157-161)。→existsBattleDetail(alias)helper。 - D-3. rank マッチ判定の二方式 ⚠️(不整合):
LOWER(m.type) LIKE 'rank\_%' ESCAPE '\'(MatchRepository.ts:483,617)とm.type='Rank_1v1'(:552)が併存。「rank とは何か」が2通り。→isRankType(col)に統一。 - D-4.
battle_id書き戻し UPDATE の完全一致(2箇所):ApiResponseWriter.ts:260-262≡MatchReconciler.ts:231-233。 - D-5. Facade ボイラープレート ⚠️:
database.tsのrequireXxx9個 + 委譲58 + export68。require<T>()ジェネリックで集約可。
E. ワークフロー並行実装
- E-1.
workflowOrchestrator(ブラウザ)とdirectWorkflows(直叩き)の完全二重実装 🔴: 4 ワークフロー(試合終了/全件/season/list+season)が2系統。ブラウザ経路は無効化済み(§5)。matchId 解決が3箇所重複(saveBattleDetailToDbL243-258 /executeFullWorkflowL772-774 /resolveMatchIdL47-65)、Winner/Loser→players 変換も手書き map でbattleDetailFromResponse()を未使用。 - E-2. Manager(
BrowserAutomationHandlers)と Recorder(RecorderAutoStart)の重複 🔴:makeBackfillStore()完全一致(RecorderAutoStart.ts:226≡BrowserAutomationHandlers.ts:211)、storageStatePath()3コピー、UID 解決myUid ?? myUids?.[0]が Recorder 内だけで6回(L340,496,788,842,980,1047)。共通ヘルパrunDirectWorkflow(BrowserAutomationHandlers:87)・validateLoginStatus(loginValidation.ts:29)が既存なのに Recorder が未使用で、creds.refresh()+catch(TokenExpiredError)を5回手書き(L377,481,761,795,850)。 - E-3. browser-automation ワークフロー UI の二重実装 🟠:
realtime-analysis/hooks/useBrowserAutomation.ts(311) とbrowser-automation/hooks/useBrowserAutomationSession.ts(297) が status polling(:78-91 ≡ :91-104)、log-watcher-match-completed→executeMatchEndWorkflow(:126-163 ≡ :57-88)、handleTestWorkflow/handleFullWorkflow、browser start/stop を ~250 行コピペ。差はappendLog/notify注入 vssetNotification直呼びのみ。→ 共通useBattleRecordWorkflow({ onLog, onNotify })に一本化。 - E-4.
waitForResponseの3実装 🟠: 正規ヘルパresponseWaiter.ts:14があるのにfetchBattleListFromPage(L116-153) とfetchBattleDetailFromMatchEndPage(L171-208) が完全 inline、loginProber.waitForAnyAuthApiResponse(L166-228) が4個目。 - E-5. MatchData→DB 変換 + 時刻ヘルパの重複 🟠:
extractDateFromLogFileName(LogWatcher:389≡startupImport:81)、parseLogClockToMilliseconds(:370 ≡ :90)、calculateDurationSeconds(:382 ≡ :101)、本体convertToDbFormat(:311) ≈convertParsedMatchToDbFormat(:111)。
F. UI コンポーネント / hooks(タブ)
- F-1. season フィルタ state ブロックの完全重複 🟠:
player-dashboard/hooks/useMatchesData.ts:42-59≡db-viewer/hooks/useMatchListState.ts:35-53(15行が文字レベルでほぼ同一 + 日付レンジ絞り込みも重複)。→useSeasonFilter()。 - F-2. チャートデータ整形フックの重複数学 ⚠️:
useCharacterWinRateChartData.ts(301)/useCharacterMatchupChartData.ts(281)/useCharacterStatsChartData.tsに (a)「上位8件+その他」pie 生成が計6回、(b) 加重平均勝率Σ(winRate*matches)/Σmatchesが winRate 版だけで3回(:83,110,141)、(c)ChartMode/PieViewConfig/ScatterViewConfig型の重複定義、(d) scatter 点マッピングがほぼ同形。→buildTopNPlusOthers()/weightedWinRate()を util 化。 - F-3.
resolveImageHrefの完全重複 ❌:CharacterImagePieChart.tsx:43-52≡CharacterImageScatterChart.tsx:43-52(同一10行)。→ utils へ。 - F-4. 「自分/対戦相手/味方」導出の重複(7箇所)⚠️:
match.players.find(p=>uids.includes(p.uid))+ team 分離がuseMatchListState.ts:132/common/hooks/useMatchTableData.ts:22/useOpponentDetails.ts:65/useMatchesData.ts:137/MatchStatistics.tsx:38/useOpponentStats.ts:35,69。→getPlayerRelations(match, uids)pure util。 - F-5.
NotificationState/notify パターンの散在 ⚠️:{open, message, severity}Snackbar state がuseRealtimeSessionState.ts:14-40/useBrowserAutomationNotifications.ts/browser-automation/hooks/types.tsに散在。→useSnackbar()。 - F-6. ガイド起動シグナル effect の重複(3箇所)⚠️:
control-tab-selectedでguideOpenSignalを +1 する effect がPlayerDashboardTab.tsx:123-132/LogAnalysisTab.tsx:111-120/MatchListTab.tsx:69-78(差は tab 番号のみ)。→useGuideOpenSignal(tabIndex)。 - F-7. 同名
useMatchesData.tsが2つ ⚠️:player-dashboard/hooks/(179) とbrowser-automation/hooks/(60) が同名別物(命名衝突リスク)。 - F-8. 共有部品の配置ミス ⚠️:
db-viewer/containers/MatchDetailが db-viewer / log-analysis / browser-automation / realtime-analysis の4タブから import。MatchStatistics,WinRatePanel,CharacterStatsSnapshotSectionも特定タブに住みながら他タブが参照。→tabs/common/へ昇格。
G. Electron ボイラープレート
- G-1. window→renderer 送信ガード ⚠️:
if (win && !win.isDestroyed()) win.webContents.send(...)が.isDestroyed()58箇所 /webContents.send33箇所(LogWatcherHandlers.ts:187,197,240,265,291,296,308等)。→sendToWindow()。 - G-2. DB ハンドラ try/catch + log ボイラープレート ⚠️:
throw errを含む同形が ipc/ に35箇所(MatchHandlers 14 / Stats 8 / Season 5 / Profile 4 …)。→registerDbHandler(channel, fn, label)。 - G-3. IPC チャンネル名リテラルの多重定義 ⚠️: 共有定数モジュールが皆無。
'browser-automation-workflow-result'は handler +eventChannels.ts:12+automation.ts:42の3箇所、'db-get-matches'はMatchHandlers.ts:80+db.ts:24。typo で silent break。→channels.ts定数化。 - G-4. 窓生成ボイラープレート ⚠️:
usePackagedAssets算出(5窓)、webPreferences(sandbox:false + preload.cjs)(6窓)、devOrigin ?? '127.0.0.1:3000'(9箇所)、setupDevToolsHotkeys(OverlayWindowManager.ts:23-42≡ControlWindowManager.ts:20-39)。→createAppWindow()ファクトリ。
H. その他
- H-1. プレイヤー名
normalizeのローカル再定義 ⚠️:(value||'').trim().toLowerCase()系がuseAutoDeckApply.ts:19/useRealtimeSessionState.ts:281 と 298(同一ファイル内で二重定義)/useLogWatcherIPC.ts:199/useMatchConverter.ts:54-59。一方 core にはLineParser.ts:49 normalizeName()とCharacterService.ts:119が別実装で存在。→normalizePlayerNameを共有化。 - H-2. 画面 bootstrap のコピペ ⚠️: entry 6本(bp-opponent/recorder-stats/api-tester/telemetry/commemorative-card ほか)が
installGlobalErrorReporting → CharacterService.registerImageProvider → createRoot().render(ThemeProvider+CssBaseline)を丸写し(コメントで自認)。→bootstrapScreen()。 - H-3. overlay パネル装飾の重複 ⚠️:
${theme.colors.background}dd+ border + glow が overlay 全10エリアにベタ書き(SeasonInfoArea:69-77/MvpCounterArea:81-90/RecentResultsArea:76-85/MatchPhaseArea:57-66)。→overlayPanelSx(theme)/<OverlayPanel>。 - H-4. OverlayState の3〜4重同期 ⚠️:
types/index.ts:60-237(OverlayState) ↔layoutDefaults.ts:6-162(既定値) ↔OverlayView.tsx:17-157(props) ↔Overlay.tsx:64-187(転記)。avatarSize/cornerDecoration*は既にドリフト(OverlayState にあるが props に無い)。 - H-5. 相手チーム判定の小重複 ⚠️:
myTeam==='TeamA'?'TeamB':'TeamA'がBPOpponentContainer.tsx:48/useBPOpponentStats.ts:34,47/StreamLogParserV2.ts:221。→getOpponentTeam(team)。 - H-6.
ResizeDirectionunion の4重複 ⚠️:ResizeHandle.tsx:4-12≡electron.d.ts:218-222≡preload/types.ts:179-183≡BPOpponentView.tsx:42-51。 - H-7.
CharacterServiceの多重インスタンス化 ⚠️:new CharacterService()が本番で19箇所以上(tabs 内だけで ~9)。singleton/context 不在でenglishNameToIdを各自再構築。一部(MatchSummaryCard.tsx:6)はモジュール評価時生成。 - H-8. telemetry ファイル列挙の4重複 🟡:
TelemetryAnalyzer FILE_RE/TelemetryUploader TELEMETRY_FILE/DiagnosticBundle INCLUDE_PATTERNS(正規表現が微妙に相違)。 - H-9. win/loss リセット重複 ⚠️:
useStartupWinLossReset.ts:23-29≡useGameLaunchWinLossReset.ts:24-30(トリガのみ相違)。
7. 維持すべき良い設計
リファクタ時に壊してはいけない成功パターン:
- DB 層の分割: 単一
DatabaseConnection(本番new DatabaseはDatabaseConnection.ts:44のみ)、リポジトリ間循環依存ゼロ、stats→match のコールバック注入。backfillWorkflow/MilestoneServiceの完全 DI は他サービスの手本。 - ログ解析の単一エンジン: batch/realtime とも
StreamLogParserV2に収束。logAnalyzerは components/store へ import ゼロで移植可能。正規表現はLineParser.tsに単一ソース化。 - 同期の抽象:
SyncAdapterは各トランスポートを綺麗に分離し、isReceivingガードをapplyRemoteStateに一元化してアダプタ間重複を回避。 - フロントの合成:
ControlPanel(13 hooks 委譲)・usePlayerDashboardState(4 サブフック合成)・DebugTab(Card 自律)・useLogWatcher(3 衛星分解)は container/view/hooks の好例。DraggableArea/MatchTable(common 集約)は共有の成功例。 - Electron の安全性: preload の Node 漏れなし・event allowlist(
eventChannels.ts)・StartupSequencerの fault isolation。
8. 改善提案とロードマップ
深刻度と着手コストで層別化。フェーズ1(バグ源の是正)を最優先とし、以降は独立に着手可能。
フェーズ1 — 正しさに関わる是正(🔴 高優先・低〜中コスト)
- IPC 型契約の単一ソース化(§6-B):
ElectronAPIと row 型をsrc・electron双方が import する共有モジュールへ集約。まずcreateProfile/getUserSettings/executeMatchEndWorkflow/openUpdateLogのドリフトを実装に合わせ修正し、electron.d.ts:292の重複行を削除。唯一「実バグを生む」重複。 - BAN/PICK dedupe の一本化(§6-A #2):
aggregate.tsの正しいキー(bannerName 込み)に統一し、Aggregatorの誤ったデッド実装を修正 or 削除。 - 勝率計算ヘルパの単一化(§6-A #1):
winRatePercent/formatWinRateを新設し 15+ 箇所を置換。0除算・丸め規則を統一。StatsAreaの文字列往復を数値 props 化で除去。
フェーズ2 — デッドコード除去 & god-file 分割(🟠 中優先)
workflowOrchestratorのブラウザ fetch 群を撤去し direct 版に一本化(約896 LOC 削減、§5/§6-E1)。ProcessManager・形骸定数も整理。RecorderAutoStartを分割(BattleRecordController抽出、§5)。Manager/Recorder の共通ヘルパ(runDirectWorkflow/validateLoginStatus/makeBackfillStore)を再利用徹底(§6-E2)。BrowserAutomationHandlersを分割(6責務→モジュール、§5)。useMatchListStateの分解(useMatchDeletion/useSeasonFilter/getPlayerRelations、§5/§6-F1/F4)。
フェーズ3 — 構造的重複の共通化(🟠 中優先)
- electronAPI ファサードへの寄せ + イベント購読の一本化(§6-C1/C2): 直叩き61ファイルを facade へ、生
electronAPI.onをuseElectronEventへ。lint で強制。 - browser-automation ワークフローの共通フック化(
useBattleRecordWorkflow、§6-E3、~250行削減)。 - チャート集計ユーティリティ化(
buildTopNPlusOthers/weightedWinRate/resolveImageHref、§6-F2/F3)。 - 共有部品の
tabs/common/昇格(MatchDetailほか、§6-F8)と view→container 逆流の解消。
フェーズ4 — ボイラープレート削減(🟡 低優先・機械的)
- Electron:
sendToWindow()/registerDbHandler()/channels.ts/createAppWindow()(§6-G)。 - DB: battle-stats 列の定数配列化 /
existsBattleDetail()/isRankType()/ Facade ジェネリック化(§6-D)。 - 共通 util:
normalizePlayerName/formatDuration集約 /bootstrapScreen()/overlayPanelSx()/getOpponentTeam()/ localStorage キーレジストリ(§6-A/C/H)。 CharacterServiceの provider/singleton 化 +getImagePathをImageResolverへ分離(§6-H7)。
着手の目安
- 費用対効果が高い順: フェーズ1(バグ源)→ #4 デッドコード削減(一括で ~1000 LOC 減)→ #8 facade 寄せ(新規バグ予防)。
- 各項目は独立に着手可能(相互依存なし)。god-file 分割(#5/#6)は影響範囲が広いためテスト整備とセットで。
9. 付録
9.1 責務評価インデックス(❌/⚠️ のみ抜粋)
| 評価 | ファイル | 主因 |
|---|---|---|
| ❌ | services/recorder/RecorderAutoStart.ts | 8責務の god-file |
| ❌ | ipc/BrowserAutomationHandlers.ts | 6責務の god-file + 形骸定数 |
| ❌ | types/electron.d.ts | preload と二重管理・実ドリフト |
| ❌ | core/ProcessManager.ts | デッドコード |
| ❌ | db-viewer/hooks/useMatchListState.ts | god-hook(7責務) |
| ❌ | screens/bp-opponent/BPOpponentView.tsx | ヘッダ block 3重複 |
| ⚠️ | services/browserAutomation/workflowOrchestrator.ts | ほぼ死にコード |
| ⚠️ | main.ts / main/recorderModeBootstrap.ts | 可変状態 + 巨大クロージャ / CLI dispatcher |
| ⚠️ | database/{MatchRepository,DatabaseConnection,types}.ts | 肥大 repo / 380行DDL / 結合中心 |
| ⚠️ | ipc/{Simulator,LogWatcher}Handlers.ts | 巨大ディスパッチ |
| ⚠️ | screens/broadcast/overlay/views/OverlayView.tsx | ~100 props |
| ⚠️ | realtime-analysis/views/RealtimeSessionView.tsx | 55 props drilling |
| ⚠️ | logAnalyzer/{Aggregator,CharacterService,StatisticsCalculator}.ts | デッド層 / 2責務 / 反復集計 |
9.2 重複カテゴリ早見表
| カテゴリ | 代表 | 深刻度 | 節 |
|---|---|---|---|
| 勝率/百分率計算 | 15+箇所・4ヘルパ | 🟠 | §6-A |
| IPC 型二重管理 | electron.d.ts ⇔ preload/types | 🔴 | §6-B |
| electronAPI 直叩き | 61ファイル | 🟠 | §6-C1 |
| イベント購読コピペ | 生 electronAPI.on 多数 | 🟠 | §6-C2 |
| SQL 列/述語重複 | battle-stats 12列×6 | 🔴 | §6-D |
| ワークフロー並行実装 | orchestrator/direct, Manager/Recorder, BA tab×2 | 🔴🟠 | §6-E |
| チャート集計/関係導出 | pie生成×6, resolveImageHref×2, player relations×7 | ⚠️ | §6-F |
| Electron ボイラープレート | window送信58, DBハンドラ35, channel名 | ⚠️ | §6-G |
| normalize/bootstrap/overlay装飾 | 各所 | ⚠️ | §6-H |
9.3 調査手法メモ
- サブシステム別に精読(本体読了)+ 親側で横断 grep(import エッジ、
winRate/window.electronAPI/normalize/localStorageパターン、ファサード採用率)。 - 最重要 findings(型ドリフト、死にコード、dedupe 不整合)は file:line で実地再確認済み。
- LOC は非テスト .ts/.tsx を対象。評価は点検時点のスナップショットであり、以後の変更で陳腐化しうる。