---
title: 勉強会_現役向け_設計判断
tags: 
author: [smile_yukiko_it](https://docswell.com/user/smile_yukiko_it)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/Y76W4DXQ7V.jpg?width=480
description: https://github.com/Forbusinessuseyukikoishiguro/-PRESENTATION-6-APP_VER-v5.0.0-hexmode-
published: June 02, 26
canonical: https://docswell.com/s/smile_yukiko_it/KY8M4D-2026-06-02-062250
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/Y76W4DXQ7V.jpg)

現 役 エ ン ジ ニ ア 勉 強会
ビルドレス単一HTMLの設計判断
タイマー精度 / 状態管理 / XSS / メモリ / テスト容易性 ― 制約が設計を決める
題材：うさうさ研修ツール 6モード統合版（v5.0.0 / 単一HTML・外部依存ゼロ）


# Page. 2

![Page Image](https://bcdn.docswell.com/page/G75MQ3X274.jpg)

なぜビルドレスの単一HTMLか
研修現場PCの制約 ＝ インストール権限なし・ネット制限・プロキシ・古いブラウザ
配布単位はファイル1つ
メール添付・USBで配れる。CDN/npm依存は不可
外部リソースゼロ
Webフォントも読まない（CORS・オフライン両面で安全側）
永続化しない
個人情報を扱う可能性。ブラウザストレージに残さない
トレードオフ：型・モジュール分割・テストランナーを捨て、純粋関数＋自己テストで品質担保


# Page. 3

![Page Image](https://bcdn.docswell.com/page/9J29PZX1ER.jpg)

論点1：タイマー精度
アンチパターン：カウントダウン
採用：終了時刻からの逆算
setInterval(() =&gt; { remain--; }, 1000);
endAt = Date.now() + ms;
間引き・スロットリングで累積ドリフト → 30秒が32〜33秒に
残りは常に実時計の引き算。setIntervalは再描画トリガに格下げ
要点：
•
残り時間＝endAt − Date.now()。真実は単調増加する実時計に委ねる
•
描画は100msに。残り秒は Math.ceil で切り上げ（最後の1秒が一瞬で消えるのを防ぐ）
•
一時停止＝残msを保存／再開＝endAtを引き直す。状態を2変数で表現
厳密さを求めるなら performance.now() も選択肢（システム時刻変更の影響を受けない）
rem = endAt - Date.now();


# Page. 4

![Page Image](https://bcdn.docswell.com/page/DEY45R84JM.jpg)

論点2：フレームワークなしの状態管理
// single source of truth
•
状態→描画の一方向
function judge(ok){
records.push({ok, who:sanitize(...), n:...});
•
records更新→render*を呼ぶだけ（React的re-render
を手で再現）
•
renderは毎回 innerHTML=&quot;&quot; で全描画。数十件なら差
分計算は過剰
•
UIの表示/非表示も状態(running)から導出し乖離を防
ぐ
const records = [];
renderLog(); renderScore(ok);
// 状態→描画
}
有限状態機械として意識する：idle / running / paused / done
「二重start防止」「過剰undoでも空配列で壊れない」といったガードは、状態遷移を意識すると漏れにくい


# Page. 5

![Page Image](https://bcdn.docswell.com/page/VJNYNDQ678.jpg)

論点3：入力の正規化とXSS（二段構え）
入口：sanitize
出口：escape
s.replace(/[&amp;&lt;&gt;&quot;]/g, c =&gt; map[c])
.replace(/[\r\n\t]+/g,&quot; &quot;)
.replace(/\s{2,}/g,&quot; &quot;).trim()
slice(0,60)+&quot;…&quot;
改行を残すと「1行1問」のコピー出力が壊れる。表示崩れで
はなく出力前提を守る正規化
•
ログはinnerHTMLで組むためescape必須
•
textContentで組めば不要だが、1行に複数要素を組むため
innerHTMLを選択
•
不変条件：innerHTMLを使う箇所は必ずescapeを通す


# Page. 6

![Page Image](https://bcdn.docswell.com/page/YE9PRGDRJ3.jpg)

論点4・5：メモリと、ビルドレスのテスト
メモリ：演出DOMは必ず破棄
•
WAAPI(element.animate)で宣言的に
•
完了後 setTimeout→remove() で確実に破棄
•
フラグで多重生成をガード（連続発火の積み上がり防止
）
•
DLは createObjectURL→revokeObjectURL で解放
テスト：純粋関数＋自己テスト
•
副作用(DOM)と純粋ロジック(集計・整形)を分離
•
起動時にインライン自己テスト → 画面に n/n 表示
•
CI代わりにNode+vmで擬似DOM実行し回帰確認
•
本ツールは自己テスト38項目を全通過


# Page. 7

![Page Image](https://bcdn.docswell.com/page/GE8DWV3GED.jpg)

まとめ：制約が設計を決める
論点
採用した判断
理由
配布形態
単一HTML・外部依存ゼロ
インストール/ネット制限への適合
タイマー
時刻基準(endAt − now)
setIntervalのドリフト回避
状態管理
スコープ変数＋全描画
規模に対し差分計算は過剰
入力
sanitize＋escapeの二段
出力崩れとXSSの両方を防ぐ
演出
WAAPI＋確実なremove
長時間運用のリーク防止
テスト
純粋関数＋自己テスト
ビルドレスでの回帰検知
「FWがないから雑でいい」ではなく「制約が厳しいからこそ設計判断が要る」


