Skip to content

コード責務・重複・依存関係 調査レポート

調査日: 2026-07-03 / 対象コミット: 2881aaf 系列 / ブランチ: claude/code-responsibility-analysis-vqkn3z 対象規模: 非テスト src 428 ファイル・electron 142 ファイル・合計 約 70,483 LOC(テスト src 140+) 目的: 各ファイルの責務分割が適切か、共通化できる重複実装がないか、ファイル依存関係を横断的に棚卸しする。

このドキュメントは点検時点のスナップショットです。指摘は「事実(file:line)」と「評価(✅ 良 / ⚠️ 要注意 / ❌ 問題)」を分けて記載します。改善の実施計画ではなく現状把握が主目的であり、リファクタ着手時の地図として使うことを想定しています。


目次

  1. エグゼクティブサマリー
  2. 調査方法とスコープ
  3. 全体アーキテクチャと依存関係
  4. レイヤ別 責務分析
  5. 責務の問題: god-file / デッドコード一覧
  6. 横断的な重複・共通化候補(最重要)
  7. 維持すべき良い設計
  8. 改善提案とロードマップ
  9. 付録: 依存エッジ表・評価インデックス

各節へは VitePress 右側のアウトライン、または見出し番号で移動してください。


1. エグゼクティブサマリー

総評

全体として 設計意図は明確で、多くのサブシステムが「container / view / hooks」分離や DI・ファサード分割を実践できている(特に player-dashboard, DB リポジトリ分割, StartupSequencer, SyncAdapter)。過去の God-object 分割(electron/database.ts 1370行 → 現行の modular repository)も完了しており重複は残っていない

一方で、成長に伴う 2 種類の負債が全域に散在している:

  1. 機械的コピペ(構造的重複) — 共有ヘルパが存在するのに使われず、同じ定型が数十箇所に手書きされている。
  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:297preload/types.ts:89
2🔴 バグ源BAN/PICK dedupe キーの三分裂Aggregator.ts:20bannerId-characterIdaggregate.ts:81characterId-bannerId-bannerName。bannerId 空時に別人の同キャラ BAN を過剰削除しうる不整合(うち Aggregator 版はデッドコード化)Aggregator.ts:20aggregate.ts:81StreamLogParserV2.ts:225
3🔴 god-fileRecorderAutoStart.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/PathResolverbackfillWorkflow/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/ipcelectron/services/*35✅ IPC→サービスの正方向
tabs/player-dashboardsrc/utils/logAnalyzer28共有コアへの正当な依存
electron/ipcelectron/*(横断)24
tabs/db-viewersrc/utils/logAnalyzer18
screens/broadcastsrc/types17
electron/main+coreelectron/services/*17起動オーケストレーション
electron/services/*electron/database12✅ サービス→DBの正方向
electron/ipcelectron/database11
tabs/settingssrc/store / tabs/common13/12tabs/common は実共有モジュール
electron/services/recordersrc/utils/logAnalyzer6electron→src の共有参照

要点:

  • レイヤリングは概ね健全(main → ipc/services/windowsipc → 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:20log-analysis/containers/RealtimeAnalysisPanel を、:19db-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.ts170critical/deferred 分割 + timeout + fault isolation。設計プリミティブとして綺麗
core/AppLifecycle.ts362単一インスタンスロック / エラーハンドラ / navigation guard
core/PathResolver.ts121純粋パスユーティリティ
core/ProcessManager.ts126デッドコードforceKillProcess/executeCommand は本番参照ゼロ
main.ts596⚠️モジュールレベル可変状態を多数保持。initServices(約100行) 等の巨大インラインクロージャ。database 直 import
main/recorderModeBootstrap.ts438⚠️サービス生成 + tray 配線 + 約160行の CLI trigger dispatcher(handleTrigger L267-427) が同居
ipc/IPCRegistry.ts279合成ルート。ただし BrowserAutomationHandlers への pass-through 委譲が漏出
ipc/BrowserAutomationHandlers.ts802God ファイル(詳細 §5)
ipc/SimulatorHandlers.ts397⚠️handleSimulatorAction(L188-347) が約160行の if-else 連鎖
ipc/LogWatcherHandlers.ts381⚠️startWatchersForDirs(約180行) に検証/watcher/DB保存/telemetry/通知/窓制御が同居
windows/OverlayWindowManager.ts249⚠️「標準サイズ強制復帰」try/catch が 4 回反復(L163-236)
preload/*.ts (api×11)Node 漏れなし。import type のみで database/services を参照
preload/types.ts517⚠️単一巨大 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.ts468⚠️createTables() 単一メソッドが約380行(DDL+migration 混在)
MatchRepository.ts625⚠️肥大化 repo。CRUD + 一覧/詳細 + ウィジェット集計クエリ混入。if(!this.db) throw が18箇所(型的デッド)
types.ts561⚠️全方位から import される結合中心。services への逆依存あり
stats/MatchReconciler.ts422⚠️battle-stats UPDATE の巨大列リスト重複保持
stats/ApiResponseWriter.ts284⚠️同上(§6-D)
stats/StatsCalculator.ts260集計クエリ。コールバック注入で match 層に直依存しない
match/matchDetailQueries.ts151players/bans/picks 共有クエリ(良い抽出)
MilestoneRepository.ts / SeasonFetchStateRepository.ts172/99共有 SELECT 定数 + mapRow、ON CONFLICT の好例

4.3 Electron サービス層

ファイルLOC評価所見
recorder/RecorderAutoStart.ts1119最大の god-file(8 責務、§5)
browserAutomation/workflowOrchestrator.ts896⚠️責務は単一だがほぼ死にコード(§5)
BrowserAutomationService.ts466薄いファサード。Playwright ライフサイクルに専念、各モジュールへ委譲
LogWatcherService.ts403chokidar 監視 + UTF-8 境界バッファリングは堅実。ただし変換ヘルパ4個が startupImport と重複
battleRecordApi/backfillWorkflow.ts276DB 副作用を完全 DI 化した模範
milestone/MilestoneService.ts68EvaluateDeps 注入の模範
battleRecordApi/BattleRecordApiClient.ts228request() に共通処理集約 + FetchLike DI
telemetry/*fail-safe と I/O・純関数分離が良好
utils/startupImport.ts327⚠️変換ヘルパ4個が LogWatcherService と重複

4.4 ログ解析 + 共有 utils

ファイルLOC評価所見
logAnalyzer/core/line/LineParser.ts166正規表現の単一ソース
logAnalyzer/core/StreamLogParserV2.ts428⚠️FSM 駆動の解析エンジン。batch/realtime 両方の単一実体。大きいが凝集
logAnalyzer/LogParser.tsV2 を回す薄いラッパ(重複なし)。ただしコンストラクタが marker カウントで全行を二度舐め
logAnalyzer/StreamLogParser.tsV1名 → V2 の re-export シム(実体重複なし。クリーン)
logAnalyzer/core/aggregator/Aggregator.ts91⚠️dedupe/sort が本番未使用のデッド層、かつ dedupe キーが誤り(§6-A/§5)
logAnalyzer/CharacterService.ts159⚠️DB照会 + 40行 fuzzy 画像マッチの 2 責務。nameAliases は空初期化のまま未配線
logAnalyzer/StatisticsCalculator.ts283⚠️集計は委譲するが match 反復+winRate をほぼ同一構造で 3 回
utils/electronAPI/database.ts396⚠️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.ts212⚠️責務単一だがモジュールスコープ可変シングルトン。循環 import 回避コードが集中
store/sync/*Adapter.ts20-75SyncAdapter 抽象は綺麗isReceiving ガードは applyRemoteState に一元化(アダプタ間重複なし)
components/ControlPanel.tsx306god-component ではない。13 hooks に委譲。難点はセレクタ無し全ストア購読(L54)
controlPanel/hooks/**(13個)概ね単一責務。win/loss reset 2本と「一度だけ表示」系に重複(§6)
hooks/useLogWatcher.ts397fat hook を IPC/State/Settings 3 衛星に分解した好例
hooks/useLogWatcherIPC.ts274⚠️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.tsx404⚠️props interface が141行=約100個の神 props
broadcast/Overlay.tsx190⚠️約100 props を手転記(L64-187)
overlay/SeasonInfoArea.tsx334SeasonBar/TitleSide 等に分解済み
overlay/AnnouncementArea.tsx276⚠️5 state + 2 effect の状態機械が肥大
bp-opponent/BPOpponentView.tsx228ヘッダ block を3状態で丸ごと重複(L99-109/136-146/169-179)
common/DraggableArea.tsx133overlay 全10エリアで共有(重複回避の核)
constants/layoutDefaults.ts164⚠️OverlayState と1:1で三重管理(§6-H)
types/electron.d.ts671preload と二重管理・実ドリフト(§6-B)
types/index.ts334⚠️overlay 型 + API 型が同居しやや雑多
画面 entry *.tsx ×820-61⚠️6個が bootstrap コピペ(§6-H)

5. 責務の問題: god-file / デッドコード一覧

god-file / 責務過多

ファイルLOC抱える責務数分割の方向性
recorder/RecorderAutoStart.ts11198戦績フェッチ+ログイン管理を BattleRecordController へ、勝率DB集計を recentResultsSummary 側へ抽出しコアを ~400 LOC に
ipc/BrowserAutomationHandlers.ts8026純粋ヘルパ / API スタック生成 / ログイン通知 / ブラウザ lifecycle / workflow / IPC登録 を分離。形骸定数 shouldTryDirect/shouldFallbackToBrowser を削除
db-viewer/hooks/useMatchListState.ts3437useMatchDeletion / useSeasonFilter / getPlayerRelations(util) に分割
MatchRepository.ts6253+ウィジェット集計クエリを stats 側へ、if(!this.db) ガードを基底へ
DatabaseConnection.ts4682380行 createTables() を宣言的DDL配列 + migration runner に分離
RealtimeSessionView.tsx37255 props を state オブジェクト or Context 渡しへ
OverlayView.tsx404~100 props を位置/サイズ束 + visibility map へ再編

デッドコード / 形骸

箇所状態備考
core/ProcessManager.ts(126 LOC 全体)本番参照ゼロ削除 or 結線の判断が必要
workflowOrchestrator.ts のブラウザ fetch 群(約896 LOC)shouldFallbackToBrowser = () => falseBrowserAutomationHandlers.ts:71 確認済)で到達不能direct API に一本化済み。最大の削減余地
Aggregator.dedupeBans/dedupePicks/sortPlayersAggregator.ts:17-57テストのみ呼出実 dedupe は StreamLogParserV2 / aggregate.ts にインライン。しかも誤ったキー(§6-A #2)
shouldTryDirect/shouldFallbackToBrowserBrowserAutomationHandlers.ts:63,71常に true/false を返す形骸定数fetch mode 分岐の残骸
StreamFsm.canTransition() / CharacterService.nameAliases(:15)未使用 / 空のまま未配線
MatchRepository.tsif(!this.db) throw(18箇所)db は非null注入のため型的に到達不能他 repo は0箇所で不統一

6. 横断的な重複・共通化候補(最重要)

同種の重複をカテゴリ別に整理する。**「共有すべき正版が既に存在するのに使われていない」**ケースを優先的に示す。

A. 計算・フォーマットロジック

A-1. 勝率 / 百分率計算 🔴(最重要・全レイヤ横断) 共有ヘルパが不在で、(wins/total)*1004 種の独立関数 + 約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:43useWinRateRecalc が生成した文字列を /(\d+\.\d+)%/再パースする往復実装
  • → 提案: src/utils/format/winRate.tswinRatePercent(wins, total, opts?) / formatWinRate() / pctFrom01() を新設し全面置換。丸め・0除算規則を単一化。

A-2. formatDuration 三重定義 ⚠️: utils/formatters.ts:8(export 済)があるのに dailySummary/formatDailySummaryText.ts:54PerformanceSection.tsx:56 が local 再実装。

A-3. 時刻/日付フォーマット重複 ⚠️: ClockArea.tsx:52-94(12/24h・曜日配列)、recorderStatsView.ts:13-22telemetry/types.ts:53-57scoreMap.ts:37useSeasonAnalytics.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/saveUserSettingsbattleRecordFetchMode?/battleRecordBackfill? を含む (L341-342)含まない (L148-149)🟠
browserAutomation.executeMatchEndWorkflowreturns {detailPageUrl?} (L355)returns {battleList?, battleDetail?} (L207)🟠
openUpdateLog{ok, path?} (L197){success} (L158)🟠
db.logWatcherStatusL291 と L292 に同一行コピペ事故🟡

加えて row 型(SeasonInfoView, ApiResponseRow, Profile, SeasonSummaryRow 等)が electron.d.tselectron/database/types.ts二重定義BattleTypeSlicesrc/utils/logAnalyzer/battleType/roleListBattleType.ts:15electron/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.tsutils/electronAPI/events.ts:16 subscribeToEvent2つがあるのに、生 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-15useAutoDeckApply.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-262MatchReconciler.ts:231-233
  • D-5. Facade ボイラープレート ⚠️: database.tsrequireXxx 9個 + 委譲58 + export68。require<T>() ジェネリックで集約可。

E. ワークフロー並行実装

  • E-1. workflowOrchestrator(ブラウザ)と directWorkflows(直叩き)の完全二重実装 🔴: 4 ワークフロー(試合終了/全件/season/list+season)が2系統。ブラウザ経路は無効化済み(§5)。matchId 解決が3箇所重複(saveBattleDetailToDb L243-258 / executeFullWorkflow L772-774 / resolveMatchId L47-65)、Winner/Loser→players 変換も手書き map で battleDetailFromResponse() を未使用。
  • E-2. Manager(BrowserAutomationHandlers)と Recorder(RecorderAutoStart)の重複 🔴: makeBackfillStore() 完全一致(RecorderAutoStart.ts:226BrowserAutomationHandlers.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-completedexecuteMatchEndWorkflow(:126-163 ≡ :57-88)、handleTestWorkflow/handleFullWorkflow、browser start/stop を ~250 行コピペ。差は appendLog/notify 注入 vs setNotification 直呼びのみ。→ 共通 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:389startupImport:81)、parseLogClockToMilliseconds(:370 ≡ :90)、calculateDurationSeconds(:382 ≡ :101)、本体 convertToDbFormat(:311) ≈ convertParsedMatchToDbFormat(:111)。

F. UI コンポーネント / hooks(タブ)

  • F-1. season フィルタ state ブロックの完全重複 🟠: player-dashboard/hooks/useMatchesData.ts:42-59db-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-52CharacterImageScatterChart.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-selectedguideOpenSignal を +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タブから importMatchStatistics, WinRatePanel, CharacterStatsSnapshotSection も特定タブに住みながら他タブが参照。→ tabs/common/ へ昇格。

G. Electron ボイラープレート

  • G-1. window→renderer 送信ガード ⚠️: if (win && !win.isDestroyed()) win.webContents.send(...).isDestroyed() 58箇所 / webContents.send 33箇所(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箇所)、setupDevToolsHotkeysOverlayWindowManager.ts:23-42ControlWindowManager.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. ResizeDirection union の4重複 ⚠️: ResizeHandle.tsx:4-12electron.d.ts:218-222preload/types.ts:179-183BPOpponentView.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-29useGameLaunchWinLossReset.ts:24-30(トリガのみ相違)。

7. 維持すべき良い設計

リファクタ時に壊してはいけない成功パターン:

  • DB 層の分割: 単一 DatabaseConnection(本番 new DatabaseDatabaseConnection.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 — 正しさに関わる是正(🔴 高優先・低〜中コスト)

  1. IPC 型契約の単一ソース化(§6-B): ElectronAPI と row 型を srcelectron 双方が import する共有モジュールへ集約。まず createProfile/getUserSettings/executeMatchEndWorkflow/openUpdateLog のドリフトを実装に合わせ修正し、electron.d.ts:292 の重複行を削除。唯一「実バグを生む」重複
  2. BAN/PICK dedupe の一本化(§6-A #2): aggregate.ts の正しいキー(bannerName 込み)に統一し、Aggregator の誤ったデッド実装を修正 or 削除。
  3. 勝率計算ヘルパの単一化(§6-A #1): winRatePercent/formatWinRate を新設し 15+ 箇所を置換。0除算・丸め規則を統一。StatsArea の文字列往復を数値 props 化で除去。

フェーズ2 — デッドコード除去 & god-file 分割(🟠 中優先)

  1. workflowOrchestrator のブラウザ fetch 群を撤去し direct 版に一本化(約896 LOC 削減、§5/§6-E1)。ProcessManager・形骸定数も整理。
  2. RecorderAutoStart を分割BattleRecordController 抽出、§5)。Manager/Recorder の共通ヘルパ(runDirectWorkflow/validateLoginStatus/makeBackfillStore)を再利用徹底(§6-E2)。
  3. BrowserAutomationHandlers を分割(6責務→モジュール、§5)。
  4. useMatchListState の分解useMatchDeletion/useSeasonFilter/getPlayerRelations、§5/§6-F1/F4)。

フェーズ3 — 構造的重複の共通化(🟠 中優先)

  1. electronAPI ファサードへの寄せ + イベント購読の一本化(§6-C1/C2): 直叩き61ファイルを facade へ、生 electronAPI.onuseElectronEvent へ。lint で強制。
  2. browser-automation ワークフローの共通フック化useBattleRecordWorkflow、§6-E3、~250行削減)。
  3. チャート集計ユーティリティ化buildTopNPlusOthers/weightedWinRate/resolveImageHref、§6-F2/F3)。
  4. 共有部品の tabs/common/ 昇格MatchDetail ほか、§6-F8)と view→container 逆流の解消。

フェーズ4 — ボイラープレート削減(🟡 低優先・機械的)

  1. Electron: sendToWindow() / registerDbHandler() / channels.ts / createAppWindow()(§6-G)。
  2. DB: battle-stats 列の定数配列化 / existsBattleDetail() / isRankType() / Facade ジェネリック化(§6-D)。
  3. 共通 util: normalizePlayerName / formatDuration 集約 / bootstrapScreen() / overlayPanelSx() / getOpponentTeam() / localStorage キーレジストリ(§6-A/C/H)。
  4. CharacterService の provider/singleton 化 + getImagePathImageResolver へ分離(§6-H7)。

着手の目安

  • 費用対効果が高い順: フェーズ1(バグ源)→ #4 デッドコード削減(一括で ~1000 LOC 減)→ #8 facade 寄せ(新規バグ予防)。
  • 各項目は独立に着手可能(相互依存なし)。god-file 分割(#5/#6)は影響範囲が広いためテスト整備とセットで。

9. 付録

9.1 責務評価インデックス(❌/⚠️ のみ抜粋)

評価ファイル主因
services/recorder/RecorderAutoStart.ts8責務の god-file
ipc/BrowserAutomationHandlers.ts6責務の god-file + 形骸定数
types/electron.d.tspreload と二重管理・実ドリフト
core/ProcessManager.tsデッドコード
db-viewer/hooks/useMatchListState.tsgod-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.tsx55 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 を対象。評価は点検時点のスナップショットであり、以後の変更で陳腐化しうる。