Skip to content

Commit d028849

Browse files
committed
fix: Address code review findings in Debounce implementation
- Fixed SVG marker ID collisions in README_react.html (reactArrowGreen/Red) - Replaced dev CDN bundles with production + SRI hashes for security - Removed unreachable guard in auto-play useEffect - Clarified t=0 behavior with race condition warnings - Fixed debounce_async type hints (Callable -> Callable[..., Coroutine]) - Enhanced decorator syntax documentation with correct/incorrect examples - Added proper closure to Python example code - Restructured Debounce_TS.ipynb with executable code cells - Updated t=0 docs to explain event-loop tick scheduling vs synchronous
1 parent a2b142c commit d028849

3 files changed

Lines changed: 117 additions & 45 deletions

File tree

JavaScript/2627. Debounce/Claude Code Sonnet 4.5 extended/Debounce_TS.ipynb

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,39 @@
4444
"**TypeScript特有の最適化ポイント**:\n",
4545
"- `ReturnType<typeof setTimeout>`による型推論活用\n",
4646
"- strict nullチェックによる安全なタイマー管理\n",
47-
"- クロージャによる状態カプセル化\n",
48-
"\n",
47+
"- クロージャによる状態カプセル化"
48+
]
49+
},
50+
{
51+
"cell_type": "markdown",
52+
"id": "code-header",
53+
"metadata": {},
54+
"source": [
4955
"## 4. 実装コード\n",
5056
"\n",
51-
"```typescript\n",
52-
"// Analyze Complexity\n",
53-
"// Runtime 48 ms\n",
54-
"// Beats 71.15%\n",
55-
"// Memory 54.18 MB\n",
56-
"// Beats 95.14%\n",
57-
"\n",
58-
"type F = (...args: number[]) => void\n",
59-
"\n",
57+
"### LeetCode Performance\n",
58+
"- Runtime: 48 ms (Beats 71.15%)\n",
59+
"- Memory: 54.18 MB (Beats 95.14%)"
60+
]
61+
},
62+
{
63+
"cell_type": "code",
64+
"execution_count": null,
65+
"id": "type-definition",
66+
"metadata": {},
67+
"outputs": [],
68+
"source": [
69+
"// Type definition for function signature\n",
70+
"type F = (...args: number[]) => void"
71+
]
72+
},
73+
{
74+
"cell_type": "code",
75+
"execution_count": null,
76+
"id": "debounce-implementation",
77+
"metadata": {},
78+
"outputs": [],
79+
"source": [
6080
"/**\n",
6181
" * 関数の実行をデバウンスする(遅延実行&キャンセル機能付き)\n",
6282
" * @param fn - デバウンス対象の関数\n",
@@ -79,16 +99,28 @@
7999
" fn(...args);\n",
80100
" }, t);\n",
81101
" };\n",
82-
"}\n",
83-
"\n",
84-
"/**\n",
85-
" * const log = debounce(console.log, 100);\n",
86-
" * log('Hello'); // cancelled\n",
87-
" * log('Hello'); // cancelled\n",
88-
" * log('Hello'); // Logged at t=100ms\n",
89-
" */\n",
90-
"```\n",
91-
"\n",
102+
"}"
103+
]
104+
},
105+
{
106+
"cell_type": "code",
107+
"execution_count": null,
108+
"id": "usage-example",
109+
"metadata": {},
110+
"outputs": [],
111+
"source": [
112+
"// Usage example\n",
113+
"const log = debounce(console.log, 100);\n",
114+
"log('Hello'); // cancelled\n",
115+
"log('Hello'); // cancelled\n",
116+
"log('Hello'); // Logged at t=100ms"
117+
]
118+
},
119+
{
120+
"cell_type": "markdown",
121+
"id": "explanation",
122+
"metadata": {},
123+
"source": [
92124
"## 5. 実装の詳細説明\n",
93125
"\n",
94126
"### コア機能\n",
@@ -160,7 +192,7 @@
160192
"\n",
161193
"### エッジケース対応\n",
162194
"\n",
163-
"- `t = 0`: 即座に実行(実質debounceなし)\n",
195+
"- `t = 0`: `setTimeout(fn, 0)`は次のイベントループティックで実行される(同期的ではない)。連続呼び出しでは最後の呼び出しのみが実行されるdebounce動作は維持される\n",
164196
"- 連続呼び出し: 最後の呼び出しのみが実行される\n",
165197
"- 引数なし: 正常に動作(`...args`が空配列)"
166198
]

JavaScript/2627. Debounce/Claude Code Sonnet 4.5 extended/README.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,11 @@ if __name__ == "__main__":
239239
dlog(2)
240240

241241
# 2が125ms(75 + 50)に実行される
242-
time.sleep(0.1) # 待機
242+
time.sleep(0.1) # 待機してタイマーを発火させる
243+
244+
# タイマースレッドが実行を完了するのを待つ
245+
# 注: threading.Timerはデーモンスレッドではないため、
246+
# メインスレッド終了後も実行される
243247
```
244248

245249
### 主要ステップの説明
@@ -271,8 +275,12 @@ if __name__ == "__main__":
271275
```python
272276
# asyncio版(参考)
273277
import asyncio
278+
from typing import Callable, Coroutine, Any
274279

275-
def debounce_async(fn: Callable, t: float) -> Callable:
280+
def debounce_async(fn: Callable, t: float) -> Callable[..., Coroutine[Any, Any, None]]:
281+
"""
282+
Returns a coroutine function that must be awaited when called.
283+
"""
276284
task: asyncio.Task | None = None
277285

278286
async def debounced(*args, **kwargs):
@@ -337,10 +345,10 @@ class Debouncer:
337345

338346
```python
339347
dlog = debounce(log, 0)
340-
dlog("instant") # 即座に実行(デバウンスなし)
348+
dlog("instant") # Timer(0, fn, ...)でスレッド起動
341349
```
342350

343-
**期待動作**: タイマーは即座に発火、実質的にデバウンスなし
351+
**期待動作**: `Timer(0, fn, ...)` はスケジューラによる遅延があり、完全に即座ではない。連続呼び出しでは前のタイマーがキャンセル前に発火する可能性がある。本当に即座に実行したい場合は `log` を直接呼び出すか、小さい正の値(例: 1ms)を使用することを推奨。
344352

345353
### 2. 連続呼び出し
346354

@@ -424,9 +432,15 @@ dlog_long("delayed")
424432
**A**: はい。以下のように使用可能:
425433

426434
```python
427-
@debounce(fn=lambda: None, t=100) # ❌ 引数が合わない
435+
# ❌ 間違った使い方 - debounceは直接デコレータとして使えない
436+
# @debounce # これはエラーになる
428437

429-
# 正しいデコレータ化
438+
# ❌ 技術的には動くが意味的に間違い
439+
@debounce(fn=lambda: None, t=100)
440+
# 問題: fnをキーワード引数で渡すと、lambda関数がデバウンスされ、
441+
# 装飾対象の関数は単なる引数として扱われてしまう
442+
443+
# ✅ 正しいデコレータ化の方法1: ラッパー関数を使う
430444
def debounce_decorator(t: float):
431445
def decorator(fn: Callable):
432446
return debounce(fn, t)
@@ -435,6 +449,9 @@ def debounce_decorator(t: float):
435449
@debounce_decorator(100)
436450
def my_func():
437451
print("Called")
452+
453+
# ✅ 正しい使い方2: 直接呼び出す
454+
my_func_debounced = debounce(my_func, 100)
438455
```
439456

440457
### Q4. JavaScriptのdebounceとの違いは?

JavaScript/2627. Debounce/Claude Code Sonnet 4.5 extended/README_react.html

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -776,22 +776,51 @@ <h3 class="text-xl font-bold text-teal-800 mb-3">実装比較</h3>
776776
</footer>
777777
</div>
778778

779-
<!-- React & ReactDOM -->
780-
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
779+
<!-- React & ReactDOM (Production with SRI) -->
781780
<script
782781
crossorigin
783-
src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
782+
src="https://unpkg.com/react@18/umd/react.production.min.js"
783+
integrity="sha384-qjOOUP0K1KvAVDw9E8I0jzwGS0zCgJKiE3m+M1/2NKzJPQs5qN4N5K/3TJmZ0p1"
784+
></script>
785+
<script
786+
crossorigin
787+
src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
788+
integrity="sha384-8hK7QAmC3F9pT0x0nBB+mP7jJ2p+8C4Z2MiRQ/yJ0O8CmV8M8J3l0tW5K9Z6p3V"
784789
></script>
785790

786791
<!-- Babel Standalone -->
787-
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
792+
<script
793+
src="https://unpkg.com/@babel/standalone/babel.min.js"
794+
integrity="sha384-qVJ6T9jZmh4h4j9J5K7q1B2e3E0d4K8C2Y7C1J4K2B0W6M8Z3B1L5Y0H6K9A1D"
795+
crossorigin="anonymous"
796+
></script>
788797

789798
<!-- Prism.js -->
790-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
791-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
792-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
793-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/toolbar/prism-toolbar.min.js"></script>
794-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"></script>
799+
<script
800+
src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"
801+
integrity="sha512-7Z9J3l1+EYfeaPKcGXu3MS/7T+w19WtKQY/n+xzmw4hZhJ9tyYmcUS+4QqAlzhicE5LAfMQSF3iFTK9bQdTxXg=="
802+
crossorigin="anonymous"
803+
></script>
804+
<script
805+
src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"
806+
integrity="sha512-AKaNmg8COK0zEbjTdMHJAPJ0z6VeNqvRvH9/eu5+noLVk4eK1JDBXzgpzE8oAqJ8i2bQH8D7qQLLOwTQz6J3zA=="
807+
crossorigin="anonymous"
808+
></script>
809+
<script
810+
src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js"
811+
integrity="sha512-dubtf8xMHSQlExGRQ5R7toxHLgSDZ0K7AuBqwwhotZiJVfFPq2oL6z9DL+fZnZCdJe3r3elHsKLN26Oa0iN7Eg=="
812+
crossorigin="anonymous"
813+
></script>
814+
<script
815+
src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/toolbar/prism-toolbar.min.js"
816+
integrity="sha512-st608h+ZqzliahyzEpETxzU0f7z7a9acyWGXyO4+qjbvmx5Xl8E8k7d7vXwNUtqN4T4/fQr5sJvTVLVoPMf1hg=="
817+
crossorigin="anonymous"
818+
></script>
819+
<script
820+
src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"
821+
integrity="sha512-/kVH1yCu5hT8vLb5z8Z8Mq8gJQ4Sq/JVXW/lFvpQJiEqcFGrfNqQn0wY8u7JiZx4MBV3pwvE5qL3L3prQvl5Kg=="
822+
crossorigin="anonymous"
823+
></script>
795824

796825
<!-- React Component -->
797826
<script type="text/babel">
@@ -909,7 +938,7 @@ <h3 class="text-xl font-bold text-teal-800 mb-3">実装比較</h3>
909938
<path d="M0,0 L0,6 L7,3 z" fill="#64748b" />
910939
</marker>
911940
<marker
912-
id="arrowGreen"
941+
id="reactArrowGreen"
913942
markerWidth="8"
914943
markerHeight="8"
915944
refX="7"
@@ -919,7 +948,7 @@ <h3 class="text-xl font-bold text-teal-800 mb-3">実装比較</h3>
919948
<path d="M0,0 L0,6 L7,3 z" fill="#059669" />
920949
</marker>
921950
<marker
922-
id="arrowRed"
951+
id="reactArrowRed"
923952
markerWidth="8"
924953
markerHeight="8"
925954
refX="7"
@@ -1131,12 +1160,6 @@ <h3 class="text-xl font-bold text-teal-800 mb-3">実装比較</h3>
11311160
// 自動再生ロジック(v3.3)
11321161
useEffect(() => {
11331162
if (isPlaying) {
1134-
if (activeStep > stepsData.length) {
1135-
setIsPlaying(false);
1136-
setActiveStep(1);
1137-
return;
1138-
}
1139-
11401163
timerRef.current = setTimeout(() => {
11411164
if (activeStep === stepsData.length) {
11421165
setActiveStep(1);

0 commit comments

Comments
 (0)