Microsoftが発表したWindowsおよび周辺アプリの変更について利用者が注意すべきポイント

以下では、Microsoftの公式情報にもとづき、Windows 11 24H2の強制アップデートに関する発表と既知の不具合、BitLocker(ストレージ暗号化)の自動有効化によるデータ喪失リスク、そしてMicrosoft Authenticatorのパスワード管理機能廃止について解説します。いずれも利用者に影響し得る重要トピックです。それぞれ明確な見出しのもとに詳細をまとめます。

Windows 11 24H2の強制アップデート

Windows 11 24H2への大型アップデートは順次すべての適格デバイスに提供され、自動適用が進められています。利用者にとっては、不安定な更新が強制されるリスクに注意が必要です。

MicrosoftはWindows 11 バージョン24H2(通称「Windows 11 2024 Update」)の一般提供を開始し、段階的ロールアウトの最終フェーズに入ったと述べています。特に、Windows Update経由での自動更新について「Windows 11 バージョン23H2/22H2/21H2を実行しているHomeおよびProエディションのデバイス(かつIT部門に管理されていないもの)は、バージョン24H2への更新プログラムが自動的に配信される」ことが公式にアナウンスされています。ユーザーは再起動のタイミングを選択したり、短期間(通常1週間程度)であれば延期も可能ですが、基本的には管理されていない一般ユーザーPCには強制的に24H2へのアップデートが適用される流れです。企業の管理下にない端末(例えば自宅利用のPCや小規模事業のPC)が対象となるため、企業ユーザーであってもこうした端末を業務利用している場合は注意が必要です。一方、社内でWindows Update for BusinessやIntune、グループポリシーなどにより更新管理されているデバイスはこの自動適用の対象外ですが、遅かれ早かれ24H2へのアップグレード計画を立てる必要があります。

こうした強制アップデートに対し、利用者からは「十分に検証されていない不安定なアップデートが強制されるのではないか」という懸念が出ています。アップデート中はPCの再起動が繰り返され長時間かかる可能性があり、業務に支障をきたすおそれがあります。また、アップデート適用後にシステム不具合(最悪の場合Blue Screen of Death、いわゆるSTOPエラー)が発生すると業務停止につながりかねません。実際、Windows 11 24H2にはいくつかの既知の不具合が公式に報告されています。企業環境ではWSUSや更新ポリシーでアップデートのタイミングを制御できますが、最新バージョンへの追随そのものは避けられないため、以下のような問題点を把握した上で慎重に展開することが重要です。

未だ不具合が報告されている状況にある24H2ですが、強制アップデートによる影響を軽減させるためにバックアップなどの対策を行いつつ、アップデートに伴う業務停止時間を最小限にするように計画を立てておく必要が

Windows 11 24H2のストレージ暗号化自動化(BitLocker)とデータ喪失リスク

Windows 11 24H2ではデバイスのストレージ暗号化(BitLocker)が初期設定で有効になるケースが増えています。暗号化自体はセキュリティ強化策ですが、万一に備えて回復キーの管理に注意しないと、ユーザーが自分のデータにアクセスできなくなるリスクがあります。

MicrosoftはWindows 11のセキュリティ強化の一環として、「モダンなシステムのほとんどでBitLockerをデフォルト有効化した」と述べています。従来、BitLockerによるドライブ暗号化は主にPro以上のエディションや企業向けに重視されていましたが、Windows 11 24H2では要件緩和によりHomeエディションを含む幅広いシステムで自動的にデバイス暗号化(BitLocker相当)が有効となるよう変更されています。例えばモダンスタンバイ対応のPCだけでなく、TPMを備えた一般的なPCでも条件を満たせばセットアップ時に暗号化がオンになります。これは盗難・紛失時のデータ保護には有効であり、Microsoftも「チップからクラウドまでWindows 11は既定でより安全になった」とアピールしています。

BitLocker自動化の仕組み

初期セットアップや24H2へのアップグレード時に、ユーザーが明示的に操作しなくてもバックグラウンドでドライブ暗号化が開始される場合があります。この際、回復キー(Recovery Key)のバックアップが自動で行われるのが通常です。具体的には、個人のMicrosoftアカウントでWindowsにサインインしている場合、BitLockerの48桁の回復キーはそのアカウントにひも付けられオンラインで取得可能な状態で保存されます。一方、職場や学校の管理下にあるデバイスでは、回復キーはAzure ADやActive Directoryに自動バックアップされ、組織のIT部門が管理します。このように、ユーザー自身が意識しなくとも「回復キーの保管」自体は行われる設計です。ただしMicrosoftも強調しているように、「このバックアップが確実に存在しアクセス可能かを検証すること、必要に応じて自前の追加バックアップを作成すること」が極めて重要です。万一クラウドへのキー保存がされていなかったり、ユーザーが誤ってキー情報を削除してしまっていると、暗号化ドライブにアクセスできなくなった際に復旧手段がなくなるためです。

データ喪失のリスクと注意点

BitLocker暗号化が自動有効になることで懸念されているのが、ユーザーが回復キーを把握していない場合のデータ喪失リスクです。実際、「Windowsのアップデートや設定変更後に突如BitLockerによってロックがかかり、自分のデータにアクセスできなくなった」という報告が増えていると指摘する声もあります。例えばあるユーザーは、「MicrosoftはMicrosoftアカウントでサインインすると自動的にBitLockerを有効化するようになった。もしそのMSアカウントへのアクセス権を失えばデータも永遠に失う。【警告も再チャンスもなく、BitLockerに初めてロックアウトされたときにその存在を知る人が多い】」と述べ、一般ユーザーにとってはデータ機密性よりも可用性(アクセスできること)の方が重要であるケースが多いのに、バックアップの周知不足により「悲劇的な失敗(致命的なデータ損失)」が起きかねないと批判しています。これは決して大げさな懸念ではありません。企業から貸与されたPCや共有端末の場合、利用者自身が回復キーを知らされていなかったり、管理者もキーを紛失してしまうと、そのPC上のローカルデータには誰もアクセスできなくなります。特にリモートワークで従業員が各自セットアップしたPCを使っているような場合、本人が暗号化の存在を認識しておらずキーのバックアップを怠っていると、ちょっとしたハードウェア変更やファームウェア更新が引き金でBitLockerリカバリを求められ、業務データが人質に取られるような事態も起こりえます。

このようなリスクへの対策として、Microsoftは公式ガイドでBitLocker回復キーの確認・バックアップ方法を公開しています。企業環境では、可能であればAzure ADやMBAM(Microsoft BitLocker Administration and Monitoring)等で全端末の回復キーを一元管理する運用が望まれます。また、24H2への更新ポリシー適用時に自動暗号化を無効化するレジストリ設定([DisableDeviceEncryptionキーの利用など】)も技術的には可能です。しかしセキュリティ強化とのトレードオフになるため、基本方針としては暗号化は有効にした上で、キー管理を徹底することが推奨されます。具体的には以下の点に注意してください:

  • キーの所在確認: Microsoftアカウント利用時は「デバイスの一覧」ページから回復キーを確認できます。同僚や部下のPCについても、組織管理下であればAzure ADポータル等からキーを取得可能です。万一に備え、全デバイスのキー情報が揃っているか今一度チェックしましょう。
  • ユーザー周知: IT部門からエンドユーザーへ、「BitLocker暗号化が有効になっている」ことと「回復キーの重要性」について周知徹底してください。鍵を紛失するとデータ復旧不能になる旨を伝え、可能ならユーザー自身にもキーのバックアップ(印刷保管や安全なクラウド保管)を促します。
  • シナリオテスト: ハードディスク交換やBIOSパスワード変更など、BitLockerがリカバリキー入力を要求する典型的なシナリオをテストしてみましょう。キーを正しく入力できるか、手順に不備がないかを事前に確認しておくことで、実際のトラブル時に落ち着いて対応できます。

以上を踏まえ、Windows 11 24H2環境では「暗号化されていることを前提に運用を組み立てる」ことが不可欠です。セキュリティは強化されますが、その裏で鍵管理がおろそかになると本末転倒です。企業としてはデータ漏えいとデータ消失の双方のリスクを考慮し、ポリシーと教育を整備する必要があります。

Microsoft Authenticatorのパスワード管理機能廃止

Microsoft Authenticatorアプリのパスワード保存・自動入力機能が2025年中に段階的終了します。モバイルでAuthenticatorをパスワード管理に使っていたユーザーは、Edgeブラウザーへの移行やエクスポートを求められており、通知を見逃すとパスワードを失う可能性があるため注意が必要です。

Microsoftは「Authenticatorアプリ内のオートフィル(パスワード管理)機能を廃止し、今後はMicrosoft Edgeに統合する」ことを発表しました。これは2025年に入ってから公開されたサポート文書で明らかにされたもので、ユーザーにとって有用だったAuthenticatorのパスワード保存機能を停止する代わりに、より多くのユーザーをEdgeブラウザへ誘導する意図があるとされています。公式には「複数デバイス間でパスワードを容易に使えるようオートフィル体験を整理統合するため」と説明されており、実質的にモバイルにおけるパスワードのオートフィル管理はEdgeに一本化される流れです。

段階的廃止のスケジュール

廃止は一夜にして行われるわけではなく、以下の段階を追って実施されます。

  • 2025年6月 – Authenticatorアプリ上で新しいパスワードを保存する機能が無効化されます。これ以降、Authenticator内には新規のログイン情報を追加登録できなくなります。
  • 2025年7月 – Authenticatorによるパスワード自動入力(オートフィル)機能が停止します。同時に、Authenticatorに保存されていたクレジットカードなどの支払い情報はデバイス上から削除されます(これら支払い情報は他デバイスと同期されない設計のため、このタイミングで消去されると公式に説明されています)。
  • 2025年8月 – Authenticatorアプリに保存されていたパスワードが全て利用不能になります。8月1日をもってサーバー上からも関連データが消去され、以降Authenticatorアプリを開いても既存パスワードは表示されなくなります。

このスケジュールに沿い、最終的にはAuthenticatorアプリからパスワード機能そのものが完全に撤去されます。ただし、アプリ自体は今後も二要素認証コードの生成やパスキーの保存用途で引き続き利用可能です。あくまで削除されるのは「パスワードの保存・入力機能」の部分だけですが、ユーザー体験としては「Authenticatorが大幅に機能縮小される」ことになるため注意が必要です。

利用者への影響と注意点

この変更で最も懸念されるのは、「通知を見逃したユーザーが突然パスワードを失ってしまう」リスクです。MicrosoftはAuthenticatorユーザーに対し、Edgeへの移行またはサードパーティ製パスワードマネージャーへのエクスポートを推奨しています。公式メッセージでは「2025年8月1日より後はAuthenticator内のパスワードおよび情報は自動的に削除されるため、それまでにデータをエクスポートしておくように」と案内しています。しかし、この情報は主にウェブのサポート記事や一部報道を通じて伝えられているもので、アプリ内通知を見落としていたり、そもそも変更自体を知らないユーザーも存在するでしょう。例えば個人でAuthenticatorを使っていた従業員がこの事実を知らない場合、8月になってからアプリを開いて初めて「保存していた多数のサイトのパスワードが消えている」ことに気付き、業務ログインに支障が出るかもしれません。

企業のIT担当者は、従業員がAuthenticatorのパスワード機能を利用しているか把握し、必要に応じてサポートすることが求められます。具体的には:

  • 周知徹底: 会社から「Microsoft Authenticatorでパスワードを保存・自動入力している場合、近日中に使用できなくなる」旨を告知し、各自でEdgeブラウザーの機能に移行するか、データをエクスポートして別のパスワード管理ツールにインポートするよう促しましょう。
  • データエクスポート支援: AuthenticatorアプリにはパスワードデータをCSV形式でエクスポートする機能があります。IT部門として手順を案内したり、必要ならエクスポート作業を支援してください。またエクスポートしたファイルの安全な取扱い(暗号化保管・早期削除)についてもアドバイスすべきです。
  • 代替ソリューションの提示: Microsoftが推奨するようにEdgeブラウザの組み込み機能を使う方法以外にも、企業ポリシーで許可された信頼性の高いパスワード管理ソフト(例: 1PasswordやLastPass、Keeperなど)への移行を検討しても良いでしょう。重要なのは、ユーザーがパスワード管理難民にならないよう事前に受け皿を用意することです。

今回のAuthenticator機能廃止は、Microsoftアカウントを取り巻くパスワードレス戦略の一環とも言われています。実際、同時期にMicrosoftアカウントのパスワードレス認証(パスキーなど)の拡充も発表されています。しかし現実には多くのWebサービスが未だパスワードで保護されており、ユーザー側でパスワードを管理しなければならない状況は当面続きます。企業においても、従業員が誤ってAuthenticator任せにしていたパスワードを失わないよう、そして安全なパスワード管理方法へ円滑に移行できるよう、期限(2025年8月)までに対応を完了させることが望まれます。

まとめ

以上、Windows 11 24H2の強制アップデートと不具合、BitLocker自動暗号化の留意点、Microsoft Authenticatorのパスワード機能廃止について解説しました。いずれのトピックも「セキュリティ強化」や「最新環境への移行」という大きな流れの中で起きている変化ですが、その過程で一時的にユーザーや管理者の負担・リスクが増す側面があります。企業のIT管理者としては公式情報を注視し、適切なタイミングで社内システムやポリシーを調整することが重要です。幸いMicrosoftからは定期的に詳細な発表やドキュメントが提供されていますので、信頼性の高い一次情報をもとに迅速かつ的確な対応を心がけましょう。本記事の情報が、皆様の環境をアップデートしつつ安全に維持する一助になれば幸いです。

target=”_blank”を使うときはrel=”noreferrer”を指定しよう

ウェブサイトを運営する際、外部リンクを新しいタブやウィンドウで開くために、リンクにtarget="_blank"を指定することがあります。しかし、この指定だけではセキュリティ上のリスクが生じる可能性があります。そこで、rel属性にnoopenernoreferrerを追加することが推奨されています。この記事では、noopenernoreferrerの違い、サポートブラウザの違い、両方を併用する理由、そしてそれぞれの使い分けについて解説します。

noopenerとは?

<a href="..." target="_blank" rel="noopener">リンク</a>

noopenerは、リンク先のページが元のページ(リンク元)への参照を持たないようにするための値です。具体的には、target="_blank"を指定したリンクをクリックすると、新しいタブやウィンドウでリンク先が開かれますが、その際、リンク先のページからwindow.openerプロパティを介してリンク元のページにアクセスできてしまいます。これにより、リンク先のページがリンク元のページを操作するリスクが生じます。rel="noopener"を指定することで、window.openerプロパティがnullとなり、リンク先からリンク元へのアクセスを防ぐことができます。

noreferrerとは?

<a href="..." target="_blank" rel="noreferrer">リンク</a>

noreferrerは、noopenerの機能に加えて、リンク元のURL情報(リファラー)をリンク先に送信しないようにするための値です。通常、リンクをクリックすると、リンク元のURLがリファラーとしてリンク先に送信されますが、rel="noreferrer"を指定することで、これを防ぐことができます。これにより、プライバシーの保護や、リンク元の情報漏洩を防ぐことが可能となります。

サポートブラウザの違い

noopenernoreferrerのサポート状況はブラウザによって異なります。主要なブラウザの対応状況は以下の通りです。

noopenerをサポートするブラウザ

noopenerは比較的新しい仕様で、対応しているブラウザは後述のnoreferrerと比べると狭くなります。

rel=noopener – HTML: ハイパーテキストマークアップ言語 | MDN

noreferrerをサポートするブラウザ

前述のnoopenerと比べると対応しているブラウザの範囲は広くなります。

rel=noreferrer – HTML: ハイパーテキストマークアップ言語 | MDN

両方を同時に指定することが推奨されていたのは何故か?

<a href="..." target="_blank" rel="noopener noreferrer">リンク</a>

ここまで見ると、noreferrerはカバーしている機能もサポートブラウザもnoopenerよりも広いため同時に指定する理由はないように見えます。

では何故同時に推奨されていた時期があったのでしょうか?

この点について理解するためにnoopenernoreferrerに関する議論をいくつか調べました。

同時に指定することを推奨されていた理由

過去の話であるため、どこまで信憑性があるかは不明ですが、次のような理由でnoopenernoreferrerを両方指定することが推奨されていた時期があったようです。

  • 仕様に準拠していないレガシーブラウザに対応するため
  • Firefox 33-35で発生していた不具合に対する対策

非準拠ブラウザに対する対策

仕様に準拠していないブラウザでnoreferrerを指定してもwindow.openernullにならないことに対する対策として、noopenerを同時に指定していた、そういうブラウザが存在する可能性を想定して同時に指定していたようです。

@seancrater I don’t; the spec is often aspirational, and doesn’t describe historical (noncompliant) engines.

In general, if any version of any browser ever required both, then we should use both for the foreseeable future.

確かに、過去ChromeとFirefoxで動作が異なったり、一部のブラウザで問題が起きるといったことはありました。以下の記事では、EdgeでYahoo! Japanを表示したときに正しく表示されないという問題について問い合わせています。

今後もこのように特定のブラウザの特定のバージョンでのみ不具合が起きる可能性を考慮し、同時に指定することに対して、若干バンドルサイズが大きくなることしかデメリットがないことを考えると両方を指定しておくことは心理的安心という面からも適切な判断だと考えられます。

Firefox 33-35で発生してた不具合に対する対策

もう一つの理由はFirefox 33-35で発生していた、noreferrerを指定すると別タブで開けなくなる不具合に対する対策で、ESLintのIssueでこの点について議論されています。

Actually, I found a counter-example: Firefox 33–35 removes opener with rel="noreferrer" but doesn’t with rel="noreferrer noopener" (probably because it encounters an unsupported rel value). If you have a Browserstack account, you can verify it by opening http://noreferrer.hypnosphi.de (source) in FF 33–35 and clicking the links there.

Firefox側のIssueを見ると、noreferrerをつけていてもリファラーを送っているとありますが、これがwindow.openernullになっていないことと同じことなのかは今ひとつわかりませんでした。

If you open a link with the rel=”noreferrer” attribute in a new tab (eg. over middle click) it will send a referrer anyway.

ESLintではnoopenernoreferrerを両方指定するようにチェックしていた時期がありましたが、現在ではnoreferrerがついていることをチェックし、noopenerもついているかどうかはチェックしないようになっています。

WordPressでrel=”noopener”が自動付与される理由

WordPressでは、バージョンによって、target="_blank"を持つリンクにrel="noopener"が自動付与されるのか、rel="noopener noreferrer"が付与されるのかが変わっていたようです。

WorkPress 4.7.4からセキュリティ対策のためにrel="noopener noreferrer"が自動的に付与されるようになりました。しかし、WordPress 4.8では、rel="noopener"だけが自動付与されるように変更されています。

WordPress 5.0では新しいエディタ「Gutenberg」が導入され、rel="noopener noreferrer"が自動付与されるようになった一方で、クラシックエディタを使用している場合にはrel="noopener"だけが自動付与されるようになっていました。

WordPress 5.6ではrel="noopener"だけが自動付与されるようになり、以降はrel="noopener"だけが自動付与される仕様に落ち着いているようです。

WordPressが、rel="noreferrer"ではなくrel="noopener"を付与しているのは、セキュリティ対策をしつつアフィリエイトリンクなどのためにリファラーを送りたい(参照元を知らせたい)という点にあります。認証が必要なページと違って、ページのURLを知ってもらう機会を少しでも減らさないためにも、noreferrerではなくnoopenerが求められます。

パフォーマンス対策としてのnoopener

仕様には記載がなく、実装依存の話としてnoopenerを指定する効果のひとつにパフォーマンス対策を挙げている記事を見かけます。しかし、この主張は必ずしも正しいというわけではなく、ブラウザの実装依存であるという点は理解しておく必要があります。

window.openernullでない場合、元ページを開いているタブとtarget="_blank"で開いたページのタブは同じプロセス・スレッドで動作することになり、開いたページで重いスクリプトを実行すると、元ページのパフォーマンスにも影響を与えるというものです。

実際のブラウザのコードを見たわけではありませんが、最近のバージョンではここら辺は対策済みになっているのではないかと思います。タブの数だけスレッドが作成されるわけでもありませんし、特のタブが応答しなくても他のタブは問題なく操作できているので、主要ブラウザではこの点はあまり気にする必要がないと思います。

結局、どれを付与すればよいのか?

noopenernoreferrerのどちらを付与すればよいかについてまとめます。

「どちらも付与しない」は非推奨

タブブラウザが登場する以前ならまだしも、現在のブラウザではtarget="_blank"を指定した場合はnoopenernoreferrerのいずれかを指定すべきです。特に外部サイトのページをtarget="_blank"で開く場合はrel属性を付与することは必須であるといっても差し支えないと思います。

同一サイト内であっても、モードレスダイアログのようなユーザー体験を実現したければ、別タブで操作させるのではなく、同一タブ内にモードレスダイアログをHTML/CSS/JSを使って実現すれば済むので、そういった意味でもwindow.openerを使うことはもうないのではないかと思います。

認証が必要なページから外部ページを開くときはnoreferrerを指定する

認証されたユーザーのみが表示できるページで外部ページを開くときはそのページのURLを知られないようにするという意味でもnoreferrerを指定することが強く推奨されます。

もう少し簡単に言ってしまえば、リファラーを送りたいケース以外はnoreferrerを指定するのがよいと思います。noreferrerの方がサポートされているブラウザの範囲が広いため、セキュリティ対策を考えるとnoreferrerを使う方が安全なためです。

リファラーを送信する必要がある場合のみnoopenerを指定する

ブログやアフィリエイトサイトなど、target="_blank"で表示したサイトに元ページのURLを伝えたい、流入元がどこかを伝えたいというケースにおいてはnoopenerを指定するのがよいでしょう。

WordPressがnoreferrerを自動付与せずnoopenerを自動付与しているのはこれが理由だと考えられます。アフィリエイトの種類にもよりますが、リファラーがないとアフィリエイト報酬に計算されない場合もnoreferrerではなくnoopenerを使う必要があるので、アフィリエイトサイトの仕様を事前に確認しておくことが重要です。

rel属性が設定されていることをチェックしよう

基本的にはESLintなどの静的解析ツールでチェックするのが望ましいですが、アプリケーション開発ではURL部分が変数になることがあり、この場合は静的解析ツールではチェックしきれないという問題があります。

そのため、静的解析だけを頼りにするのではなく、他の手段も組み合わせて多層的にチェックするのがよいでしょう。

静的解析ツールでチェックする

チェック漏れを防ぐという点ではESLintなどの静的解析ツールを導入し、エディタの拡張機能でチェックしたり、CIでチェックすることは非常に重要です。しかし、すべてのケースをチェックしきれなかったり、必要ない場合でもエラーになるなどの弊害もあるため、導入できるかどうかはチームで慎重に検討することが望ましいです。

ESLintでチェックする場合、

npm install --save-dev eslint @html-eslint/parser @html-eslint/eslint-plugin

でHTMLファイルをチェックするためのプラグインも導入します。

設定ファイルでは、

import html from "@html-eslint/eslint-plugin";
import htmlParser from "@html-eslint/parser";
...

export default defineConfig([
  ...
  {
    files: ["**/*.html"],
    plugins: {
      "@html-eslint": html,
    },
    languageOptions: { parser: htmlParser },
    rules: {
      "@html-eslint/no-target-blank": "error",
    },
  },
]);

HTMLファイルを処理するルールを追加することで、HTMLファイルに対してLintingできるようになります。

aタグにtarget="_blank"があり、href属性が相対パスでない場合に、rel="noreferrer"がないと

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <a target="_blank" href="http://example.com/">Home</a>
  </body>
</html>

以下のようにエラーが出るようになります。

> test1@1.0.0 lint
> eslint *.html


/Users/user/test/index.html
  9:8  error  Missing `rel="noreferrer"` attribute in a tag  @html-eslint/no-target-blank

相対パスの場合はチェックが行われない点に注意が必要です。(あくまでも外部サイトを表示するためのセキュリティ対策にフォーカスされている)

また、先ほど参照したESLintのIssueでも触れられていますが、noopenerの有無はチェックしないようになっているので、rel="noreferrer"rel="noopener noreferrer"(順不同)のいずれかの場合にパスするようになっているため、noopenerがあることをチェックできない点にも注意してください。

CopilotなどのAIエージェントにチェックしてもらう

Copilotなどソースコードを参照できるAIエージェントにチェックしてもらうというのも手です。ワークスペース全体をチェックできる場合は網羅的にチェックしてもらえるので、非常に効率的です。

レビューの観点に加える

修正箇所に限定するのであれば、プルリクなどのレビューの観点としてリンク使用時にrel属性を付与しているかをチェックするのもよいでしょう。ただし、チェック漏れのリスクもあるため、前述のようなツールでのチェックを併用したり、チェックリストでのチェックを行うなどの対策を併用することも検討すべきです。

まとめ

noopenernoreferrerを指定することでどういった効果があるのか、これらのrel属性に対して過去にどのような経緯があったのかについて確認しました。

また、現在においてはどのように指定すればいいのか、どうやってrel属性の付与漏れを防ぐのがよいかについてもまとめました。

動作確認という点では、rel属性の有無にかかわらず問題なく動いてしまうので、発見しにくい問題ではありますが、セキュリティに関する重要な対策の1つであるため、ツールなども活用しながら付与漏れがないようにしていくことが重要です。

昨今ではCopilotなどでワークスペース全体をチェックすることも可能になってきているので、今一度点検をしてみてもよいかもしれません。

まずはこれだけ!GitHub Copilotの使い方

GitHub Copilot を最大限活かせるように基本的な使い方を学びましょう。

本記事では、 Visual Studio Code での操作を中心に説明していますが、ショートカットについてはIntelliJ IDEA や PyCharm などのJetGBrains 系のIDEについても掲載しています。

回答する言語を指定する

英語で回答してほしいときは特に設定変更する必要はありませんが、日本語で回答してほしいときに、毎回「日本語で回答してください」などと書くのは面倒です。GitHub Copilot では、回答する言語を指定することが可能です。

Visual Studio Code の場合は Settings > Extensions > GitHub Copilot で、Github > Copilot > Chat: Locale Overridesの設定を変更することで、指定した言語で回答を求めることができます。

デフォルトはautoになっていると思いますので、日本語で回答してほしければja に変更してください。

IntelliJ IDEA や PyCharm などでは、 Settings > Languages & Frameworks > GitHub Copilot で、Chat欄のNatural Languageを日本語に変更することで、日本語で回答を求めることができます。

3つのツールを状況に応じて使い分ける

ここでは、以下の3つのツールの使い分けについて説明します。

  1. インラインチャット
  2. チャットパネル
  3. ChatGPT などの対話型生成AIサービス

インラインチャット

チャットパネルを起動してもよいのですが、インラインチャットを使うことでより素早くコードを生成したり、改善したりできます。

Visual Studio Code で、インラインチャットを起動するには、Cmd+IWin+I)のショートカットキーを使用します。

IntelliJ IDEA や PyCharm では、Ctrl+Shift+ICtrl+Shift+G)で同様の操作ができます。

チャットパネル

チャットパネルを使用すると、ワークスペースに関する質問ができるため、説明を求めたい場合にはこちらを使用します。後述のChatGPTなどの対話型生成AIサービスでもソースコードを添付すれば似たようなことができますが、手間なくできる点ではこちらを使用するのがよいでしょう。

チャットパネルは、Cmd+Shift+ICtrl+Shift+I)で開閉できます。会話後、コードに復帰したいときはチャットパネルを閉じることですぐにコーディングに戻れます。

IntelliJ IDEA や PyCharm では、Ctrl+Shift+C(Windowsも同様)で、チャットパネルの開閉ができます。

ChatGPTなどの対話型生成AIサービス

一方、ワークスペースに依らない問題の解決方法を質問したいとかであれば GitHub Copilot Chat ではなく、 ChatGPT などを使う方が効率的です。これは GitHub Copilot Chat ではコーディングに関する内容にのみ答えてくれるという点が影響しており、会話の途中で答えてくれなくなることはストレスになるので、使い分けをした方がよいのではないかと思います。

現時点ではアプリ版かWeb版を使うことになるかと思います。ChatGPT 以外にもツールはたくさんありますので、使いやすいものを選択してください。

コードを生成する

インラインチャットを含めたコード生成のパターンを考えます。

適切な名称をつけてコードを生成してもらう

もっとも基本的な生成の仕方になります。関数を生成する場合、適切な関数名をつけることでその関数名に沿ったコードを生成してくれます。

たとえば、JavaScriptで2つの数を加算する関数を作成する場合、addTwoNumbersという名前をつけることで2つの引数と加算をするコードが生成されます。

function addTwoNumbers と入力すると、候補が表示されます。TABキーでこれを確定すると関数が完成します。

コメントをつけて生成してもらう

実現したいことをコメントで書いて、その内容にあったコードを生成させます。

コメントを書いてから改行すると、そのコメントにあったコードが生成されます。

生成された内容が問題なければ、TABキーで確定させます。

候補が表示されているとき、マウスカーソルをカーソルをホバーすると、生成するコードの他の候補に切り替えられます。

これを表示しなくても、Option+[Alt+[)とOption+]Alt+])で前の候補と次の候補を行き来でき、生成したい候補でTABキーで確定できます。最初に生成されたコードが期待したコードでなくても別の候補が期待したコードである可能性があるので、切り替えて確認するとよいでしょう。

コードを書きながらコードを生成してもらう

これがもっともよく使われている方法かもしれません。コードを書きながらその次のコードを生成してもらう方法です。

同じようなコードを生成する場合や常套句のようなコードを生成する場合には高い精度のコードを生成してくれますが、意図が正しく伝わっていないと期待したコードを生成してくれないので、後述のインラインチャットでコード生成を指示するのがよいと思います。

インラインチャットでコード生成を指示する

適切な関数名が思いつかなくてもコメントを書かなくてもインラインチャットを使うことでコードの生成を指示できます。

ENTERRETURN)キーで生成してくれます。

Visual Studio Code では、再生成したければCmd+RWin+R)、受け入れるならCmd+ENTERWin+RETURN)を押下します。IntelliJ IDEA や PyCharm ではショートカットは用意されていないようで、マウス操作で再生成し、

受け入れるなら、同様にマウス操作で

エディタに反映します。

インラインチャットでコードを書き換えてもらう

書き換えてほしいコードを選択し、インラインチャットで書き換えてほしい内容を入力することでコードの改善ができます。とりあえず書いたコードにバグがあったり、非効率なコードだったりするときに改善してもらうことができるので便利です。

修正してほしいコードを範囲選択し、インラインチャットを起動、修正後どうなってほしいかを伝えます。

候補が表示されるので、

問題なければCmd+ENTERWin+RETURN)で確定します。

文章による説明を求める必要がなく、さっさと修正したい場合はチャットパネルではなくインラインチャットを使う方が効率よくコーディングを進められます。

何をするかを指定する

文章で書かなくてもコマンドを使ってやってほしいことを指定することができます。

/generate

実際には文章で書いても手間ではなく、先にコメントを書いておけば生成してくれるので、使う機会はほぼないかもしれませんが、Visual Studio Code ではコード生成をするコマンドがあります。

/generateを使って作成してほしいコードの仕様を伝えると、

以下のようにコードを生成してくれます。

IntelliJ IDEA や PyCharm ではこのコマンドはないようですので、ご注意ください。

/fix

修正についても文章で書いてもそれほど手間ではないので使う機会は多くないですが、/fixコマンドを使うことで修正を指示することができます。

/fixコマンドに続いて修正してほしいことを記述することで、

修正結果を表示してくれます。

このコマンドは IntelliJ IDEA や PyCharm でも使用可能です。

/explain

コードやワークスペースが何を行っているのかを説明・要約してもらうことで、コードを一から読むよりも理解が早くなります。たとえば、業務で自分が関わったことのない古いコードがある場合、そのコードについて説明をしてもらってからコードを読んだ方が理解しやすいと思います。

説明はインラインチャットではなくチャットパネルで聞いた方がわかりやすいです。

チャットパネルを開くと、現在エディタで表示しているファイルが選択済みなので、このまま/explainコマンドを指定します。

説明の中で不明な点や深掘りしたい点があれば、チャットで追加質問してください。

このコマンドは IntelliJ IDEA や PyCharm でも使用可能です。

/doc

これまではクラスやメソッドのコメントを書くこともメンテナンスすることも非常にコストの高い作業だったと思います。生成AIの登場でこのような問題は過去のものとなりつつあります。

関数の先頭で以下のように/docコマンドを使用すると、

以下のようにドキュメントテーションコメントを生成してくれます。

コメントが古くて最新化したい場合でも/docコマンドが使用できます。以下のようにドキュメンテーションコメントが適切にメンテナンスされていない場合でも、コメントと関数を範囲指定して/docコマンドを使うと、

以下のようにコメントを生成し直してくれます。

このコマンドは IntelliJ IDEA や PyCharm でも使用可能です。

/tests

主にGitHub Copilot Chatで使用しますが、テスト対象を選択してテストコードを生成することができます。ただし、注意したいのは、テストの準備にプロジェクト固有の特殊が必要な場合にはうまく生成されないことがあります。

基本的には、最初の1つはコード補完を使いながら自身で作成し、その派生形で記述できる他のバリエーションを指示しながら作成するのがよいでしょう。テストで使用している値についても細かく指示するよりは一旦生成させて自身で修正した方が早い場合があります。

その他のコマンド

上記以外にもいくつかのコマンドがあります。

Visual Studio Code では、コード編集を行うための /edit コマンドが用意されています。

IntelliJ IDEA や PyCharm ではコードをシンプルにするための/simplyfy コマンドが用意されていますが、冗長なコードという点であれば、IDE自体の機能で修正できる場合もありますので、それほど使用頻度は高くないと思います。

何を参照するかを指定する

何を参照するのかを明示的に指定することで、精度の高い回答を得ることができます。これらはVisual Studio Code のみで使用可能ですが、ファイルだけはIntelliJ IDEA や PyCharm でもマウス操作で追加することができます。

@workspace

ワークスペース内のソースコードをすべて参照し、関連性の高いファイルをピックアップしてコードや回答を生成します。説明上、この記事中ではワークスペースという表現に統一していますが、ワークスペースでもプロジェクトでも伝わりますので、特に気にする必要はありません。

@vscode

Visual Studio Code 限定の機能になりますが、Visual Studio Codeの機能に着目した回答をしてくれます。設定やショートカットキーに関する質問をしたい場合は、@vscodeをつけるようにしてください。会話の中で設定に遷移するボタンも出力してくれるので、こちらも活用してみてください。

Show in Settings Editorをクリックすると、

設定が表示されます。

@terminal

コードの話ではなく、コマンドに関する質問をしたい場合は、ターミナルに着目した回答をしてくれる@terminalを使うのがよいでしょう。

#file

参照してほしいファイルを指定します。基本的にはアイコンをクリックしてファイルを選ぶのがよいでしょう。基本的に単独で使用可能ですが、もし効かないようであれば@workspaceと併用してください。

#editor

エディタの表示領域を含めるには#editorを使用します。ただし、現在エディタで表示しているファイルはデフォルトで参照していることがほとんどなので、あまり使う機会はないかもしれません。

#selection

エディタの選択箇所を参照します。IntelliJ IDEA や

#terminalLastCommand

ターミナルで最後に実行したコマンドとその実行結果を参照します。非常に便利な機能ですが、

#terminalSelection

ターミナルの選択箇所を参照します。コマンドを実行していてエラーが出たときに対処方法を聞くときは#terminalLastCommandで構いませんが、もう少しピンポイントで聞きたいときは#terminalSelectionを使うとよいでしょう。

目的別プロンプト集

いくつか、よく使いそうなプロンプト例をまとめてみました。これらはGitHub Copilot Chatで使用します。

ワークスペース全体を説明してもらう

/explainコマンドと@workspaceを組み合わせることで、ワークスペース全体について説明してもらうことができます。

@workspace /explain

新しく参画したメンバーが担当するワークスペースの構造を把握する際にとても有用だと思います。オンボーディング中やオンボーディング後の自習時間に使ってみてはいかがでしょうか。

ワークスペースのディレクトリ構造を明らかにする

ワークスペースのディレクトリ構造に可視化したいときは、以下のように聞くとよいです。

@workspace /explain ワークスペースのディレクトリ構造を教えて

こちらも、どこにどんなファイルがあるのかをディレクトリ構造を把握したい場合に有効な方法です。

コードレビューをしてもらう

作成したコードに対してコードレビューを行ってもらうのも有効な使い方のひとつです。

コードレビューを行う場合は、包括的なレビューをするのか、特定の何かについてレビューをするのかを明確にすることが重要です。

包括的なレビューを行ってほしい場合

たとえば、改善すべて点はありますかと質問するとよりよくするためのアドバイスを回答してもらうことができます。この方法は、最初のレビューや最終的なレビューとして使うとよいでしょう。

特定の観点に基づいてレビューを行ってほしい場合

特定の観点についてレビューをしてもらうことで、改善すべき点をより明確にすることができます。

具体的には、コード品質(命名規則、冗長なコード、可読性)、セキュリティ、パフォーマンスなどの問題を指摘してもらうのがよいと思います。

  • 命名は適切ですか
  • 冗長なコードや簡潔に書けるコードはありますか
  • エラーハンドリングは適切ですか
  • 実装漏れやデバッグコードなどは残っていませんか
  • 脆弱性はありますか
  • パフォーマンスの問題はありますか
  • 不足しているテスト観点はありますか

これ以外にも、プロジェクト固有の観点(特定の機能が実装されているか、など)を確認するのもよいでしょう。

また、レビューア側でも目視によるレビューと併用するのもよいと思います。

まとめ

当然といえば当然ですが、 GitHub Copilotの機能はVisual Studio Codeがもっとも充実しています。ただ、EclipseやIntellJ IDEA など他のIDEでもまったく使えないわけではないため、もし使っているのであれば積極的に活用していくことが重要です。

GitHub Copilotを活用にするには、基本的なショートカットやコマンドなどを理解することももちろんですが、やってほしいことを明確に指示することが大切です。これはインラインチャットで指示するだけでなく、コメントを書いてからコードを書き始めるということも含みます。

Copilot や AI Agent 関連はかなり活発に進化しています。この記事で紹介した機能以外もありますし、今後もっとよいやり方が出てくることも容易に想像できます。コーディングの生産性を高めるため、定期的にキャッチアップが必要だと思います。

macOSでもクリップボード履歴を使用できるようにする(Clipy)

WindowsではWin+Vでクリップボード履歴が使用できます。macOSでもこれと同じような機能が使えるClipyというアプリがあります。

インストール

公式サイトにダウンロードボタンがありますので、そこからdmgファイルをダウンロードし、インストールしてください。インストールして起動すると、アクセシビリティなどの設定を求められますので、設定を行ってください。

インストールが終わるとメニューバーにClipyのアイコンが表示されるので、これでインストールは完了です。

環境設定

メニューバーのClipyアイコンをクリックして表示されるメニューから環境設定を選択すると、環境設定ダイアログが表示されます。そのままでもよいですが使いやすいようにカスタマイズするとよいでしょう。

私の設定

私は連続してコピーしたのちに、それを連続で貼り付けたいだけなので、以下のようにカスタマイズしました。

記録する履歴の数を10に制限し、履歴のソート順を作成日にしました。よく貼り付けるクリップボードはClipyが提供するスニペットを使えばいいと思ったのと、貼り付ける度に順番が変わるとどこまで貼り付けたかわからなくなるので作成日を指定しています。

一般で履歴の数を10にして、インライン表示する項目の数を10にしたことで、すべてインラインで表示し、選択の手間をひとつ減らしました。一度にコピーする数と想定される件数にしておくとよいでしょう。

使い方

コピーはいつもどおりで、貼り付けにCmd+Vの代わりにCmd+Shift+Vを使用します。ショートカットを使うと、

コンテキストメニューが表示されるので、十字キーで貼り付けたいクリップボードを選択し、RETURNキーを押下します。

ショートカットキーは変更できますので、ご使用のキーボードのキー配置に合わせてカスタマイズすることも検討してください。

IntelliJ IDEAで次の出現箇所を選択する

Visual Studio Codeでの❖+D/⌘+Dのような現在選択しているテキストと同じテキストが出現する次の箇所を選択するショートカットは、IntelliJ IDEAにも存在します。

ただ、WindowsとmacOSでショートカットが異なるので両方を使用している方は注意が必要です。

Windowsの場合

WindowsのIntelliJ IDEAでは、Alt+Jで現在選択しているテキストと同じテキストが次に出現する箇所を選択することができます。

macOSの場合

macOSのIntelliJ IDEAでは、^+Gで現在選択しているテキストと同じテキストが次に出現する箇所を選択することができます。

Linuxの場合

最近では開発用マシンとして使用している人もいるので、ちょっと調べてみました。Ubuntu 24.04 ARM64で動作確認を行っていますが、他のディストロやCPUでも変わらないかと思います。

結論からいうと、Windowsと同じAlt+Jで同様の操作ができました。macOSだけが異なるショートカットになっているため、こちらについても意識しておいた方がよいでしょう。

まとめ

Visual Studio Codeとは異なり、IntelliJ IDEAをはじめとするJetBrainsの製品はWindowおよびLinuxとmacOSでショートカットが異なります。これはCtrlキーとcommandキーの違いというような差異ではなく、使用するアルファベット自体が異なっている(まったくの別ショートカットとなっている)点に注意が必要です。

特に、私のようにプライベートと仕事で使用OSが異なる人は混乱しやすいと思いますので、OSを揃えるようにするなど何かしらの対策をしておいた方がよいかもしれません。

Amazon API Gateway REST APIにIP制限をかける

API Gateway REST APIに対してIP制限をかける必要があったので手順を確認しました。

動作確認用のAPI Gateway REST APIを作成する

まずはAPI GatewayでAPIを作成します。以下の手順に従って作成していきます。

手順

API Gatewayコンソールhttps://console.aws.amazon.com/apigatewayにアクセスします。

選択肢の中から、REST APIの構築ボタンをクリックします。

API Gatewayを作成することは今回の主眼とは関係ないため、サンプルAPIを選択して作成します。

REST APIPetStoreが作成されました。

これは必須ではありませんが、デプロイする前にテストをして動作確認をしておきます。/ > /pets > POSTをクリックして、「テスト」を選択します。

手順に従って、{"type": "dog","price": 249.99}というテキストをリクエスト本部に貼り付け、テストボタンをクリックします。

ステータスが200になっていればテスト成功です。

動作確認ができたらAPIをデプロイボタンをクリックしてデプロイします。

今回はテスト用なので手順と同じくtestというステージを作成します。

デプロイはすぐに完了します。

動作確認のためにURLを呼び出すボタンをクリックしてのURLをコピーし、実際に使用可能なことを確認します。

自端末のターミナルからcurlコマンドで実行してみます。

$ curl https://dy1o7l2qa9.execute-api.us-east-1.amazonaws.com/test
<html>
    <head>
        <style>
        body {
            color: #333;
            font-family: Sans-serif;
            max-width: 800px;
            margin: auto;
        }
        </style>
    </head>
    <body>
        <h1>Welcome to your Pet Store API</h1>
        <p>
            You have successfully deployed your first API. You are seeing this HTML page because the <code>GET</code> method to the root resource of your API returns this content as a Mock integration.
        </p>
        <p>
            The Pet Store API contains the <code>/pets</code> and <code>/pets/{petId}</code> resources. By making a <a href="/test/pets/" target="_blank"><code>GET</code> request</a> to <code>/pets</code> you can retrieve a list of Pets in your API. If you are looking for a specific pet, for example the pet with ID 1, you can make a <a href="/test/pets/1" target="_blank"><code>GET</code> request</a> to <code>/pets/1</code>.
        </p>
        <p>
            You can use a REST client such as <a href="https://www.getpostman.com/" target="_blank">Postman</a> to test the <code>POST</code> methods in your API to create a new pet. Use the sample body below to send the <code>POST</code> request:
        </p>
        <pre>
{
    "type" : "cat",
    "price" : 123.11
}
        </pre>
    </body>
</html>%

大丈夫そうですね。

リソースポリシーで特定のIPのみがアクセスできるように制限する

次にリソースポリシーで特定のIP(今回は自端末のIP)のみアクセスできるように制限をかけてみます。

手順は以下の「特定の IP アドレスのみが API Gateway REST API にアクセスすることを許可するリソースポリシーを作成およびアタッチする」に従って行っていきます。

手順

API Gatewayコンソールhttps://console.aws.amazon.com/apigatewayにアクセスします。

リソースから先ほど作成したPetStoreAPIを選択します。

左のナビゲーションペインでリソースポリシーを選択すると、以下のような画面が表示されます。

デプロイ直後はリソースポリシーは設定されていないため、ポリシーを作成ボタンをクリックし、ポシリーの詳細下のドロップダウンリストからIP範囲拒否リストを選択すると、以下のようなポリシーが表示されます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "execute-api:/{{stageNameOrWildcard}}/{{httpVerbOrWildcard}}/{{resourcePathOrWildcard}}",
            "Condition" : {
                "IpAddress": {
                    "aws:SourceIp": [ "{{sourceIpOrCIDRBlock}}", "{{sourceIpOrCIDRBlock}}" ]
                }
            }
        },
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "execute-api:/{{stageNameOrWildcard}}/{{httpVerbOrWildcard}}/{{resourcePathOrWildcard}}"
        }
    ]
}

色々試してみたのですが、許可のみ記載したポリシーだと指定したIPまたはCIDRブロックのみを許可できるようです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "execute-api:/*",
            "Condition" : {
                "IpAddress": {
                    "aws:SourceIp": [ "{{sourceIpOrCIDRBlock}}" ]
                }
            }

        }
    ]
}

このポリシー中の{{sourceIpOrCIDRBlock}}を自身のグローバルIPに置き換えてください。

ポリシーが作成できたら、右下の変更を保存ボタンをクリックします。リソースポリシーを変更したら、それを反映させるために左のナビゲーションペインでリソースを選択し、APIをデプロイボタンをクリックしします。

先ほど作成したステージを選択してデプロイします。

デプロイが完了したことを確認します。

動作確認

設定が完了したので、動作確認をします。まずは自端末から変わらずアクセスできることを確認します。

$ curl https://dy1o7l2qa9.execute-api.us-east-1.amazonaws.com/test
<html>
    <head>
        <style>
        body {
            color: #333;
            font-family: Sans-serif;
            max-width: 800px;
            margin: auto;
        }
        </style>
    </head>
    <body>
        <h1>Welcome to your Pet Store API</h1>
        <p>
            You have successfully deployed your first API. You are seeing this HTML page because the <code>GET</code> method to the root resource of your API returns this content as a Mock integration.
        </p>
        <p>
            The Pet Store API contains the <code>/pets</code> and <code>/pets/{petId}</code> resources. By making a <a href="/test/pets/" target="_blank"><code>GET</code> request</a> to <code>/pets</code> you can retrieve a list of Pets in your API. If you are looking for a specific pet, for example the pet with ID 1, you can make a <a href="/test/pets/1" target="_blank"><code>GET</code> request</a> to <code>/pets/1</code>.
        </p>
        <p>
            You can use a REST client such as <a href="https://www.getpostman.com/" target="_blank">Postman</a> to test the <code>POST</code> methods in your API to create a new pet. Use the sample body below to send the <code>POST</code> request:
        </p>
        <pre>
{
    "type" : "cat",
    "price" : 123.11
}
        </pre>
    </body>
</html>%

問題なくアクセスできています。

次に異なるグローバルIPを持つ端末からアクセスしてみてください。例えば、PCからアクセスしている場合はスマートフォンのWiFiをOFFにして、ブラウザで同じアドレスにアクセスしてみてください。

{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:us-east-1:********0149:dy1o7l2qa9/test/GET/"}

IP制限が効いていると、上記のようなメッセージが返ってきます。

まとめ

IP制限や経路制限を変える方法はいくつかあります。

  • HTTP APIで作成し、VPCリンクで制限する
  • REST APIプライベートで作成し、リソースポリシーで許可するVPCを制限

今回はAPI Gatewayを作成し直す訳にはいかない事情があったので、このような対応にしました。

axiosのエラーかを判別する(axios.isAxiosError)

axiosでREST APIにアクセスするコードを含む複数のコードをtry-catchで囲んでいるときに、axiosのエラーをログなどに出力する際、JSON.stringifyしていたのですが、axios以外が原因でエラーになっている場合、JSON.stringifyでは文字列を得られなかったので対処方法を検討していました。

axios.isAxiosError

最初objectかどうかで判定しようとしていましたがうまく判定できませんでした。axiosではaxiosのエラーかどうかを判別するためのaxios.isAxiosErrorが提供されているため、これを使う方が良さそうです。

async function test() {
  try {
    const response = axios.get("http://httpstat.us/200");
  } catch (error) {
    if (axios.isAxiosError(error)) {
      console.log(JSON.stringify(error, null, 2));
    }
  }

動作確認

この動作を確認するために以下のようなプログラムで確認しました。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js"></script>
    <script>
      async function test() {
        try {
          const response = await axios.get("http://httpstat.us/200");
          const a = [];
          console.log(a[1].b);
        } catch (error) {
          if (axios.isAxiosError(error)) {
            console.log("Axios Error:", JSON.stringify(error, null, 2));
          } else {
            console.log("Error:", String(error));
          }
        }
      }

      test();
    </script>
  </body>
</html>

このプログラムでは、httpstat.usという指定したHTTPステータスのレスポンスを返してくれるサービスで任意のHTTPステータスを返すようにしています。HTTPステータスが200の場合は例外が発生しないため、後続の処理でundefinedなオブジェクトのプロパティにアクセスしているため例外が発生します。

上記のHTMLにブラウザからアクセスすると、コンソールログに

Error: TypeError: Cannot read properties of undefined (reading 'b')

と表示されます。

一方、

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js"></script>
    <script>
      async function test() {
        try {
          const response = await axios.get("http://httpstat.us/404");
          const a = [];
          console.log(a[1].b);
        } catch (error) {
          if (axios.isAxiosError(error)) {
            console.log("Axios Error:", JSON.stringify(error, null, 2));
          } else {
            console.log("Error:", String(error));
          }
        }
      }

      test();
    </script>
  </body>
</html>

のようにURLの200部分を404に変えてアクセスすると、

Axios Error: {
  "message": "Request failed with status code 404",
  "name": "AxiosError",
  "stack": "AxiosError: Request failed with status code 404\n    at Qe (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:31804)\n    at XMLHttpRequest.y (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:36641)\n    at e.<anonymous> (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:48621)\n    at p (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:3448)\n    at Generator.<anonymous> (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:4779)\n    at Generator.throw (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:3858)\n    at p (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:9996)\n    at u (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:10235)",
  "config": {
    "transitional": {
      "silentJSONParsing": true,
      "forcedJSONParsing": true,
      "clarifyTimeoutError": false
    },
    "adapter": [
      "xhr",
      "http",
      "fetch"
    ],
    "transformRequest": [
      null
    ],
    "transformResponse": [
      null
    ],
    "timeout": 0,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "maxBodyLength": -1,
    "env": {},
    "headers": {
      "Accept": "application/json, text/plain, */*"
    },
    "method": "get",
    "url": "http://httpstat.us/404"
  },
  "code": "ERR_BAD_REQUEST",
  "status": 404
}

と表示され、axios.isAxiosErroraxiosのエラーかどうかを判別できていることが確認できます。

まとめ

例外が発生したときにエラーページに遷移するという動作をするアプリケーションがあり、コンソールにログを出力しても記録に残らないため、サーバーにログが送るという対処を行っていました。実際に試してみると、axiosのエラーだと、エラーの内容がサーバーのログで確認できましたが、それ以外のエラーの場合は{}になって何も確認できないという事象があり、これを確認できるようにするために、このような検証をしてみました。

try-catchで例外をcatchしていることまでははっきりしていても、axiosのエラーかそれ以外のエラーかを判定する場合は型ではなく、axios.isAxiosErrorで判定するのが確実だということが確認できました。

Emmetを活用してコーディングのパフォーマンスを向上させよう

EmmetはHTMLやCSSを省略記法で記述できるようにプラグイン・拡張機能です。

最近Youtube動画で、Beginner/JuniorエンジニアとPro/Seniorエンジニアのコーディングの仕方についた動画がよく流れてきたので、使い方をまとめてみました。

Emmetの使い方

Emmetの省略記法からHTMLのタグやCSSのプロパティを作成するにはENTER/RETURNまたはTABを使用します。どちらが使用するかはエディタによって異なっており、Visual Studio CodeはENTER/RETURNTABのどちらでも変換できますが、WebStormでは[TAB]のみで変換します。

例えば、<div></div>タグを作成するには

div[ENTER/RETURN or TAB]

のように入力します。

HTMLのタグの作成

主にEmmetが使用されるのはHTMLのタグを記述する場合です。少ないタイピング量で素早くタグを作成できます。

よく使われる省略記法について見ていきます。

空タグを作成

単に空タグを作成したい場合はタグ名を入力後、[ENTER/RETURN or TAB]を入力します。

h2[ENTER/RETURN or TAB]
->
<h2></h2>

空タグを作成するとカーソルは<h2></h2>の間にあるので、十字キーやマウスでカーソル位置を変えることなくタグ内に入力することができるようになります。

id属性付きの空タグを作成

実際にコーディングしていると、単純に空タグを作成するということはid属性やclass属性がついたタグを作成することがほとんどだと思います。id属性がついた空タグを作成するには、タグ名に続いて#を入力してid属性の名称を入力し、[ENTER/RETURN or TAB]を入力します。

h2#title[ENTER/RETURN or TAB]
->
<h2 id="title"></h2>

ここではHTMLファイル内で一意になるtitleというid属性をもったh2タグを作成しました。

クラス属性付きの空タグを作成

class属性をついた空タグを作成するにはタグ名に続いて.を入力してclass属性の名称を入力し、[ENTER/RETURN or TAB]を入力します。

div.sidebar[ENTER/RETURN or TAB]
->
<div class="sidebar"></div>

2つ以上のクラスを定義したい場合は、.class名を繰り返します。

div.sidebar.active[ENTER/RETURN or TAB]
->
<div class="sidebar active"></div>

BootstrapやTailwind CSSなどのCSSフレームワークを使っている場合はタグにいくつものクラスを定義することになるため、Emmetを使った省略記法でのコーディングは生産性に大きな影響を与えます。

div.mb-3.row[ENTER/RETURN or TAB]
->
<div class="mb-3 row"></div>

子要素をまとめて作成

例えば、ulタグの中にliタグがあるような構造をまとめて作成するには、>を使用します。

ul>li[ENTER/RETURN or TAB]
->
<ul>
  <li></li>
</ul>

作成後のカーソル位置は<li></li>の間にあります。

要素を複数まとめて作成

同じタグを複数個まとめて作成したい場合は*に続いて繰り返し回数を入力することでまとめて作成できます。

前述の例で、子要素のliタグをまとめて3個作成したい場合は

ul>li*3[ENTER/RETURN or TAB]
->
<ul>
  <li></li>
  <li></li>
  <li></li>
</ul>

のようにします。作成後のカーソル位置は一番最初のliタグの<li></li>の間にあり、[TAB]で次のliタグの<li></li>の間に移動できるので、

ul>li*3[ENTER/RETURN or TAB]
->
<ul>
  <li></li>
  <li></li>
  <li></li>
</ul>

abc[TAB]
->
<ul>
  <li>abc</li>
  <li></li>
  <li></li>
</ul>

def[TAB]
->
<ul>
  <li>abc</li>
  <li>def</li>
  <li></li>
</ul>

ghi[TAB]
->
<ul>
  <li>abc</li>
  <li>def</li>
  <li>ghi</li>
</ul>

のように十字キーやマウスを使うことなく効率的にコーディングすることができます。

テキスト付きの要素を作成

前述ではh2タグを空タグで作成して、その後にテキストを入力していました。Emmetではテキスト付きの要素を作成することもできます。

h2#title.text-5xl.font-semibold{タイトル}
->
<h2 id="title" class="text-5xl font-semibold">タイトル</h2>

少し注意点すべき点があるとすれば、Copilotなどが有効になっていると、{を入力したあと、CSSのプロパティを候補として提示することがあり、何も考えずに[TAB]を押すと変換できない場合がある点です。初めての場合になりやすいので注意しましょう。

ダミーテキストを作成

デザインを確認するために実際のコンテンツの代わりにダミーテキストを挿入したいということがあります。

このような場合にはloremを入力して[ENTER/RETURN or TAB]を入力すると、

lorem[ENTER/RETURN or TAB]
Lorem ipsum dolor sit amet consectetur adipisicing elit. Architecto nisi facere, velit facilis quo rerum quae eveniet animi alias, voluptate rem, tempora placeat ut. Voluptates saepe earum nesciunt! Repudiandae, earum.

のようにダミーテキストが作成されます。

独自の属性付きの要素を作成

ピュアなHTMLのタグしか生成できないというわけではなく、独自の属性をつけることもできます。

例えば、Vue.jsのv-ifディレクティブをもったdivタグを作成するとします。v-ifディレクティブが真になる条件はisShowtrueになる場合とします。

div[v-if="isShow"]
->
<div v-if="isShow"></div>

今回はVueのディレクティブの例を示しましたが、aria-*属性などにも使用できるため、利用シーンは多いと思います。

CSSプロパティの作成

あまり知られていないかもしれませんが、CSSのプロパティを省略記法で作成することもできます。

.title {

}

というセレクタがあるとき、

.title {
  mb10[ENTER/RETURN or TAB]
}

のように省略記法をでプロパティを書くと、

.title {
  margin-bottom: 10px;
}

のようにプロパティが作成されます。

HTMLの場合と異なり、省略記法を覚えていないと使えませんが、ある程度法則性があり、Tailwind CSSのクラス名と同じようなルールのものもあるので、よく使うものから覚えていくのがよいと思います。

Emmetチートシート

Emmetの使い方をまとめたチートシートが公開されています。

HTMLとCSSの両方をサポートしていますので、是非ご活用ください。

EmmetとCopilotの使い分け

少ないタイピング量でコードを生成するといえば、CopilotやTabnineなどの生成AIがあります。

生成量という意味では生成AIに軍配が上がりますが、必ずしも意図した内容が生成されるとは限りません。同じような内容を何度も生成する場合、定石のような内容を生成する場合、どう書けばいいかわからない場合などは生成AIを使用し、何を出力したいのかがはっきりしている場合は生成AIが提示する候補を[ESC]でキャンセルしてEmmetの省略記法を使うのがよいと思います。

どちらか一方だけを使うのではなく、それぞれの長所と短所を理解し、適したツールを選択していくことを大切です。

コーディング時の意識をアップデートする

HTMLのコーディング時にEmmetを活用する場合、コーディング時の意識をアップデートするようにしていくことが必要かもしれません。

Emmetを使わないときは、タグの構造だけに着目してタグを作成し、その後にデザインを整えるためにクラスをどこにつけようか、といった流れで書くことができます。

例えば、3つの列を持つテーブルを作成する場合、

<table>
  <thead>
    <th>
      <td></td>
      <td></td>
      <td></td>
    </th>
  </thead>
  <tbody>
    <tr>
      <td></td>
      <td></td>
      <td></td>
    </tr>
</table>

のようにタグの構造を作成してから、テーブル全体をデザインするためにdata-tableクラスをtableタグにつけて、ヘッダー部分をデザインするためにtheadtable-headerをつけて、ボディ部分をデザインするためにtbodytable-bodyクラスをつけて、といったように後からつけていくことができます。

<table class="data-table">
  <thead class="table-header">
    <th>
      <td></td>
      <td></td>
      <td></td>
    </th>
  </thead>
  <tbody class="table-body">
    <tr>
      <td></td>
      <td></td>
      <td></td>
    </tr>
</table>

一方、Emmetを活用しようとすると、タグの構造を考えるときにそのタグにどんな意味を持たせるかということを考えながらやった方が効率的に生成できます。

// データテーブルを意味するtableを用意して、
table.data-table
↓
// その中にヘッダーを意味するtheadを用意して、
table.data-table>thead.table-header
↓
// ヘッダータイトルはまだ決めていないけど、3列用意しよう
table.data-table>thead.table-header>tr>td*3
↓
// さらにボディ部分を意味するtbodyを用意して、
table.data-table>thead.table-header>tr>td*3^tbody.table-body
↓
// 同じように3列用意しよう
table.data-table>thead.table-header>tr>td*3^^tbody.table-body>tr>td*3[ENTER/RETURN or TAB]
->
<table class="data-table">
  <thead class="table-header">
    <tr>
      <td></td>
      <td></td>
      <td></td>
    </tr>
  </thead>
  <tbody class="table-body">
    <tr>
      <td></td>
      <td></td>
      <td></td>
    </tr>
  </tbody>
</table>

思考の順序と省略記法の書く順序が一致すると、もっとも自然にもっとも効率的にコーディングすることができます。これは慣れが必要となるので、最初は声に出しながら何度もトレーニングするのがよいでしょう。

まとめ

Emmetを活用することで効率的にHTMLやCSSを効率的にコーディングできるようになります。ただし、効率性を高めるにはCopilotとの使い分けをうまくやったり、コーディング時の意識をアップデートする必要があります。

Emmet以外にも効率的にコーディングを効率化するためのプラグイン・拡張機能やツールがあります。少しずつ活用できる幅を広げてコーディングを爆速にしていくのがよいと思います。

参考文献

Visual Studio Codeでタグをまるごと選択する

Visual Studio Codeで開始タグから終了タグまでをまるごと選択したいことが多々あります。

これまでは一度対象のタグを閉じて範囲選択をしていたのですが、タグの範囲を見やすくする拡張機能と相性がよくなかったので困っていました。

Emmetショートカットを使う

Emmetショートカットを使用することで、追加の拡張機能なしで実現できます。

コマンドパレットからEmmet: Balance (outward)を見つけ、歯車ボタンを選択します。

任意のショートカットを設定します。

開始タグで設定したショートカットを使用すると、

このように範囲選択できます。

あまり使うことはないかもしれませんが、Emmet: Balance (inward)を使用することで、

選択しているタグの内側を選択することができます。

[関係データベース]スーパーキー、候補キー、主キー、代替キー

スーパーキーと候補キーの違いがよくわからなかったので、関係データベースにおけるキーについてまとめてみました。

用語について

関係データベースの用語で説明しますが、データベースでどの用語に対応するかについてまとめておきます。

  • 関係(リレーション) → 表(テーブル)
  • タプル → 行(レコード)
  • 属性 → 列(カラム)
  • 空値 → NULL値

各キーの概念の階層化

これから説明するスーパークラス、候補キー、主キー、代替キーは、より広い概念(上位)とより狭い概念(下位)といった階層構造の関係があります。

これらを図示すると以下のようになります。

スーパーキー
└── 候補キー
    ├── 主キー
    └── 代替キー

上位の概念から下位の概念の順に説明していきます。

各キーについて説明する際の具体的な例として、関係”社員”

社員(社員ID, 社員番号, 氏名, 部署, メールアドレス)

を使用します。

スーパーキー(Super Key)

今回説明するキーの中ではもっとも広い概念です。関係のタプルを一意に識別できるすべての属性や属性の組み合わせがスーパーキーです。後述の候補キーとは異なり、余分な属性が含まれてもよいという特徴があり、テーブル内で一意性を保証できる組み合わせであればすべてスーパーキーになります。

関係”社員”の場合、

  • {社員ID}
  • {社員番号}
  • {メールアドレス}
  • {氏名, メールアドレス}
  • {社員番号, 部署}
  • ・・・

などがスーパーキーになります。

候補キー(Candidate Key)

スーパーキーの中でも、最小限の属性で一意性を保証できるキーが候補キーです。余分な属性がないため、スーパーキーよりも狭い概念になります。後述の主キー/プライマリーキーとの大きな違いは空値を許すという点です。

関係”社員”の場合、

  • {社員ID}
  • {社員番号}
  • {メールアドレス}

が候補キーになります。たとえば、{社員ID, 氏名}はスーパーキーですが、氏名がなくても一意性を保証できるため、候補キーにはなりません。

主キー/プライマリーキー(Primary Key)

主キーは、候補キーの中から選んだ1つの候補キーです。前述の候補キーとの違いとして、空値(NULL値)を許しません。

関係”社員”の場合、

  • {社員ID}

が一意かつ空値(NULL値)を許さないため、主キーに適しています。

代替キー/代用キー(Alternate Key)

候補キーのうち、主キーとして選ばれなかったキーです。代替キーは主キーとして選ばれなかった候補キーであるため、空値を許します。

候補キーのうち、空値を許さないキーが1つしかなければ、そのキーが主キーになり、それ以外が代替キーになりますが、主キーとして選択可能なキーが複数ある場合はどれを主キーにするかは設計上の判断次第になるため、必ずこれが主キーになるといったルールがあるわけではありません。

関係”社員”の場合、

  • {社員番号}
  • {メールアドレス}

が代替キーになります。

ナチュラルキーとサロゲートキー

キーの分類には、前述の階層構造と直交する「ナチュラルキー」「サロゲートキー」という考え方があります。

ナチュラルキー/自然キー(Natural Key)

ナチュラルキーとは、データそのものに基づいて一意性を保証するキーです。すなわち、すでに現実世界で意味を持っているデータをプライマリーキーとして使用します。

関係”社員”の場合、

  • {社員番号}
  • {メールアドレス}

がナチュラルキーになります。今回の例にはありませんが、SSN(社会保障番号)、マイナンバー(個人番号)なども社員を一意に特定できるナチュラルキーになります。

サロゲートキー/代理キー(Surrogate Key)

一方、サロゲートキーとは、データベース内で独自に生成され、現実世界のデータとは直接の関係がない一意なキーのことです。主に整数値(自動増分のID)、GUID(グローバル一意識別子)、UUID(ユニバーサル一意識別子)などが使用されます。

関係”社員”の場合、

  • {社員ID}

がサロゲートキーになります。これについては少しわかりづらいので順を追って説明します。

現実世界の社員を分析した結果作成した関係”社員”が

社員(社員番号, 氏名, 部署, メールアドレス)

だったとします。当然ですが、企業においては社員番号で社員を一意に識別することができます。概念データモデルでは{社員番号}を主キーにすることで特に問題なさそうですが、次のケースを考えてみます。

まず、関係”社員”に

社員(社員番号, 氏名, 部署, メールアドレス, 入社年度)

のように属性入社年度を追加します。

次に定年退職後嘱託社員としてシステムに再登録することことを考えてみます。このとき、社員番号は変えずに入社年度は元々の入社年度の情報を残しつつ、嘱託社員として入社したときの入社年度を記録したいとしましょう。具体的には

('S001', '山田太郎', '総務部', 'taro@example.com', 1964)

という社員がいる場合、この社員が2024年に嘱託社員として再雇用された場合、

('S001', '山田太郎', '総務部', 'taro@example.com', 1964)
('S001', '山田太郎', '総務部', 'taro@example.com', 2024)

のようにデータを登録したい場合、社員番号は関係”社員”でタプルを一意に特定するキーとは使用できなくなります。(普通は嘱託社員として再雇用した場合、社員番号は変えることが多いと思いますが、ここでは変えない運用を想定しています)

この場合、

社員(社員番号, 連番, 氏名, 部署, メールアドレス)

のように、属性連番を追加して{社員番号, 連番}で一意になるようにすることもできますが、属性社員IDというタプルを一意に特定する属性を追加する方法もあります。この社員IDはタプルを一意に特定する以外の意味を持たないため、サロゲートキー(ナチュラルキーの代理で使われるキー)とよばれます。

まずはナチュラルキーから検討する

ここではデータベースの用語で説明します。

プロジェクトの方針によってはすべてのテーブルにサロゲートキーを設定する場合もありますが、ナチュラルキーを使いつつ、どうしてもナチュラルキーが使えない場合のみサロゲートキーを使用するという方針もあります。具体的には以下のような場合です。

  • ナチュラルキーではレコードを一意に特定できない
  • レコードインサート時にナチュラルキーが設定されないケースがある
  • 要件や処理の都合でナチュラルキーが一意にならない/一意にならない瞬間がある

主キーにすることで、NOT NULL制約と一意性制約が付与されるため、同じ値が存在しないことを保証できますが、前述のように社員番号ではなく社員IDを主キーとして使用すると、社員番号が重複してしまう可能性があるという問題が起こるため、1件だけ取得したつもりが2件以上返ってきてしまうといった問題につながります。対策として一意性制約を付与するという方法がありますが、意外と忘れがちだったります。

こういったことから、基本的にはナチュラルキーを主キーにできないかを検討し、どうしても難しい場合はサロゲートキーを使用するのがよいでしょう。

外部キー(Foreign Key)

今回の内容とは関係ありませんが、外部キーというキーもあります。

外部キーは、ある関係の候補キーの属性または属性の組を参照するキーです。外部キーは、関係間の関係性を定義し、データの整合性を保証する役割を果たします。

  • 関係間の関係性をあらわす
    ある関係が他の関係のデータを参照することによって、関係間にリレーションを形成します。
  • 一貫性の保証
    参照元の関係(親)に存在しないデータを参照することができなくなります。

まとめ

関係データベースでは、

  • スーパーキー
  • 候補キー
  • 主キー
  • 代替キー

というキーがあり、それらは階層化された関係を持ちます。また、これらのキーと直交する概念として、

  • ナチュラルキー
  • サロゲートキー

というものもあります。

これらの用語の違いを理解することで、キーの設計を円滑に進められるでしょう。

[PostgreSQL]外部キー制約を含むテーブルをDROPするとデッドロックが発生する

PostgreSQLでDROP TABLEを行うときにデッドロックが発生するという事象がありました。私自身が遭遇したわけではないですが、Liquibaseで使用していないテーブルをDROP TABLEしようとしたときに、デッドロックが発生したようです。

そんなわけはないと思いましたが、どうやら発生するケースがあるようです。

以下の記事を参考に調査しました。この記事ではFlywayを使っていますが、Liquibaseでも同様の事象が発生しますし、こういったマイグレーションツールを使っていなくてもBEGINEND内でDROP TABLEするケースでは発生し得る事象になります。

外部キー制約を外すときに参照先のテーブルに対してAccessExclusiveLockを獲得している

問題の根元は外部キー制約を外すときに参照先のテーブルに対してAccessExclusiveLockを獲得している点あるようでした。これを確認するために、以下の2つのテーブルを作成します。

親テーブルであり、外部キー制約における参照先のテーブルにあたるcompaniesテーブルを作成します。

CREATE TABLE companies (
    id SERIAL NOT NULL,
    name VARCHAR,
    PRIMARY KEY (id)
);

次に子テーブルであり、外部キー制約を設定するusersテーブルを作成します。

CREATE TABLE users (
    id SERIAL NOT NULL,
    name VARCHAR,
    company_id INTEGER NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (company_id) REFERENCES companies (id)
);

このDDLによって作成される外部キーの名前を確認します。

SELECT
    table_name, constraint_name
FROM
    information_schema.table_constraints
WHERE
    table_schema = 'public'
    AND
    constraint_type = 'FOREIGN KEY';

このSQLを実行すると、以下のような結果が得られます。

table_nameconstraint_name
usersusers_company_id_fkey

外部キーの名前はあとで外部キー制約をDROP CONSTRAINTするときに使用します。

外部キー制約を外すときにAccessExclusiveLockを獲得していることを確認する

では、外部キー制約を外すときにAccessExclusiveLockを獲得していることを確認しましょう。

確認のために、トランザクション内で実行するようにします。これは、今回遭遇したケースではトランザクション内でDDLを実行していたため、同じようにするためにトランザクションを作成しています。

BEGIN;
SELECT locktype, mode FROM pg_locks WHERE pid = pg_backend_pid() AND relation = 'companies'::regclass;

トランザクションを作成した時点では、親テーブルのcompaniesテーブルに対してロックは獲得されていません。

locktypemode

次に外部キー制約をDROP CONSTRAINTしてからもう一度ロックが獲得されているかを確認してみます。

ALTER TABLE users DROP CONSTRAINT users_company_id_fkey;
SELECT locktype, mode FROM pg_locks WHERE pid = pg_backend_pid() AND relation = 'companies'::regclass;

今度はAccessExclusiveLockが獲得されていることを確認できました。

locktypemode
relationAccessExclusiveLock

公式マニュアルによると、AccessExclusiveLockFOR UPDATEがないSELECT文もブロックするため、オンライン稼働中に外部キーを削除するとデッドロックが発生する場合があります。

例えば、外部キーを2つ持つ以下のテーブルを考えてみます。

CREATE TABLE companies (
    id SERIAL NOT NULL,
    name VARCHAR,
    PRIMARY KEY (id)
);

CREATE TABLE organizations (
    id SERIAL NOT NULL,
    name VARCHAR,
    company_id INTEGER,
    PRIMARY KEY (id),
    FOREIGN KEY (company_id) REFERENCES companies (id)
);

CREATE TABLE users (
    id SERIAL NOT NULL,
    name VARCHAR,
    company_id INTEGER NOT NULL,
    organization_id INTEGER  NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (company_id) REFERENCES companies (id),
    FOREIGN KEY (organization_id) REFERENCES organizations (id)
);

CREATE TABLE products (
    id SERIAL NOT NULL,
    name VARCHAR,
    company_id INTEGER NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (company_id) REFERENCES companies (id)
);

このとき、2つのトランザクションを考えます。

  • トランザクション①
    productsテーブルをDROP TABLEするトランザクションです。一応、この例ではproductsテーブルはまだ使われていないテーブルということで、オンライン稼働中にDROP TABLEしても影響はないだろうという考えのもとでオンライン稼働中に実行しています。
  • トランザクション②
    usersテーブルにcompaniesテーブルとorganizationsテーブルをJOINして検索するトランザクションです。これはオンラインで頻繁に実行されるSQLです。

次のような流れで実行されるとデッドロックが発生します。

  1. トランザクション①でcompaniesテーブルへの外部キーが削除され、companiesテーブルへのAccessExclusiveLockが獲得されます。
  2. トランザクション②でusersテーブルとorganizationsテーブルに対するAccessShareLockが獲得されます。
  3. トランザクション①でorganizationsテーブルへの外部キーを削除しようとしますが、トランザクション②がすでにorganiztionsテーブルに対するAccessShareLockを獲得しているため、トランザクション①はAccessExclusiveLockが獲得できずに待ちになります。
  4. トランザクション②はcompaniesテーブルに対するAccessShareLockを獲得使用としますが、トランザクション①がAccessExclusiveLockを獲得しているため、AccessShareLockが獲得できずに待ちになります。(ここでデッドロックが発生)

発生パターンは他にも考えられますが、使われていないテーブルであっても外部キー制約を持つテーブルの削除を行うだけでデッドロックが発生するという点が最も注意すべき点だと思います。

ではどうすればよいか

参考にした記事とも重複しますが、以下のような対策を行うのがよいでしょう。

  1. 外部キー制約をもったテーブルをDROP TABLEする際には、複数のリソースを扱わないようにトランザクションを分ける(Liquibaseの場合は外部キー制約を解除するDDL(1外部キー制約1changelog)とDROP TABLEをするDDLのchangelogをわける)
  2. DDLとDMLを1つのトランザクション内で実行しない(Liquibaseならchangelogを分ける)
  3. 大量更新(登録・更新・削除)を行うテーブルを参照している場合、その処理が行われているタイミングを避けて実行する

外部キー制約を使っているプロジェクトの場合、使っていないテーブルだからといってテーブル定義を綺麗にするために安易にDROP TABLECREATE TABLEによる再作成を選択しないように慎重に検討すべきです。

どうしてもDROP TABLEをしたい場合は1DDL1トランザクションになるように分けて、先に外部キー制約を解除し、それからテーブルをDROP TABLEするようにした方がよいでしょう。

最後に

最後にデッドロックが発生した場合にどのように対応していくかについて記載しておきます。

以下のSQLを実行すると、ロックが発生しているトランザクション一覧が表示されます。

SELECT
    pg_stat_activity.pid,
    pg_stat_activity.usename,
    pg_stat_activity.query,
    pg_class.relname AS table_name,
    pg_locks.locktype,
    pg_locks.mode,
    pg_locks.granted,
    pg_stat_activity.state,
    pg_stat_activity.application_name
FROM
    pg_stat_activity
JOIN
    pg_locks
    ON pg_stat_activity.pid = pg_locks.pid
LEFT JOIN
    pg_class
    ON pg_class.oid = pg_locks.relation
WHERE
    pg_locks.locktype = 'relation'
    AND
    pg_class.relname NOT LIKE 'pg_%'
ORDER BY
    pg_stat_activity.pid;

application_nameなどをもとに、どのpidが問題になっているかを特定し、

SELECT pg_cancel_backend(<killしたいpid>);

で実行できます。例えば、pidが62なら

SELECT pg_cancel_backend(62);

のように書きます。

これでもkillできない場合は、

SELECT pg_terminate_backend(<killしたいpid>)

でkillします。

[CSS]疑似クラス:first-child、:last-child、:nth-childを活用して特定の要素にスタイルを適用しよう

ウェブサイト制作をしていると、繰り返し登場する任意の要素にスタイルを適用したい場面が多々あります。特に、リストやテーブルの特定の項目に異なるデザインを施す際に便利なのがCSSの疑似クラスです。この記事では、:first-child:last-child:nth-childの疑似クラスを活用し、特定の要素にスタイルを適用する方法を学びます。

疑似クラスとは

疑似クラスは、CSSでHTML要素に追加のクラスを付けなくても特定の条件に基づいてスタイルを適用するためのものです。これにより、HTMLを変更せずにスタイル制御が可能になります。例えば、リスト項目の最初や最後の要素に特別なスタイルを適用することができます。

疑似クラスを使うとコードが簡潔になり、HTMLとCSSの分離が保たれるため、メンテナンス性が向上します。

:first-child疑似クラス

:first-child疑似クラスは、親要素の中で最初の子要素を選択するための擬似クラスです。

.container :first-child {
  ...
}

このように書くことで、containerクラスの子要素のうち、最初の子要素を選択することができます。

:last-child疑似クラス

:last-child疑似クラスは、親要素の中で最後の子要素を選択するための擬似クラスです。

.container :last-child {
   ...
}

このように書くことで、containerクラスの子要素のうち、最後の子要素を選択することができます。

:nth-child疑似クラス

:nth-child疑似クラスは、親要素の中でn番目の子要素を選択するための擬似クラスです。nの部分には、数字やキーワード、または数式が入ります。

.container :nth-child(2) {
  ...
}

このように書くことで、containerクラスの子要素のうち、2番目の子要素を選択することができます。

:nth-last-child疑似クラス

:nth-child疑似クラスは、:nth-childの逆順で選択するための疑似クラスです。

.container :nth-last-child(2) {
  ...
}

このように書くことで、containerクラスの子要素のうち、最後から2番目の子要素を選択することができます。

:not疑似クラス

:first-child疑似クラス、:last-child疑似クラス、:nth-child疑似クラス、:nth-last-child疑似クラスの選択を否定したい場合、:not疑似クラスを使用します。

.container :not(:first-child) {
  ...
}

このように書くことで、containerクラスの子要素のうち、最初の要素以外を選択することができます。

要素の適用に対する注意点

疑似クラスの適用範囲について、気をつけておいた方がよい点が2つあります。

親要素と子要素の関係性

例えば、次のようなHTMLを考えてみます。

<div class="container">
  <div class="item">1-1</item>
  <div class="item">1-2</item>
  <div class="item">1-3</item>
</div>
<div class="container">
  <div class="item">2-1</item>
  <div class="item">2-2</item>
  <div class="item">2-3</item>
</div>
<div class="container">
  <div class="item">3-1</item>
  <div class="item">3-2</item>
  <div class="item">3-3</item>
</div>

このHTMLに対して、

.item:nth-child(odd) {
  color: red;
}

を適用すると、どの文字が赤字になるでしょうか?

実際にやってみると一目瞭然ですが、

のように親要素.containerごとに子要素.itemの奇数番目の要素が赤字になることを確認することができます。同じ子要素の指定だとしても親要素を超えて処理されないという点に注意が必要なため、誤解や誤認を避けるために

.item:nth-child(odd) {
  color: red;
}

のように子要素だけ定義するのではなく、

.container .item:nth-child(odd) {
  color: red;
}

のように、親要素 子要素:疑似クラスのように必ず親要素をつけて定義する方がよいと思います。

以降の説明でも、親要素をつけて定義する形式でスタイルを定義するようにしています。

複数種類の子要素が混在している場合

テーブルのように単一の子要素のみで構成される場合は問題ありませんが、複数種類の子要素が混在している状況では期待したとおりにスタイルが適用されない場合があります。

例えば、次のようなHTMLを考えてみます。

<div class="container">
  <h2>タイトル</h2>
  <p class="phrase">1行目</p>
  <p class="phrase">2行目</p>
  <p class="phrase">3行目</p>
  <p class="phrase">4行目</p>
  <p class="phrase">5行目</p>
</div>

phaseクラスが定義されたp要素以外にh2要素があります。

このHTMLに対して次のスタイルを適用します。

.container .phrase:nth-child(2n) {
  color: red;
}

このスタイルでは、phaseクラスが適用された要素の偶数行目の文字を赤字にします。

実際にやってみると、期待したとおりの結果になりませんでした。疑似クラスを使用したとき、その要素が何個目であるかは指定したクラス(今回はphraseクラス)の中で何個目かではなく、親要素から見たときに何個目の要素かで決まります。

すなわち、タイトルが1個目、1行目が2個目、2行目が3個目、・・・という風に数えられ、その中で指定したクラス(今回はphraseクラス)にのみスタイルが適用されます。

このことを確認するために、:first-child疑似クラスを適用してみて色が変わらないこと、:last-child疑似クラスを適用してみて色が変わることを確認しましょう。

まずは、:first-child疑似クラスを確認します。

.pattern .phrase:first-child {
  color: red;
}

:first-child疑似クラスで指しているのはタイトルで、phaseクラスが適用されていないため、色は変わりませんでした。

次に、:last-child疑似クラスを確認します。

.pattern .phrase:last-child {
  color: red;
}

:last-child疑似クラスで指しているのは4行目で、phaseクラスが適用されているため、色は変わりました。

1つの親要素の中に複数種類の子要素が混在する場合、何個目かは要素の種類に関係なく親要素から見て何個目かで数える点に留意してください。

パターン別

今回はすべて、以下のHTMLに対して適用するパターンで説明します。

<!DOCTYPE html>
<html lang="en">
  <body>
    <div class="container">
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
      <div class="item"></div>
    </div>
  </body>
</html>

スタイルが適用されている要素はアクアで色づけし、スタイルが適用されていない要素はグレーのままになるようにします。

最初の要素にのみ適用

最初の要素にのみ適用したい場合は、:first-child疑似クラスを使用します。

.container .item:first-child {
  background-color: aqua;
}

最後の要素にのみ適用

最後の要素にのみスタイルを適用したい場合は、:last-child疑似クラスを使用します。

.container .item:last-child {
  background-color: aqua;
}

最初と最後の要素に適用

:first-child疑似クラスと:last-child疑似クラスを組み合わせることで、最初と最後のようにスタイルを適用することができます。

.container .item:first-child,
.container .item:last-child {
  background-color: aqua;
}

最初の要素以外に適用

最初の要素以外にスタイルを適用したい場合、:first-child疑似クラスとそれを否定する:not疑似クラスを組み合わせることで実現できます。

.container .item:not(:first-child) {
  background-color: aqua;
}

最後の要素以外に適用

最後の要素以外にスタイルを適用したい場合は、:last-child疑似クラスとそれを否定する:not疑似クラスを組み合わせることで実現できます。

.container .item:not(:last-child) {
  background-color: aqua;
}

最初と最後の要素以外に適用

最初と最後の要素以外にスタイルを適用したい場合、:first-child疑似クラス、:last-child疑似クラス、:not疑似クラスを組み合わせて実現します。こちらは最初の要素以外 かつ 最後の要素以外というAND条件になりますので、OR条件の場合とは書き方が異なります。

.container .item:not(:first-child):not(:last-child) {
  background-color: aqua;
}

偶数個目の要素に適用

テーブルの行をストライプにするときによく使われるのが偶数個目または奇数個目の要素にスタイルを適用する方法です。これには:nth-child疑似クラスを使用します。この書き方には2つあるのでそれぞれ説明します。

まずはnを使って書く方法です。nを使って偶数をあらわすためには2nと書きます。

.container .item:nth-child(2n) {
  background-color: aqua;
}

もう少し簡単に書く方法があります。偶数をあらわすevenを使います。

.container .item:nth-child(even) {
  background-color: aqua;
}

奇数個目の要素に適用

奇数個目の要素に適用する方法も2つあります。

まずはnを使って書く方法です。nを使って奇数をあらわすには2n+1と書きます。

.container .item:nth-child(2n+1) {
  background-color: aqua;
}

偶数のときと同じように簡単に書く方法があります。奇数をあらわすoddを使います。

.container .item:nth-child(odd) {
  background-color: aqua;
}

特定の要素に適用

:nth-child疑似クラスを使うことで特定の要素にスタイルを適用することができます。

一例として、3個目の要素にのみ適用する場合を考えてみます。:nth-child疑似クラスに3を指定します。

.container .item:nth-child(3) {
  background-color: aqua;
}

ただし、:nth-child疑似クラスは万能ではありません。書けないケースや書き方に工夫が必要なケースもあります。

以下のようなことは:nth-child疑似クラスで実現可能です。

  1. 特定の順序の要素を選択
    • :nth-child(2)は、親の中で2番目に位置する子要素を選択します
  2. 特定のパターンに基づく要素を選択
    • :nth-child(odd)または:nth-child(2n+1)は、奇数番目の要素を選択します
    • :nth-child(even)または:nth-child(2n)は、偶数番目の要素を選択します
    • :nth-child(3n)は、3番目、6番目、9番目など、3の倍数の位置にある要素を選択します
  3. 複雑なパターンで要素を選択
    • :nth-child(3n+1)は、1番目、4番目、7番目など、3つおきに要素を選択します
    • :nth-child(-n+3)は、最初の3つの要素(1番目、2番目、3番目)を選択します
  4. 他の疑似クラスとの組み合わせ
    • :nth-child(2):hoverのように、特定の順序にある要素がホバーされたときのスタイルを指定できます
  5. 特定のタグに対して使用
    • p:nth-child(3)のように、特定のタグ(例えばp要素)が兄弟要素の中で指定された順序に位置する場合にスタイルを適用できます

一方で以下のようなことは:nth-child疑似クラスではで実現できません。

  1. 特定のクラスや属性を持つ要素を選択
    • 前述の注意点でも挙げたとおり、:nth-child疑似クラスは要素の順序に基づいているため、クラス名や属性を無視します。例えば、p.special:nth-child(2)のように、特定のクラスを持つ2番目の要素を選択することはできません
  2. 逆順の選択
    • :nth-child疑似クラスは要素の逆順での選択はできません。例えば、末尾から2番目の要素を選択することはできません。ただし、:nth-last-child疑似クラスを使用することで実現可能です
  3. 特定の要素タイプに対してだけの選択
    • :nth-child疑似クラスは指定された順序にあるすべての子要素を対象とするため、特定の要素タイプだけを選択することはできません。例えば、特定の順序にある div要素だけを選択することはできません。div:nth-child(2)は、2番目の子要素がdiv要素である場合にのみ適用されます
  4. an+bの形式であらわせない要素を選択
    • :nth-child疑似要素では、2などの特定値での指定、evenまたはoddan+bの形式(abは省略可)のいずれかの方法でしか指定できません。そのため、可変個数の要素のちょうど真ん中の要素に対してスタイルを適用するということはできません

an+bの形式であらわせない形式であっても工夫によって表現可能な場合があります。例えば、1番目と4番目の要素に対してスタイルを適用したい場合、

.container .item:nth-child(1,4) {
  background-color: aqua;
}

とは書けませんが、

.container .item:nth-child(1),
.container .item:nth-child(4) {
  background-color: aqua;
}

と書くことで実現できます。

まとめ

:first-child疑似クラス、:last-child疑似クラス、:nth-child疑似クラス(または:nth-last-child疑似クラス)および:not疑似クラスを使用することで、一部制約はあるものの様々要素を指定することができるようになります。

テーブルをストライプ模様にする以外にも、最初と最後の要素以外にマージンを設けたり、最初の要素だけデザインを変えたりなど、工夫によって様々なことができるようになります。

指定方法には少し注意すべき点がありますが、活用することでデザインの幅はさらに広がることでしょう。

モバイルバージョンを終了