5 ポイント 投稿者 GN⁺ 2023-11-12 | 1件のコメント | WhatsAppで共有
  • ブラウザの開発者ツールを中級以上に扱えるなら、条件付きブレークポイントを単なる停止条件ではなく、ログ、トレース、状態変更、時間測定ツールへ拡張できる
  • console.logconsole.countconsole.traceconsole.time などの Console API をブレークポイントや Watch Pane に入れると、実行を止めずにフローと状態を記録できる
  • 関数の引数の数、時間、CSS の計算値、実行回数、ランダムサンプリング、グローバルな boolean を組み合わせると、停止するタイミングをはるかに精密に制御できる
  • Chrome の monitordebugcopy$0getEventListenersmonitorEvents は、関数呼び出し、DOM、イベント、クリップボード操作をコンソールから素早く扱えるようにする
  • DOM が JS やユーザーイベントによって継続的に変わる画面では、JS の一時停止、setTimeout(debugger)、DOM スナップショット、フォーカス監視が HTML/CSS デバッグに特に有用

条件付きブレークポイントをデバッグツールへ拡張する

  • 条件付きブレークポイントには、真偽条件だけでなく 副作用のある JavaScript 式も入れられる

  • これを活用すると、基本的なブレークポイントをログ、トレース、状態変更、時間測定ツールのように使える

  • ログポイントとトレースポイント

    • 条件付きブレークポイントで console.log を呼び出すと、実行を止めずにコンソールへ値を残す ログポイントのように動作する
    • console.count を使うと、その行が何回実行されたかもあわせて数えられる
    • 2020年5月のアップデート時点で、主要ブラウザはログポイント/トレースポイントを直接サポートしている
  • Watch Pane で Console API を使う

    • Watch Pane にも console.log のような式を入れられる
    • アプリケーションがデバッガで停止するたびに、console.table(localStorage)localStorage のスナップショットを取れる
    • DOM 変更ブレークポイントと Watch 式を併用すると、DOM サブツリー変更後のスナップショットを配列に保存できる
    (window.doms = window.doms || []).push(document.documentElement.outerHTML)
    
    • DOM 変更ブレークポイントは、実行を止めないようにはできない
  • コールスタックのトレース

    • ローディングスピナーを表示する関数と隠す関数があり、表示呼び出しだけが残る問題を探すときは、console.trace を条件付きブレークポイントに入れられる
    • 表示関数の最後のスタックトレースで呼び出し元をクリックすると、その呼び出し位置へ移動できる
  • 実行中にプログラムの動作を変える

    • 条件付きブレークポイント式で関数の引数を上書きすると、ブラウザ内でプログラムの動作を即座に変えられる
    • たとえば getPerson 関数の id 値を変えられる
    • id=1 のように true と評価される式はデバッガを停止させるため、止めたくない場合は , false を付け足せる
  • すばやい性能測定

    • 条件付きブレークポイントの評価時間が混ざるため、精密な性能プロファイリングには向かない
    • すばやく測定したい場合は、開始地点の条件に console.time('label')、終了地点の条件に console.timeEnd('label') を入れて、実行時間をコンソールに残せる

停止条件をより精密にする

  • 関数の引数の数を活用する

    • 現在の関数が3個の引数で呼び出されたときだけ止めるには、次の条件を使える
    arguments.callee.length === 3
    
    • オプション引数を持つオーバーロード関数で有用
    • 定義された引数の数と実際に渡された引数の数が異なるときだけ止めるには、次の条件を使える
    (arguments.callee.length) != arguments.length
    
    • 関数呼び出し位置のバグを見つける助けになる
  • 時間ベースの条件

    • ページ読み込み後5秒が経過するまでは止めたくない場合、次の条件を使える
    performance.now() > 5000
    
    • 初期ページ読み込み後だけブレークポイントに関心がある場合に適している
    • ブレークポイントが設定された後の次の5秒間は止まらず、その後は止まるようにするには、基準時刻をグローバル変数に保存できる
    window.baseline = window.baseline || Date.now(),
    (Date.now() - window.baseline) > 5000
    
    • コンソールで window.baseline = Date.now() としてカウンターを再開できる
  • CSS 状態ベースの条件

    • 計算済み CSS 値を条件にして停止できる
    • たとえば document.body の背景色が赤のときだけ止めるには、次の条件を使える
    window.getComputedStyle(document.body).backgroundColor === "rgb(255,0,0)"
    
  • 実行回数とサンプリング

    • 行が実行されるたびにカウンターを増やし、2回に1回だけ止められる
    window.counter = (window.counter || 0) + 1, window.counter % 2 === 0
    
    • ランダムサンプリングで10回に1回だけ止めるには、次の条件を使える
    Math.random() < 0.1
    
  • 特定の位置では絶対に止まらないようにする

    • Chrome で gutter を右クリックして “Never Pause Here” を選ぶと、常に false である条件付きブレークポイントが作成され、その行では止まらなくなる
    • XHR ブレークポイントで特定の行を除外したり、特定の例外を無視したりするときに活用できる
  • インスタンス ID の自動付与

    • クラスコンストラクターに条件付きブレークポイントを入れて、各インスタンスを配列に保存できる
    (window.instances = window.instances || []).push(this)
    
    • 特定インスタンスの一意な ID は配列インデックスで確認できる
    window.instances.indexOf(instance)
    
  • ブレークポイントのグローバルトグル

    • 1つ以上の条件付きブレークポイントをグローバル booleanで制御できる
    • コンソールで直接変える、別のブレークポイントで変える、タイマーで変えるといった方法が可能
    window.enableBreakpoints = true
    setTimeout(() => (window.enableBreakpoints = true), 5000)
    

関数とクラス呼び出しのトレース

  • クラスメソッド呼び出しの監視

    • Chrome の monitor コマンドラインメソッドを使うと、クラスメソッド呼び出しを簡単にトレースできる
    • Dog クラスのすべてのインスタンスに対するメソッド呼び出しを見たいなら、prototype のすべてのプロパティに monitor を適用できる
    var p = Dog.prototype;
    Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
    
    • コンソールには function bark called with arguments: 2 のような呼び出し情報が出力される
    • ログだけを残す代わりに呼び出し時に実行を止めたいなら、monitor の代わりに debug を使える
  • 特定インスタンスでクラスをトレースする

    • クラス名は分からないがインスタンスがある場合、コンストラクターの prototype を通じて同じ方法で監視できる
    var p = instance.constructor.prototype;
    Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
    
    • 特定のクラスに固定せず、任意のインスタンスに対して動作する関数を作るときに有用
  • コンソールで関数呼び出し直前にデバッグする

    • デバッグしたい関数をコンソールから呼び出す前に debugger を実行し、次の関数呼び出しへ Step into すると関数実装に入れる
    debugger; fn(1);
    
    • 関数定義の場所を探すのが面倒な場合や、fn が動的にバインドされていてソース位置が分からない場合に有用
    • Chrome では debug(fn) を呼び出して、fn が呼び出されるたびに関数内部で止めることもできる

URL 変更とプロパティ読み取りのデバッグ

  • SPA ルーティング前に止める

    • シングルページアプリケーションが URL を変える直前に止めるには、history.pushStatehistory.replaceStatewindow.onhashchangewindow.onpopstatedebugger を入れた関数を接続できる
    const dbg = () => {
      debugger;
    };
    history.pushState = dbg;
    history.replaceState = dbg;
    window.onhashchange = dbg;
    window.onpopstate = dbg;
    
    • このバージョンの dbg には、ナビゲーションを壊さずに停止する実装までは含まれていない
    • window.location.replacewindow.location.assign を直接呼び出す場合は、代入直後にページがアンロードされるため、この方法では処理できない
    • Chrome では関連メソッドに debug を設定して、リダイレクト元とその時点の状態を確認できる
    debug(window.location.replace);
    debug(window.location.assign);
    
  • プロパティ読み取り地点を見つける

    • オブジェクトの特定プロパティが読み取られる瞬間を知りたいなら、getter の中に debugger を入れられる
    { get configOption() { debugger; return true; } }
    
    • 設定オプションをどのコードがどのように使っているか確認するときに有用
    • 元のソースで変更するか、条件付きブレークポイントを使って適用できる

コンソールでデータをコピーする

  • copy() Console API を使うと、文字列が切り詰められることなくブラウザの情報をクリップボードへコピーできる
  • 現在の DOM スナップショット、画像のようなリソースメタデータ、大きな JSON blob、localStorage ダンプをコピーするときに使える
copy(document.documentElement.outerHTML)
copy(performance.getEntriesByType("resource"))
copy(JSON.parse(blob))
copy(localStorage)

HTML/CSS デバッグ

  • JS 実行を止めた状態で DOM を検査する

    • DOM inspector で Chrome/Windows 基準の ctrl+\ を押すと、いつでも JS 実行を止められる
    • JS が DOM を変えたり、mouseover のようなイベントが DOM を変えたりする状況でも、現在の DOM スナップショットを安定して検査できる
  • 消える要素を検査する

    • マウスを移動すると消える条件付き DOM 要素は、コンソールに5秒タイマーを仕掛けてデバッガを実行すると検査できる
    setTimeout(function() { debugger; }, 5000);
    
    • 5秒間 UI を表示した後に JS 実行が止まると要素が消えないため、開発者ツールへ移動して検査できる
    • JS 実行が止まっている間は、要素の検査、CSS 編集、JS コンソールコマンドの実行が可能
    • カーソル位置やフォーカスに依存する DOM を検査するときに効果的
  • DOM スナップショットを記録する

    • 現在の DOM をコピーするには、次のコマンドを使える
    copy(document.documentElement.outerHTML);
    
    • 1秒ごとに DOM 文字列を配列へ保存するには、次のように setInterval を使える
    doms = [];
    setInterval(() => {
      const domStr = document.documentElement.outerHTML;
      doms.push(domStr);
    }, 1000);
    
    • 配列に保存せず、コンソールへ継続的に出力することもできる
  • フォーカスされた要素を監視する

    • document.activeElement を定期的に比較して、フォーカス変更をコンソールへ出力できる
    (function () {
      let last = document.activeElement;
      setInterval(() => {
        if (document.activeElement !== last) {
          last = document.activeElement;
          console.log("Focus changed to: ", last);
        }
      }, 100);
    })();
    
  • 太字表示された要素を見つける

    • すべての要素から、計算済みの fontWeight"bold" または "700" である要素を見つけられる
    const isBold = (e) => {
      let w = window.getComputedStyle(e).fontWeight;
      return w === "bold" || w === "700";
    };
    Array.from(document.querySelectorAll("*")).filter(isBold);
    
    • 現在 inspector で選択している要素の子孫だけを探すには、$0 を基準に検索できる
    Array.from($0.querySelectorAll("*")).filter(isBold);
    
  • 選択された要素とイベントを扱う

    • コンソールの $0 は、Element Inspector で現在選択されている要素を指す自動参照
    • Chrome と Edge では、直前に検査した要素を $1、その前の要素を $2 のようにアクセスできる
    • Chrome では、現在選択中の要素のイベントリスナーを確認できる
    getEventListeners($0)
    
    • 選択された要素のすべてのイベントをデバッグするには、monitorEvents($0) を使える
    • 特定のイベントグループだけを見たい場合は、配列を渡せる
    monitorEvents($0, ["control", "key"])
    

1件のコメント

 
GN⁺ 2023-11-12
Hacker Newsのコメント
  • この数十年で、ブラウザ内蔵のデバッグツールは本当に大きく進化した。
    JSを長く使ってきた身として、ブラウザ内でコードのデバッグをここまで直感的にしてくれた人たちには深く感謝している。
    バックエンドや他の言語など別の開発領域に行くと、現代のブラウザが標準で提供しているこのデバッグツールのエコシステムが恋しくなる。

    • ブラウザ開発者ツールを本格的に押し上げたFirebug拡張を作った人たちにも拍手を送りたい。
    • JSのデバッグ体験で、特に何がそんなに特別だと感じるのか気になる。
      Python、Java、C++で何年もかなりデバッグしてきたが、ツールが足りないと感じたことはなかった。
      もちろん、この分野で使える選択肢を知らない人はかなり多く見てきた。
    • 完璧ではないが、同僚の開発者が推していたVS CodeでNode.jsをデバッグする体験よりはずっとまし。
      あの地獄よりはコンソールを選ぶ。
  • setTimeout(function() { debugger; }, 5000); は賢い。
    結局、Chromeデバッガが自分自身をデバッグする再帰的な亀のスタックに勝つ唯一の方法は、debuggerだけだ。
    悪名高いMyspace Sammyワームのsam.plも、訪問者が難読化されたHTMLホームページを掘り下げられないようにデバッグの罠を使っていた。

    • よく使っているブックマークレットの一つ: https://darekkay.com/blog/debugging-dynamic-content/
    • Webサイトが開かれたConsole/DevToolsのサイドパネルを妨害すると、こういうことが起きる。
      メインループを回しながらあちこちに厄介な debugger; 文を挿入し、30FPS / 32ms間隔で実行してDevToolsを使い物にならなくする。
      debugger文を「無視」する方法がないからだ。
    • 5秒待ちたくないなら、スクリプト実行の一時停止ショートカット cmd-\ を使えばいい。
      現在実行中だったJSの行でページが止まり、問題の要素をいつものように検査できる。
    • ブラウザ制作者には、DevToolsで明示的にオンにするオプションにして、デフォルトでは**debugger文を無視**してほしい。
      正当な debugger 文の使用例のほとんどは、自分のコードで作業中なら単なるブレークポイントで置き換えられる。
    • 待って、これだけ?シンプルすぎるのに、すごく良い。
  • queryObjects が抜けているのが目についた。
    特定のコンストラクタで作られたすべてのオブジェクト一覧を返す、かなりぶっ飛んだAPIだ。
    例えば queryObjects(Function) で、ヒープ上にあるすべての関数一覧を取得できる。
    あるモジュール内に入っている「private」関数まで返してくれる。

    • 正直、これは広めたくないとしても正当化できる。
      本当にぶっ飛んだ機能で、おそらくChromium専用だと思う。
  • ウォッチ式はどうにもまともに使いこなせない。
    スコープと更新ルールがあまりにも分かりにくく、グローバル変数しか監視できないのかと思うが、それすら期待どおりに動かず、テスト時には結局ログを値で埋め尽くすことになる。
    コンソールにData.gui [1]風のUIを入れて、変数や設定値を見ながらテストできるようにすべきだと何年も考えてきた。
    このCodePen [2]で実際の動作を見られる。

    1. https://github.com/dataarts/dat.gui
    2. https://codepen.io/russellbeattie/full/kGxaqM
    • 同じようなフラストレーションを感じた。
      ブラウザには理論上は優れたデバッグ機能がたくさんあるが、実際には安定して動作しているようには思えない。
      ブレークポイントでさえ、すべて安定してヒットさせることができない。
      コードが展開されているときは大丈夫だが、バンドルされただけで、ミニファイしていなくても多くのデバッガ機能が壊れるように感じた。
      フロントエンドエンジニアではないので、たぶん何か間違ったことをしているのかもしれない。
    • デバッガでソースマップをオフにしてみるといい。
      フラストレーションの原因である可能性が高いし、オンの状態で能動的にデバッグする体験がこれほど悪いのに、なぜデフォルトでオンになっているのか今でも理解できない。
    • Chromeのサイドバーパネルではミニファイされた変数が正しい名前で表示されるのに、watchpointでは未定義だというエラーが出ていらいらする。
  • IIFE のスコープ内で、コード実行を止めずに IIFE のローカル変数へアクセスする方法があるとよいのですが。
    デバッガを説得してそうさせる方法はあるのでしょうか?

    • IIFE は 即時実行関数式です。
      https://en.m.wikipedia.org/wiki/Immediately_invoked_function...
    • IIFE が呼び出した関数でブレークポイントに入ったなら、単に スタックをさかのぼればよいです。
      IIFE の外に完全にいるなら不可能だと思います。
      探している変数は、実行前後にはメモリ上に存在しない可能性もあります。
      JavaScript には他の言語のような静的変数がないので、IIFE が呼び出されるたびに内部変数は捨てられます。
      JavaScript は Web Worker のようなものを使わない限りかなり強いシングルスレッドモデルなので、async/await 呼び出しで直接レースコンディションを作ろうとしているのでなければ、IIFE スコープの外にそうした変数がメモリ上に残っているという概念自体が成り立ちにくいです。
      var を乱用して IIFE スコープの外で変数を初期化すれば、最後の IIFE 呼び出しが作った値は見られます。
    • 12年ほど前にそういうことをするツールを作りました。
      少し修正は必要ですが、労力に対する効果は大きいです。
      https://github.com/kristopolous/_inject
      任意の時点で、ほぼどんな関数コンテキストでも渡り歩き、何が起きていたかを見ることができます。
      参照カウンタを悪用して、コンテキストが破棄されないようにする方式です。
      クライアント側 JS をたくさんやっていた頃には本当に有用でした。
      これをきちんと作ったキラーアプリ版は、どのコンテキストでも REPL を開ける形になるでしょう。
      今の状態でもうまく使うにはかなり熟練が必要です。
    • 探しているのは ログポイントですか?
      https://firefox-source-docs.mozilla.org/devtools-user/debugg...
      追記: リンク先の記事で文字どおり最初に扱っている内容だと、たった今気づきました。
    • Chrome では、https://news.ycombinator.com/item?id=38226743#38231705 で説明されているクロージャを Watch パネルで調べ、その後 [[Scopes]] 疑似プロパティを見ればよいです。
      Firefox には方法がないようです。
  • 完全を期すなら Werkzeug を勧められます。
    Django バックエンド開発で使っていますが、ものすごく便利です。
    例外が発生する場所なら、いつでもどこでもブラウザ内で「PDB」シェルを使えるようにしてくれます。

    • ブラウザに Werkzeug を出したい場所に 1/0 を入れるだけでよいので簡単です。
    • nose.js やブラウザを含め、どこでもそういう体験がほしいです。
      とても単純で直感的です。
    • まだ django-extension/runserver_plus が必要ですか?
  • いつも使っているコツの一つは、UI 文字列で読み込まれたスクリプトを検索してデバッグすることです。
    Network パネルに移動してネットワークリクエストの記録を開始し、左サイドバーを開いて、探したいコードや UI 文字列を検索します。
    たいてい怪しいバンドル JS チャンクファイルの中で見つかり、結果をクリックするとそのファイルのネットワークリクエストが開きます。
    そのファイル内のどこかで右クリックして「Open in Sources」のような項目を選ぶと、デバッガに移動します。
    あとは debugger 文を置けばよく、この過程でソースマップもおそらく読み込まれます。

  • Python/Elixir プログラマとして長く働いてきて、pdb.set_trace()IEx.pry() をよく使ってきました。
    最近、かなり汚い NodeJS バックエンドを引き継いだのですが、まともなデバッグツールなしで残り少ない髪をかきむしっています。
    console.log デバッグに戻りましたが、原始人になった気分です。
    これほど人気のあるエコシステムにまともなデバッグ REPL がないとは信じられません。どこへ行けばよいか教えてもらえますか?

    • あります。
      node --inspect-brk を使えばよく、VS Code や Chrome DevTools で接続できます。
      難しい部分は、TypeScript 変換のようなビルドツールが nodejs コマンドの前にあるかどうかです。
      単に node を実行しているならかなり簡単です。
      デバッグ REPL は JavaScript コンソールそのものです。
    • VSCode の開発者ツールを使えば、NodeJS でもかなり堅実にデバッグできます。
      デバッグオプションとして追加すれば、ブレークポイントを設定して関数の中へステップ実行できます。
      ほとんどがオブジェクト内にカプセル化されているので、JavaScript はかなりデバッグしやすいです。
    • 他の人も言っているように、求めているのは --inspect または --inspect-brk です。
      VSCode では “open JavaScript debug terminal” コマンドも使えます。
      このコマンドは、どんな node コマンドでも自動的にデバッガが付いた状態で起動されるターミナルを開いてくれます。
    • 十分ではないかもしれませんが、たった今 python snoop を知りました。
      https://pypi.org/project/snoop/
      深いトレース用のデコレータです。
    • Elixir REPL のよいところは Erlang REPL と似ているように見えますが、実行中のプロセスに接続できる点です。
      Node JS エコシステムではこの機能を見たことがありません。
  • “Debugging Property Reads” セクションで、条件付きブレークポイントを使って {configOption: true}{get configOption() { debugger; return true; }} にどう変えるのか気になります。

    • それは条件付きブレークポイントという見出しの下の内容ではありません。
      コンソールで値を getter で上書きすればよく、書き込み権限があるならソースコードで直接変えてもかまいません。
  • これはぜひ学ぶべきもののように思うけれど、専用の本や学習資料があるのか気になる
    それとも、ひたすら Web 開発とフロントエンドを掘り下げるしかないのだろうか?

    • Chrome チームの「What’s new in DevTools」シリーズを追うことを強くおすすめする
      作業の真っ最中にリリースノートを読もうとリンクをクリックする気にはならないかもしれないが、新バージョンが出るたびに 5 分だけ目を通すだけでも十分価値がある
      数分で見られて消化しやすい動画もあり、簡単な概要をつかめる
      目的は新機能の紹介だが、経験上、ツールの現在の限界もよく理解できる