<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>SHOYAN BLOG</title>
  
  
  <link href="https://48n.jp/atom.xml" rel="self"/>
  
  <link href="https://48n.jp/"/>
  <updated>2026-02-25T00:43:41.854Z</updated>
  <id>https://48n.jp/</id>
  
  <author>
    <name>Shohei Yamasaki</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>「筋が悪い」「美しくない」が設計の議論を止める理由</title>
    <link href="https://48n.jp/blog/2026/02/01/why-abstract-words-stop-design-thinking/"/>
    <id>https://48n.jp/blog/2026/02/01/why-abstract-words-stop-design-thinking/</id>
    <published>2026-02-01T12:00:00.000Z</published>
    <updated>2026-02-25T00:43:41.854Z</updated>
    
    <content type="html"><![CDATA[<p>ソフトウェア開発の現場において、次のような言葉を聞くことがあります。</p><ul><li>「この設計は筋がいい／悪い」</li><li>「美しい／汚いコードだ」</li><li>「モダン／レガシーな技術だ」</li></ul><p>こういった言葉は開発現場においてよく聞く言葉ではありますが、いくつかの問題を含んでいます。そのため、私はこういった言葉を極力使わないようにしています。<br>この記事ではこういった言葉がなぜ問題なのか、ソフトウェア設計をどのように考え、どのように評価するべきかについてまとめています。</p><h2 id="抽象語の問題"><a href="#抽象語の問題" class="headerlink" title="抽象語の問題"></a>抽象語の問題</h2><p>「筋が悪い」「美しくない」。もしこういった言葉であなたのコードや設計が評価されたら困りませんか？なぜ困るのかというと、こういった言葉は曖昧で人によって使っている意味や判断基準が違うからです。具体的には次の問題があります。</p><ul><li>意図が正確に伝わらない</li><li>議論を終了させる言葉になりやすい</li></ul><h3 id="意図が正確に伝わらない"><a href="#意図が正確に伝わらない" class="headerlink" title="意図が正確に伝わらない"></a>意図が正確に伝わらない</h3><p>「筋が悪い」「美しくない」といった抽象語の問題は<strong>意図が正確に伝わらない</strong>ことです。多くの場合、抽象語の裏には具体的な評価軸があります。例えば次のような評価軸です。</p><ul><li>変更に強いか</li><li>責務が分離されているか</li><li>ドメインの考え方がコードに素直に反映されているか</li></ul><p>しかし、これを言語化せずに「それ、設計として筋が悪いよね」と言われると、聞いている側は相手が何を言いたいのかがわかりません。抽象語は「<strong>お互いの意思疎通を阻害する言葉</strong>」になりやすいです。</p><h3 id="議論を終了させる言葉になりやすい"><a href="#議論を終了させる言葉になりやすい" class="headerlink" title="議論を終了させる言葉になりやすい"></a>議論を終了させる言葉になりやすい</h3><p>抽象語の問題は<strong>議論を終了させる言葉</strong>になることです。抽象語が議論を終了させる流れは次のようになります。</p><ol><li>違和感・不満・結論が先にある</li><li>それを表現するために「筋が悪い」「美しくない」「技術負債」といったラベルを貼る</li><li>ラベルづけをすると、議論が深まらずに終了する（なぜなら“悪いもの”だから）</li></ol><p>ラベルづけは便利ですが、安易なラベルづけによって議論が深まることなく、「問題である」「直すべき」という結論に至ってしまいます。</p><p>具体的にどのような問題が起こる可能性があるか？それは許容できるのか、許容できないのか？現実として大事なことは、こういった問いです。</p><h2 id="ソフトウェア設計をどう考えるべきか"><a href="#ソフトウェア設計をどう考えるべきか" class="headerlink" title="ソフトウェア設計をどう考えるべきか"></a>ソフトウェア設計をどう考えるべきか</h2><h3 id="ソフトウェア設計はアートではない"><a href="#ソフトウェア設計はアートではない" class="headerlink" title="ソフトウェア設計はアートではない"></a>ソフトウェア設計はアートではない</h3><p>ソフトウェア設計はアートではありません。美しさや先進性の競争でもありません。ソフトウェア設計とは<strong>制約と目的に対するトレードオフの選択である</strong>と私は考えています。</p><h3 id="設計とはトレードオフである"><a href="#設計とはトレードオフである" class="headerlink" title="設計とはトレードオフである"></a>設計とはトレードオフである</h3><p>私は設計に絶対の正解はないと考えています。あるのは、要件と制約と現実的な手段の選択です。そこには必ずトレードオフがあります。</p><ul><li>実装を単純にする代わりに、拡張性を捨てる</li><li>将来の変更に強くする代わりに、設計が複雑になる</li></ul><p>本来、設計の議論は <strong>「どのトレードオフを選ぶか」</strong> の話のはずです。</p><h3 id="設計の良し悪しは「時間の使われ方」で測れる"><a href="#設計の良し悪しは「時間の使われ方」で測れる" class="headerlink" title="設計の良し悪しは「時間の使われ方」で測れる"></a>設計の良し悪しは「時間の使われ方」で測れる</h3><p>結局、設計評価はこれに集約されます。</p><p><strong>エンジニアの時間がどこに消えているか</strong></p><ul><li>運用・保守・依存更新 → 悪化</li><li>機能開発・改善 → 良好</li></ul><p>優れた設計は運用・保守の時間を最小化し、機能開発の時間を創出します。</p><h3 id="本質的な評価軸"><a href="#本質的な評価軸" class="headerlink" title="本質的な評価軸"></a>本質的な評価軸</h3><p>設計を評価するなら、見るべきは次のポイントです。</p><ul><li>要件に対して不足していたり過剰ではないか</li><li>要件に対してコストのバランスが取れているか</li><li>将来的なメンテナンスや依存リスクはないか</li><li>新規参入のエンジニアが容易に理解することができるか</li></ul><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><ul><li><strong>抽象語の問題</strong>：「筋が悪い」「美しくない」「技術負債」といった言葉は意図を正確に伝えず、議論を終了させて思考を止めてしまう</li><li><strong>設計の本質</strong>：設計に絶対的な良し悪しはなく、トレードオフである。制約の中で何を楽にして何を捨てるか、どのトレードオフを選ぶかの話である</li><li><strong>評価の軸</strong>：設計の良し悪しは、エンジニアの時間が運用・保守に消えているか、機能開発・改善に使えているかで測れる</li></ul>]]></content>
    
    
    <summary type="html">「筋が悪い」「美しくない」といった抽象語が設計の議論でなぜ問題になるのかを解説します。意図が伝わらない・議論を終了させる言葉になる理由と、設計をトレードオフと時間の使われ方で評価する考え方を紹介します。</summary>
    
    
    
    <category term="ソフトウェア開発" scheme="https://48n.jp/categories/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA/"/>
    
    
  </entry>
  
  <entry>
    <title>テーブル初期設計のポイント：事実（Fact）を元に設計する</title>
    <link href="https://48n.jp/blog/2026/01/21/table-design-patterns/"/>
    <id>https://48n.jp/blog/2026/01/21/table-design-patterns/</id>
    <published>2026-01-21T01:00:00.000Z</published>
    <updated>2026-02-25T00:43:41.853Z</updated>
    
    <content type="html"><![CDATA[<p>アプリケーションにおいてのコアはデータです。<br>データ設計に問題があるアプリケーションは、意図がわかりづらく、実装が複雑になり、その結果、不具合が発生しやすくなります。また、後からデータ設計を変更することはコストが大きく、簡単に変更もできません。</p><p>この記事では、データベースの初期設計において考慮しておくべきポイントと実務で使いやすい方法について解説します。</p><h2 id="テーブル初期設計の基本ポイント"><a href="#テーブル初期設計の基本ポイント" class="headerlink" title="テーブル初期設計の基本ポイント"></a>テーブル初期設計の基本ポイント</h2><p>テーブル設計において、特に「状態を持つデータ」の設計は難しいものです。<br>ユーザー情報を例にすると、<code>status</code> カラムに <code>active</code> &#x2F; <code>inactive</code> &#x2F; <code>banned</code> などの状態を持つという設計はよくある設計でしょう。このように状態を持つ設計は一見シンプルに見えますが、実は後々問題になりがちです。</p><h3 id="なぜ状態を持つ設計が問題になりやすいのか"><a href="#なぜ状態を持つ設計が問題になりやすいのか" class="headerlink" title="なぜ状態を持つ設計が問題になりやすいのか"></a>なぜ状態を持つ設計が問題になりやすいのか</h3><p>状態を1つのカラムで管理する設計は、一見シンプルに見えますが、実際には以下のような問題が発生しやすくなります。</p><h4 id="データの不整合が発生しやすい"><a href="#データの不整合が発生しやすい" class="headerlink" title="データの不整合が発生しやすい"></a>データの不整合が発生しやすい</h4><p>状態を持つとデータの不整合が発生しやすくなります。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 例：派生情報をカラムに保存</span></span><br><span class="line">users</span><br><span class="line"><span class="operator">-</span> id</span><br><span class="line"><span class="operator">-</span> email</span><br><span class="line"><span class="operator">-</span> is_active  <span class="comment">-- 派生情報</span></span><br><span class="line"><span class="operator">-</span> deleted_at <span class="comment">-- 事実</span></span><br></pre></td></tr></table></figure><p>この設計では、<code>deleted_at</code> が設定されているのに <code>is_active = true</code> のまま、という矛盾した状態が発生する可能性があります。</p><h4 id="データが曖昧になりやすい"><a href="#データが曖昧になりやすい" class="headerlink" title="データが曖昧になりやすい"></a>データが曖昧になりやすい</h4><p>初期設計では <code>active</code> &#x2F; <code>inactive</code> の2つだった状態が、後から以下のような状態が必要になることがあります。</p><ul><li><code>banned</code> （アカウント停止）</li><li><code>suspended</code>（一時停止）</li><li><code>pending_verification</code>（認証待ち）</li><li><code>trial_expired</code>（トライアル期限切れ）</li></ul><p>このように後から状態を追加していくと、いつの間にか複数の意味を持つステータスが１つのカラムに入っているということが発生します。そうなると、カラムの意味がぼやけてしまい、明確なデータ表現が難しくなってしまいます。</p><h2 id="テーブル設計の原則"><a href="#テーブル設計の原則" class="headerlink" title="テーブル設計の原則"></a>テーブル設計の原則</h2><h3 id="事実-Fact-だけを保存する"><a href="#事実-Fact-だけを保存する" class="headerlink" title="事実(Fact)だけを保存する"></a>事実(Fact)だけを保存する</h3><p>テーブル設計の原則は <strong>業務・ドメインの事実（Fact）だけを保存する</strong> です。</p><ul><li><strong>事実</strong>：登録された、退会した、BANされた、メール認証した</li><li><strong>派生</strong>：有効ユーザーかどうか、ログイン可能かどうか</li></ul><p><strong>派生情報は保存せずに事実から算出します</strong>。<br>このように、事実のみを保存するという原則に則ることにより、データの不整合や曖昧さに強い設計にすることができます。</p><p>以下は、ユーザーの情報を日付型として扱う例です。１つのカラムでは１つの意味を取り扱うことで、明確なデータ設計にしているのがポイントです。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 例：日時型を使う（状態が増えたらカラム追加で対応）</span></span><br><span class="line">users</span><br><span class="line"><span class="operator">-</span> id</span><br><span class="line"><span class="operator">-</span> email</span><br><span class="line"><span class="operator">-</span> deleted_at      <span class="comment">-- 退会日時</span></span><br><span class="line"><span class="operator">-</span> banned_at        <span class="comment">-- BAN日時</span></span><br><span class="line"><span class="operator">-</span> suspended_at     <span class="comment">-- 一時停止日時</span></span><br><span class="line"><span class="operator">-</span> email_verified_at <span class="comment">-- メール認証日時</span></span><br><span class="line"><span class="comment">-- 新しい状態が増えても、新しい日時カラムを追加するだけ</span></span><br></pre></td></tr></table></figure><h3 id="状態＝「事実の集合」として考える"><a href="#状態＝「事実の集合」として考える" class="headerlink" title="状態＝「事実の集合」として考える"></a>状態＝「事実の集合」として考える</h3><p>状態は事実の組み合わせです。例えば、ユーザーが「利用可能」かどうかは、<strong>これらの事実の組み合わせ</strong>から判断します。</p><ul><li>退会していない</li><li>BANされていない</li><li>メール認証済み</li></ul><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 利用可能ユーザー</span></span><br><span class="line"><span class="keyword">WHERE</span> deleted_at <span class="keyword">IS</span> <span class="keyword">NULL</span></span><br><span class="line">  <span class="keyword">AND</span> banned_at <span class="keyword">IS</span> <span class="keyword">NULL</span></span><br><span class="line">  <span class="keyword">AND</span> email_verified_at <span class="keyword">IS</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span></span><br></pre></td></tr></table></figure><p>ユーザーが「利用可能」というような状態は事実を元に算出する設計が安定します。</p><h2 id="状態として持った方が扱いやすいケース"><a href="#状態として持った方が扱いやすいケース" class="headerlink" title="状態として持った方が扱いやすいケース"></a>状態として持った方が扱いやすいケース</h2><p>一方で <strong>「割り切って状態を持たせた方が扱いやすい設計になるケース」</strong> は、実務では普通にあります。</p><h3 id="UI-管理画面で即座に使いたいとき"><a href="#UI-管理画面で即座に使いたいとき" class="headerlink" title="UI &#x2F; 管理画面で即座に使いたいとき"></a>UI &#x2F; 管理画面で即座に使いたいとき</h3><h4 id="例：決済管理画面"><a href="#例：決済管理画面" class="headerlink" title="例：決済管理画面"></a>例：決済管理画面</h4><p>例えば、一覧で「未払い」「支払済み」「返金済み」を表示したい場合は、状態を持ったほうがよいことがあります。<br>一覧画面で毎回集計の計算をするのはパフォーマンスが重くなりがちです。そういった場合は割り切って状態を持ったほうがトータルとして安定します。</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">invoices</span><br><span class="line"><span class="bullet">- </span>id</span><br><span class="line"><span class="bullet">- </span>total_amount</span><br><span class="line"><span class="bullet">- </span>status  -- unpaid / paid</span><br></pre></td></tr></table></figure><h4 id="ルール"><a href="#ルール" class="headerlink" title="ルール"></a>ルール</h4><p>状態を持つときは以下のルールを守るのがポイントです。</p><ul><li>状態が事実から再計算できる</li></ul><h3 id="ワークフローが明確に有限なとき"><a href="#ワークフローが明確に有限なとき" class="headerlink" title="ワークフローが明確に有限なとき"></a>ワークフローが明確に有限なとき</h3><h4 id="例：審査フロー"><a href="#例：審査フロー" class="headerlink" title="例：審査フロー"></a>例：審査フロー</h4><p>審査フローのようにワークフローが明確で有限な場合は状態として持って問題ありません。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">draft</span> → submitted → reviewing → approved / rejected</span><br></pre></td></tr></table></figure><h4 id="なぜOKか"><a href="#なぜOKか" class="headerlink" title="なぜOKか"></a>なぜOKか</h4><ul><li>状態遷移が業務ルールとして固定</li><li>「今どこか」が重要</li></ul><h3 id="一生変わらない意味の状態"><a href="#一生変わらない意味の状態" class="headerlink" title="一生変わらない意味の状態"></a>一生変わらない意味の状態</h3><p>一度設定された状態の意味が変わらない場合は、状態として持って問題ない場合が多いです。例えば、次のような場合です。</p><h4 id="例"><a href="#例" class="headerlink" title="例"></a>例</h4><ul><li>is_admin（管理者かどうか）</li><li>is_trial_user（トライアルユーザーかどうか）</li><li>is_archived（アーカイブされているかどうか）</li></ul><h4 id="考え方"><a href="#考え方" class="headerlink" title="考え方"></a>考え方</h4><ul><li><strong>定義が将来もブレない</strong>：運用や要件が変わっても「何を意味するか」が変わらない</li><li><strong>派生状態ではない</strong>：他の複数の事実の組み合わせ（計算結果）になっていない</li></ul><p>例えば <code>is_admin</code> は「管理者権限が付与されているか」という <strong>明確な事実</strong> に近いのでフラグとして持ちやすいです。一方で <code>is_active</code> は「退会していない」「BANされていない」「認証済み」など条件が増えやすい <strong>派生状態</strong> なので、フラグとして持つと破綻しやすくなります。</p><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p><strong>「状態」を保存するのではなく、まずは「事実（Fact）」を保存する</strong>のが、拡張と運用に強い設計です。</p><ul><li><strong>原則</strong>: DBには <strong>事実（イベント・日時）</strong> を保存し、状態は <strong>計算結果</strong> として扱う</li><li><strong>避けたいこと</strong>: <code>status</code> のような1カラムに、意味の違う状態を押し込める（曖昧さ・不整合の原因）</li><li><strong>例外（状態を持ってもよい）</strong>: UI&#x2F;管理画面で即時に必要、ワークフローが有限で明確（例：請求ステータス、審査フロー）など、<strong>割り切る理由がある</strong></li><li><strong>状態を持つときのルール</strong>: <strong>再計算できる &#x2F; 壊れても直せる</strong>（できないなら、まずFactの持ち方を見直す）</li></ul><p>テーブルは「状態を持つ存在」ではなく、<strong>「状態を生む事実を持つ存在」</strong> として設計することがポイントです。</p>]]></content>
    
    
    <summary type="html">テーブル初期設計のポイントである「事実（Fact）を保存し、状態は計算結果として扱う」考え方を解説します。ユーザーの状態管理を例に、事実と派生情報の識別と扱い方を紹介します。</summary>
    
    
    
    <category term="ソフトウェア開発" scheme="https://48n.jp/categories/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA/"/>
    
    
  </entry>
  
  <entry>
    <title>「社員と業務委託を区別しない」は法的リスクのサイン</title>
    <link href="https://48n.jp/blog/2026/01/10/freelance-contract-risks/"/>
    <id>https://48n.jp/blog/2026/01/10/freelance-contract-risks/</id>
    <published>2026-01-10T01:00:00.000Z</published>
    <updated>2026-02-25T00:43:41.849Z</updated>
    
    <content type="html"><![CDATA[<h2 id="はじめに"><a href="#はじめに" class="headerlink" title="はじめに"></a>はじめに</h2><p>「うちは社員も業務委託も区別しません」</p><p>スタートアップやベンチャーの現場で、こうした言葉を聞いたことがある人も多いのではないでしょうか。</p><p>一見するとフラットで良さそうに聞こえますが、法的・構造的には実は危ないサインです。</p><p>本記事では、</p><ul><li>なぜ社員と業務委託を同じように扱えないのか</li><li>同じ扱いをすると何が起きるのか</li><li>具体的な事例</li><li>経営者・リーダー・フリーランスがそれぞれ注意すべきポイント</li></ul><p>を、実務目線で整理します。</p><hr><h2 id="結論：社員と業務委託は「同じように扱えない」"><a href="#結論：社員と業務委託は「同じように扱えない」" class="headerlink" title="結論：社員と業務委託は「同じように扱えない」"></a>結論：社員と業務委託は「同じように扱えない」</h2><p>結論から言うと、社員と業務委託は同じようには扱えません。</p><p>理由はシンプルで、日本の法制度では次のように位置づけられているからです。</p><ul><li>社員（雇用）：労働力を提供する人（民法第623条「雇用」、労働基準法第9条「労働者」）</li><li>業務委託：成果を提供する事業者（民法第632条「請負」、第656条「準委任」）</li></ul><p>このように法律として区別されているため、社員と業務委託は同じように扱うことができません。</p><h2 id="業務委託とは何か？"><a href="#業務委託とは何か？" class="headerlink" title="業務委託とは何か？"></a>業務委託とは何か？</h2><p>会社が社外のフリーランスに業務を委託する際の契約が業務委託で、<strong>成果物や業務の遂行に対して報酬が支払われます</strong>。<br>業務委託には、<strong>請負契約</strong>と<strong>準委任契約</strong>の2種類がありますが、どちらも以下の特徴があります。</p><ul><li><strong>裁量の委譲</strong>：作業の方法や手順は受託者が決定する</li><li><strong>時間の自由</strong>：作業時間や場所は受託者が決定する</li><li><strong>独立性</strong>：受託者は独立した事業者として業務を遂行する</li><li><strong>指揮命令権がない</strong>：クライアントは作業の方法や手順を指示できない</li></ul><h2 id="雇用契約との違い"><a href="#雇用契約との違い" class="headerlink" title="雇用契約との違い"></a>雇用契約との違い</h2><p>雇用契約と業務委託契約は、法的に全く異なる性質を持ちます。</p><h3 id="雇用契約の特徴"><a href="#雇用契約の特徴" class="headerlink" title="雇用契約の特徴"></a>雇用契約の特徴</h3><p>雇用契約は、<strong>労働力を提供する契約</strong>です。</p><ul><li><strong>労働力の提供</strong>：時間と労力を提供することで報酬を受け取る</li><li><strong>指揮命令権</strong>：使用者（会社）が労働者に対して業務の内容や方法を指示できる</li><li><strong>時間の拘束</strong>：就業時間や場所が指定される</li><li><strong>リスクの所在</strong>：使用者が業務のリスクを負う</li></ul><h2 id="なぜ区別が重要なのか"><a href="#なぜ区別が重要なのか" class="headerlink" title="なぜ区別が重要なのか"></a>なぜ区別が重要なのか</h2><p>この区別が曖昧になると、<strong>偽装請負・偽装委任</strong>とみなされる可能性があります。</p><p>偽装請負・偽装委任とは、実質的には雇用関係にあるのに、業務委託契約（請負契約や準委任契約）の形式を取ることで、労働法上の義務（社会保険、残業代など）を回避しようとする行為です。</p><h2 id="社員にできて、業務委託にできないこと"><a href="#社員にできて、業務委託にできないこと" class="headerlink" title="社員にできて、業務委託にできないこと"></a>社員にできて、業務委託にできないこと</h2><h3 id="社員にだけ許されている管理（指揮命令権）"><a href="#社員にだけ許されている管理（指揮命令権）" class="headerlink" title="社員にだけ許されている管理（指揮命令権）"></a>社員にだけ許されている管理（指揮命令権）</h3><p>指揮命令権とは、次のような権限のことです。</p><ul><li>いつ働くかを決める</li><li>どこで働くかを決める</li><li>どの順番で、どの方法で作業するかを指示する</li><li>業務の進め方を細かく統制する</li><li>従わなければ不利益を与える（評価・報酬・契約継続など）</li></ul><p>これは 労働契約（雇用）にだけ認められている権限 です。<br>こういったことを業務委託に行うのは法律的にNGです。</p><h2 id="問題になりやすいケース"><a href="#問題になりやすいケース" class="headerlink" title="問題になりやすいケース"></a>問題になりやすいケース</h2><p>社員と業務委託の区別をしていない現場でありがちな、問題になりやすいケースです。</p><h3 id="1-フラットなことが良い文化だと思っている"><a href="#1-フラットなことが良い文化だと思っている" class="headerlink" title="1. フラットなことが良い文化だと思っている"></a>1. フラットなことが良い文化だと思っている</h3><p>「区別しない＝対等」という認識は一見よさそうに思えますが、そもそも、法律の扱いが違うため、同じ扱いはできません。法律に則った対応をする必要があります。</p><h3 id="2-管理職が雇用しか知らない"><a href="#2-管理職が雇用しか知らない" class="headerlink" title="2. 管理職が雇用しか知らない"></a>2. 管理職が雇用しか知らない</h3><p>管理職に法的な知識がなく、業務委託を社員と同じように管理することに問題意識がない場合があります。</p><h3 id="3-社員も業務委託も同じように扱いたい"><a href="#3-社員も業務委託も同じように扱いたい" class="headerlink" title="3. 社員も業務委託も同じように扱いたい"></a>3. 社員も業務委託も同じように扱いたい</h3><p>現場でよくある本音です。</p><ul><li>業務委託でも一緒にSlackにいてほしい</li><li>社員と同じスピード感で動いてほしい</li><li>社員と同じように働いてほしい</li></ul><p>これは全部、雇用の安心感を委託で代替しようとしている状態です。雇用関係の契約をするのが本来あるべき姿です。</p><h2 id="具体事例"><a href="#具体事例" class="headerlink" title="具体事例"></a>具体事例</h2><p>エンジニアの現場でありがちな具体的なケースを例に説明します。</p><h3 id="法的リスクが高いケース"><a href="#法的リスクが高いケース" class="headerlink" title="法的リスクが高いケース"></a>法的リスクが高いケース</h3><p>以下のケースは、明らかに雇用関係とみなされる可能性が高く、法的リスクが高いケースです。</p><h4 id="ケース1：出退勤の管理を求められる"><a href="#ケース1：出退勤の管理を求められる" class="headerlink" title="ケース1：出退勤の管理を求められる"></a>ケース1：出退勤の管理を求められる</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">「業務委託でも、毎日9時から18時までSlackにいてください。</span><br><span class="line">離席する時は必ず報告してください。」</span><br></pre></td></tr></table></figure><p><strong>問題点</strong>：勤務時間の拘束と離席連絡の義務は、指揮命令権の行使です。業務委託にこれを求めることは、雇用関係とみなされる可能性が極めて高いです。</p><h4 id="ケース2：作業方法を細かく指示される"><a href="#ケース2：作業方法を細かく指示される" class="headerlink" title="ケース2：作業方法を細かく指示される"></a>ケース2：作業方法を細かく指示される</h4><p>① 実装手順をステップ単位で指定される場合</p><figure class="highlight pf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">まずこのAPIを呼んで</span><br><span class="line">次にこの<span class="keyword">state</span>に入れて</span><br><span class="line">useEffectはここで使って</span><br><span class="line">この条件分岐で描画を切り替えてください</span><br></pre></td></tr></table></figure><p>② レビューが「やり方の矯正」になっている場合</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">「この書き方は違います。このように直してください」</span><br></pre></td></tr></table></figure><p><strong>問題点</strong>：作業の方法や手順を細かく指示することは、指揮命令権の行使です。業務委託では、作業の方法は受託者が決定します。</p><h4 id="ケース3：即レス文化を強制される"><a href="#ケース3：即レス文化を強制される" class="headerlink" title="ケース3：即レス文化を強制される"></a>ケース3：即レス文化を強制される</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">「業務委託でも、Slackのメッセージには</span><br><span class="line">1時間以内に返信してください。」</span><br></pre></td></tr></table></figure><p><strong>問題点</strong>：常時オンラインを前提とした即レス文化は、時間の拘束にあたります。業務委託では、作業時間や場所は受託者が決定します。</p><h4 id="ケース4：ビデオチャットでの常時接続を要求される"><a href="#ケース4：ビデオチャットでの常時接続を要求される" class="headerlink" title="ケース4：ビデオチャットでの常時接続を要求される"></a>ケース4：ビデオチャットでの常時接続を要求される</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">「勤務中はビデオチャットで常時接続しておく必要があります。</span><br><span class="line">作業中は常にカメラをオンにしておいてください。」</span><br></pre></td></tr></table></figure><p><strong>問題点</strong>：勤務時間中にビデオチャットで常時接続を要求することは、<strong>時間の拘束</strong>と<strong>場所の拘束</strong>の両方に該当します。業務委託では、作業時間や場所は受託者が決定します。</p><h3 id="グレーなケース"><a href="#グレーなケース" class="headerlink" title="グレーなケース"></a>グレーなケース</h3><p>以下のケースは、境界線上で、実態によっては雇用関係とみなされる可能性があります。</p><h4 id="ケース1：定期的なミーティングへの参加"><a href="#ケース1：定期的なミーティングへの参加" class="headerlink" title="ケース1：定期的なミーティングへの参加"></a>ケース1：定期的なミーティングへの参加</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">「毎週月曜の10時から定例ミーティングがあります。</span><br><span class="line">業務委託の方も参加してください。」</span><br></pre></td></tr></table></figure><p><strong>判断基準</strong></p><ul><li><strong>問題あり</strong>：参加が必須で、欠席すると評価に影響する場合</li><li><strong>問題なし</strong>：参加は任意で、成果物の確認や情報共有が目的の場合</li></ul><h4 id="ケース2：コードレビューの指摘"><a href="#ケース2：コードレビューの指摘" class="headerlink" title="ケース2：コードレビューの指摘"></a>ケース2：コードレビューの指摘</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">「この実装方法はパフォーマンスに問題があるので、こちらの方法に変更してください。」</span><br></pre></td></tr></table></figure><p><strong>判断基準</strong></p><ul><li><strong>問題あり</strong>：実装方法を強制し、従わないとレビューをOKとしない場合</li><li><strong>問題なし</strong>：技術的なアドバイスや改善提案として提示される場合</li></ul><h4 id="ケース3：納期の設定"><a href="#ケース3：納期の設定" class="headerlink" title="ケース3：納期の設定"></a>ケース3：納期の設定</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">「この機能は来週金曜までに完成させてください。」</span><br></pre></td></tr></table></figure><p><strong>判断基準</strong></p><ul><li><strong>問題あり</strong>：納期が短すぎて、実質的に作業時間が拘束される場合</li><li><strong>問題なし</strong>：成果物の納期として、合理的な期間が設定されている場合</li></ul><h4 id="ケース4：Slackでのコミュニケーション"><a href="#ケース4：Slackでのコミュニケーション" class="headerlink" title="ケース4：Slackでのコミュニケーション"></a>ケース4：Slackでのコミュニケーション</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">「業務委託の方も、社員と同じようにSlackでコミュニケーションしてください。」</span><br></pre></td></tr></table></figure><p><strong>判断基準</strong></p><ul><li><strong>問題あり</strong>：常時オンラインを前提とし、即レスを強制する場合</li><li><strong>問題なし</strong>：成果物の確認や情報共有のためのコミュニケーションツールとして使用する場合</li></ul><h4 id="ケース5：稼働時間"><a href="#ケース5：稼働時間" class="headerlink" title="ケース5：稼働時間"></a>ケース5：稼働時間</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">「準委任契約で、月に160時間稼働していただきます。」</span><br></pre></td></tr></table></figure><p><strong>判断基準</strong></p><ul><li><strong>問題あり</strong>：毎月必ず160時間働くことを義務付けている場合（時間の拘束）<ul><li>例：「月160時間の稼働を必須とします」「160時間未満の場合は報酬を減額します」</li></ul></li><li><strong>問題なし</strong>：月160時間を想定しているが、時間の拘束はなく、実際に行った事務処理の時間に対して報酬が支払われる場合<ul><li>例：「月160時間程度を想定していますが、作業時間や場所はお任せします。実際に作業を行った時間に対して報酬を支払います」</li></ul></li></ul><p><strong>重要なポイント</strong>：</p><ul><li>準委任契約で時給ベースの報酬設定は法的に可能です</li><li>しかし、「月にxxx時間働くことを必須とする」という時間の拘束は、雇用関係とみなされる可能性が高いです</li><li>実際に行った作業の時間に対して報酬が支払われる場合は、問題ありません</li></ul><h3 id="問題なし"><a href="#問題なし" class="headerlink" title="問題なし"></a>問題なし</h3><p>以下のケースは、業務委託として適切な運用です。</p><h4 id="ケース1：成果物ベースの評価"><a href="#ケース1：成果物ベースの評価" class="headerlink" title="ケース1：成果物ベースの評価"></a>ケース1：成果物ベースの評価</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">「この機能の実装をお願いします。</span><br><span class="line">完成したら、コードレビューをして納品してください。」</span><br></pre></td></tr></table></figure><p><strong>適切な理由</strong>：成果物の完成を目的としており、作業の方法は受託者が決定します。</p><h4 id="ケース2：技術的なアドバイスの提供"><a href="#ケース2：技術的なアドバイスの提供" class="headerlink" title="ケース2：技術的なアドバイスの提供"></a>ケース2：技術的なアドバイスの提供</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">「この機能は、パフォーマンスを考慮するとこちらの実装方法がおすすめです。</span><br><span class="line">ただし、最終的な判断はお任せします。」</span><br></pre></td></tr></table></figure><p><strong>適切な理由</strong>：技術的なアドバイスは提供していますが、最終的な判断は受託者に委ねられています。</p><h4 id="ケース3：成果物の確認とフィードバック"><a href="#ケース3：成果物の確認とフィードバック" class="headerlink" title="ケース3：成果物の確認とフィードバック"></a>ケース3：成果物の確認とフィードバック</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">「完成した成果物を確認しました。</span><br><span class="line">この部分は要件と異なるので、修正をお願いします。」</span><br></pre></td></tr></table></figure><p><strong>適切な理由</strong>：成果物の確認とフィードバックは、業務委託契約の範囲内です。ただし、修正の方法は受託者が決定します。</p><h4 id="ケース4：情報共有のためのコミュニケーション"><a href="#ケース4：情報共有のためのコミュニケーション" class="headerlink" title="ケース4：情報共有のためのコミュニケーション"></a>ケース4：情報共有のためのコミュニケーション</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">「プロジェクトの進捗を共有するため、</span><br><span class="line">週1回、30分程度のミーティングを設定しています。</span><br><span class="line">参加は任意です。」</span><br></pre></td></tr></table></figure><p><strong>適切な理由</strong>：情報共有が目的で、参加が任意であれば、時間の拘束にはあたりません。</p><h4 id="ケース5：成果物の納期設定"><a href="#ケース5：成果物の納期設定" class="headerlink" title="ケース5：成果物の納期設定"></a>ケース5：成果物の納期設定</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">「この機能の実装をお願いします。</span><br><span class="line">納期は2週間後を想定していますが、作業の進め方はお任せします。」</span><br></pre></td></tr></table></figure><p><strong>適切な理由</strong>：成果物の納期として合理的な期間が設定されており、作業の方法は受託者が決定します。</p><h2 id="経営者・リーダーに伝えたいこと"><a href="#経営者・リーダーに伝えたいこと" class="headerlink" title="経営者・リーダーに伝えたいこと"></a>経営者・リーダーに伝えたいこと</h2><p>もし、</p><ul><li>常に一緒に動いてほしい</li><li>時間帯を揃えたい</li><li>社員と同じように働いてほしい</li></ul><p>のであれば、業務委託ではなく雇用を選ぶべきです。</p><p>業務委託は、</p><ul><li>裁量を渡す</li><li>成果で評価する</li></ul><p>という覚悟が必要な契約形態です。<strong>会社の都合で契約を終了できる社員ではありません</strong>。</p><h3 id="同じように扱った場合に会社が被るリスク"><a href="#同じように扱った場合に会社が被るリスク" class="headerlink" title="同じように扱った場合に会社が被るリスク"></a>同じように扱った場合に会社が被るリスク</h3><p>現実的なリスクは次の通りです。</p><ul><li>労基署からの是正勧告</li><li>残業代・社会保険の遡及請求</li><li>外注費の税務否認（業務委託として計上した外注費が、実態が雇用関係と判断され、給与として計上し直すよう指摘される）</li><li>IPO・M&amp;A時の重大指摘</li></ul><p>実際に厚生労働省はフリーランスが実態は労働者として働いていないかの確認を強化する方針を掲げており、全国の労基署に相談窓口が設置されています。</p><p><a href="https://www.mhlw.go.jp/stf/newpage_44487.html" target="_blank">「労働者性に疑義がある方の労働基準法等違反相談窓口」を労働基準監督署に設置します</a></p><h2 id="フリーランスがクライアントを選別するために"><a href="#フリーランスがクライアントを選別するために" class="headerlink" title="フリーランスがクライアントを選別するために"></a>フリーランスがクライアントを選別するために</h2><p>健全なクライアントは、次のように答えられます。</p><ul><li>「勤怠管理はしません」</li><li>「成果物ベースでお願いします」</li><li>「やり方はお任せします」</li></ul><p>社員と同じようにやっていただきたいという考え方の会社の場合、業務委託は長期的には消耗戦になりがちです。</p><h3 id="正社員脳-→-業務委託脳-への切り替え"><a href="#正社員脳-→-業務委託脳-への切り替え" class="headerlink" title="正社員脳 → 業務委託脳 への切り替え"></a>正社員脳 → 業務委託脳 への切り替え</h3><p>長年正社員で経験を積んできたエンジニアにとっては、勤怠管理は当たり前の概念です。私もそうでした。<br>しかし、正社員と業務委託では法的に区別されるため、考え方を切り替える必要があります。</p><p>整理すると、こうです。</p><p><strong>正社員の世界</strong></p><ul><li>時間を会社に提供する</li><li>管理されるのが前提</li><li>出勤・退勤が価値の測定単位</li></ul><p><strong>業務委託の世界</strong></p><ul><li>専門性・判断・作業を提供する</li><li>管理されないのが前提</li><li>時間は 計測するが、拘束されない</li></ul><h2 id="おわりに"><a href="#おわりに" class="headerlink" title="おわりに"></a>おわりに</h2><p>「社員と業務委託を区別しない」という言葉は、善意であっても、無知であっても、リスクのサインです。<br>契約形態を正しく理解し、お互いが健全に協力できる関係を構築することが、結果的にプロダクトにも組織にもプラスになります。</p><p>この記事が、</p><ul><li>経営者・リーダーにとっては運用を見直すきっかけに</li><li>フリーランスにとってはクライアント選別の軸</li></ul><p>になれば幸いです。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;はじめに&quot;&gt;&lt;a href=&quot;#はじめに&quot; class=&quot;headerlink&quot; title=&quot;はじめに&quot;&gt;&lt;/a&gt;はじめに&lt;/h2&gt;&lt;p&gt;「うちは社員も業務委託も区別しません」&lt;/p&gt;
&lt;p&gt;スタートアップやベンチャーの現場で、こうした言葉を聞いたことがある人も</summary>
      
    
    
    
    <category term="フリーランス" scheme="https://48n.jp/categories/%E3%83%95%E3%83%AA%E3%83%BC%E3%83%A9%E3%83%B3%E3%82%B9/"/>
    
    
  </entry>
  
  <entry>
    <title>エンジニアの退職は「人の問題」ではなく「意思決定プロセスの問題」</title>
    <link href="https://48n.jp/blog/2026/01/04/strong-engineers-go-silent/"/>
    <id>https://48n.jp/blog/2026/01/04/strong-engineers-go-silent/</id>
    <published>2026-01-04T01:00:00.000Z</published>
    <updated>2026-02-25T00:43:41.853Z</updated>
    
    <content type="html"><![CDATA[<p>近年、プロダクト部門はビジネスの成果を左右する重要な部門となっており、その組織を構成するエンジニアの質がビジネスの成果に大きく影響するようになっています。一方でエンジニアの離職率の高さと採用の難しさが経営の問題となっている組織も少なくありません。</p><p>この記事では、なぜエンジニアがやめるのかをエンジニアの心理と構造の点から解説し、組織がとるべき対策について論じます。</p><h2 id="1-退職の理由"><a href="#1-退職の理由" class="headerlink" title="1.退職の理由"></a>1.退職の理由</h2><p>まず前提として、<strong>エンジニアはその会社をできる限り続けたい</strong>と考えています。<br>なぜなら、転職にはリスクを伴うからです。新しい組織に移動すると今までの人間関係がリセットされ、ゼロから関係を築いていく必要があります。そういった苦労はできれば避けたいと考えています。</p><p>それなのに、なぜやめてしまうのでしょうか。給料が安い、残業が多い、技術的な学びが少ない、福利厚生が乏しい。どれもそれらしい理由に見えますが、実はこういった問題は本質的な問題ではありません。</p><p>エンジニアが退職する根本的な理由は、<strong>その会社や組織がどうにもならない</strong>と判断したからです。多くのエンジニアは退職以外に現状を改善する手段がなくなったため、退職しているのです。</p><h2 id="2-静かな危険信号"><a href="#2-静かな危険信号" class="headerlink" title="2.静かな危険信号"></a>2.静かな危険信号</h2><p>以前は意見を言っていたのに、最近静かになったと感じる人はいませんか？<strong>最も退職のリスクが高い人は最近静かになった人</strong>です。</p><p>文句・反論・衝突は「改善の余地がある」というサインです。声を上げている人は、まだ組織に期待をしている状態です。その期待がなくなった時、不満を表に出さなくなります。</p><h3 id="沈黙は判断の結果"><a href="#沈黙は判断の結果" class="headerlink" title="沈黙は判断の結果"></a>沈黙は判断の結果</h3><p>エンジニアが不満を表に出さない理由は、単に我慢しているからではありません。声を上げるコストと、それによって得られる利益を天秤にかけた結果、声を上げることをやめているのです。黙るのは「合理的撤退判断」です。</p><p>論理的思考の強いエンジニアは、感情的な反応ではなく、冷静な判断に基づいて行動します。組織が変わる可能性が低いと判断した時点で、声を上げることをやめ、次のステップを考え始めます。</p><h2 id="3-なぜ組織は「沈黙」を危険信号として認識できないのか"><a href="#3-なぜ組織は「沈黙」を危険信号として認識できないのか" class="headerlink" title="3.なぜ組織は「沈黙」を危険信号として認識できないのか"></a>3.なぜ組織は「沈黙」を危険信号として認識できないのか</h2><p>このように「沈黙」は危険信号ですが、多くの組織は危険信号として認識することができていません。これは沈黙を計測するのが難しいという構造上の理由からです。</p><h3 id="表面上は何も問題が起きていないように見える"><a href="#表面上は何も問題が起きていないように見える" class="headerlink" title="表面上は何も問題が起きていないように見える"></a>表面上は何も問題が起きていないように見える</h3><p>納期は守られています。いつもと変わらず仕事は進んでいるように見えます。表面上は何も問題が起きていないように見えるため、組織は沈黙を危険信号として認識できません。</p><h3 id="マネジメントが見ている指標がズレている"><a href="#マネジメントが見ている指標がズレている" class="headerlink" title="マネジメントが見ている指標がズレている"></a>マネジメントが見ている指標がズレている</h3><p>マネジメントは数値化できる指標、例えば、アウトプット量・稼働率を見ています。意思決定の質については計測することが難しく、現場レベルでの感覚でしかわからないことも多いです。</p><p>このズレが、沈黙を見逃す原因となっています。沈黙はKPIに現れません。エンジニアがやめる時、組織は初めて問題に気づくのです。</p><h2 id="4-問題は「意思決定プロセス」"><a href="#4-問題は「意思決定プロセス」" class="headerlink" title="4.問題は「意思決定プロセス」"></a>4.問題は「意思決定プロセス」</h2><p>声を上げることをやめる原因の多くは、「意思決定プロセス」にあります。<strong>反対意見を言うことにリスクがあったり意味を持たない状態が続くと、エンジニアは意見を言うことをやめます</strong>。例えば、</p><ul><li>反対意見を言ってもその場で突っぱねられる</li><li>意見の違いに寛容ではない（意見を言うこと自体がリスクになる）</li><li>その場では検討しますと言われるが、実際は検討されない</li></ul><p>意見 → 何も変わらない、の繰り返しが続くと、発言コストだけが蓄積していきます。この状態が続くと、エンジニアは「この組織では言っても無駄だ」と判断し、意見を言うことをやめます。</p><p>以下は私が実際に観察した問題のある意思決定プロセスです。</p><h3 id="ファシリテーターが意見を評価する"><a href="#ファシリテーターが意見を評価する" class="headerlink" title="ファシリテーターが意見を評価する"></a>ファシリテーターが意見を評価する</h3><p>ある開発チームの会議で、開発チームのリーダーが会議をファシリテーションしたのですが、そのファシリテーションに問題がありました。</p><p>開発チームのリーダーはメンバーが意見を言ったその場でその意見を評価し、その内容からはそれはちょっと違うというようなニュアンスが感じられました。そのような反応をされてしまうと、メンバーは正直に自分の意見を言うことが難しくなります。</p><p>ファシリテーターは参加者の発言を促し、会議を前に進めることが目的です。ファシリテーター自身は意見を評価するべきではありません。ファシリテーターが意見を評価すると、ファシリテーターの顔色を伺いながらメンバーが発言をすることになり、意見を言うことを躊躇したり、意見を忖度することになるからです。</p><p>このように誰かの顔色を伺いながら発言しないといけない状態は、意思決定プロセスに問題があると言えるでしょう。</p><h3 id="意見の違いに寛容ではない（意見を言うこと自体がリスクになる）"><a href="#意見の違いに寛容ではない（意見を言うこと自体がリスクになる）" class="headerlink" title="意見の違いに寛容ではない（意見を言うこと自体がリスクになる）"></a>意見の違いに寛容ではない（意見を言うこと自体がリスクになる）</h3><p>意見の違いに寛容でないことは長期的にリスクがあります。短期的には意思決定の速度が上がりますが、メンバーが考えることをやめてしまい、長期的には組織としての意思決定力が弱くなってしまうからです。</p><p>このような事例がありました。</p><p>ある会議で開発者の一人がシステム的な問題について意見を述べているところ、リーダーがその話を遮り、問題は別にあるということを話し始めました。話を遮られたメンバーは自分の意見が尊重されていないと感じるでしょう。</p><p><strong>リーダーとメンバーのような構造はリーダーの意見が構造上強くなります</strong>。リーダーがメンバーの人事権を持っていることが多いためです。<br><strong>リーダーは組織の構造的な力学を理解し、幅広い意見を収集するという意識を持つ必要があります</strong>。しかし、そのような視点を持っているリーダーは多くありません。特にプレイヤーとして優秀だった人がリーダーになった場合は、自分の意見が正しいという意識が強い傾向にあります。</p><p>本来は様々な観点から問題を分析する必要がありますが、誰かと異なる観点で発言をした時に話を遮られてしまうような状態は、意思決定プロセスに問題があると言えるでしょう。</p><h2 id="5-沈黙の代償は、想像よりも高い"><a href="#5-沈黙の代償は、想像よりも高い" class="headerlink" title="5.沈黙の代償は、想像よりも高い"></a>5.沈黙の代償は、想像よりも高い</h2><p>意思決定プロセスのまずさは次のような問題を引き起こします。</p><h3 id="エンジニアの退職"><a href="#エンジニアの退職" class="headerlink" title="エンジニアの退職"></a>エンジニアの退職</h3><p>黙っている人はある日突然やめていきます。さらに悪いことに優秀なエンジニアからやめていきます。なぜなら、エンジニアは売り手市場であり、優秀なエンジニアであればよい条件で次のポストを見つけることができるからです。<br>優秀なエンジニアは「代替が効きにくい人」でもあります。特に</p><ul><li>ドメイン知識</li><li>設計思想</li><li>暗黙知</li></ul><p>これらを持っている人がやめると、組織は大きな損失を被ります。</p><h3 id="意思決定の負債の蓄積"><a href="#意思決定の負債の蓄積" class="headerlink" title="意思決定の負債の蓄積"></a>意思決定の負債の蓄積</h3><p>技術負債という言葉がありますが、<strong>技術負債が発生する根本原因には意思決定の負債</strong>があります。ソフトウェアは設計思想がなくなると、途端に乱れます。設計思想は暗黙知として共有されていることが多く、設計思想の継承がされない状態でエンジニアがやめてしまうと次のような問題が顕在化します。</p><ul><li>なぜその設計だったのか分からない</li><li>誰も判断できず、決定が遅くなる</li><li>場当たり的なコードが増え、開発速度や品質が低下する</li></ul><p><strong>意思決定の負債が発生</strong>（根本原因）→<strong>場当たり的なコードで対応</strong>（原因から引き起こされる結果）→<strong>場当たり的なコードが蓄積した結果、技術負債が発生</strong>（表面に見える現象）というのが技術負債の構造です。</p><h3 id="沈黙が組織文化に与える長期的な影響"><a href="#沈黙が組織文化に与える長期的な影響" class="headerlink" title="沈黙が組織文化に与える長期的な影響"></a>沈黙が組織文化に与える長期的な影響</h3><p>沈黙は個人の問題ではなく、組織文化の問題として広がっていきます。優秀なエンジニアが黙り始めると、その影響は組織全体に波及します。</p><p>残ったエンジニアは「意見を言っても無駄だ」という文化を学習し、自分も意見を言わなくなる傾向があります。このように、<strong>沈黙は組織文化として定着し、長期的に組織の意思決定力を弱めていきます</strong>。</p><p>また、心理的安全性が低下することで、エンジニアはリスクを取らなくなり、イノベーションが生まれにくくなります。<strong>組織は現状維持に留まり、競争力が低下していくことになります</strong>。</p><h2 id="6-経営者・事業責任者・リーダーが取るべき視点"><a href="#6-経営者・事業責任者・リーダーが取るべき視点" class="headerlink" title="6.経営者・事業責任者・リーダーが取るべき視点"></a>6.経営者・事業責任者・リーダーが取るべき視点</h2><p>まず、<strong>意思決定のプロセスのまずさがエンジニアの退職を引き起こしていると認識すること</strong>が第１歩です。<br>意思決定のプロセスについてはリーダーの影響が大きいため、現場のエンジニアレベルでの改善は難しいということを理解する必要があります。</p><p>意思決定のプロセスは一見すると人の問題に見えますが、「<strong>人の問題ではなく、構造の問題</strong>」です。<strong>本質的な問題は意思決定のプロセスが整備されていないところにあります</strong>。構造の問題は水が高いところから低いところに流れるように、再現性を持って繰り返されます。そのため、新しく人を入れても数年で離職ということを繰り返すことになります。</p><h3 id="沈黙を生まない組織設計が必要"><a href="#沈黙を生まない組織設計が必要" class="headerlink" title="沈黙を生まない組織設計が必要"></a>沈黙を生まない組織設計が必要</h3><p>意思決定のプロセスの成否は仕組みとマネジメント層の適切な運用にかかっています。意見を言いやすい環境づくりをすることで、エンジニアが意見を言い続けられる環境を作る必要があります。</p><h3 id="意見を言いやすい環境づくり"><a href="#意見を言いやすい環境づくり" class="headerlink" title="意見を言いやすい環境づくり"></a>意見を言いやすい環境づくり</h3><p>意見を言いやすい環境を作るためには、以下のような取り組みが有効です。</p><ul><li><strong>意見を言うこと自体を評価する</strong>: 意見の内容が採用されなくても、意見を言ったこと自体を評価することで、意見を言いやすい環境を作ることができます</li><li><strong>意見の違いに寛容である</strong>: 意見の違いは組織の多様性であり、長期的な組織力の源泉です。意見の違いに寛容であることで、多様な意見が出やすくなります</li><li><strong>意見を言うリスクを下げる</strong>: 意見を言うことで不利益を被るリスクがあると、意見を言いにくくなります。意見を言うこと自体がリスクにならないような環境を作ることが重要です</li></ul><h3 id="定期的なフィードバックの仕組み"><a href="#定期的なフィードバックの仕組み" class="headerlink" title="定期的なフィードバックの仕組み"></a>定期的なフィードバックの仕組み</h3><p>意思決定プロセスが適切に機能しているかを定期的に確認する仕組みを作ることも重要です。例えば、定期的に「意見を言いやすい環境かどうか」をアンケートで確認したり、1on1で意見を聞いたりすることで、問題を早期に発見できます。</p><p>沈黙を生まない組織設計は、一度作れば終わりではありません。継続的に改善していく必要があります。組織の状況に応じて、適切な仕組みを調整していくことが重要です。</p><h2 id="おわりに"><a href="#おわりに" class="headerlink" title="おわりに"></a>おわりに</h2><p>「沈黙は同意を意味しない」という言葉があります。同意のない沈黙は組織の危険シグナルです。組織はそこで働く人が建設的な意見が言えるように意思決定プロセスを作り、適切に運用する必要があります。そうでなければ、優秀な人材を失い、組織の競争力が低下することになるでしょう。</p><p>この記事が、あなたの組織でも起きているかもしれない問題に気づくきっかけになれば幸いです。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;近年、プロダクト部門はビジネスの成果を左右する重要な部門となっており、その組織を構成するエンジニアの質がビジネスの成果に大きく影響するようになっています。一方でエンジニアの離職率の高さと採用の難しさが経営の問題となっている組織も少なくありません。&lt;/p&gt;
&lt;p&gt;この記事では</summary>
      
    
    
    
    <category term="ソフトウェア開発" scheme="https://48n.jp/categories/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA/"/>
    
    
  </entry>
  
  <entry>
    <title>2025年の振り返り｜フリーランス1年目に学んだ、判断・構造・距離感</title>
    <link href="https://48n.jp/blog/2025/12/25/2025-year-in-review/"/>
    <id>https://48n.jp/blog/2025/12/25/2025-year-in-review/</id>
    <published>2025-12-25T03:00:00.000Z</published>
    <updated>2026-02-25T00:43:41.847Z</updated>
    
    <content type="html"><![CDATA[<h2 id="はじめに"><a href="#はじめに" class="headerlink" title="はじめに"></a>はじめに</h2><p>2025年という1年が、静かに終わりへと向かっています。今年はフリーランスとして本格的に活動を始めた年であり、技術的にも個人的にも大きな変化があった1年でした。今年1年の技術面での学び、成長を感じた点を振り返り、少し先の未来に向けた展望を整理したいと思います。</p><h2 id="技術面での学び"><a href="#技術面での学び" class="headerlink" title="技術面での学び"></a>技術面での学び</h2><h3 id="Go-ReactによるマルチテナントSaaSの開発"><a href="#Go-ReactによるマルチテナントSaaSの開発" class="headerlink" title="Go &#x2F; ReactによるマルチテナントSaaSの開発"></a>Go &#x2F; ReactによるマルチテナントSaaSの開発</h3><p>2025年は開発に集中をした1年となりました。<br>ここ数年のキャリアはマネジメント中心であったり、開発をしつつチームビルディングを行うことが多かったのですが、マネジメント方面の仕事はほとんどやらず、開発や設計についてじっくり腰を据えて取り組むことができました。<br>技術スタックは、バックエンドがGo&#x2F;Node.js、フロントエンドがReactです。<br>マルチテナントSaaSの開発に携わることができ、<strong>マルチテナントSaaSの設計について開発を通して理解</strong>することができました。</p><h3 id="AIを使った開発"><a href="#AIを使った開発" class="headerlink" title="AIを使った開発"></a>AIを使った開発</h3><p>この1年で開発方法が大きく変わりました。従来の人間が全てを行う開発からAIを活用した開発がこの1年で大きく変わったポイントです。私の18年のキャリアの中で最も大きな変化だと感じています。<br>コーディングだけでなく、仕様の調査や設計、コードレビューなど多くの領域でAIを使うようになりました。<br><strong>AIによる開発は今までの人による開発と比べて開発速度、品質ともに向上する</strong>と断言できます。<br>一方で<strong>AIは万能ではなく、最終的な品質を決めるのは人間の判断である</strong>ことも同時に学びました。<br>こういったことを知識ではなく、現場で肌で感じれたことが貴重な経験となりました。</p><h2 id="「判断」「構造」「距離感」の学び"><a href="#「判断」「構造」「距離感」の学び" class="headerlink" title="「判断」「構造」「距離感」の学び"></a>「判断」「構造」「距離感」の学び</h2><p>技術的に収穫のあった1年となりましたが、特に成長として実感していることは技術そのものというより、仕事の進め方や思考の質が変わったことにあると考えています。特に「判断」「構造」「距離感」の３つのポイントについては言語化できるレベルで明確な学びがありました。</p><h3 id="判断｜状況に応じた判断と振る舞い"><a href="#判断｜状況に応じた判断と振る舞い" class="headerlink" title="判断｜状況に応じた判断と振る舞い"></a>判断｜状況に応じた判断と振る舞い</h3><p>以前は「自分で判断しなければならない」「自分が決めるべきだ」という思いが強く、時としてまわりとの衝突に繋がってしまう恐れがありました。</p><p>あらためて、いちプレイヤーとして働くようになり、それに伴い仕事での観察や内省を通して、</p><ul><li>判断に必要な情報を整理して提示する</li><li>メリット・デメリットやリスクを言語化する</li><li>最終的な判断は相手に委ねる</li></ul><p>という進め方のほうがスムーズにいくのではないかと考えました。<br><br><br>結果として、</p><ul><li>まわりとの摩擦が減る</li><li>エネルギーを無駄に消耗しない</li><li>決定者が明確になり意思決定の速度が上がる</li></ul><p>といった効果が生まれ、仕事がスムーズに進むようになったと感じています。<br>主体性がなくなったのではないかとの懸念を持たれるかもしれませんが、<strong>「判断を委ねること」に価値を置けるようになったということが本質的な変化であり、状況に応じて適切な判断や振る舞いができるようになった</strong>のではないかと思います。</p><h3 id="構造｜仕組みに目を向ける"><a href="#構造｜仕組みに目を向ける" class="headerlink" title="構造｜仕組みに目を向ける"></a>構造｜仕組みに目を向ける</h3><p>問題を「<strong>構造の問題なのか、個人の問題なのか</strong>」で切り分けて考えられるようになったことが以前と比べた大きな違いです。<br>以前は、現場で起きる違和感やトラブルに対して「能力や姿勢の問題ではないか」「自分が何とかすべきではないか」と考え、解決の難しい領域にエネルギーを消耗してしまうことがありました。</p><p>現在は構造に問題がないかという観点で捉えるようになりました。<br>具体的には次のように考えます。</p><ul><li>役割設計や責任範囲に歪みはないか</li><li>意思決定やレビューの仕組みに不備はないか</li><li>ルールや前提が適切に共有されているか</li></ul><br><p><strong>構造の問題に対しては、前提や仕組みを少し変えるだけで、再発防止や影響範囲の縮小といった効果的なアプローチが取れる</strong>ことを学びました。<br>一方で、個人の能力や性格、価値観に起因する問題については、それが自分の立場で解決できる領域なのかを判断し、<strong>自分が介入すべきでないものには介入しない</strong>と割り切れるようにもなりました。<br>このように問題の対象について識別することで、より効果的に時間とエネルギーを使えるようになったと思います。</p><h3 id="距離感｜適切な余白を保つ"><a href="#距離感｜適切な余白を保つ" class="headerlink" title="距離感｜適切な余白を保つ"></a>距離感｜適切な余白を保つ</h3><p>仕事との間に、適切な余白を置けるようになったことも、この1年での変化です。<br>距離感が近いと相手や組織への期待であったり、その期待とのギャップが生じます。<br>期待とのギャップからはネガティブな感情が生じやすく、不要にエネルギーを消耗してしまい、安定したパフォーマンスを発揮するのが難しくなってしまいます。<br><strong>安定したパフォーマンスを発揮するには、適度な距離感を持つほうがよい</strong>ことがわかってきました。</p><br>また、コードレビューへの向き合い方にも変化がありました。以前は、コードレビューで多くの指摘が入ると少なからず気持ちが揺れていましたが、こちらについても感情とは切り離して淡々と対応することができるようになりました。この変化はAIによるコーディングができるようになり、対応コストが下がったことも影響していると考えています。<br><br>この距離感が身についたことで、不要にエネルギーを消耗せず、安定して価値を出し続けられる感覚を持てるようになりました。<br><br>以上のような変化は正社員→フリーランスへの立場の変化とAIによる開発手法の変化が影響していると思っており、継続的にポジティブな効果をもたらすと考えています。<h2 id="これからについて"><a href="#これからについて" class="headerlink" title="これからについて"></a>これからについて</h2><p>この1年間を通して、自分の強みは「実装力」そのもの以上に、<strong>構造の問題を識別しそれをどう解決するかを考えられることにある</strong>と、よりはっきりしてきました。</p><p>なぜそれができるのかというと、<strong>18年間の現場経験の中で、さまざまなチーム・プロダクト・フェーズを見てきたことで、何が機能しやすく、何が形骸化しやすく、どこで現場が詰まりやすいのかを肌感覚として理解できている</strong>からだと考えています。<br>理論として正しいかどうかだけでなく、「この現場で本当に回るか」「いま導入すべきかどうか」といった観点で判断できることは、これまでの経験が積み重なった結果です。</p><p>こういった自分の強みがわかってきたため、今後はより包括的な技術判断が求められる仕事に携わっていきたいと考えています。<br>現場で手を動かしつつも、プロダクトやチームが前に進むための「判断」と「構造」に関わる役割を担うことで、より大きな価値を提供していければと思います。</p><h2 id="おわりに"><a href="#おわりに" class="headerlink" title="おわりに"></a>おわりに</h2><p>この1年間を振り返ると表面的な飛躍的成長というよりかは内面的に成長したと思える1年でした。技術・働き方・メンタルの一つひとつの学びが深く、実りのある1年だったように思います。</p><br>最後に少し抽象的な話しになりますが、今まではどちらかというと燃え盛る炎に象徴されるようなエネルギーや情熱に魅力を感じていましたが、そういったものは一時的で長く続くものではありません。もちろん、時にはそういったエネルギーも必要ですが、今は雪の荒野に静かに立つ常盤木のように静かで持続的な生命力に魅力を感じます。この常盤木のように静かに、歩みを止めず、持続的に成長していけたらと感じるこの頃です。<br><br>今回は1年の振り返りということで個人的な内容となってしまいましたが、同じようにソフトウェアエンジニアやフリーランスとしての働き方に興味がある方の参考になれば幸いです。]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;はじめに&quot;&gt;&lt;a href=&quot;#はじめに&quot; class=&quot;headerlink&quot; title=&quot;はじめに&quot;&gt;&lt;/a&gt;はじめに&lt;/h2&gt;&lt;p&gt;2025年という1年が、静かに終わりへと向かっています。今年はフリーランスとして本格的に活動を始めた年であり、技術的にも個人</summary>
      
    
    
    
    <category term="フリーランス" scheme="https://48n.jp/categories/%E3%83%95%E3%83%AA%E3%83%BC%E3%83%A9%E3%83%B3%E3%82%B9/"/>
    
    
  </entry>
  
  <entry>
    <title>TypeScriptで多言語システムの型チェックを実装する</title>
    <link href="https://48n.jp/blog/2025/11/28/translation-type-check-mechanism/"/>
    <id>https://48n.jp/blog/2025/11/28/translation-type-check-mechanism/</id>
    <published>2025-11-28T09:07:53.000Z</published>
    <updated>2026-02-25T00:43:41.854Z</updated>
    
    <content type="html"><![CDATA[<h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p>多言語対応は一般的に翻訳関数に翻訳キーを渡すことで実装されます。その際に問題になるのが、キー名の間違い、キー名の変更漏れなどによる実装不備です。このようなエラーを人の目で全て確認するのは難しいため、システムによる検知が有効です。</p><p>この記事では、TypeScriptの型システムを活用した、型チェックの方法を説明します。これにより、正しい翻訳キーが割り当てられていることがコンパイル時にチェックされ、アプリケーションの品質向上に大きな効果が期待できます。</p><h2 id="多言語システムの実装"><a href="#多言語システムの実装" class="headerlink" title="多言語システムの実装"></a>多言語システムの実装</h2><h3 id="1-ベースとなる型の定義"><a href="#1-ベースとなる型の定義" class="headerlink" title="1. ベースとなる型の定義"></a>1. ベースとなる型の定義</h3><p>翻訳の型チェックは、日本語のメッセージファイル（<code>ja.ts</code>）をベースとして構築します。翻訳ファイルはJSONファイルで扱われることもありますが、型チェックという観点ではtsファイルで管理するのがおすすめです。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; jaMessage &#125; <span class="keyword">from</span> <span class="string">&#x27;./ja&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> <span class="title class_">JaMessage</span> = <span class="keyword">typeof</span> jaMessage</span><br></pre></td></tr></table></figure><h4 id="TypeScriptの機能：typeof演算子"><a href="#TypeScriptの機能：typeof演算子" class="headerlink" title="TypeScriptの機能：typeof演算子"></a>TypeScriptの機能：<code>typeof</code>演算子</h4><p><code>typeof</code>演算子は、値から型を抽出するTypeScriptの機能です。<code>typeof jaMessage</code>により、<code>jaMessage</code>オブジェクトの構造から型を自動的に生成します。これにより、<code>jaMessage</code>の構造が変更されると、型定義も自動的に更新されます。</p><h4 id="ja-tsのサンプルコード"><a href="#ja-tsのサンプルコード" class="headerlink" title="ja.tsのサンプルコード"></a><code>ja.ts</code>のサンプルコード</h4><p>以下は、日本語メッセージファイル（<code>ja.ts</code>）のサンプルです。このファイルが型定義のベースとなります。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> jaMessage = &#123;</span><br><span class="line">  <span class="attr">term</span>: &#123;</span><br><span class="line">    <span class="attr">next</span>: <span class="string">&#x27;次へ&#x27;</span>,</span><br><span class="line">    <span class="attr">back</span>: <span class="string">&#x27;戻る&#x27;</span>,</span><br><span class="line">    <span class="attr">cancel</span>: <span class="string">&#x27;キャンセル&#x27;</span>,</span><br><span class="line">    <span class="attr">submit</span>: <span class="string">&#x27;送信&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">pages</span>: &#123;</span><br><span class="line">    <span class="attr">home</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;ホーム&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">about</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;紹介&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">contact</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;お問い合わせ&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">terms</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;利用規約&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125; <span class="keyword">as</span> <span class="keyword">const</span></span><br></pre></td></tr></table></figure><p>このファイルでは、<code>as const</code>を使用することで、値がリテラル型として推論されます。これにより、型の精度が向上し、より厳密な型チェックが可能になります。</p><h4 id="as-constとは"><a href="#as-constとは" class="headerlink" title="as constとは"></a>as constとは</h4><p><code>as const</code>は、TypeScriptの「constアサーション」という機能です。オブジェクトや配列に<code>as const</code>を付けることで、以下の効果が得られます。</p><ol><li><strong>リテラル型として推論される</strong>: 値が具体的なリテラル型（例：<code>&#39;マイページ&#39;</code>）として推論されます</li><li><strong>読み取り専用になる</strong>: オブジェクトのプロパティが<code>readonly</code>になります</li><li><strong>型の精度が向上する</strong>: より具体的な型情報が保持されます</li></ol><h4 id="as-constがない場合"><a href="#as-constがない場合" class="headerlink" title="as constがない場合"></a>as constがない場合</h4><p><code>as const</code>がない場合、TypeScriptは値の型を一般的な型として推論します。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// as const がない場合</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> jaMessage = &#123;</span><br><span class="line">  <span class="attr">term</span>: &#123;</span><br><span class="line">    <span class="attr">next</span>: <span class="string">&#x27;次へ&#x27;</span>,</span><br><span class="line">    <span class="attr">back</span>: <span class="string">&#x27;戻る&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 推論される型</span></span><br><span class="line"><span class="comment">// &#123;</span></span><br><span class="line"><span class="comment">//   term: &#123;</span></span><br><span class="line"><span class="comment">//     next: string;  // 一般的な string 型</span></span><br><span class="line"><span class="comment">//     back: string;  // 一般的な string 型</span></span><br><span class="line"><span class="comment">//   &#125;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><p>この場合、<code>typeof jaMessage</code>で取得できる型は、値が<code>string</code>型として推論されます。</p><h4 id="as-constがある場合"><a href="#as-constがある場合" class="headerlink" title="as constがある場合"></a><code>as const</code>がある場合</h4><p><code>as const</code>がある場合、TypeScriptは値の型をリテラル型として推論します。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// as const がある場合</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> jaMessage = &#123;</span><br><span class="line">  <span class="attr">term</span>: &#123;</span><br><span class="line">    <span class="attr">next</span>: <span class="string">&#x27;次へ&#x27;</span>,</span><br><span class="line">    <span class="attr">back</span>: <span class="string">&#x27;戻る&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125; <span class="keyword">as</span> <span class="keyword">const</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 推論される型</span></span><br><span class="line"><span class="comment">// &#123;</span></span><br><span class="line"><span class="comment">//   readonly term: &#123;</span></span><br><span class="line"><span class="comment">//     readonly next: &#x27;次へ&#x27;;         // リテラル型</span></span><br><span class="line"><span class="comment">//     readonly back: &#x27;戻る&#x27;;         // リテラル型</span></span><br><span class="line"><span class="comment">//   &#125;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><p>この場合、<code>typeof jaMessage</code>で取得できる型は、値が具体的なリテラル型（<code>&#39;次へ&#39;</code>、<code>&#39;戻る&#39;</code>）として推論されます。</p><h4 id="なぜas-constが必要なのか"><a href="#なぜas-constが必要なのか" class="headerlink" title="なぜas constが必要なのか"></a>なぜ<code>as const</code>が必要なのか</h4><p>翻訳メッセージの型チェックにおいて、<code>as const</code>は以下の理由で重要です。</p><ol><li><strong>型の一貫性</strong>: <code>typeof jaMessage</code>で取得した型が、実際の値の構造を正確に反映します</li><li><strong>型の精度</strong>: <code>GenericType</code>が適用される際、元の構造が正確に保持されます</li><li><strong>読み取り専用の保証</strong>: 翻訳メッセージが誤って変更されることを防ぎます</li></ol><h4 id="実際の違い"><a href="#実際の違い" class="headerlink" title="実際の違い"></a>実際の違い</h4><p><code>as const</code>の有無による型の違いを確認してみましょう。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// as const がない場合</span></span><br><span class="line"><span class="keyword">const</span> message1 = &#123; <span class="attr">term</span>: &#123; <span class="attr">next</span>: <span class="string">&#x27;次へ&#x27;</span> &#125; &#125;</span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Type1</span> = <span class="keyword">typeof</span> message1</span><br><span class="line"><span class="comment">// Type1 = &#123; term: &#123; next: string &#125; &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// as const がある場合</span></span><br><span class="line"><span class="keyword">const</span> message2 = &#123; <span class="attr">term</span>: &#123; <span class="attr">next</span>: <span class="string">&#x27;次へ&#x27;</span> &#125; &#125; <span class="keyword">as</span> <span class="keyword">const</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Type2</span> = <span class="keyword">typeof</span> message2</span><br><span class="line"><span class="comment">// Type2 = &#123; readonly term: &#123; readonly next: &#x27;次へ&#x27; &#125; &#125;</span></span><br></pre></td></tr></table></figure><p><code>as const</code>がある場合、<code>next</code>の型は<code>&#39;次へ&#39;</code>という具体的なリテラル型になります。これにより、型システムがより正確に動作し、翻訳キーの型チェックがより厳密になります。</p><h3 id="2-再帰的な型変換"><a href="#2-再帰的な型変換" class="headerlink" title="2. 再帰的な型変換"></a>2. 再帰的な型変換</h3><p>翻訳メッセージはネストされたオブジェクト構造を持っています。この構造を型として表現するために、<code>GenericType</code>というユーティリティ型を使用しています。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">GenericType</span>&lt;T <span class="keyword">extends</span> <span class="built_in">object</span>&gt; = &#123;</span><br><span class="line">  [K <span class="keyword">in</span> keyof T]: T[K] <span class="keyword">extends</span> <span class="built_in">object</span> ? <span class="title class_">GenericType</span>&lt;T[K]&gt; : <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> I18nMessage = <span class="title class_">GenericType</span>&lt;<span class="title class_">JaMessage</span>&gt;</span><br></pre></td></tr></table></figure><h4 id="TypeScriptの機能：条件型（Conditional-Types）と再帰的な型"><a href="#TypeScriptの機能：条件型（Conditional-Types）と再帰的な型" class="headerlink" title="TypeScriptの機能：条件型（Conditional Types）と再帰的な型"></a>TypeScriptの機能：条件型（Conditional Types）と再帰的な型</h4><ul><li><p><code>[K in keyof T]</code>: マップ型（Mapped Types）により、オブジェクトの各キーを反復処理します</p></li><li><p><code>T[K] extends object ? GenericType&lt;T[K]&gt; : string</code>: 条件型により、値がオブジェクトの場合は再帰的に<code>GenericType</code>を適用し、そうでない場合は<code>string</code>型とします</p></li></ul><p>これにより、以下のような構造が正しく型付けされます。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">term</span>: &#123;</span><br><span class="line">    <span class="attr">next</span>: <span class="built_in">string</span>  <span class="comment">// 文字列型</span></span><br><span class="line">    <span class="attr">back</span>: <span class="built_in">string</span></span><br><span class="line">    <span class="attr">cancel</span>: <span class="built_in">string</span></span><br><span class="line">    <span class="attr">submit</span>: <span class="built_in">string</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">pages</span>: &#123;</span><br><span class="line">    <span class="attr">home</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="built_in">string</span>  <span class="comment">// 3段階のネストも正しく型付けされる</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">about</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="built_in">string</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">contact</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="built_in">string</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">terms</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="built_in">string</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>GenericType</code>は再帰的に動作するため、何段階のネストでも正しく型付けされます。</p><h3 id="3-各言語ファイルでの型チェック"><a href="#3-各言語ファイルでの型チェック" class="headerlink" title="3. 各言語ファイルでの型チェック"></a>3. 各言語ファイルでの型チェック</h3><p>各言語ファイルでは、<code>I18nMessage</code>型を明示的に指定することで、型チェックが行われます。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; I18nMessage &#125; <span class="keyword">from</span> <span class="string">&#x27;./types&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="attr">enMessage</span>: I18nMessage = &#123;</span><br><span class="line">  <span class="attr">term</span>: &#123;</span><br><span class="line">    <span class="attr">next</span>: <span class="string">&#x27;Next&#x27;</span>,</span><br><span class="line">    <span class="attr">back</span>: <span class="string">&#x27;Back&#x27;</span>,</span><br><span class="line">    <span class="attr">cancel</span>: <span class="string">&#x27;Cancel&#x27;</span>,</span><br><span class="line">    <span class="attr">submit</span>: <span class="string">&#x27;Submit&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">pages</span>: &#123;</span><br><span class="line">    <span class="attr">home</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;Home&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">about</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;About&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">contact</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;Contact&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">terms</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;Terms&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>型チェックの効果</strong></p><ul><li><p>必須キーが欠落している場合、コンパイルエラーが発生します</p></li><li><p>存在しないキーを追加しようとすると、エラーが発生します</p></li><li><p>値の型が<code>string</code>でない場合、エラーが発生します</p></li><li><p>キーのタイポがある場合、エラーが発生します</p></li></ul><h3 id="4-翻訳関数の型安全な実装"><a href="#4-翻訳関数の型安全な実装" class="headerlink" title="4. 翻訳関数の型安全な実装"></a>4. 翻訳関数の型安全な実装</h3><p>翻訳関数はライブラリを利用してもよいですが、シンプルな翻訳システムであれば自前で実装するのもよいと思います。ここでは、必要最低限の翻訳関数のサンプルを紹介します。</p><p>翻訳関数についても型安全を担保する必要があります。型システムを実装するために、<code>Paths</code>型と<code>PathValue</code>型を使用します。</p><h4 id="キーパスの型生成"><a href="#キーパスの型生成" class="headerlink" title="キーパスの型生成"></a>キーパスの型生成</h4><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">PathsInternal</span>&lt;T&gt; = T <span class="keyword">extends</span> <span class="built_in">string</span></span><br><span class="line">  ? <span class="built_in">never</span></span><br><span class="line">  : T <span class="keyword">extends</span> <span class="built_in">object</span></span><br><span class="line">  ? &#123;</span><br><span class="line">      [K <span class="keyword">in</span> keyof T]: K <span class="keyword">extends</span> <span class="built_in">string</span></span><br><span class="line">        ? T[K] <span class="keyword">extends</span> <span class="built_in">object</span></span><br><span class="line">          ? T[K] <span class="keyword">extends</span> <span class="built_in">string</span></span><br><span class="line">            ? K</span><br><span class="line">            : <span class="string">`<span class="subst">$&#123;K&#125;</span>.<span class="subst">$&#123;PathsInternal&lt;T[K]&gt;&#125;</span>`</span></span><br><span class="line">          : K</span><br><span class="line">        : <span class="built_in">never</span></span><br><span class="line">    &#125;[keyof T]</span><br><span class="line">  : <span class="built_in">never</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> <span class="title class_">Paths</span> = <span class="title class_">PathsInternal</span>&lt;<span class="title class_">JaMessage</span>&gt;</span><br></pre></td></tr></table></figure><p>この型により、<code>&#39;term.next&#39;</code>、<code>&#39;pages.home.title&#39;</code>のような文字列リテラル型が自動生成されます。</p><h4 id="キーパスから値を取得する型"><a href="#キーパスから値を取得する型" class="headerlink" title="キーパスから値を取得する型"></a>キーパスから値を取得する型</h4><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> <span class="title class_">PathValue</span>&lt;T, P <span class="keyword">extends</span> <span class="built_in">string</span>&gt; = P <span class="keyword">extends</span> <span class="string">`<span class="subst">$&#123;infer Key&#125;</span>.<span class="subst">$&#123;infer Rest&#125;</span>`</span></span><br><span class="line">  ? <span class="title class_">Key</span> <span class="keyword">extends</span> keyof T</span><br><span class="line">    ? T[<span class="title class_">Key</span>] <span class="keyword">extends</span> <span class="built_in">object</span></span><br><span class="line">      ? <span class="title class_">PathValue</span>&lt;T[<span class="title class_">Key</span>], <span class="title class_">Rest</span>&gt;</span><br><span class="line">      : <span class="built_in">never</span></span><br><span class="line">    : <span class="built_in">never</span></span><br><span class="line">  : P <span class="keyword">extends</span> keyof T</span><br><span class="line">  ? T[P] <span class="keyword">extends</span> <span class="built_in">string</span></span><br><span class="line">    ? T[P]</span><br><span class="line">    : <span class="built_in">never</span></span><br><span class="line">  : <span class="built_in">never</span></span><br></pre></td></tr></table></figure><p>この型により、キーパスから対応する文字列型を取得できます。</p><h4 id="翻訳関数の実装"><a href="#翻訳関数の実装" class="headerlink" title="翻訳関数の実装"></a>翻訳関数の実装</h4><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; I18nMessage, <span class="title class_">Paths</span>, <span class="title class_">PathValue</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./types&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> I18nFunction = &lt;P <span class="keyword">extends</span> <span class="title class_">Paths</span>&gt;<span class="function">(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">  <span class="attr">key</span>: P</span></span></span><br><span class="line"><span class="params"><span class="function"></span>) =&gt;</span> <span class="title class_">PathValue</span>&lt;I18nMessage, P&gt;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">createI18n</span>(<span class="params"><span class="attr">message</span>: I18nMessage</span>): I18nFunction &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span> &lt;P <span class="keyword">extends</span> <span class="title class_">Paths</span>&gt;(<span class="attr">key</span>: P &amp; <span class="built_in">string</span>): <span class="title class_">PathValue</span>&lt;I18nMessage, P&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> keys = (key <span class="keyword">as</span> <span class="built_in">string</span>).<span class="title function_">split</span>(<span class="string">&#x27;.&#x27;</span>)</span><br><span class="line">    <span class="keyword">let</span> <span class="attr">value</span>: <span class="built_in">any</span> = message</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> k <span class="keyword">of</span> keys) &#123;</span><br><span class="line">      <span class="keyword">if</span> (value &amp;&amp; <span class="keyword">typeof</span> value === <span class="string">&#x27;object&#x27;</span> &amp;&amp; k <span class="keyword">in</span> value) &#123;</span><br><span class="line">        value = value[k <span class="keyword">as</span> keyof <span class="keyword">typeof</span> value]</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`Translation key &quot;<span class="subst">$&#123;key&#125;</span>&quot; not found`</span>)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> value !== <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`Translation key &quot;<span class="subst">$&#123;key&#125;</span>&quot; does not point to a string value`</span>)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> value <span class="keyword">as</span> <span class="title class_">PathValue</span>&lt;I18nMessage, P&gt;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="使用例"><a href="#使用例" class="headerlink" title="使用例"></a>使用例</h2><p>翻訳関数の使用例です。</p><h3 id="翻訳関数の使用"><a href="#翻訳関数の使用" class="headerlink" title="翻訳関数の使用"></a>翻訳関数の使用</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createI18n &#125; <span class="keyword">from</span> <span class="string">&#x27;./i18n&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; jaMessage &#125; <span class="keyword">from</span> <span class="string">&#x27;./ja&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; enMessage &#125; <span class="keyword">from</span> <span class="string">&#x27;./en&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 日本語の翻訳関数を作成</span></span><br><span class="line"><span class="keyword">const</span> tJa = <span class="title function_">createI18n</span>(jaMessage)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 英語の翻訳関数を作成</span></span><br><span class="line"><span class="keyword">const</span> tEn = <span class="title function_">createI18n</span>(enMessage)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用例</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">tJa</span>(<span class="string">&#x27;term.next&#x27;</span>)) <span class="comment">// &#x27;次へ&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">tJa</span>(<span class="string">&#x27;pages.home.title&#x27;</span>)) <span class="comment">// &#x27;ホーム&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">tJa</span>(<span class="string">&#x27;pages.about.title&#x27;</span>)) <span class="comment">// &#x27;紹介&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">tJa</span>(<span class="string">&#x27;pages.contact.title&#x27;</span>)) <span class="comment">// &#x27;お問い合わせ&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">tJa</span>(<span class="string">&#x27;pages.terms.title&#x27;</span>)) <span class="comment">// &#x27;利用規約&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">tEn</span>(<span class="string">&#x27;term.next&#x27;</span>)) <span class="comment">// &#x27;Next&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">tEn</span>(<span class="string">&#x27;pages.home.title&#x27;</span>)) <span class="comment">// &#x27;Home&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">tEn</span>(<span class="string">&#x27;pages.about.title&#x27;</span>)) <span class="comment">// &#x27;About&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">tEn</span>(<span class="string">&#x27;pages.contact.title&#x27;</span>)) <span class="comment">// &#x27;Contact&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">tEn</span>(<span class="string">&#x27;pages.terms.title&#x27;</span>)) <span class="comment">// &#x27;Terms&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="正しい使用例"><a href="#正しい使用例" class="headerlink" title="正しい使用例"></a>正しい使用例</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="attr">enMessage</span>: I18nMessage = &#123;</span><br><span class="line">  <span class="attr">term</span>: &#123;</span><br><span class="line">    <span class="attr">next</span>: <span class="string">&#x27;Next&#x27;</span>, <span class="comment">// ✅ 正しい</span></span><br><span class="line">    <span class="attr">back</span>: <span class="string">&#x27;Back&#x27;</span>,</span><br><span class="line">    <span class="attr">cancel</span>: <span class="string">&#x27;Cancel&#x27;</span>,</span><br><span class="line">    <span class="attr">submit</span>: <span class="string">&#x27;Submit&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">pages</span>: &#123;</span><br><span class="line">    <span class="attr">home</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;Home&#x27;</span>,  <span class="comment">// ✅ 3段階のネストも正しい</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">about</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;About&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">contact</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;Contact&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">terms</span>: &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;Terms&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="エラーが発生する例"><a href="#エラーが発生する例" class="headerlink" title="エラーが発生する例"></a>エラーが発生する例</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="attr">enMessage</span>: I18nMessage = &#123;</span><br><span class="line">  <span class="attr">term</span>: &#123;</span><br><span class="line">    <span class="attr">next</span>: <span class="string">&#x27;Next&#x27;</span>,</span><br><span class="line">    <span class="comment">// ❌ エラー: &#x27;back&#x27; プロパティが必須だが欠落している</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ❌ エラー: &#x27;unknownKey&#x27; は存在しないキー</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="attr">enMessage</span>: I18nMessage = &#123;</span><br><span class="line">  <span class="attr">term</span>: &#123;</span><br><span class="line">    <span class="attr">next</span>: <span class="string">&#x27;Next&#x27;</span>,</span><br><span class="line">    <span class="attr">unknownKey</span>: <span class="string">&#x27;Value&#x27;</span>,  <span class="comment">// エラー</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ❌ エラー: 翻訳関数で存在しないキーを指定</span></span><br><span class="line"><span class="keyword">const</span> t = <span class="title function_">createI18n</span>(enMessage)</span><br><span class="line"><span class="title function_">t</span>(<span class="string">&#x27;term.invalidKey&#x27;</span>)      <span class="comment">// コンパイルエラー</span></span><br><span class="line"><span class="title function_">t</span>(<span class="string">&#x27;pages.home.invalid&#x27;</span>)   <span class="comment">// コンパイルエラー</span></span><br><span class="line"><span class="title function_">t</span>(<span class="string">&#x27;invalid.path&#x27;</span>)         <span class="comment">// コンパイルエラー</span></span><br><span class="line"><span class="title function_">t</span>(<span class="string">&#x27;pages.invalid.title&#x27;</span>)  <span class="comment">// コンパイルエラー</span></span><br></pre></td></tr></table></figure><h2 id="メリット"><a href="#メリット" class="headerlink" title="メリット"></a>メリット</h2><ol><li><p><strong>コンパイル時の安全性</strong>: 翻訳キーのエラーを実行前に検出できます</p></li><li><p><strong>自動補完</strong>: IDEが翻訳キーを自動補完してくれます</p></li><li><p><strong>リファクタリングの安全性</strong>: キー名を変更する際、使用箇所を自動的に検出できます</p></li><li><p><strong>ドキュメントとしての役割</strong>: 型定義自体が、利用可能な翻訳キーのドキュメントとなります</p></li><li><p><strong>翻訳関数の型安全</strong>: 翻訳関数を使用する際も、存在しないキーを指定するとコンパイルエラーが発生します</p></li></ol><h2 id="サンプルコード"><a href="#サンプルコード" class="headerlink" title="サンプルコード"></a>サンプルコード</h2><p>サンプルコードは <a href="https://github.com/shoyan/i18n-sample">https://github.com/shoyan/i18n-sample</a> で公開しています。</p>]]></content>
    
    
    <summary type="html">多言語対応におけるTypeScriptの型チェックの仕組みについて説明します。翻訳キーのタイポや必須キーの欠落をコンパイル時に防ぐ方法を解説します。</summary>
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
    <category term="TypeScript" scheme="https://48n.jp/tags/TypeScript/"/>
    
  </entry>
  
  <entry>
    <title>Goのインターフェース実装チェックイディオムを理解する</title>
    <link href="https://48n.jp/blog/2025/09/17/go-interface-implementation-check/"/>
    <id>https://48n.jp/blog/2025/09/17/go-interface-implementation-check/</id>
    <published>2025-09-17T03:30:00.000Z</published>
    <updated>2026-02-25T00:43:41.849Z</updated>
    
    <content type="html"><![CDATA[<h2 id="はじめに"><a href="#はじめに" class="headerlink" title="はじめに"></a>はじめに</h2><p>Goを書いていると、以下のようなコードを見かけることがあります。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> _ Interface = (*Type)(<span class="literal">nil</span>)</span><br></pre></td></tr></table></figure><p>この一見奇妙なコードは、Goのインターフェース実装をコンパイル時にチェックするためのイディオムです。今回は、このイディオムの目的、必要性、そして適切な使用場面について詳しく解説します。</p><h2 id="このイディオムの目的"><a href="#このイディオムの目的" class="headerlink" title="このイディオムの目的"></a>このイディオムの目的</h2><h3 id="基本的な仕組み"><a href="#基本的な仕組み" class="headerlink" title="基本的な仕組み"></a>基本的な仕組み</h3><p>もう少し具体的なコードで説明してみます。以下のコードは、Counterインターフェースを実装していることをチェックするイディオムです。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> _ Counter = (*CountRepository)(<span class="literal">nil</span>)</span><br></pre></td></tr></table></figure><p>このコードは以下の要素で構成されています：</p><ul><li><code>var _</code> : 空白識別子（blank identifier）を使用した変数宣言</li><li><code>Counter</code> : チェック対象のインターフェース</li><li><code>(*CountRepository)(nil)</code> : チェック対象の型（nilポインタ）</li></ul><h3 id="何をしているのか"><a href="#何をしているのか" class="headerlink" title="何をしているのか"></a>何をしているのか</h3><p>このイディオムは、<code>*CountRepository</code> 型が <code>Counter</code> インターフェースを実装しているかを<strong>コンパイル時に</strong>チェックします。</p><h2 id="実際の動作例"><a href="#実際の動作例" class="headerlink" title="実際の動作例"></a>実際の動作例</h2><h3 id="正常な場合"><a href="#正常な場合" class="headerlink" title="正常な場合"></a>正常な場合</h3><p>以下のコードは <code>Counter</code> インターフェースを実装したサンプルコードです。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;context&quot;</span></span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Counter <span class="keyword">interface</span> &#123;</span><br><span class="line">    Increment(ctx context.Context, key <span class="type">string</span>) (<span class="type">int</span>, <span class="type">error</span>)</span><br><span class="line">    Get(ctx context.Context, key <span class="type">string</span>) (<span class="type">int</span>, <span class="type">error</span>)</span><br><span class="line">    Reset(ctx context.Context, key <span class="type">string</span>) <span class="type">error</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> CountRepository <span class="keyword">struct</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// すべてのメソッドを実装</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(r *CountRepository)</span></span> Increment(ctx context.Context, key <span class="type">string</span>) (<span class="type">int</span>, <span class="type">error</span>) &#123;</span><br><span class="line">    <span class="comment">// 実装</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(r *CountRepository)</span></span> Get(ctx context.Context, key <span class="type">string</span>) (<span class="type">int</span>, <span class="type">error</span>) &#123;</span><br><span class="line">    <span class="comment">// 実装</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(r *CountRepository)</span></span> Reset(ctx context.Context, key <span class="type">string</span>) <span class="type">error</span> &#123;</span><br><span class="line">    <span class="comment">// 実装</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// インターフェース実装チェック</span></span><br><span class="line"><span class="keyword">var</span> _ Counter = (*CountRepository)(<span class="literal">nil</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="comment">// インターフェース実装チェックのサンプル</span></span><br><span class="line">    repo := &amp;CountRepository&#123;&#125;</span><br><span class="line">    ctx := context.Background()</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// メソッド呼び出し</span></span><br><span class="line">    count, _ := repo.Increment(ctx, <span class="string">&quot;test&quot;</span>)</span><br><span class="line">    fmt.Printf(<span class="string">&quot;Count: %d\n&quot;</span>, count)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>CountRepositoryはCounterインターフェースで定義してある、Inctement、Get、Resetが実装してあるため、ビルドコマンドでビルドすることができます。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">go</span> build sample.<span class="keyword">go</span></span><br></pre></td></tr></table></figure><h3 id="エラーの場合"><a href="#エラーの場合" class="headerlink" title="エラーの場合"></a>エラーの場合</h3><p>エラーを発生させるために、<code>Counter</code> インターフェースにDecrementを追加してみましょう。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Counter <span class="keyword">interface</span> &#123;</span><br><span class="line">    Increment(ctx context.Context, key <span class="type">string</span>) (<span class="type">int</span>, <span class="type">error</span>)</span><br><span class="line">    Get(ctx context.Context, key <span class="type">string</span>) (<span class="type">int</span>, <span class="type">error</span>)</span><br><span class="line">    Reset(ctx context.Context, key <span class="type">string</span>) <span class="type">error</span></span><br><span class="line">    Decrement(ctx context.Context, key <span class="type">string</span>) (<span class="type">int</span>, <span class="type">error</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ビルドすると、ビルドエラーが発生します。インターフェースの未実装が検知されていますね。</p><p><strong>コンパイルエラーメッセージ：</strong></p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./sample.go:35:17: cannot use (<span class="number">*C</span>ountRepository)(<span class="literal">nil</span>) (value of<span class="built_in"> type </span><span class="number">*C</span>ountRepository) as Counter value <span class="keyword">in</span> variable declaration: <span class="number">*C</span>ountRepository does <span class="keyword">not</span> implement Counter (missing method Decrement)</span><br></pre></td></tr></table></figure><p>では、イディオムをコメントアウトしてみるとどうでしょうか。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// インターフェース実装チェック</span></span><br><span class="line"><span class="comment">// var _ Counter = (*CountRepository)(nil)</span></span><br></pre></td></tr></table></figure><p>ビルドすると、エラーが発生しなくなりました。これは、コンパイル時にインターフェースの未実装が検知されていないことを意味しています。</p><h2 id="なぜこのイディオムが必要なのか"><a href="#なぜこのイディオムが必要なのか" class="headerlink" title="なぜこのイディオムが必要なのか"></a>なぜこのイディオムが必要なのか</h2><p>このイディオムですが、通常は不要な場合がほとんどです。<br>例えば、以下のように <code>NewCounter</code> 関数を追加して、<code>CountRepository</code> は <code>Counter</code> インターフェースであるということを明示的に指定した場合、GOのコンパイラでインターフェースの実装を検知することができます。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> CountRepository <span class="keyword">struct</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Counterインターフェースという型が明示されている</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewCounter</span><span class="params">()</span></span> Counter &#123;</span><br><span class="line">    <span class="keyword">return</span> &amp;CountRepository&#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="エフェクティブGoの指針"><a href="#エフェクティブGoの指針" class="headerlink" title="エフェクティブGoの指針"></a>エフェクティブGoの指針</h2><p>エフェクティブGoでは、このイディオムについて以下のように述べています。</p><blockquote><p>The appearance of the blank identifier in this construct indicates that the declaration exists only for the type checking, not to create a variable. Don’t do this for every type that satisfies an interface, though. By convention, such declarations are only used when there are no static conversions already present in the code, which is a rare event.</p></blockquote><h3 id="重要なポイント"><a href="#重要なポイント" class="headerlink" title="重要なポイント"></a>重要なポイント</h3><ol><li><strong>空白識別子の役割</strong> : 変数を作成せず、型チェックのみを目的とする</li><li><strong>乱用すべきではない</strong> : すべてのインターフェース実装に使うべきではない</li><li><strong>静的変換がない場合のみ</strong> : 既に型チェックの機会がある場合は不要</li></ol><p>使いどころは、<strong>静的変換がない場面</strong> です。</p><h2 id="静的変換とは"><a href="#静的変換とは" class="headerlink" title="静的変換とは"></a>静的変換とは</h2><p><strong>静的変換（static conversion）</strong> とは、コンパイル時に型の互換性をチェックする機会のことです。</p><h3 id="静的変換がある場合（イディオムは不要）"><a href="#静的変換がある場合（イディオムは不要）" class="headerlink" title="静的変換がある場合（イディオムは不要）"></a>静的変換がある場合（イディオムは不要）</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Process</span><span class="params">(repo Counter)</span></span> &#123;  <span class="comment">// ← 静的変換が発生</span></span><br><span class="line">    <span class="comment">// 処理</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    r := &amp;CountRepository&#123;&#125;</span><br><span class="line">    Process(r)  <span class="comment">// ← ここで型チェックが行われる</span></span><br><span class="line">    <span class="comment">// そのため、var _ は不要</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="静的変換がない場合（イディオムが有用）"><a href="#静的変換がない場合（イディオムが有用）" class="headerlink" title="静的変換がない場合（イディオムが有用）"></a>静的変換がない場合（イディオムが有用）</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// インターフェースと実装が分離されていて、</span></span><br><span class="line"><span class="comment">// まだ使用箇所がない場合</span></span><br><span class="line"><span class="keyword">type</span> CountRepository <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// この時点では静的変換が発生していない</span></span><br><span class="line"><span class="comment">// そのため、明示的に型チェックを行う</span></span><br><span class="line"><span class="keyword">var</span> _ Counter = (*CountRepository)(<span class="literal">nil</span>)</span><br></pre></td></tr></table></figure><h2 id="適切な使用場面"><a href="#適切な使用場面" class="headerlink" title="適切な使用場面"></a>適切な使用場面</h2><h3 id="1-リポジトリパターン"><a href="#1-リポジトリパターン" class="headerlink" title="1. リポジトリパターン"></a>1. リポジトリパターン</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// domain/repository/user.go</span></span><br><span class="line"><span class="keyword">type</span> UserRepository <span class="keyword">interface</span> &#123;</span><br><span class="line">    Get(ctx context.Context, id <span class="type">string</span>) (*User, <span class="type">error</span>)</span><br><span class="line">    Create(ctx context.Context, user *User) <span class="type">error</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// infrastructure/repository/user.go</span></span><br><span class="line"><span class="keyword">type</span> UserRepository <span class="keyword">struct</span> &#123;</span><br><span class="line">    db *gorm.DB</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// リポジトリパターンでは、インターフェースと実装が分離されているため有用</span></span><br><span class="line"><span class="keyword">var</span> _ repository.UserRepository = (*UserRepository)(<span class="literal">nil</span>)</span><br></pre></td></tr></table></figure><h3 id="2-プラグインアーキテクチャ"><a href="#2-プラグインアーキテクチャ" class="headerlink" title="2. プラグインアーキテクチャ"></a>2. プラグインアーキテクチャ</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// plugin/interface.go</span></span><br><span class="line"><span class="keyword">type</span> Plugin <span class="keyword">interface</span> &#123;</span><br><span class="line">    Initialize() <span class="type">error</span></span><br><span class="line">    Execute() <span class="type">error</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// plugin/example.go</span></span><br><span class="line"><span class="keyword">type</span> ExamplePlugin <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// プラグインが正しくインターフェースを実装しているかをチェック</span></span><br><span class="line"><span class="keyword">var</span> _ Plugin = (*ExamplePlugin)(<span class="literal">nil</span>)</span><br></pre></td></tr></table></figure><h2 id="不適切な使用例"><a href="#不適切な使用例" class="headerlink" title="不適切な使用例"></a>不適切な使用例</h2><h3 id="既に静的変換がある場合"><a href="#既に静的変換がある場合" class="headerlink" title="既に静的変換がある場合"></a>既に静的変換がある場合</h3><p>既に静的変換がある場合はイディオムは必要ありません。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 既に使用箇所がある場合</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewUserService</span><span class="params">(repo UserRepository)</span></span> *UserService &#123;</span><br><span class="line">    <span class="keyword">return</span> &amp;UserService&#123;repo: repo&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// この場合、NewUserServiceの呼び出し時に既に型チェックが行われるため</span></span><br><span class="line"><span class="comment">// var _ は不要</span></span><br></pre></td></tr></table></figure><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p><code>var _ Interface = (*Type)(nil)</code> イディオムは <strong>静的変換がない場合のみ</strong> 使用すればOKです。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;はじめに&quot;&gt;&lt;a href=&quot;#はじめに&quot; class=&quot;headerlink&quot; title=&quot;はじめに&quot;&gt;&lt;/a&gt;はじめに&lt;/h2&gt;&lt;p&gt;Goを書いていると、以下のようなコードを見かけることがあります。&lt;/p&gt;
&lt;figure class=&quot;highlight </summary>
      
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
    <category term="Go" scheme="https://48n.jp/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>コードの複雑度を測る：循環的複雑度（Cyclomatic Complexity）の計算方法と実践ガイド</title>
    <link href="https://48n.jp/blog/2025/08/07/code-complexity-cyclomatic-complexity-guide/"/>
    <id>https://48n.jp/blog/2025/08/07/code-complexity-cyclomatic-complexity-guide/</id>
    <published>2025-08-07T01:00:00.000Z</published>
    <updated>2024-08-07T01:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>コードの品質を測る指標として「複雑度」がありますが、特に**循環的複雑度（Cyclomatic Complexity）**は、コードの保守性やバグの発生リスクを定量的に評価できる指標です。</p><p>この記事では、循環的複雑度の計算方法、複雑度を削減する方法、実践的な測定ツールについて、詳しく解説していきます。</p><h2 id="循環的複雑度（Cyclomatic-Complexity）とは"><a href="#循環的複雑度（Cyclomatic-Complexity）とは" class="headerlink" title="循環的複雑度（Cyclomatic Complexity）とは"></a>循環的複雑度（Cyclomatic Complexity）とは</h2><p>循環的複雑度は、プログラムの制御フローの分岐の数から算出される複雑度の指標です。この値が高いほど、コードは複雑で理解しにくく、バグが発生しやすくなります。</p><h3 id="基本計算式"><a href="#基本計算式" class="headerlink" title="基本計算式"></a>基本計算式</h3><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">M</span> = E - N + <span class="number">2</span>P</span><br></pre></td></tr></table></figure><ul><li><strong>M</strong>: 循環的複雑度</li><li><strong>E</strong>: 制御フローグラフの辺の数（edge）</li><li><strong>N</strong>: 制御フローグラフのノード数（node）</li><li><strong>P</strong>: 連結成分の数（通常は1で計算する）</li></ul><h3 id="簡易計算方法（現場でよく使われる）"><a href="#簡易計算方法（現場でよく使われる）" class="headerlink" title="簡易計算方法（現場でよく使われる）"></a>簡易計算方法（現場でよく使われる）</h3><p>実際の開発現場では、以下の簡易計算方法がよく使われます。</p><ol><li><code>if</code>、<code>for</code>、<code>while</code>、<code>case</code>、<code>catch</code> などの分岐やループの数を数える</li><li>その数に <strong>1</strong> を足す</li></ol><h2 id="計算例で理解する循環的複雑度"><a href="#計算例で理解する循環的複雑度" class="headerlink" title="計算例で理解する循環的複雑度"></a>計算例で理解する循環的複雑度</h2><p>簡単な関数を例に説明をしていきます。</p><h3 id="例1：簡易計算方法"><a href="#例1：簡易計算方法" class="headerlink" title="例1：簡易計算方法"></a>例1：簡易計算方法</h3><p>まずは、簡易的な計算方法で関数の複雑度を計算していきましょう。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">example</span>(<span class="params">x</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (x &gt; <span class="number">0</span>) &#123;           <span class="comment">// 分岐 1</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;A&quot;</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (x === <span class="number">0</span>) &#123;  <span class="comment">// 分岐 2</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;B&quot;</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;C&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;  <span class="comment">// 分岐 3</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(i);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>分岐・ループの数：3</li><li>複雑度：3 + 1 &#x3D; <strong>4</strong></li></ul><p>上記の例のように分岐を繰り返しを数えて+1をすれば概算で計算することができます。</p><h3 id="例2：基本計算式を使った計算方法"><a href="#例2：基本計算式を使った計算方法" class="headerlink" title="例2：基本計算式を使った計算方法"></a>例2：基本計算式を使った計算方法</h3><p>次は <code>M = E - N + 2P</code> を使った計算方法を見ていきましょう。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">helper</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;do something&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>制御フローグラフ：</strong><br>まずは、関数の制御フローグラフを作成します。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-attr">[Start]</span> → <span class="selector-attr">[print]</span> → <span class="selector-attr">[End]</span></span><br></pre></td></tr></table></figure><p><strong>計算：</strong><br>次に制御フローグラフからノードの数、辺の数を数えます。</p><ul><li>N（ノード数）&#x3D; 3（Start, print, End）</li><li>E（辺の数）&#x3D; 2（Start→print, print→End）</li><li>P（連結成分数）&#x3D; 1</li></ul><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">M</span> = E - N + <span class="number">2</span>P</span><br><span class="line"><span class="attribute">M</span> = <span class="number">2</span> - <span class="number">3</span> + <span class="number">2</span>*<span class="number">1</span></span><br><span class="line"><span class="attribute">M</span> = <span class="number">1</span></span><br></pre></td></tr></table></figure><p>結果：<strong>複雑度 &#x3D; 1</strong>（最低値、まったく複雑ではない関数）</p><h3 id="分岐を追加した場合"><a href="#分岐を追加した場合" class="headerlink" title="分岐を追加した場合"></a>分岐を追加した場合</h3><p>次に分岐を追加した関数の計算をしてみましょう。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">helper</span>(<span class="params">flag</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (flag) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;do something&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>制御フローグラフ：</strong></p><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-attr">[Start]</span> → (flag?) ─Yes→ <span class="selector-attr">[print]</span> → <span class="selector-attr">[End]</span></span><br><span class="line">               └─No──────────────→ <span class="selector-attr">[End]</span></span><br></pre></td></tr></table></figure><p><strong>計算：</strong></p><ul><li>N（ノード数）&#x3D; 4（Start, 条件判定, print, End）</li><li>E（辺の数）&#x3D; 4（Start→条件判定, 条件判定→print, 条件判定→End, print→End）</li><li>P（連結成分数）&#x3D; 1</li></ul><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">M</span> = E - N + <span class="number">2</span>P</span><br><span class="line"><span class="attribute">M</span> = <span class="number">4</span> - <span class="number">4</span> + <span class="number">2</span>*<span class="number">1</span></span><br><span class="line"><span class="attribute">M</span> = <span class="number">2</span></span><br></pre></td></tr></table></figure><p>結果：<strong>複雑度 &#x3D; 2</strong>（分岐を1つ追加しただけで、複雑度は 1 → 2 に増加）</p><h3 id="ネストした条件分岐"><a href="#ネストした条件分岐" class="headerlink" title="ネストした条件分岐"></a>ネストした条件分岐</h3><p>条件分岐がネストした関数の循環的複雑度を計算をしてみます。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">helper</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (a &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (b &gt; <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;A and B are positive&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>制御フローグラフ：</strong></p><figure class="highlight coq"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[Start] </span><br><span class="line">   |</span><br><span class="line"><span class="type">(a</span> &gt; <span class="number">0</span>?) ----No----&gt; [<span class="keyword">End</span>]</span><br><span class="line">   |</span><br><span class="line">  <span class="type">Yes</span></span><br><span class="line">   |</span><br><span class="line"><span class="type">(b</span> &gt; <span class="number">0</span>?) ----No----&gt; [<span class="keyword">End</span>]</span><br><span class="line">   |</span><br><span class="line">  <span class="type">Yes</span></span><br><span class="line">   |</span><br><span class="line"><span class="type">[print</span>] → [<span class="keyword">End</span>]</span><br></pre></td></tr></table></figure><p><strong>計算：</strong></p><ul><li>N（ノード数）&#x3D; 5（Start, 条件判定1, 条件判定2, print, End）</li><li>E（辺の数）&#x3D; 6（Start→条件判定1, 条件判定1→End, 条件判定1→条件判定2, 条件判定2→End, 条件判定2→print, print→End）</li><li>P（連結成分数）&#x3D; 1</li></ul><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">M</span> = E - N + <span class="number">2</span>P</span><br><span class="line"><span class="attribute">M</span> = <span class="number">6</span> - <span class="number">5</span> + <span class="number">2</span>*<span class="number">1</span></span><br><span class="line"><span class="attribute">M</span> = <span class="number">3</span></span><br></pre></td></tr></table></figure><p>結果：<strong>複雑度 &#x3D; 3</strong></p><h3 id="重要なポイント"><a href="#重要なポイント" class="headerlink" title="重要なポイント"></a>重要なポイント</h3><ul><li>循環的複雑度は「ネストの深さ」ではなく「分岐の総数」で増える</li><li>ただし、ネストが深いと認知的複雑度（Cognitive Complexity）はもっと増える</li><li>人間はネスト構造を理解するのが苦手なので、Cognitive Complexity では深さもペナルティになる</li></ul><h2 id="複雑度の目安"><a href="#複雑度の目安" class="headerlink" title="複雑度の目安"></a>複雑度の目安</h2><table><thead><tr><th>複雑度</th><th>意味</th><th>対応</th></tr></thead><tbody><tr><td>1〜10</td><td>シンプル、理解しやすい</td><td>問題なし</td></tr><tr><td>11〜20</td><td>複雑、リファクタリングを検討</td><td>リファクタリング推奨</td></tr><tr><td>21〜50</td><td>非常に複雑、バグの温床</td><td>分割必須</td></tr><tr><td>50+</td><td>ほぼ地獄</td><td>分割必須</td></tr></tbody></table><h2 id="関数呼び出しと複雑度の関係"><a href="#関数呼び出しと複雑度の関係" class="headerlink" title="関数呼び出しと複雑度の関係"></a>関数呼び出しと複雑度の関係</h2><p>循環的複雑度の計算は、原則として「関数ごとに独立して」計測するのが基本です。</p><h3 id="基本ルール"><a href="#基本ルール" class="headerlink" title="基本ルール"></a>基本ルール</h3><ul><li>呼び出している関数の中身はカウントに含めない</li><li>関数呼び出しは、それ自体は「分岐」でも「ループ」でもないので複雑度には直接影響しない</li><li>複雑度に影響するのは、その関数呼び出しが条件式の一部やループの制御として使われている場合</li></ul><h3 id="例1：単純な呼び出し（複雑度に影響なし）"><a href="#例1：単純な呼び出し（複雑度に影響なし）" class="headerlink" title="例1：単純な呼び出し（複雑度に影響なし）"></a>例1：単純な呼び出し（複雑度に影響なし）</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">helper</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;do something&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">main</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">helper</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>main</code> の中には分岐もループもなし</li><li><code>main</code> の複雑度 &#x3D; 1（最低値）</li><li><code>helper</code> は別に計算（&#x3D;1）</li></ul><h3 id="例2：条件式の中で呼び出し（影響あり）"><a href="#例2：条件式の中で呼び出し（影響あり）" class="headerlink" title="例2：条件式の中で呼び出し（影響あり）"></a>例2：条件式の中で呼び出し（影響あり）</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">isValid</span>(<span class="params">x</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> x &gt; <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">main</span>(<span class="params">x</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">isValid</span>(x)) &#123;   <span class="comment">// 分岐1</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;ok&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>isValid()</code> の中身はカウントしないが、<code>if</code> の分岐1つとして <code>main</code> の複雑度に加算される</li><li>複雑度：<ul><li><code>main</code> &#x3D;  1（if）+ 1 &#x3D; <strong>2</strong></li><li><code>isValid</code> &#x3D; 1</li></ul></li></ul><h2 id="複雑度を下げる方法"><a href="#複雑度を下げる方法" class="headerlink" title="複雑度を下げる方法"></a>複雑度を下げる方法</h2><h3 id="1-関数の分割"><a href="#1-関数の分割" class="headerlink" title="1. 関数の分割"></a>1. 関数の分割</h3><p>関数を分割することで１つ１つの関数の循環的複雑度は下がります。</p><p><strong>Before（複雑度 &#x3D; 8）</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">complexFunction</span>(<span class="params">data</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (data.<span class="property">type</span> === <span class="string">&#x27;A&#x27;</span>) &#123;</span><br><span class="line">    <span class="comment">// 処理A</span></span><br><span class="line">    <span class="keyword">if</span> (data.<span class="property">value</span> &gt; <span class="number">10</span>) &#123;</span><br><span class="line">      <span class="comment">// 処理A-1</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 処理A-2</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (data.<span class="property">type</span> === <span class="string">&#x27;B&#x27;</span>) &#123;</span><br><span class="line">    <span class="comment">// 処理B</span></span><br><span class="line">    <span class="keyword">if</span> (data.<span class="property">value</span> &gt; <span class="number">20</span>) &#123;</span><br><span class="line">      <span class="comment">// 処理B-1</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 処理B-2</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>After（複雑度 &#x3D; 2）</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">processTypeA</span>(<span class="params">data</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (data.<span class="property">value</span> &gt; <span class="number">10</span>) &#123;</span><br><span class="line">    <span class="comment">// 処理A-1</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 処理A-2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">processTypeB</span>(<span class="params">data</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (data.<span class="property">value</span> &gt; <span class="number">20</span>) &#123;</span><br><span class="line">    <span class="comment">// 処理B-1</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 処理B-2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">complexFunction</span>(<span class="params">data</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (data.<span class="property">type</span> === <span class="string">&#x27;A&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">processTypeA</span>(data);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (data.<span class="property">type</span> === <span class="string">&#x27;B&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">processTypeB</span>(data);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-ポリモーフィズムの活用"><a href="#2-ポリモーフィズムの活用" class="headerlink" title="2. ポリモーフィズムの活用"></a>2. ポリモーフィズムの活用</h3><p>ポリモーフィズムを使うことで、分岐がなくなります。それにより、複雑度が下がります。<br><strong>Before（複雑度 &#x3D; 6）</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">processPayment</span>(<span class="params">payment</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (payment.<span class="property">type</span> === <span class="string">&#x27;credit&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">processCreditCard</span>(payment);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (payment.<span class="property">type</span> === <span class="string">&#x27;debit&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">processDebitCard</span>(payment);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (payment.<span class="property">type</span> === <span class="string">&#x27;bank&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">processBankTransfer</span>(payment);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>After（複雑度 &#x3D; 1）：</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> paymentProcessors = &#123;</span><br><span class="line">  <span class="attr">credit</span>: processCreditCard,</span><br><span class="line">  <span class="attr">debit</span>: processDebitCard,</span><br><span class="line">  <span class="attr">bank</span>: processBankTransfer</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">processPayment</span>(<span class="params">payment</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> processor = paymentProcessors[payment.<span class="property">type</span>];</span><br><span class="line">  <span class="keyword">return</span> processor ? <span class="title function_">processor</span>(payment) : <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;Unknown payment type&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="測定ツール"><a href="#測定ツール" class="headerlink" title="測定ツール"></a>測定ツール</h2><p>ここでは、循環的複雑度の計算方法について説明してきました。<br>実際の関数は複雑であり、手計算は現実的ではありません。各言語でツールが開発されており、そのツールを利用するのがよいです。</p><ul><li><strong>JavaScript&#x2F;TypeScript</strong>: ESLint</li><li><strong>Python</strong>: radon</li><li><strong>Java</strong>: SonarQube &#x2F; PMD</li><li><strong>Go</strong>: gocyclo</li></ul><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>循環的複雑度は、コードの品質を数値で測れる指標です。<strong>チーム開発では「このコードは複雑すぎる」という主観的な意見ではなく、「複雑度が15だからリファクタリングが必要」と客観的に判断できます</strong>。複雑度の計算方法を理解すれば、コードレビューでも「なぜこの関数は複雑なのか」を論理的に説明できるようになり、より建設的な議論ができるようになります。</p>]]></content>
    
    
    <summary type="html">コードの複雑度を定量的に測る方法として、循環的複雑度（Cyclomatic Complexity）について詳しく解説します。計算方法、実践的な測定ツール、複雑度を下げる方法まで網羅的に紹介します。</summary>
    
    
    
    <category term="ソフトウェア開発" scheme="https://48n.jp/categories/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA/"/>
    
    
  </entry>
  
  <entry>
    <title>HTTP中継（プロキシ）のプロトコル詳細解説</title>
    <link href="https://48n.jp/blog/2025/08/06/http-proxy-protocol-guide/"/>
    <id>https://48n.jp/blog/2025/08/06/http-proxy-protocol-guide/</id>
    <published>2025-08-06T05:30:00.000Z</published>
    <updated>2025-08-06T05:30:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>HTTP中継（プロキシ）は、アプリケーション層（OSIの第7層）のHTTPプロトコルを中継（再発行）する仕組みです。プロキシサーバーはTCP&#x2F;IPのセッションを一度終了させ、新たに別のセッションを開始して通信を中継します。</p><p>この記事では、HTTPの構造、リクエスト&#x2F;レスポンスの伝播、TCP&#x2F;IP層での扱い、ヘッダーの処理など、「プロトコルとして何がどう動いているか」に焦点を当てて詳述します。</p><h2 id="HTTP中継のプロトコル的な概要"><a href="#HTTP中継のプロトコル的な概要" class="headerlink" title="HTTP中継のプロトコル的な概要"></a>HTTP中継のプロトコル的な概要</h2><p>HTTP中継（proxy）は、アプリケーション層（OSIの第7層）のHTTPプロトコルを中継（再発行）するものです。プロキシサーバーはTCP&#x2F;IPのセッションを一度終了させ、新たに別のセッションを開始して通信を中継します。</p><figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[Client] --<span class="function"><span class="params">(HTTP over TCP)</span>--&gt;</span> [<span class="built_in">Proxy</span>] --<span class="function"><span class="params">(HTTP over TCP)</span>--&gt;</span> [Server]</span><br></pre></td></tr></table></figure><p>つまり、ClientとProxy間のTCPコネクションと、ProxyとServer間のTCPコネクションは完全に別物です。</p><h2 id="プロトコル単位での構造"><a href="#プロトコル単位での構造" class="headerlink" title="プロトコル単位での構造"></a>プロトコル単位での構造</h2><h3 id="1-クライアント→プロキシ（Proxy）"><a href="#1-クライアント→プロキシ（Proxy）" class="headerlink" title="1. クライアント→プロキシ（Proxy）"></a>1. クライアント→プロキシ（Proxy）</h3><p>クライアントからプロキシへのHTTPリクエスト例</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">GET</span> <span class="string">/api/item</span> <span class="meta">HTTP/1.1</span></span><br><span class="line"><span class="attribute">Host</span><span class="punctuation">: </span>proxy.example.com</span><br><span class="line"><span class="attribute">Content-Type</span><span class="punctuation">: </span>application/json</span><br><span class="line"><span class="attribute">Content-Length</span><span class="punctuation">: </span>12345</span><br><span class="line"><span class="attribute">Authorization</span><span class="punctuation">: </span>Bearer token123</span><br></pre></td></tr></table></figure><h3 id="2-プロキシでの動作"><a href="#2-プロキシでの動作" class="headerlink" title="2. プロキシでの動作"></a>2. プロキシでの動作</h3><p>プロキシサーバーは以下を行います。</p><ul><li>TCPで受け取ったHTTPリクエストをアプリケーション層で解析</li><li>必要に応じてヘッダーを書き換え（例：Host, Authorization, X-Forwarded-For など）</li><li>新たなHTTPリクエストとしてバックエンドに送信</li></ul><h3 id="3-プロキシ→バックエンド"><a href="#3-プロキシ→バックエンド" class="headerlink" title="3. プロキシ→バックエンド"></a>3. プロキシ→バックエンド</h3><p>プロキシからバックエンドで新たなリクエストが送られます。<br>リクエスト先Hostがbackendサーバーに変更され、X-Forwarded-Forが追加、Authorizationがプロキシによって変更されています。</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">GET</span> <span class="string">/api/item</span> <span class="meta">HTTP/1.1</span></span><br><span class="line"><span class="attribute">Host</span><span class="punctuation">: </span>backend.internal</span><br><span class="line"><span class="attribute">Content-Type</span><span class="punctuation">: </span>application/json</span><br><span class="line"><span class="attribute">Content-Length</span><span class="punctuation">: </span>12345</span><br><span class="line"><span class="attribute">X-Forwarded-For</span><span class="punctuation">: </span>192.168.1.10</span><br><span class="line"><span class="attribute">Authorization</span><span class="punctuation">: </span>internal-api-key</span><br></pre></td></tr></table></figure><h3 id="4-バックエンド→プロキシ（HTTPレスポンス）"><a href="#4-バックエンド→プロキシ（HTTPレスポンス）" class="headerlink" title="4. バックエンド→プロキシ（HTTPレスポンス）"></a>4. バックエンド→プロキシ（HTTPレスポンス）</h3><p>バックエンドサーバーはプロキシにレスポンスを返します。</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">HTTP/1.1</span> <span class="number">200</span> OK</span><br><span class="line"><span class="attribute">Content-Type</span><span class="punctuation">: </span>application/json</span><br><span class="line"><span class="attribute">Content-Length</span><span class="punctuation">: </span>123</span><br><span class="line"></span><br><span class="line"><span class="language-json"><span class="punctuation">&#123;</span><span class="attr">&quot;username&quot;</span><span class="punctuation">:</span> <span class="string">&quot;taro&quot;</span><span class="punctuation">&#125;</span></span></span><br></pre></td></tr></table></figure><h3 id="5-プロキシ→クライアント"><a href="#5-プロキシ→クライアント" class="headerlink" title="5. プロキシ→クライアント"></a>5. プロキシ→クライアント</h3><p>そのレスポンスを、必要に応じて加工しながら、もとのクライアントに返します。</p><h2 id="TCP-IPレイヤーの流れ"><a href="#TCP-IPレイヤーの流れ" class="headerlink" title="TCP&#x2F;IPレイヤーの流れ"></a>TCP&#x2F;IPレイヤーの流れ</h2><table><thead><tr><th>層</th><th>内容</th></tr></thead><tbody><tr><td>アプリケーション層 (HTTP)</td><td>リクエストやレスポンスのヘッダー・ボディの構造処理</td></tr><tr><td>トランスポート層 (TCP)</td><td>クライアント→プロキシ、プロキシ→バックエンドの別個のTCPコネクション</td></tr><tr><td>ネットワーク層 (IP)</td><td>Proxyが受け取ったIPヘッダのSource IPを見て、X-Forwarded-Forに記録することが多い</td></tr><tr><td>データリンク層</td><td>イーサネットフレームなど。HTTP中継の動作とは直接関係しないが、パケット分割などは影響あり</td></tr></tbody></table><h2 id="ヘッダーの役割とプロキシにおける重要性"><a href="#ヘッダーの役割とプロキシにおける重要性" class="headerlink" title="ヘッダーの役割とプロキシにおける重要性"></a>ヘッダーの役割とプロキシにおける重要性</h2><table><thead><tr><th>ヘッダー</th><th>意味・使い道</th></tr></thead><tbody><tr><td>Host</td><td>バックエンドのホスト名に変更される</td></tr><tr><td>X-Forwarded-For</td><td>オリジナルのクライアントIPを伝えるためにプロキシが付加</td></tr><tr><td>Authorization</td><td>プロキシで独自の認証に変換することもある（例：トークン → 内部APIキー）</td></tr><tr><td>Via</td><td>どのプロキシを通ったかを記録（RFC2616で推奨）</td></tr><tr><td>Content-Length &#x2F; Transfer-Encoding</td><td>ストリーミングやチャンク転送時に注意が必要</td></tr></tbody></table><h2 id="プロキシの種類と特徴"><a href="#プロキシの種類と特徴" class="headerlink" title="プロキシの種類と特徴"></a>プロキシの種類と特徴</h2><h3 id="フォワードプロキシ"><a href="#フォワードプロキシ" class="headerlink" title="フォワードプロキシ"></a>フォワードプロキシ</h3><p>クライアントがプロキシを経由してインターネットにアクセスする形態です。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-attr">[Client]</span> → <span class="selector-attr">[Forward Proxy]</span> → <span class="selector-attr">[Internet]</span> → <span class="selector-attr">[Server]</span></span><br></pre></td></tr></table></figure><h3 id="リバースプロキシ"><a href="#リバースプロキシ" class="headerlink" title="リバースプロキシ"></a>リバースプロキシ</h3><p>サーバーの前に配置され、クライアントからのリクエストを適切なバックエンドサーバーに振り分ける形態です。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-attr">[Client]</span> → <span class="selector-attr">[Reverse Proxy]</span> → <span class="selector-attr">[Backend Server]</span></span><br></pre></td></tr></table></figure><h3 id="トランスペアレントプロキシ"><a href="#トランスペアレントプロキシ" class="headerlink" title="トランスペアレントプロキシ"></a>トランスペアレントプロキシ</h3><p>クライアントがプロキシの存在を意識せずに通信できるプロキシです。通常はネットワークレベルでトラフィックをインターセプトします。</p><h2 id="プロキシにおける重要な技術的考慮事項"><a href="#プロキシにおける重要な技術的考慮事項" class="headerlink" title="プロキシにおける重要な技術的考慮事項"></a>プロキシにおける重要な技術的考慮事項</h2><h3 id="1-コネクション管理"><a href="#1-コネクション管理" class="headerlink" title="1. コネクション管理"></a>1. コネクション管理</h3><p>プロキシは以下のコネクション管理が重要です。</p><ul><li><strong>Keep-Alive</strong>: クライアントとのコネクションを維持</li><li><strong>Connection Pooling</strong>: バックエンドサーバーとのコネクションを再利用</li><li><strong>タイムアウト設定</strong>: 適切なタイムアウト値の設定</li></ul><h3 id="2-バッファリングとストリーミング"><a href="#2-バッファリングとストリーミング" class="headerlink" title="2. バッファリングとストリーミング"></a>2. バッファリングとストリーミング</h3><ul><li><strong>メモリ使用量の最適化</strong>: 大きなファイルやデータを扱う場合、バッファリングとストリーミングでメモリ使用量を最小限に抑えることが重要です</li></ul><h3 id="3-エラーハンドリング"><a href="#3-エラーハンドリング" class="headerlink" title="3. エラーハンドリング"></a>3. エラーハンドリング</h3><p>プロキシは様々なエラーケースを適切に処理する必要があります。</p><ul><li>バックエンドサーバーの障害</li><li>ネットワークタイムアウト</li><li>不正なHTTPリクエスト</li><li>SSL&#x2F;TLS証明書の問題</li></ul><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>HTTP中継（プロキシ）は、単純に見えて実は非常に複雑な仕組みです。TCP&#x2F;IPレイヤーでのセッション管理、HTTPヘッダーの適切な処理、セキュリティ上の考慮事項など、多くの技術的要素が絡み合っています。</p><p>この記事で解説した内容を基に、実際のプロキシサーバーの設定や、カスタムプロキシの実装に取り組んでみてください。 </p>]]></content>
    
    
    <summary type="html">HTTP中継（プロキシ）は、アプリケーション層のHTTPプロトコルを中継する仕組みです。この記事では、HTTPの構造、リクエスト/レスポンスの伝播、TCP/IP層での扱い、ヘッダーの処理など、プロトコルとして何がどう動いているかに焦点を当てて詳述します。</summary>
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
  </entry>
  
  <entry>
    <title>GraphQLガイド - ファイルアップロード</title>
    <link href="https://48n.jp/blog/2025/07/31/graphql-file-upload-guide/"/>
    <id>https://48n.jp/blog/2025/07/31/graphql-file-upload-guide/</id>
    <published>2025-07-31T06:00:00.000Z</published>
    <updated>2025-07-31T06:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>GraphQLでファイルアップロードを実装する場合、いくつかの方法があります。GraphQL自体はバイナリデータを直接扱う仕様にはなっていませんが、工夫次第で実現可能です。</p><p>この記事では、GraphQLでのファイルアップロード実装について詳しく解説し、適切な方法を選択できるようにします。</p><h2 id="バイナリデータの取り扱いについて"><a href="#バイナリデータの取り扱いについて" class="headerlink" title="バイナリデータの取り扱いについて"></a>バイナリデータの取り扱いについて</h2><p>GraphQLの標準プロトコルはJSONのみをサポートしており、JSONはバイナリデータを直接扱えません。しかし、<strong>工夫次第でバイナリデータの送受信は可能</strong>です。主な方法は以下の2つです。</p><h3 id="1-Base64エンコードで送信"><a href="#1-Base64エンコードで送信" class="headerlink" title="1. Base64エンコードで送信"></a><strong>1. Base64エンコードで送信</strong></h3><ul><li>バイナリデータをBase64文字列に変換し、GraphQLのString型で送受信します</li><li>小さなファイルや簡易的な用途には使えますが、ファイルサイズが大きいと効率が悪くなります</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// クライアント側</span></span><br><span class="line"><span class="keyword">const</span> file = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;fileInput&#x27;</span>).<span class="property">files</span>[<span class="number">0</span>];</span><br><span class="line"><span class="keyword">const</span> reader = <span class="keyword">new</span> <span class="title class_">FileReader</span>();</span><br><span class="line">reader.<span class="property">onload</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> base64 = reader.<span class="property">result</span>.<span class="title function_">split</span>(<span class="string">&#x27;,&#x27;</span>)[<span class="number">1</span>]; <span class="comment">// data:image/jpeg;base64, の部分を除去</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">const</span> mutation = <span class="string">`</span></span><br><span class="line"><span class="string">    mutation UploadFile($file: String!) &#123;</span></span><br><span class="line"><span class="string">      uploadFile(file: $file) &#123;</span></span><br><span class="line"><span class="string">        id</span></span><br><span class="line"><span class="string">        url</span></span><br><span class="line"><span class="string">        filename</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">  `</span>;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// GraphQLリクエストを送信</span></span><br><span class="line">  <span class="title function_">fetch</span>(<span class="string">&#x27;/graphql&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">    <span class="attr">headers</span>: &#123; <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span> &#125;,</span><br><span class="line">    <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">      <span class="attr">query</span>: mutation,</span><br><span class="line">      <span class="attr">variables</span>: &#123; <span class="attr">file</span>: base64 &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br><span class="line">reader.<span class="title function_">readAsDataURL</span>(file);</span><br></pre></td></tr></table></figure><h3 id="2-GraphQL-multipart-request-specification（ファイルアップロード）"><a href="#2-GraphQL-multipart-request-specification（ファイルアップロード）" class="headerlink" title="2. GraphQL multipart request specification（ファイルアップロード）"></a><strong>2. GraphQL multipart request specification（ファイルアップロード）</strong></h3><ul><li>Apollo Serverやgraphql-uploadなどのライブラリを使うことで、<strong>multipart&#x2F;form-data</strong>形式でファイルアップロードが可能です</li><li>これはHTTPの拡張で、GraphQLのmutationと一緒にバイナリファイルを送信できます</li><li>サーバー側でgraphql-uploadなどのミドルウェアが必要です</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// クライアント側</span></span><br><span class="line"><span class="keyword">const</span> file = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;fileInput&#x27;</span>).<span class="property">files</span>[<span class="number">0</span>];</span><br><span class="line"><span class="keyword">const</span> formData = <span class="keyword">new</span> <span class="title class_">FormData</span>();</span><br><span class="line"></span><br><span class="line">formData.<span class="title function_">append</span>(<span class="string">&#x27;operations&#x27;</span>, <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">  <span class="attr">query</span>: <span class="string">`</span></span><br><span class="line"><span class="string">    mutation UploadFile($file: Upload!) &#123;</span></span><br><span class="line"><span class="string">      uploadFile(file: $file) &#123;</span></span><br><span class="line"><span class="string">        id</span></span><br><span class="line"><span class="string">        url</span></span><br><span class="line"><span class="string">        filename</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">  `</span>,</span><br><span class="line">  <span class="attr">variables</span>: &#123; <span class="attr">file</span>: <span class="literal">null</span> &#125;</span><br><span class="line">&#125;));</span><br><span class="line"></span><br><span class="line">formData.<span class="title function_">append</span>(<span class="string">&#x27;map&#x27;</span>, <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">  <span class="string">&quot;0&quot;</span>: [<span class="string">&quot;variables.file&quot;</span>]</span><br><span class="line">&#125;));</span><br><span class="line"></span><br><span class="line">formData.<span class="title function_">append</span>(<span class="string">&#x27;0&#x27;</span>, file);</span><br><span class="line"></span><br><span class="line"><span class="title function_">fetch</span>(<span class="string">&#x27;/graphql&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">  <span class="attr">body</span>: formData</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="multipart-form-dataとは"><a href="#multipart-form-dataとは" class="headerlink" title="multipart&#x2F;form-dataとは"></a>multipart&#x2F;form-dataとは</h2><p>multipart&#x2F;form-dataは、HTTPリクエストで<strong>テキストデータとバイナリデータ（ファイル）を同時に送信</strong>するためのMIMEタイプです。HTMLの<code>&lt;form&gt;</code>要素でファイルアップロードを行う際に使用されます。</p><h3 id="基本的な構造"><a href="#基本的な構造" class="headerlink" title="基本的な構造"></a><strong>基本的な構造</strong></h3><p><strong>1. Content-Typeヘッダー</strong></p><figure class="highlight fortran"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Content-<span class="keyword">Type</span>: multipart/<span class="keyword">form</span>-<span class="keyword">data</span>; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW</span><br></pre></td></tr></table></figure><ul><li>boundaryは、データの境界を区切る文字列です</li><li>ブラウザやクライアントが自動生成します</li></ul><p><strong>2. リクエストボディの構造</strong></p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">------WebKitFormBoundary7MA4YWxkTrZu0gW</span></span><br><span class="line">Content-Disposition: form-data; <span class="type">name</span>=&quot;text_field&quot;</span><br><span class="line"></span><br><span class="line">Hello World</span><br><span class="line"><span class="comment">------WebKitFormBoundary7MA4YWxkTrZu0gW</span></span><br><span class="line">Content-Disposition: form-data; <span class="type">name</span>=&quot;file&quot;; filename=&quot;image.jpg&quot;</span><br><span class="line">Content-<span class="keyword">Type</span>: image/jpeg</span><br><span class="line"></span><br><span class="line">[バイナリデータ]</span><br><span class="line"><span class="comment">------WebKitFormBoundary7MA4YWxkTrZu0gW--</span></span><br></pre></td></tr></table></figure><p><strong>3. 実際のHTTPリクエスト例</strong></p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">POST</span> <span class="string">/graphql</span> <span class="meta">HTTP/1.1</span></span><br><span class="line"><span class="attribute">Host</span><span class="punctuation">: </span>api.example.com</span><br><span class="line"><span class="attribute">Content-Type</span><span class="punctuation">: </span>multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW</span><br><span class="line"></span><br><span class="line"><span class="language-pgsql"><span class="comment">------WebKitFormBoundary7MA4YWxkTrZu0gW</span></span></span><br><span class="line"><span class="language-pgsql">Content-Disposition: form-data; <span class="type">name</span>=&quot;operations&quot;</span></span><br><span class="line"><span class="language-pgsql"></span></span><br><span class="line"><span class="language-pgsql">&#123;&quot;query&quot;:&quot;mutation UploadFile($file: Upload!) &#123; uploadFile(file: $file) &#123; id url filename &#125; &#125;&quot;,&quot;variables&quot;:&#123;&quot;file&quot;:<span class="keyword">null</span>&#125;&#125;</span></span><br><span class="line"><span class="language-pgsql"><span class="comment">------WebKitFormBoundary7MA4YWxkTrZu0gW</span></span></span><br><span class="line"><span class="language-pgsql">Content-Disposition: form-data; <span class="type">name</span>=&quot;map&quot;</span></span><br><span class="line"><span class="language-pgsql"></span></span><br><span class="line"><span class="language-pgsql">&#123;&quot;0&quot;:[&quot;variables.file&quot;]&#125;</span></span><br><span class="line"><span class="language-pgsql"><span class="comment">------WebKitFormBoundary7MA4YWxkTrZu0gW</span></span></span><br><span class="line"><span class="language-pgsql">Content-Disposition: form-data; <span class="type">name</span>=&quot;0&quot;; filename=&quot;document.pdf&quot;</span></span><br><span class="line"><span class="language-pgsql">Content-<span class="keyword">Type</span>: application/pdf</span></span><br><span class="line"><span class="language-pgsql"></span></span><br><span class="line"><span class="language-pgsql">[PDFファイルのバイナリデータ]</span></span><br><span class="line"><span class="language-pgsql"><span class="comment">------WebKitFormBoundary7MA4YWxkTrZu0gW--</span></span></span><br></pre></td></tr></table></figure><p><strong>4. GraphQL multipart request specificationの構造</strong></p><ol><li><strong>operations</strong>: GraphQLクエリのJSON</li><li><strong>map</strong>: ファイルとGraphQL変数のマッピング</li><li><strong>0, 1, 2…</strong>: 実際のファイルデータ</li></ol><h2 id="Base64エンコードとバイナリの比較"><a href="#Base64エンコードとバイナリの比較" class="headerlink" title="Base64エンコードとバイナリの比較"></a>Base64エンコードとバイナリの比較</h2><table><thead><tr><th>項目</th><th>Base64エンコード</th><th>バイナリ（multipart&#x2F;form-data）</th></tr></thead><tbody><tr><td>データサイズ</td><td>元サイズ + 33%</td><td>元サイズのまま</td></tr><tr><td>メモリ使用量</td><td>約33%増加</td><td>元サイズのまま</td></tr><tr><td>転送時間</td><td>約33%増加</td><td>元サイズのまま</td></tr><tr><td>CPU使用量</td><td>エンコード&#x2F;デコード処理が必要</td><td>処理不要</td></tr><tr><td>実装の複雑さ</td><td>簡単</td><td>やや複雑</td></tr><tr><td>適している用途</td><td>小さなデータ（QRコード、アイコン）</td><td>大きなファイル（画像、動画、PDF）</td></tr></tbody></table><h2 id="セキュリティ"><a href="#セキュリティ" class="headerlink" title="セキュリティ"></a>セキュリティ</h2><p>ファイルアップロード処理における、基本的なセキュリティ対策を紹介します。</p><h3 id="1-ファイルサイズの制限"><a href="#1-ファイルサイズの制限" class="headerlink" title="1. ファイルサイズの制限"></a>1. ファイルサイズの制限</h3><p>サーバー側でファイルサイズを検証します。</p><h3 id="2-ファイルタイプの検証"><a href="#2-ファイルタイプの検証" class="headerlink" title="2. ファイルタイプの検証"></a>2. ファイルタイプの検証</h3><p>ホワイトリスト方式で .jpg, .png, .pdf のように明示的に許可された拡張子のみ受付します。<br>また、クライアントから送られたContent-Typeではなく、サーバー側でMIMEタイプを再検査します。</p><h3 id="3-ファイル名のリネーム"><a href="#3-ファイル名のリネーム" class="headerlink" title="3. ファイル名のリネーム"></a>3. ファイル名のリネーム</h3><p>アップロードされたファイルをランダム名に変えて保存（例：upload_48cd1e2a.jpg）します。</p><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>GraphQLでのファイルアップロードは、Base64エンコード方式とmultipart&#x2F;form-data方式の2つのアプローチがあります。</p><ul><li><strong>Base64エンコード</strong>: 小さなファイルや簡易的な用途に適している</li><li><strong>multipart&#x2F;form-data</strong>: 大きなファイルや本格的なファイルアップロード機能に適している</li></ul><p>用途に応じて適切な方法を選択し、ファイルサイズ制限やファイルタイプ検証などのセキュリティ対策も忘れずに実装することが重要です。 </p>]]></content>
    
    
    <summary type="html">GraphQLでファイルアップロードを実装する方法について詳しく解説します。Base64エンコード方式とmultipart/form-data方式の2つのアプローチを比較し、それぞれの実装の特徴と基本的なセキュリティについて紹介します。</summary>
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
    <category term="GraphQL" scheme="https://48n.jp/tags/GraphQL/"/>
    
  </entry>
  
  <entry>
    <title>GraphQLガイド - GraphQLのプロトコルと特徴</title>
    <link href="https://48n.jp/blog/2025/07/31/graphql-protocol-guide/"/>
    <id>https://48n.jp/blog/2025/07/31/graphql-protocol-guide/</id>
    <published>2025-07-31T05:30:00.000Z</published>
    <updated>2025-07-31T05:30:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>GraphQLは、Facebook（現Meta）が2012年に開発し、2015年に公開したクエリ言語およびAPI仕様です。REST APIの代替として設計され、クライアントがサーバーから必要なデータを正確に取得できるようにすることを目的としています。</p><p>この記事では、GraphQLのプロトコルと特徴について解説し、理解を深めていきます。</p><h2 id="GraphQLとは"><a href="#GraphQLとは" class="headerlink" title="GraphQLとは"></a>GraphQLとは</h2><p>GraphQLは、クライアントがサーバーに対して必要なデータを正確に指定して取得できるクエリ言語です。従来のREST APIでは、サーバーが決めたエンドポイントから固定のデータ構造を取得する必要がありましたが、GraphQLではクライアントが自由にデータの形を指定できます。</p><h2 id="主な特徴"><a href="#主な特徴" class="headerlink" title="主な特徴"></a>主な特徴</h2><h3 id="1-単一エンドポイント"><a href="#1-単一エンドポイント" class="headerlink" title="1. 単一エンドポイント"></a>1. 単一エンドポイント</h3><p>GraphQLでは通常、<code>/graphql</code>のような単一のエンドポイントを使用します。すべてのクエリ、ミューテーション、サブスクリプションがこのエンドポイントを通じて処理されます。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// すべての操作が同じエンドポイントを使用</span></span><br><span class="line"><span class="variable constant_">POST</span> /graphql</span><br></pre></td></tr></table></figure><h3 id="2-クエリ言語"><a href="#2-クエリ言語" class="headerlink" title="2. クエリ言語"></a>2. クエリ言語</h3><p>GraphQLは独自のクエリ言語を提供し、クライアントが必要なデータを正確に指定できます。</p><figure class="highlight graphql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">query</span> <span class="punctuation">&#123;</span></span><br><span class="line">  user<span class="punctuation">(</span><span class="symbol">id</span><span class="punctuation">:</span> <span class="string">&quot;123&quot;</span><span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">    name</span><br><span class="line">    email</span><br><span class="line">    posts <span class="punctuation">&#123;</span></span><br><span class="line">      title</span><br><span class="line">      content</span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="3-型システム"><a href="#3-型システム" class="headerlink" title="3. 型システム"></a>3. 型システム</h3><p>GraphQLは強力な型システムを持ち、APIの仕様が明確になります。また、イントロスペクション機能により、スキーマ情報を動的に取得できます。</p><h3 id="4-リアルタイム通信"><a href="#4-リアルタイム通信" class="headerlink" title="4. リアルタイム通信"></a>4. リアルタイム通信</h3><p>Subscriptions機能により、WebSocketを使用したリアルタイム通信をサポートしています。</p><h2 id="プロトコルの詳細"><a href="#プロトコルの詳細" class="headerlink" title="プロトコルの詳細"></a>プロトコルの詳細</h2><h3 id="HTTPプロトコルでの実装"><a href="#HTTPプロトコルでの実装" class="headerlink" title="HTTPプロトコルでの実装"></a>HTTPプロトコルでの実装</h3><p>GraphQLは主にHTTPプロトコル上で動作します。リクエストボディはJSONです。<br>以下が基本的なリクエスト形式です。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable constant_">POST</span> /graphql</span><br><span class="line"><span class="title class_">Content</span>-<span class="title class_">Type</span>: application/json</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;query&quot;</span>: <span class="string">&quot;query &#123; user(id: \&quot;123\&quot;) &#123; name email &#125; &#125;&quot;</span>,</span><br><span class="line">  <span class="string">&quot;variables&quot;</span>: &#123;&#125;,</span><br><span class="line">  <span class="string">&quot;operationName&quot;</span>: <span class="string">&quot;GetUser&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="レスポンス形式"><a href="#レスポンス形式" class="headerlink" title="レスポンス形式"></a>レスポンス形式</h4><p>レスポンス形式はJSONです。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;data&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;user&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;name&quot;</span>: <span class="string">&quot;John Doe&quot;</span>,</span><br><span class="line">      <span class="string">&quot;email&quot;</span>: <span class="string">&quot;john@example.com&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">&quot;errors&quot;</span>: <span class="literal">null</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="WebSocketプロトコルでの実装"><a href="#WebSocketプロトコルでの実装" class="headerlink" title="WebSocketプロトコルでの実装"></a>WebSocketプロトコルでの実装</h3><p>リアルタイム通信が必要な場合は、WebSocketプロトコルを使用します。</p><h4 id="接続確立"><a href="#接続確立" class="headerlink" title="接続確立"></a>接続確立</h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable constant_">GET</span> /graphql <span class="variable constant_">HTTP</span>/<span class="number">1.1</span></span><br><span class="line"><span class="title class_">Upgrade</span>: websocket</span><br><span class="line"><span class="title class_">Connection</span>: <span class="title class_">Upgrade</span></span><br><span class="line"><span class="title class_">Sec</span>-<span class="title class_">WebSocket</span>-<span class="title class_">Key</span>: &lt;key&gt;</span><br><span class="line"><span class="title class_">Sec</span>-<span class="title class_">WebSocket</span>-<span class="title class_">Protocol</span>: graphql-ws</span><br></pre></td></tr></table></figure><h4 id="サブスクリプション例"><a href="#サブスクリプション例" class="headerlink" title="サブスクリプション例"></a>サブスクリプション例</h4><figure class="highlight graphql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">subscription</span> <span class="punctuation">&#123;</span></span><br><span class="line">  userUpdated<span class="punctuation">(</span><span class="symbol">userId</span><span class="punctuation">:</span> <span class="string">&quot;123&quot;</span><span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">    id</span><br><span class="line">    name</span><br><span class="line">    email</span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="GraphQLプロトコルレイヤー"><a href="#GraphQLプロトコルレイヤー" class="headerlink" title="GraphQLプロトコルレイヤー"></a>GraphQLプロトコルレイヤー</h2><p>GraphQLは以下のプロトコルレイヤーで動作します。</p><ol><li><strong>HTTP&#x2F;HTTPS</strong>: 主なトランスポートプロトコル</li><li><strong>WebSocket</strong>: Subscriptions用のリアルタイム通信</li><li><strong>GraphQL</strong>: アプリケーションレベルのクエリ言語</li></ol><h3 id="プロトコルスタック"><a href="#プロトコルスタック" class="headerlink" title="プロトコルスタック"></a>プロトコルスタック</h3><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────┐</span><br><span class="line">│   GraphQL       │ ← クエリ言語</span><br><span class="line">├─────────────────┤</span><br><span class="line">│   HTTP<span class="symbol">/HTTPS</span>    │ ← トランスポート</span><br><span class="line">├─────────────────┤</span><br><span class="line">│   TCP<span class="symbol">/IP</span>        │ ← ネットワーク</span><br><span class="line">└─────────────────┘</span><br></pre></td></tr></table></figure><h2 id="実装例"><a href="#実装例" class="headerlink" title="実装例"></a>実装例</h2><h3 id="基本的なクエリ"><a href="#基本的なクエリ" class="headerlink" title="基本的なクエリ"></a>基本的なクエリ</h3><p>データの取得にはクエリを利用します。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// リクエスト</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;query&quot;</span>: <span class="string">`</span></span><br><span class="line"><span class="string">    query GetUser($id: ID!) &#123;</span></span><br><span class="line"><span class="string">      user(id: $id) &#123;</span></span><br><span class="line"><span class="string">        id</span></span><br><span class="line"><span class="string">        name</span></span><br><span class="line"><span class="string">        email</span></span><br><span class="line"><span class="string">        posts &#123;</span></span><br><span class="line"><span class="string">          id</span></span><br><span class="line"><span class="string">          title</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">  `</span>,</span><br><span class="line">  <span class="string">&quot;variables&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;id&quot;</span>: <span class="string">&quot;123&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ミューテーション"><a href="#ミューテーション" class="headerlink" title="ミューテーション"></a>ミューテーション</h3><p>データの作成、更新にはミューテーションを利用します。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// リクエスト</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;query&quot;</span>: <span class="string">`</span></span><br><span class="line"><span class="string">    mutation CreateUser($input: CreateUserInput!) &#123;</span></span><br><span class="line"><span class="string">      createUser(input: $input) &#123;</span></span><br><span class="line"><span class="string">        id</span></span><br><span class="line"><span class="string">        name</span></span><br><span class="line"><span class="string">        email</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">  `</span>,</span><br><span class="line">  <span class="string">&quot;variables&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;input&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;name&quot;</span>: <span class="string">&quot;Jane Doe&quot;</span>,</span><br><span class="line">      <span class="string">&quot;email&quot;</span>: <span class="string">&quot;jane@example.com&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="サブスクリプション"><a href="#サブスクリプション" class="headerlink" title="サブスクリプション"></a>サブスクリプション</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// WebSocket接続後のメッセージ</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;type&quot;</span>: <span class="string">&quot;start&quot;</span>,</span><br><span class="line">  <span class="string">&quot;id&quot;</span>: <span class="string">&quot;1&quot;</span>,</span><br><span class="line">  <span class="string">&quot;payload&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;query&quot;</span>: <span class="string">`</span></span><br><span class="line"><span class="string">      subscription &#123;</span></span><br><span class="line"><span class="string">        userUpdated &#123;</span></span><br><span class="line"><span class="string">          id</span></span><br><span class="line"><span class="string">          name</span></span><br><span class="line"><span class="string">          email</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string">    `</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="エラーハンドリング"><a href="#エラーハンドリング" class="headerlink" title="エラーハンドリング"></a>エラーハンドリング</h2><p>GraphQLでは、エラーが発生した場合でも部分的なデータを返すことができます。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;data&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;user&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;name&quot;</span>: <span class="string">&quot;John Doe&quot;</span>,</span><br><span class="line">      <span class="string">&quot;email&quot;</span>: <span class="literal">null</span>  <span class="comment">// エラーにより取得できなかった</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">&quot;errors&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;message&quot;</span>: <span class="string">&quot;Cannot return null for non-nullable field User.email&quot;</span>,</span><br><span class="line">      <span class="string">&quot;locations&quot;</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="string">&quot;line&quot;</span>: <span class="number">3</span>,</span><br><span class="line">          <span class="string">&quot;column&quot;</span>: <span class="number">7</span></span><br><span class="line">        &#125;</span><br><span class="line">      ],</span><br><span class="line">      <span class="string">&quot;path&quot;</span>: [<span class="string">&quot;user&quot;</span>, <span class="string">&quot;email&quot;</span>]</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="パフォーマンス最適化"><a href="#パフォーマンス最適化" class="headerlink" title="パフォーマンス最適化"></a>パフォーマンス最適化</h2><h3 id="1-クエリの最適化"><a href="#1-クエリの最適化" class="headerlink" title="1. クエリの最適化"></a>1. クエリの最適化</h3><p>必要なフィールドのみを指定することで、ネットワーク転送量を削減できます。</p><figure class="highlight graphql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 良い例：必要なフィールドのみ</span></span><br><span class="line"><span class="keyword">query</span> <span class="punctuation">&#123;</span></span><br><span class="line">  user<span class="punctuation">(</span><span class="symbol">id</span><span class="punctuation">:</span> <span class="string">&quot;123&quot;</span><span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">    name</span><br><span class="line">    email</span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 悪い例：不要なフィールドも含む</span></span><br><span class="line"><span class="keyword">query</span> <span class="punctuation">&#123;</span></span><br><span class="line">  user<span class="punctuation">(</span><span class="symbol">id</span><span class="punctuation">:</span> <span class="string">&quot;123&quot;</span><span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">    name</span><br><span class="line">    email</span><br><span class="line">    posts <span class="punctuation">&#123;</span></span><br><span class="line">      title</span><br><span class="line">      content</span><br><span class="line">      comments <span class="punctuation">&#123;</span></span><br><span class="line">        text</span><br><span class="line">        author <span class="punctuation">&#123;</span></span><br><span class="line">          name</span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="2-バッチ処理"><a href="#2-バッチ処理" class="headerlink" title="2. バッチ処理"></a>2. バッチ処理</h3><p>複数のクエリを一度に実行することで、ネットワークリクエスト数を削減できます。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;query&quot;</span>: <span class="string">`</span></span><br><span class="line"><span class="string">    query &#123;</span></span><br><span class="line"><span class="string">      user1: user(id: &quot;1&quot;) &#123; name email &#125;</span></span><br><span class="line"><span class="string">      user2: user(id: &quot;2&quot;) &#123; name email &#125;</span></span><br><span class="line"><span class="string">      user3: user(id: &quot;3&quot;) &#123; name email &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">  `</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="セキュリティ"><a href="#セキュリティ" class="headerlink" title="セキュリティ"></a>セキュリティ</h2><h3 id="1-認証・認可"><a href="#1-認証・認可" class="headerlink" title="1. 認証・認可"></a>1. 認証・認可</h3><p>Authorizationヘッダーに認証・認可トークンを付与します。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ヘッダーにトークンを含める</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;query&quot;</span>: <span class="string">&quot;...&quot;</span>,</span><br><span class="line">  <span class="string">&quot;variables&quot;</span>: &#123;&#125;,</span><br><span class="line">  <span class="string">&quot;headers&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;Authorization&quot;</span>: <span class="string">&quot;Bearer &lt;token&gt;&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-クエリの複雑度制限"><a href="#2-クエリの複雑度制限" class="headerlink" title="2. クエリの複雑度制限"></a>2. クエリの複雑度制限</h3><p>悪意のあるクエリによる攻撃を防ぐため、クエリの複雑度を制限することもできます。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// サーバー側での実装例</span></span><br><span class="line"><span class="keyword">const</span> depthLimit = <span class="built_in">require</span>(<span class="string">&#x27;graphql-depth-limit&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> server = <span class="keyword">new</span> <span class="title class_">ApolloServer</span>(&#123;</span><br><span class="line">  typeDefs,</span><br><span class="line">  resolvers,</span><br><span class="line">  <span class="attr">validationRules</span>: [<span class="title function_">depthLimit</span>(<span class="number">7</span>)]</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>GraphQLは、モダンなWebアプリケーション開発において、REST APIの代替として広く採用されており、REST APIと比較して通信量の削減が見込めます。<br>今後もGraphQLの普及は進むことが予想され、API設計の標準的なアプローチの一つとして確立されていくと思われます。 </p>]]></content>
    
    
    <summary type="html">GraphQLは、Facebook（現Meta）が2012年に開発し、2015年に公開したクエリ言語およびAPI仕様です。REST APIの代替として設計され、クライアントがサーバーから必要なデータを正確に取得できるようにすることを目的としています。この記事では、GraphQLのプロトコルと特徴について解説しています。</summary>
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
    <category term="GraphQL" scheme="https://48n.jp/tags/GraphQL/"/>
    
  </entry>
  
  <entry>
    <title>Gitで日本語ファイル名が正しく表示されない問題の解決方法</title>
    <link href="https://48n.jp/blog/2025/07/02/git-japanese-filename-encoding-fix/"/>
    <id>https://48n.jp/blog/2025/07/02/git-japanese-filename-encoding-fix/</id>
    <published>2025-07-02T03:00:00.000Z</published>
    <updated>2026-02-25T00:43:41.849Z</updated>
    
    <content type="html"><![CDATA[<h2 id="はじめに"><a href="#はじめに" class="headerlink" title="はじめに"></a>はじめに</h2><p>Gitで日本語ファイル名が正しく表示されず、エンコードされた文字列として表示されることがあります。<br>エンコードされた文字は一見何が書いてあるかはわからないし、ものすごく表示の幅をとるのでなんとかしたい。</p><h2 id="問題の原因"><a href="#問題の原因" class="headerlink" title="問題の原因"></a>問題の原因</h2><p>Gitはデフォルトで、非ASCII文字（日本語など）を含むファイル名を引用符で囲んで表示したり、適切なエンコーディングで処理しない場合があります。これにより、日本語ファイル名が正しく表示されない問題が発生します。</p><h2 id="解決方法"><a href="#解決方法" class="headerlink" title="解決方法"></a>解決方法</h2><h3 id="日本語ファイル名を正しく表示するための設定"><a href="#日本語ファイル名を正しく表示するための設定" class="headerlink" title="日本語ファイル名を正しく表示するための設定"></a>日本語ファイル名を正しく表示するための設定</h3><p>以下の3つの設定を変更することで、日本語ファイル名が正しく表示されるようになります。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 日本語などの非ASCII文字を含むファイル名を引用符で囲まずに表示</span></span><br><span class="line">git config --global core.quotepath <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Gitのログ出力をUTF-8エンコーディングで表示</span></span><br><span class="line">git config --global i18n.logoutputencoding utf-8</span><br><span class="line"></span><br><span class="line"><span class="comment"># コミットメッセージをUTF-8エンコーディングで処理</span></span><br><span class="line">git config --global i18n.commitencoding utf-8</span><br></pre></td></tr></table></figure><p>設定を変更した後、<code>git status</code> コマンドなどで日本語ファイルを表示してみると、正しく表示されるはずです。</p><h2 id="設定内容の詳細説明"><a href="#設定内容の詳細説明" class="headerlink" title="設定内容の詳細説明"></a>設定内容の詳細説明</h2><p>設定した内容は以下の通りです。</p><h3 id="core-quotepath-false"><a href="#core-quotepath-false" class="headerlink" title="core.quotepath false"></a><code>core.quotepath false</code></h3><ul><li><strong>目的</strong>: 日本語などの非ASCII文字を含むファイル名を引用符で囲まずに表示</li><li><strong>効果</strong>: ファイル名が <code>&quot;日本語ファイル名.txt&quot;</code> ではなく <code>日本語ファイル名.txt</code> として表示される</li></ul><h3 id="i18n-logoutputencoding-utf-8"><a href="#i18n-logoutputencoding-utf-8" class="headerlink" title="i18n.logoutputencoding utf-8"></a><code>i18n.logoutputencoding utf-8</code></h3><ul><li><strong>目的</strong>: Gitのログ出力をUTF-8エンコーディングで表示</li><li><strong>効果</strong>: <code>git log</code> や <code>git status</code> などの出力で日本語が正しく表示される</li></ul><h3 id="i18n-commitencoding-utf-8"><a href="#i18n-commitencoding-utf-8" class="headerlink" title="i18n.commitencoding utf-8"></a><code>i18n.commitencoding utf-8</code></h3><ul><li><strong>目的</strong>: コミットメッセージをUTF-8エンコーディングで処理</li><li><strong>効果</strong>: 日本語のコミットメッセージが正しく保存・表示される（この設定は直接は関係ないが、設定しておいてよいと思う）</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;はじめに&quot;&gt;&lt;a href=&quot;#はじめに&quot; class=&quot;headerlink&quot; title=&quot;はじめに&quot;&gt;&lt;/a&gt;はじめに&lt;/h2&gt;&lt;p&gt;Gitで日本語ファイル名が正しく表示されず、エンコードされた文字列として表示されることがあります。&lt;br&gt;エンコードされた文</summary>
      
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
  </entry>
  
  <entry>
    <title>Slack Block Kitの制約とデザインパターンガイド</title>
    <link href="https://48n.jp/blog/2025/06/05/slack-block-kit-detailed-guide/"/>
    <id>https://48n.jp/blog/2025/06/05/slack-block-kit-detailed-guide/</id>
    <published>2025-06-05T02:00:00.000Z</published>
    <updated>2026-02-25T00:43:41.853Z</updated>
    
    <content type="html"><![CDATA[<h2 id="はじめに"><a href="#はじめに" class="headerlink" title="はじめに"></a>はじめに</h2><p>前回の記事「<a href="/2025/06/04/go-slack-notification-guide/">GoでSlack通知を実装する方法</a>」では、Slack通知の基本的な実装方法とBlock Kitの初歩的な使い方について解説しました。</p><p>今回は、Slack Block Kitをより深く掘り下げて、その特性、制約、そして効果的なデザインパターンについて詳しく説明します。特に、Block Kitの「見た目があまり変えられない」という特性と、その制約の中でいかに美しく機能的なメッセージを作成するかに焦点を当てます。</p><h2 id="Slack-Block-Kitの特性と制約"><a href="#Slack-Block-Kitの特性と制約" class="headerlink" title="Slack Block Kitの特性と制約"></a>Slack Block Kitの特性と制約</h2><h3 id="1-デザインの統一性と制約"><a href="#1-デザインの統一性と制約" class="headerlink" title="1. デザインの統一性と制約"></a>1. デザインの統一性と制約</h3><p>Slack Block Kitの最も大きな特徴は、<strong>デザインの自由度が意図的に制限されている</strong>ことです。これにはいくつかの理由があります。</p><ul><li><strong>一貫性の確保</strong>: すべてのアプリからのメッセージが統一された見た目になる</li><li><strong>可読性の向上</strong>: Slackの標準UIパターンに従うことで、ユーザーが迷わない</li><li><strong>アクセシビリティ</strong>: スクリーンリーダーやキーボードナビゲーションに配慮</li></ul><h3 id="2-制約の具体例"><a href="#2-制約の具体例" class="headerlink" title="2. 制約の具体例"></a>2. 制約の具体例</h3><p>以下のような点でカスタマイズが制限されています。</p><ul><li><strong>色の変更</strong>: テキストやブロックの背景色は基本的に変更不可</li><li><strong>フォントサイズ</strong>: 固定のフォントサイズ体系</li><li><strong>余白・レイアウト</strong>: ブロック間の余白やレイアウトは Slack側で制御</li><li><strong>アニメーション</strong>: 動的な効果は実装不可</li></ul><p>これらの制約があるからこそ、<strong>コンテンツの構成とブロックの組み合わせ</strong>が重要になります。</p><h2 id="Block-Kitの主要ブロックタイプ解説"><a href="#Block-Kitの主要ブロックタイプ解説" class="headerlink" title="Block Kitの主要ブロックタイプ解説"></a>Block Kitの主要ブロックタイプ解説</h2><p>実際のサンプルコードを基に、各ブロックタイプの特性と使用例を詳しく見てみましょう。</p><h3 id="1-Header-ブロック"><a href="#1-Header-ブロック" class="headerlink" title="1. Header ブロック"></a>1. Header ブロック</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;header&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Slack Block Kitデザインシステム見本&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>特徴:</strong></p><ul><li>メッセージの最上部に配置される大きなタイトル</li><li>plain_textのみ対応（Markdownは使用不可）</li><li>絵文字を使用することで視覚的なアクセントを追加可能</li></ul><p><strong>使用場面:</strong></p><ul><li>通知のタイトル</li></ul><h3 id="2-Section-ブロック"><a href="#2-Section-ブロック" class="headerlink" title="2. Section ブロック"></a>2. Section ブロック</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*1. 基本的なセクション*\nこれは基本的なセクションブロックです。*太字*や_斜体_、~取り消し線~、`コード`などのMarkdown書式が使えます。&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>特徴:</strong></p><ul><li>最も汎用的で使用頻度が高いブロック</li><li>Markdownによる豊富なテキスト装飾</li><li>accessoryフィールドで画像やボタンを右側に配置可能</li></ul><p><strong>応用例 - 画像付きセクション:</strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;画像を右側に配置できます&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;accessory&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;image&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;image_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.slack.com/img/blocks/bkb_template_images/beagle.png&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;alt_text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;かわいい犬の画像&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="3-Context-ブロック"><a href="#3-Context-ブロック" class="headerlink" title="3. Context ブロック"></a>3. Context ブロック</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;context&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;👆 Contextブロックは小さいテキストで補足情報を表示するのに最適です&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>特徴:</strong></p><ul><li>小さなフォントサイズで表示</li><li>補足情報や注釈に最適</li><li>複数の要素を横並びで配置可能</li></ul><p><strong>使用場面:</strong></p><ul><li>タイムスタンプ</li><li>作成者情報</li><li>追加の説明文</li></ul><h3 id="4-Divider-ブロック"><a href="#4-Divider-ブロック" class="headerlink" title="4. Divider ブロック"></a>4. Divider ブロック</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;divider&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>特徴:</strong></p><ul><li>シンプルな水平線</li><li>セクション間の視覚的な区切り</li><li>パラメータ不要で最もシンプルなブロック</li></ul><h3 id="5-Actions-ブロック"><a href="#5-Actions-ブロック" class="headerlink" title="5. Actions ブロック"></a>5. Actions ブロック</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;actions&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Primary ボタン&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;style&quot;</span><span class="punctuation">:</span> <span class="string">&quot;primary&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;primary_button&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://example.com/primary&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>特徴:</strong></p><ul><li>ボタンやその他のインタラクティブ要素を配置</li><li>最大5つの要素まで横並び配置可能</li><li>3つのボタンスタイル：default（境界線のみ）、primary（青色）、danger（赤色）</li></ul><h2 id="制約の中での効果的なデザインパターン"><a href="#制約の中での効果的なデザインパターン" class="headerlink" title="制約の中での効果的なデザインパターン"></a>制約の中での効果的なデザインパターン</h2><h3 id="1-疑似的な枠線の実装"><a href="#1-疑似的な枠線の実装" class="headerlink" title="1. 疑似的な枠線の実装"></a>1. 疑似的な枠線の実装</h3><p>Block Kitには明確な「枠線」がありませんが、以下のようなテクニックで視覚的なグループ化を実現できます。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*2. 擬似的な枠線付きセクション*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;```項目:値```&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;```ステータス:完了```&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>工夫のポイント:</strong></p><ul><li>Markdownを使用した疑似的な枠線</li></ul><h3 id="2-状態表示のパターン"><a href="#2-状態表示のパターン" class="headerlink" title="2. 状態表示のパターン"></a>2. 状態表示のパターン</h3><p>色が変更できない制約の中で、絵文字とテキストを組み合わせて状態を表現。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;• *&lt;https://example.com/task/goals|目標の基本を学ぶ&gt;*\n  :alarm_clock: 2024年9月24日&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;• *&lt;https://example.com/task/personal-goals|個人目標設定の方法を学ぶ&gt;*\n  :warning: 2024年9月24日（期限超過）&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>使用される表現方法:</strong></p><ul><li><code>:alarm_clock:</code> - 通常の期限</li><li><code>:warning:</code> - 期限超過や注意</li><li><code>:white_check_mark:</code> - 完了</li><li><code>:x:</code> - エラーやキャンセル</li></ul><h3 id="3-複数ボタンのレイアウトパターン"><a href="#3-複数ボタンのレイアウトパターン" class="headerlink" title="3. 複数ボタンのレイアウトパターン"></a>3. 複数ボタンのレイアウトパターン</h3><p>Actionsブロックでは、最大5つまでのボタンを配置できます。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;actions&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;確認&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;style&quot;</span><span class="punctuation">:</span> <span class="string">&quot;primary&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;confirm&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;後で&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;later&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;キャンセル&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;style&quot;</span><span class="punctuation">:</span> <span class="string">&quot;danger&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cancel&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>デザインの考慮点:</strong></p><ul><li>Primary（青色）は最重要アクション用</li><li>Default（境界線のみ）は通常アクション用</li><li>Danger（赤色）は削除や危険なアクション用</li></ul><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>Slack Block Kitは確かに「見た目があまり変えられない」という制約がありますが、これらの制約を理解し、適切にブロックを組み合わせることで、美しく機能的なメッセージを作成できます。</p><p>重要なのは以下の点です。</p><ol><li><strong>制約を受け入れる</strong>: デザインの自由度は制限されているが、その分一貫性と可読性が保たれる</li><li><strong>コンテンツ構成に集中</strong>: 色やレイアウトではなく、情報の構造化と優先順位付けに注力</li><li><strong>パターンの活用</strong>: 疑似的な枠線や絵文字による状態表現など、制約内でのテクニックを習得</li></ol><p>Block Kitの詳細なリファレンスは<a href="https://app.slack.com/block-kit-builder">Slack Block Kit Builder</a>で実際に構築しながら確認できますので、ぜひご活用ください。</p><p>この記事で紹介したテクニックのサンプルもぜひご利用ください！</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;blocks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;header&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Slack Block Kitデザインシステム見本&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;こんにちは、鈴木 太郎さん\nこちらはデザインパターンの総合的な見本です。&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;divider&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*1. 基本的なセクション*\nこれは基本的なセクションブロックです。 *太字* や _斜体_ 、 ~取り消し線~ 、 `コード` などのMarkdown書式が使えます。&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;context&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;👆 Contextブロックは小さいテキストで補足情報を表示するのに最適です&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;divider&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*2. 擬似的な枠線付きセクション*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;```項目:値```&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;```ステータス:完了```&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;divider&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*3. リンク付きのテキスト*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;• *&lt;https://example.com/task/1|リンク付きタスク名&gt;*\n  2024年9月24日&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;• リンクなしタスク名\n  2024年9月25日&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;divider&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*4. 画像付きセクション*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;画像を右側に配置できます&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;accessory&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;image&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;image_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.slack.com/img/blocks/bkb_template_images/beagle.png&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;alt_text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;かわいい犬の画像&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;divider&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*5. ボタンとアクション*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;actions&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Primary ボタン&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;style&quot;</span><span class="punctuation">:</span> <span class="string">&quot;primary&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;primary_button&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://example.com/primary&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;actions&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Default ボタン（アウトライン）&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;default_button&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://example.com/default&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;actions&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Danger ボタン&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;style&quot;</span><span class="punctuation">:</span> <span class="string">&quot;danger&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;danger_button&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://example.com/danger&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;divider&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*6. 通知カード*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;context&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;┌──────────────────────────────────────────┐&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;　*【オンボーディング】入社後1ヶ月間のTODO*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;　*社員*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;　佐藤 花子、田中 一郎、山田 太郎&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;context&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;└──────────────────────────────────────────┘&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;actions&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;コースを確認&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;style&quot;</span><span class="punctuation">:</span> <span class="string">&quot;primary&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;check_course&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://example.com/onboarding/course&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;divider&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*7. タスクリスト（期限付き）*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;• *&lt;https://example.com/task/goals|目標の基本を学ぶ&gt;*\n  :alarm_clock: 2024年9月24日&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;• *&lt;https://example.com/task/management|代表的なマネジメントの型を知る&gt;*\n  :alarm_clock: 2024年9月25日&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;• *&lt;https://example.com/task/personal-goals|個人目標設定の方法を学ぶ&gt;*\n  :warning: 2024年9月24日（期限超過）&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;divider&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;section&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*8. 複数ボタン配置*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;actions&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;確認&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;style&quot;</span><span class="punctuation">:</span> <span class="string">&quot;primary&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;confirm&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;後で&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;later&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;plain_text&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;キャンセル&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;emoji&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;style&quot;</span><span class="punctuation">:</span> <span class="string">&quot;danger&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cancel&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;divider&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;context&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;elements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mrkdwn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;text&quot;</span><span class="punctuation">:</span> <span class="string">&quot;このメッセージはデザインシステムの参考用に自動生成されました&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;はじめに&quot;&gt;&lt;a href=&quot;#はじめに&quot; class=&quot;headerlink&quot; title=&quot;はじめに&quot;&gt;&lt;/a&gt;はじめに&lt;/h2&gt;&lt;p&gt;前回の記事「&lt;a href=&quot;/2025/06/04/go-slack-notification-guide/&quot;&gt;GoでSl</summary>
      
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
  </entry>
  
  <entry>
    <title>GoでSlack通知を実装する方法</title>
    <link href="https://48n.jp/blog/2025/06/04/go-slack-notification-guide/"/>
    <id>https://48n.jp/blog/2025/06/04/go-slack-notification-guide/</id>
    <published>2025-06-04T01:30:00.000Z</published>
    <updated>2026-02-25T00:43:41.849Z</updated>
    
    <content type="html"><![CDATA[<h2 id="はじめに"><a href="#はじめに" class="headerlink" title="はじめに"></a>はじめに</h2><p>Slackは現代のチーム開発において欠かせないコミュニケーションツールです。システムからの通知やアラート、定期的なレポートなど、様々な情報をSlackに送信することで、チーム全体での情報共有を効率化できます。</p><p>この記事では、Goを使ってSlackに通知を送信する方法を、基本的なテキストメッセージから高度なBlock Kitを使ったリッチなメッセージまで、実際のコード例とともに解説します。</p><h2 id="使用するライブラリ"><a href="#使用するライブラリ" class="headerlink" title="使用するライブラリ"></a>使用するライブラリ</h2><p>今回は、<a href="https://github.com/slack-go/slack"><code>github.com/slack-go/slack</code></a>というGoの公式Slackクライアントライブラリを使用します。このライブラリは活発に開発されており、Slack APIの最新機能もサポートしています。</p><h2 id="セットアップ"><a href="#セットアップ" class="headerlink" title="セットアップ"></a>セットアップ</h2><h3 id="1-Slackアプリの作成とトークンの取得"><a href="#1-Slackアプリの作成とトークンの取得" class="headerlink" title="1. Slackアプリの作成とトークンの取得"></a>1. Slackアプリの作成とトークンの取得</h3><p>まず、<a href="https://api.slack.com/apps">Slack API</a>でアプリを作成し、Bot User OAuth Tokenを取得する必要があります。</p><ol><li>Slack APIのページにアクセス</li><li>“Create New App” → “From scratch”でアプリを作成</li><li>“OAuth &amp; Permissions”から必要な権限を設定<ul><li><code>users:read</code> - ユーザー一覧の取得</li><li><code>chat:write</code> - メッセージの送信</li><li><code>im:write</code> - ダイレクトメッセージの送信</li></ul></li><li>Bot User OAuth Tokenをコピー</li></ol><h3 id="2-環境変数の設定"><a href="#2-環境変数の設定" class="headerlink" title="2. 環境変数の設定"></a>2. 環境変数の設定</h3><p>取得したトークンを環境変数として設定します。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> SLACK_BOT_TOKEN=<span class="string">&quot;xoxb-your-token-here&quot;</span></span><br></pre></td></tr></table></figure><h2 id="基本的な実装"><a href="#基本的な実装" class="headerlink" title="基本的な実装"></a>基本的な実装</h2><h3 id="Slackクライアントの初期化"><a href="#Slackクライアントの初期化" class="headerlink" title="Slackクライアントの初期化"></a>Slackクライアントの初期化</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;log&quot;</span></span><br><span class="line">    <span class="string">&quot;os&quot;</span></span><br><span class="line">    <span class="string">&quot;github.com/slack-go/slack&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="comment">// Slack APIトークンを環境変数から取得</span></span><br><span class="line">    token := os.Getenv(<span class="string">&quot;SLACK_BOT_TOKEN&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> token == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">        log.Fatal(<span class="string">&quot;SLACK_BOT_TOKEN is not set&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Slackクライアントの初期化</span></span><br><span class="line">    api := slack.New(token)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ユーザー一覧の取得"><a href="#ユーザー一覧の取得" class="headerlink" title="ユーザー一覧の取得"></a>ユーザー一覧の取得</h3><p>通知を送信する前に、まずワークスペース内のユーザー一覧を取得してみましょう。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getUsers</span><span class="params">(api *slack.Client)</span></span> &#123;</span><br><span class="line">    <span class="comment">// ユーザー一覧を取得</span></span><br><span class="line">    users, err := api.GetUsers()</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Fatalf(<span class="string">&quot;ユーザー一覧の取得に失敗しました: %v&quot;</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ユーザー情報を表示</span></span><br><span class="line">    log.Printf(<span class="string">&quot;ワークスペース内のユーザー数: %d\n&quot;</span>, <span class="built_in">len</span>(users))</span><br><span class="line">    <span class="keyword">for</span> i, user := <span class="keyword">range</span> users &#123;</span><br><span class="line">        <span class="comment">// 必要な情報だけを表示（すべての情報を表示するとログが長くなりすぎるため）</span></span><br><span class="line">        log.Printf(<span class="string">&quot;%d: ID=%s, Name=%s, RealName=%s, IsBot=%v\n&quot;</span>,</span><br><span class="line">            i+<span class="number">1</span>, user.ID, user.Name, user.RealName, user.IsBot)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="シンプルなダイレクトメッセージの送信"><a href="#シンプルなダイレクトメッセージの送信" class="headerlink" title="シンプルなダイレクトメッセージの送信"></a>シンプルなダイレクトメッセージの送信</h3><p>ユーザーIDを指定して、シンプルなテキストメッセージを送信する関数です。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">sendDirectMessage</span><span class="params">(api *slack.Client, userID, message <span class="type">string</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">    <span class="comment">// ユーザーとのDMチャンネルを開く</span></span><br><span class="line">    channel, _, _, err := api.OpenConversation(&amp;slack.OpenConversationParameters&#123;</span><br><span class="line">        Users: []<span class="type">string</span>&#123;userID&#125;,</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;DMチャンネルを開けませんでした: %v&quot;</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// DMを送信</span></span><br><span class="line">    _, _, err = api.PostMessage(</span><br><span class="line">        channel.ID,</span><br><span class="line">        slack.MsgOptionText(message, <span class="literal">false</span>),</span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;DMの送信に失敗しました: %v&quot;</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    log.Printf(<span class="string">&quot;ユーザー %s にDMを送信しました&quot;</span>, userID)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Block-Kitを使った高度なメッセージ"><a href="#Block-Kitを使った高度なメッセージ" class="headerlink" title="Block Kitを使った高度なメッセージ"></a>Block Kitを使った高度なメッセージ</h2><p>Slack Block Kitを使用すると、画像、ボタン、フォーマットされたテキストなどを含む、より視覚的にリッチなメッセージを作成できます。</p><h3 id="Block-Kitメッセージの構築"><a href="#Block-Kitメッセージの構築" class="headerlink" title="Block Kitメッセージの構築"></a>Block Kitメッセージの構築</h3><p>以下は、様々なBlock Kit要素を含むサンプルメッセージの構築例です。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">buildSampleBlockKit</span><span class="params">()</span></span> slack.MsgOption &#123;</span><br><span class="line">    <span class="comment">// Block Kitを使ったメッセージの構築</span></span><br><span class="line">    headerText := slack.NewTextBlockObject(<span class="string">&quot;mrkdwn&quot;</span>, <span class="string">&quot;*ブロックキットのサンプル*&quot;</span>, <span class="literal">false</span>, <span class="literal">false</span>)</span><br><span class="line">    headerSection := slack.NewSectionBlock(headerText, <span class="literal">nil</span>, <span class="literal">nil</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// テキストブロック</span></span><br><span class="line">    textBlock := slack.NewTextBlockObject(<span class="string">&quot;mrkdwn&quot;</span>, <span class="string">&quot;これは `Block Kit` を使ったメッセージです。\n*太字* や _斜体_ などのMarkdownも使えます！&quot;</span>, <span class="literal">false</span>, <span class="literal">false</span>)</span><br><span class="line">    textSection := slack.NewSectionBlock(textBlock, <span class="literal">nil</span>, <span class="literal">nil</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 画像ブロック</span></span><br><span class="line">    accessory := slack.NewImageBlockElement(<span class="string">&quot;https://api.slack.com/img/blocks/bkb_template_images/beagle.png&quot;</span>, <span class="string">&quot;犬の画像&quot;</span>)</span><br><span class="line">    imageText := slack.NewTextBlockObject(<span class="string">&quot;mrkdwn&quot;</span>, <span class="string">&quot;こちらはかわいい犬の画像です :dog:&quot;</span>, <span class="literal">false</span>, <span class="literal">false</span>)</span><br><span class="line">    imageSection := slack.NewSectionBlock(imageText, <span class="literal">nil</span>, slack.NewAccessory(accessory))</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ボタン要素</span></span><br><span class="line">    btnText := slack.NewTextBlockObject(<span class="string">&quot;plain_text&quot;</span>, <span class="string">&quot;クリックしてください&quot;</span>, <span class="literal">false</span>, <span class="literal">false</span>)</span><br><span class="line">    btn := slack.NewButtonBlockElement(<span class="string">&quot;click_button&quot;</span>, <span class="string">&quot;button_clicked&quot;</span>, btnText)</span><br><span class="line">    btnAccessory := slack.NewAccessory(btn)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ボタンセクション</span></span><br><span class="line">    btnSectionText := slack.NewTextBlockObject(<span class="string">&quot;mrkdwn&quot;</span>, <span class="string">&quot;アクションを実行するには:&quot;</span>, <span class="literal">false</span>, <span class="literal">false</span>)</span><br><span class="line">    btnSection := slack.NewSectionBlock(btnSectionText, <span class="literal">nil</span>, btnAccessory)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 区切り線</span></span><br><span class="line">    divider := slack.NewDividerBlock()</span><br><span class="line"></span><br><span class="line">    <span class="comment">// フッターブロック</span></span><br><span class="line">    footerText := slack.NewTextBlockObject(<span class="string">&quot;mrkdwn&quot;</span>, <span class="string">&quot;Block Kitの詳細は &lt;https://api.slack.com/block-kit|こちら&gt; をご覧ください&quot;</span>, <span class="literal">false</span>, <span class="literal">false</span>)</span><br><span class="line">    footerSection := slack.NewSectionBlock(footerText, <span class="literal">nil</span>, <span class="literal">nil</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// すべてのブロックを一つのメッセージにまとめる</span></span><br><span class="line">    <span class="keyword">return</span> slack.MsgOptionBlocks(</span><br><span class="line">        headerSection,</span><br><span class="line">        divider,</span><br><span class="line">        textSection,</span><br><span class="line">        imageSection,</span><br><span class="line">        divider,</span><br><span class="line">        btnSection,</span><br><span class="line">        divider,</span><br><span class="line">        footerSection,</span><br><span class="line">    )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Block-Kitメッセージの送信"><a href="#Block-Kitメッセージの送信" class="headerlink" title="Block Kitメッセージの送信"></a>Block Kitメッセージの送信</h3><p>構築したBlock Kitメッセージを送信する関数です。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">sendDirectMessageWithBlocks</span><span class="params">(api *slack.Client, userID <span class="type">string</span>, blocks slack.MsgOption)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">    <span class="comment">// ユーザーとのDMチャンネルを開く</span></span><br><span class="line">    channel, _, _, err := api.OpenConversation(&amp;slack.OpenConversationParameters&#123;</span><br><span class="line">        Users: []<span class="type">string</span>&#123;userID&#125;,</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;DMチャンネルを開けませんでした: %v&quot;</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ブロックキットメッセージを送信</span></span><br><span class="line">    _, _, err = api.PostMessage(</span><br><span class="line">        channel.ID,</span><br><span class="line">        blocks,</span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;DMの送信に失敗しました: %v&quot;</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    log.Printf(<span class="string">&quot;ユーザー %s にブロックキットDMを送信しました&quot;</span>, userID)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="完全なサンプルコード"><a href="#完全なサンプルコード" class="headerlink" title="完全なサンプルコード"></a>完全なサンプルコード</h2><p>以下が、これまでの機能をすべて含んだ完全なサンプルです。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="comment">// Slack APIトークンを環境変数から取得</span></span><br><span class="line">    token := os.Getenv(<span class="string">&quot;SLACK_BOT_TOKEN&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> token == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">        log.Fatal(<span class="string">&quot;SLACK_BOT_TOKEN is not set&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Slackクライアントの初期化</span></span><br><span class="line">    api := slack.New(token)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ユーザー一覧を取得して表示</span></span><br><span class="line">    getUsers(api)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 特定のユーザーにDMを送信</span></span><br><span class="line">    userID := <span class="string">&quot;U02K6JU8D&quot;</span> <span class="comment">// 実際のユーザーIDに置き換えてください</span></span><br><span class="line">    err := sendDirectMessage(api, userID, <span class="string">&quot;これはテストメッセージです！&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Fatalf(<span class="string">&quot;DMの送信に失敗しました: %v&quot;</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ブロックキットを使用したDMを送信</span></span><br><span class="line">    blocks := buildSampleBlockKit()</span><br><span class="line">    err = sendDirectMessageWithBlocks(api, userID, blocks)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Fatalf(<span class="string">&quot;ブロックキットDMの送信に失敗しました: %v&quot;</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="実行方法"><a href="#実行方法" class="headerlink" title="実行方法"></a>実行方法</h2><ol><li>依存関係をインストール：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go mod tidy</span><br></pre></td></tr></table></figure><ol start="2"><li>環境変数を設定：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> SLACK_BOT_TOKEN=<span class="string">&quot;xoxb-your-token&quot;</span></span><br></pre></td></tr></table></figure><ol start="3"><li>プログラムを実行：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go run main.go</span><br></pre></td></tr></table></figure><h2 id="注意事項"><a href="#注意事項" class="headerlink" title="注意事項"></a>注意事項</h2><ol><li><p><strong>レート制限</strong>: Slack APIにはレート制限があります。大量のメッセージを送信する場合は、適切な間隔を設けましょう。</p></li><li><p><strong>エラーハンドリング</strong>: 本番環境では、ネットワークエラーやAPI制限エラーに対する適切なリトライ機構を実装することを推奨します。</p></li><li><p><strong>ユーザーID</strong>: 実際の運用では、ユーザー名からユーザーIDを動的に取得する仕組みを構築することが一般的です。</p></li><li><p><strong>セキュリティ</strong>: Slack APIトークンは機密情報です。ソースコードに直接記述せず、必ず環境変数や設定ファイルから読み込むようにしてください。</p></li></ol><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>この記事では、Goを使ったSlack通知の実装方法を、基本的なテキストメッセージから高度なBlock Kitを使ったリッチなメッセージまで幅広く解説しました。</p><p><code>github.com/slack-go/slack</code>ライブラリを使用することで、Slack APIの豊富な機能を簡単に利用できます。システム監視、定期レポート、チーム内通知など、様々な用途でSlack通知を活用して、より効率的なチーム開発を実現してください。</p><p>Block Kitの詳細については、<a href="https://app.slack.com/block-kit-builder">Slack Block Kit Builder</a>で実際にブロックを構築しながら学習することをおすすめします。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;はじめに&quot;&gt;&lt;a href=&quot;#はじめに&quot; class=&quot;headerlink&quot; title=&quot;はじめに&quot;&gt;&lt;/a&gt;はじめに&lt;/h2&gt;&lt;p&gt;Slackは現代のチーム開発において欠かせないコミュニケーションツールです。システムからの通知やアラート、定期的なレポートな</summary>
      
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
    <category term="Go" scheme="https://48n.jp/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>Go言語でAsynqにレート制限を実装する</title>
    <link href="https://48n.jp/blog/2025/06/03/asynq-rate-limiter-implementation-guide/"/>
    <id>https://48n.jp/blog/2025/06/03/asynq-rate-limiter-implementation-guide/</id>
    <published>2025-06-03T03:00:00.000Z</published>
    <updated>2026-02-25T00:43:41.847Z</updated>
    
    <content type="html"><![CDATA[<p>タスクキューは多くのWebアプリケーションで必要不可欠な機能ですが、大量のタスクを処理する際にはレート制限が重要になります。特に外部API呼び出しやメール送信などのタスクでは、適切なレート制限なしに処理すると、サービス制限に引っかかったり、相手先のサーバーに負荷をかけてしまう可能性があります。</p><p>今回は、Go言語の人気タスクキューライブラリである<a href="https://github.com/hibiken/asynq">Asynq</a>にレート制限を実装する方法を、複数のアプローチとともに解説します。</p><h2 id="プロジェクト構成"><a href="#プロジェクト構成" class="headerlink" title="プロジェクト構成"></a>プロジェクト構成</h2><p>このサンプルプロジェクトは以下の構成になっています。</p><ul><li><a href="https://github.com/shoyan/asynq-late-limiter">サンプルコード</a>はGitHubで公開しています。</li></ul><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">asynq-late-limiter<span class="symbol">/</span></span><br><span class="line">├── go.mod</span><br><span class="line">├── tasks<span class="symbol">/</span></span><br><span class="line">│   └── tasks.go          <span class="comment"># タスクの定義と処理ロジック</span></span><br><span class="line">├── limiter<span class="symbol">/</span></span><br><span class="line">│   ├── limiter.go        <span class="comment"># Limiterインターフェース</span></span><br><span class="line">│   ├── rate_limiter.go   <span class="comment"># golang.org/x/time/rateを使った実装</span></span><br><span class="line">│   ├── redis_rate_limiter.go  <span class="comment"># Redisベースの実装（シンプル版）</span></span><br><span class="line">│   ├── redis_rate_limiterv2.go <span class="comment"># Redisベースの実装（改良版）</span></span><br><span class="line">├── producer<span class="symbol">/</span></span><br><span class="line">│   └── main.go           <span class="comment"># タスクを生成するプロデューサー</span></span><br><span class="line">└── worker<span class="symbol">/</span></span><br><span class="line">    └── main.go           <span class="comment"># タスクを処理するワーカー</span></span><br></pre></td></tr></table></figure><h2 id="1-インターフェース設計"><a href="#1-インターフェース設計" class="headerlink" title="1. インターフェース設計"></a>1. インターフェース設計</h2><p>まず、レート制限の基盤となるインターフェースを定義します。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// limiter/limiter.go</span></span><br><span class="line"><span class="keyword">package</span> limiter</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// レート制限を確認し、エラーを返す</span></span><br><span class="line"><span class="keyword">type</span> Limiter <span class="keyword">interface</span> &#123;</span><br><span class="line">    Check() <span class="type">error</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// RateLimitErrorは、レート制限が超過されたときに発生するエラー</span></span><br><span class="line"><span class="keyword">type</span> RateLimitError <span class="keyword">struct</span> &#123;</span><br><span class="line">    RetryIn time.Duration</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *RateLimitError)</span></span> Error() <span class="type">string</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> fmt.Sprintf(<span class="string">&quot;rate limited (retry in %v)&quot;</span>, e.RetryIn)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>このインターフェース設計により、異なるレート制限の実装を簡単に差し替えることができます。</p><h2 id="2-レート制限の実装方式"><a href="#2-レート制限の実装方式" class="headerlink" title="2. レート制限の実装方式"></a>2. レート制限の実装方式</h2><h3 id="2-1-golang-org-x-time-rateを使った実装"><a href="#2-1-golang-org-x-time-rateを使った実装" class="headerlink" title="2.1 golang.org&#x2F;x&#x2F;time&#x2F;rateを使った実装"></a>2.1 golang.org&#x2F;x&#x2F;time&#x2F;rateを使った実装</h3><p>標準的なトークンバケットアルゴリズムを使った実装です。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// limiter/rate_limiter.go</span></span><br><span class="line"><span class="keyword">type</span> RateLimiter <span class="keyword">struct</span> &#123;</span><br><span class="line">    limiter *rate.Limiter</span><br><span class="line">    limit   <span class="type">int</span>           <span class="comment">// 1秒間に処理できるリクエスト数</span></span><br><span class="line">    burst   <span class="type">int</span>           <span class="comment">// 瞬間的に許可される最大リクエスト数</span></span><br><span class="line">    retryIn time.Duration <span class="comment">// レート制限時の再試行待機時間</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewRateLimiter</span><span class="params">(limit <span class="type">int</span>, burst <span class="type">int</span>, retryIn time.Duration)</span></span> *RateLimiter &#123;</span><br><span class="line">    <span class="keyword">return</span> &amp;RateLimiter&#123;</span><br><span class="line">        limiter: rate.NewLimiter(rate.Limit(limit), burst),</span><br><span class="line">        limit:   limit,</span><br><span class="line">        burst:   burst,</span><br><span class="line">        retryIn: retryIn,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(rl *RateLimiter)</span></span> Check() <span class="type">error</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> !rl.limiter.Allow() &#123;</span><br><span class="line">        <span class="keyword">return</span> &amp;RateLimitError&#123;</span><br><span class="line">            RetryIn: rl.retryIn,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>メリット</strong></p><ul><li>実装が簡単</li><li>メモリ効率が良い</li><li>高速</li></ul><p><strong>デメリット</strong></p><ul><li>単一プロセスでのみ動作</li><li>分散環境では使用できない</li></ul><h3 id="2-2-Redisベースの実装（改良版）"><a href="#2-2-Redisベースの実装（改良版）" class="headerlink" title="2.2 Redisベースの実装（改良版）"></a>2.2 Redisベースの実装（改良版）</h3><p>複数のワーカープロセス間でレート制限を共有する場合に適しています。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// limiter/redis_rate_limiterv2.go</span></span><br><span class="line"><span class="keyword">type</span> RedisRateLimiterV2 <span class="keyword">struct</span> &#123;</span><br><span class="line">    client     *redis.Client</span><br><span class="line">    key        <span class="type">string</span></span><br><span class="line">    limit      <span class="type">int</span></span><br><span class="line">    windowSize time.Duration</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(r *RedisRateLimiterV2)</span></span> Allow(taskType <span class="type">string</span>) (<span class="type">bool</span>, <span class="type">int64</span>, <span class="type">error</span>) &#123;</span><br><span class="line">    ctx := context.Background()</span><br><span class="line">    key := fmt.Sprintf(<span class="string">&quot;rate_limit:%s&quot;</span>, taskType)</span><br><span class="line">    now := time.Now().Unix()</span><br><span class="line"></span><br><span class="line">    script := <span class="string">`</span></span><br><span class="line"><span class="string">        local key = KEYS[1]</span></span><br><span class="line"><span class="string">        local limit = tonumber(ARGV[1])</span></span><br><span class="line"><span class="string">        local window = tonumber(ARGV[2])</span></span><br><span class="line"><span class="string">        local now = tonumber(ARGV[3])</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        -- 古いエントリを削除</span></span><br><span class="line"><span class="string">        redis.call(&quot;ZREMRANGEBYSCORE&quot;, key, &quot;-inf&quot;, now - window)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        -- 現在のリクエスト数を取得</span></span><br><span class="line"><span class="string">        local count = redis.call(&quot;ZCOUNT&quot;, key, &quot;-inf&quot;, &quot;+inf&quot;)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        if count &gt;= limit then</span></span><br><span class="line"><span class="string">            -- 最も古いリクエストのタイムスタンプを取得</span></span><br><span class="line"><span class="string">            local oldest = redis.call(&quot;ZRANGE&quot;, key, 0, 0, &quot;WITHSCORES&quot;)</span></span><br><span class="line"><span class="string">            local retryIn = 0.0</span></span><br><span class="line"><span class="string">            if #oldest &gt; 0 then</span></span><br><span class="line"><span class="string">                retryIn = math.max(0, oldest[2] + window - now)</span></span><br><span class="line"><span class="string">            end</span></span><br><span class="line"><span class="string">            return &#123;0, retryIn&#125;</span></span><br><span class="line"><span class="string">        end</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        -- 新しいリクエストを追加</span></span><br><span class="line"><span class="string">        redis.call(&quot;ZADD&quot;, key, now, tostring(now) .. &quot;-&quot; .. redis.call(&quot;INCR&quot;, &quot;request_counter&quot;))</span></span><br><span class="line"><span class="string">        redis.call(&quot;EXPIRE&quot;, key, window)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        return &#123;1, 0&#125;</span></span><br><span class="line"><span class="string">    `</span></span><br><span class="line"></span><br><span class="line">    result, err := r.client.Eval(ctx, script, []<span class="type">string</span>&#123;key&#125;, r.limit, <span class="type">int</span>(r.windowSize.Seconds()), now).Result()</span><br><span class="line">    <span class="comment">// ... エラーハンドリングと結果の解析</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>メリット</strong></p><ul><li>分散環境での動作</li><li>複数のワーカー間でレート制限を共有</li><li>正確な待機時間の算出</li></ul><p><strong>デメリット</strong></p><ul><li>Redisへの依存</li><li>わずかなパフォーマンスオーバーヘッド</li></ul><h2 id="3-Asynqとの統合"><a href="#3-Asynqとの統合" class="headerlink" title="3. Asynqとの統合"></a>3. Asynqとの統合</h2><h3 id="タスクの定義"><a href="#タスクの定義" class="headerlink" title="タスクの定義"></a>タスクの定義</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// tasks/tasks.go</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">EmailNotificationTask</span><span class="params">(ctx context.Context, t *asynq.Task, limit limiter.Limiter)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">    <span class="comment">// レート制限を確認</span></span><br><span class="line">    <span class="keyword">if</span> err := limit.Check(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> err</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// メール送信処理</span></span><br><span class="line">    <span class="keyword">var</span> payload <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span></span><br><span class="line">    <span class="keyword">if</span> err := json.Unmarshal(t.Payload(), &amp;payload); err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> err</span><br><span class="line">    &#125;</span><br><span class="line">    log.Println(<span class="string">&quot;Sending Email to:&quot;</span>, payload[<span class="string">&quot;email&quot;</span>], <span class="string">&quot;with subject:&quot;</span>, payload[<span class="string">&quot;subject&quot;</span>])</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ワーカーの設定"><a href="#ワーカーの設定" class="headerlink" title="ワーカーの設定"></a>ワーカーの設定</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// worker/main.go</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="comment">// 1秒間1リクエストに制限</span></span><br><span class="line">    emailRateLimiter := limiter.NewRedisRateLimiterV2(<span class="string">&quot;email&quot;</span>, <span class="number">1</span>, time.Second)</span><br><span class="line"></span><br><span class="line">    srv := asynq.NewServer(</span><br><span class="line">        asynq.RedisClientOpt&#123;Addr: <span class="string">&quot;:6379&quot;</span>&#125;,</span><br><span class="line">        asynq.Config&#123;</span><br><span class="line">            Concurrency: <span class="number">5</span>,</span><br><span class="line">            Queues: <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line">                <span class="string">&quot;default&quot;</span>: <span class="number">7</span>,</span><br><span class="line">                <span class="string">&quot;email&quot;</span>:   <span class="number">3</span>,</span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="comment">// レート制限エラーを失敗としてカウントしない</span></span><br><span class="line">            IsFailure: <span class="function"><span class="keyword">func</span><span class="params">(err <span class="type">error</span>)</span></span> <span class="type">bool</span> &#123; </span><br><span class="line">                <span class="keyword">return</span> !IsRateLimitError(err) </span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="comment">// 再試行の間隔を指定</span></span><br><span class="line">            RetryDelayFunc: retryDelay,</span><br><span class="line">            DelayedTaskCheckInterval: <span class="number">100</span> * time.Millisecond,</span><br><span class="line">        &#125;,</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    mux := asynq.NewServeMux()</span><br><span class="line">    mux.HandleFunc(tasks.TypeEmailTask, <span class="function"><span class="keyword">func</span><span class="params">(ctx context.Context, t *asynq.Task)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">        <span class="comment">// TaskにRateLimiterを設定する</span></span><br><span class="line">        <span class="keyword">return</span> tasks.EmailNotificationTask(ctx, t, emailRateLimiter)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> err := srv.Run(mux); err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Fatalf(<span class="string">&quot;サーバーを起動できませんでした: %v&quot;</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="エラーハンドリングとリトライ戦略"><a href="#エラーハンドリングとリトライ戦略" class="headerlink" title="エラーハンドリングとリトライ戦略"></a>エラーハンドリングとリトライ戦略</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// レート制限エラーの判定</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">IsRateLimitError</span><span class="params">(err <span class="type">error</span>)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line">    _, ok := err.(*limiter.RateLimitError)</span><br><span class="line">    <span class="keyword">return</span> ok</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// カスタムリトライ遅延</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">retryDelay</span><span class="params">(n <span class="type">int</span>, err <span class="type">error</span>, task *asynq.Task)</span></span> time.Duration &#123;</span><br><span class="line">    <span class="keyword">var</span> ratelimitErr *limiter.RateLimitError</span><br><span class="line">    <span class="keyword">if</span> errors.As(err, &amp;ratelimitErr) &#123;</span><br><span class="line">        <span class="keyword">return</span> ratelimitErr.RetryIn</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> asynq.DefaultRetryDelayFunc(n, err, task)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="4-実行方法"><a href="#4-実行方法" class="headerlink" title="4. 実行方法"></a>4. 実行方法</h2><h3 id="1-依存関係のインストール"><a href="#1-依存関係のインストール" class="headerlink" title="1. 依存関係のインストール"></a>1. 依存関係のインストール</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go mod tidy</span><br></pre></td></tr></table></figure><h3 id="2-Redisの起動"><a href="#2-Redisの起動" class="headerlink" title="2. Redisの起動"></a>2. Redisの起動</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Dockerを使用する場合</span></span><br><span class="line">docker run -d -p 6380:6379 redis:latest</span><br><span class="line"></span><br><span class="line"><span class="comment"># または直接実行</span></span><br><span class="line">redis-server --port 6380</span><br></pre></td></tr></table></figure><h3 id="3-ワーカーの起動"><a href="#3-ワーカーの起動" class="headerlink" title="3. ワーカーの起動"></a>3. ワーカーの起動</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go run worker/main.go</span><br></pre></td></tr></table></figure><h3 id="4-タスクの投入"><a href="#4-タスクの投入" class="headerlink" title="4. タスクの投入"></a>4. タスクの投入</h3><p>別のターミナルで以下のコマンドを実行。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go run producer/main.go</span><br></pre></td></tr></table></figure><h2 id="5-実装のポイント"><a href="#5-実装のポイント" class="headerlink" title="5. 実装のポイント"></a>5. 実装のポイント</h2><h3 id="レート制限の適切な設定"><a href="#レート制限の適切な設定" class="headerlink" title="レート制限の適切な設定"></a>レート制限の適切な設定</h3><ul><li><strong>外部API呼び出し</strong>: APIの制限に合わせて設定（例：100リクエスト&#x2F;分）</li><li><strong>メール送信</strong>: メール送信サービスの制限に合わせて設定（例：10通&#x2F;秒）</li><li><strong>データベース操作</strong>: データベースの負荷を考慮して設定</li></ul><h3 id="エラーハンドリング"><a href="#エラーハンドリング" class="headerlink" title="エラーハンドリング"></a>エラーハンドリング</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">srv := asynq.NewServer(</span><br><span class="line">    redisOpt,</span><br><span class="line">    asynq.Config&#123;</span><br><span class="line">        <span class="comment">// レート制限エラーは失敗としてカウントしない</span></span><br><span class="line">        IsFailure: <span class="function"><span class="keyword">func</span><span class="params">(err <span class="type">error</span>)</span></span> <span class="type">bool</span> &#123; </span><br><span class="line">            <span class="keyword">return</span> !IsRateLimitError(err) </span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="comment">// レート制限エラーの場合は適切な待機時間でリトライ</span></span><br><span class="line">        RetryDelayFunc: retryDelay,</span><br><span class="line">    &#125;,</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>このサンプルコードでは、Asynqにレート制限を実装する複数の方法を紹介しました。どの実装を選ぶかは、システムの要件によって決まります：</p><ul><li><strong>単一プロセス環境</strong>: <code>golang.org/x/time/rate</code>ベースの実装</li><li><strong>分散環境</strong>: Redisベースの実装</li></ul><p>インターフェースベースの設計により、後から実装を切り替えることも容易です。適切なレート制限により、安定したタスク処理システムを構築できます。</p><h2 id="参考リンク"><a href="#参考リンク" class="headerlink" title="参考リンク"></a>参考リンク</h2><ul><li><a href="https://github.com/hibiken/asynq">Asynq公式ドキュメント</a></li><li><a href="https://pkg.go.dev/golang.org/x/time/rate">golang.org&#x2F;x&#x2F;time&#x2F;rate</a></li><li><a href="https://github.com/shoyan/asynq-late-limiter">サンプルコード</a></li></ul>]]></content>
    
    
    <summary type="html">Go言語のタスクキューライブラリAsynqにレート制限を実装する方法を、複数のアプローチとともに詳しく解説します。golang.org/x/time/rateとRedisベースの実装を比較し、実用的なサンプルコードを提供します。</summary>
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
    <category term="Go" scheme="https://48n.jp/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>Go 1.24.3アップデート後のビルドエラーと解決方法</title>
    <link href="https://48n.jp/blog/2025/05/30/go-1-24-3-build-error/"/>
    <id>https://48n.jp/blog/2025/05/30/go-1-24-3-build-error/</id>
    <published>2025-05-30T07:03:12.000Z</published>
    <updated>2026-02-25T00:43:41.849Z</updated>
    
    <content type="html"><![CDATA[<h2 id="はじめに"><a href="#はじめに" class="headerlink" title="はじめに"></a>はじめに</h2><p>最近Go 1.24.3へアップデートしたところ、ローカル環境でビルドエラーが発生するようになりました。この記事では、発生した問題と解決方法について共有します。</p><h2 id="発生した問題"><a href="#発生した問題" class="headerlink" title="発生した問題"></a>発生した問題</h2><p>Go 1.24.3にアップデートした後、以下のようなエラーメッセージが表示されるようになりました。</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">link: duplicated definition of symbol dlopen, from github.com<span class="regexp">/ebitengine/</span>purego and github.com<span class="regexp">/ebitengine/</span>purego (<span class="keyword">exit</span> status <span class="number">1</span>)</span><br></pre></td></tr></table></figure><h2 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h2><p>調査の結果、このエラーはMacのIntel CPU（darwin&#x2F;amd64）環境で発生する既知の問題であることがわかりました。Go公式リポジトリでも同様の問題が報告されています。</p><p><a href="https://github.com/golang/go/issues/73617">Github Issue #73617: cmd&#x2F;link: Go 1.24.3 and 1.23.9 regression - duplicated definition of symbol dlopen</a></p><h2 id="解決方法"><a href="#解決方法" class="headerlink" title="解決方法"></a>解決方法</h2><p>この問題は<code>github.com/ebitengine/purego</code>の最新バージョン（v0.8.3）で修正されています。以下のコマンドでpuregoをアップデートすることで解決できます。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go get github.com/ebitengine/purego@v0.8.3</span><br></pre></td></tr></table></figure><p>アップデート後、ビルドが正常に完了することを確認しました。</p><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>Go言語のバージョンアップ後にはライブラリとの互換性の問題が発生することがあります。特にGoのマイナーバージョンアップデート（1.24.2→1.24.3など）でも互換性の問題が生じる可能性があるため、ビルドエラーが発生した場合は依存ライブラリの更新も検討するとよいでしょう。</p><h2 id="参考リンク"><a href="#参考リンク" class="headerlink" title="参考リンク"></a>参考リンク</h2><ul><li><a href="https://github.com/golang/go/issues/73617">Go公式リポジトリのIssue #73617</a></li><li><a href="https://github.com/ebitengine/purego/releases/tag/v0.8.3">purego v0.8.3リリースノート</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;はじめに&quot;&gt;&lt;a href=&quot;#はじめに&quot; class=&quot;headerlink&quot; title=&quot;はじめに&quot;&gt;&lt;/a&gt;はじめに&lt;/h2&gt;&lt;p&gt;最近Go 1.24.3へアップデートしたところ、ローカル環境でビルドエラーが発生するようになりました。この記事では、発生した</summary>
      
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
    <category term="Go" scheme="https://48n.jp/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>Goのエラーハンドリングのベストプラクティス</title>
    <link href="https://48n.jp/blog/2025/05/30/go-error-handling-best-practice/"/>
    <id>https://48n.jp/blog/2025/05/30/go-error-handling-best-practice/</id>
    <published>2025-05-30T03:33:47.000Z</published>
    <updated>2026-02-25T00:43:41.849Z</updated>
    
    <content type="html"><![CDATA[<h2 id="はじめに"><a href="#はじめに" class="headerlink" title="はじめに"></a>はじめに</h2><p>Goのエラーハンドリングは他の言語と少し異なるアプローチを取ります。例外機構を持たないGoでは、エラーは値として扱われ、関数の戻り値として明示的に返されます。このシンプルなアプローチは強力ですが、効果的に活用するにはいくつかのベストプラクティスを理解する必要があります。</p><p>この記事では、Goのエラーハンドリングの基本から、カスタムエラーの作成、エラーのラッピングとアンラッピング、そして<code>errors.Is()</code>と<code>errors.As()</code>を使用した効果的なエラー判定の方法について解説します。</p><h2 id="Goのエラーハンドリングのベストプラクティス"><a href="#Goのエラーハンドリングのベストプラクティス" class="headerlink" title="Goのエラーハンドリングのベストプラクティス"></a>Goのエラーハンドリングのベストプラクティス</h2><p>Goのエラーハンドリングのベストプラクティスを理解するために、いくつかの具体例を見ていきましょう。</p><h3 id="1-基本的なエラー定義と判定"><a href="#1-基本的なエラー定義と判定" class="headerlink" title="1. 基本的なエラー定義と判定"></a>1. 基本的なエラー定義と判定</h3><p>Goでは、標準パッケージの<code>errors</code>を使って簡単にエラーを定義できます。以下のサンプルコードでは、よく使われるパターンを示しています。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;errors&quot;</span></span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// カスタムエラー型の定義</span></span><br><span class="line"><span class="keyword">var</span> (</span><br><span class="line">ErrNotFound = errors.New(<span class="string">&quot;not found&quot;</span>)</span><br><span class="line">ErrInvalid  = errors.New(<span class="string">&quot;invalid input&quot;</span>)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// データベース操作を模擬する関数</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">findUser</span><span class="params">(id <span class="type">string</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line"><span class="comment">// エラーをラップして返す</span></span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;failed to find user: %w&quot;</span>, ErrNotFound)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> NotFoundError <span class="keyword">struct</span> &#123;</span><br><span class="line">ID <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *NotFoundError)</span></span> Error() <span class="type">string</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Sprintf(<span class="string">&quot;user %s not found&quot;</span>, e.ID)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// 悪い例: == 演算子での比較</span></span><br><span class="line">err := findUser(<span class="string">&quot;123&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err == ErrNotFound &#123; <span class="comment">// これは動作しない！</span></span><br><span class="line">fmt.Println(<span class="string">&quot;== で比較。ユーザーが見つかりませんでした&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 良い例1: errors.Is() を使用</span></span><br><span class="line">err = findUser(<span class="string">&quot;123&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> errors.Is(err, ErrNotFound) &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;errors.Is() で比較。ユーザーが見つかりませんでした&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">err = &amp;NotFoundError&#123;ID: <span class="string">&quot;123&quot;</span>&#125;</span><br><span class="line"><span class="keyword">var</span> notFoundErr *NotFoundError</span><br><span class="line"><span class="keyword">if</span> errors.As(err, &amp;notFoundErr) &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;ユーザー %s が見つかりませんでした\n&quot;</span>, notFoundErr.ID)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// エラーのラップと展開の例</span></span><br><span class="line">err = findUser(<span class="string">&quot;123&quot;</span>)</span><br><span class="line">fmt.Printf(<span class="string">&quot;元のエラー: %v\n&quot;</span>, err)</span><br><span class="line"></span><br><span class="line"><span class="comment">// ラップされたエラーを展開</span></span><br><span class="line">unwrapped := errors.Unwrap(err)</span><br><span class="line">fmt.Printf(<span class="string">&quot;展開されたエラー: %v\n&quot;</span>, unwrapped)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>実行結果</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">errors.Is() で比較。ユーザーが見つかりませんでした</span><br><span class="line">ユーザー 123 が見つかりませんでした</span><br><span class="line">元のエラー: failed to find user: not found</span><br><span class="line">展開されたエラー: not found</span><br></pre></td></tr></table></figure><h3 id="2-エラー判定の種類と使い分け"><a href="#2-エラー判定の種類と使い分け" class="headerlink" title="2. エラー判定の種類と使い分け"></a>2. エラー判定の種類と使い分け</h3><p>上記のコードには、エラー判定のための重要な方法がいくつか含まれています。それぞれを詳しく解説します。</p><h4 id="エラー比較の落とし穴-演算子"><a href="#エラー比較の落とし穴-演算子" class="headerlink" title="エラー比較の落とし穴: == 演算子"></a>エラー比較の落とし穴: <code>==</code> 演算子</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 悪い例: == 演算子での比較</span></span><br><span class="line">err := findUser(<span class="string">&quot;123&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err == ErrNotFound &#123; <span class="comment">// これは動作しない！</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;== で比較。ユーザーが見つかりませんでした&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>このコードでは、<code>findUser</code>関数が返すエラーと<code>ErrNotFound</code>を<code>==</code>演算子で直接比較しています。しかし、このアプローチには大きな問題があります。<code>findUser</code>関数は単純に<code>ErrNotFound</code>を返すのではなく、<code>fmt.Errorf(&quot;failed to find user: %w&quot;, ErrNotFound)</code>を使ってエラーをラップしているため、<code>==</code>比較は失敗します。ラップされたエラーは元のエラーと等価ではないからです。</p><h4 id="推奨方法1-errors-Is"><a href="#推奨方法1-errors-Is" class="headerlink" title="推奨方法1: errors.Is()"></a>推奨方法1: <code>errors.Is()</code></h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 良い例1: errors.Is() を使用</span></span><br><span class="line">err = findUser(<span class="string">&quot;123&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> errors.Is(err, ErrNotFound) &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;errors.Is() で比較。ユーザーが見つかりませんでした&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Go 1.13以降で導入された<code>errors.Is()</code>関数は、エラーチェーン内のどこかに特定のエラー値が含まれているかを確認します。これにより、ラップされたエラーでも正しく比較できるようになります。<code>errors.Is()</code>は、エラーが同一かどうかを確認する際の推奨方法です。</p><h4 id="推奨方法2-errors-As"><a href="#推奨方法2-errors-As" class="headerlink" title="推奨方法2: errors.As()"></a>推奨方法2: <code>errors.As()</code></h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">err = &amp;NotFoundError&#123;ID: <span class="string">&quot;123&quot;</span>&#125;</span><br><span class="line"><span class="keyword">var</span> notFoundErr *NotFoundError</span><br><span class="line"><span class="keyword">if</span> errors.As(err, &amp;notFoundErr) &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;ユーザー %s が見つかりませんでした\n&quot;</span>, notFoundErr.ID)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>errors.As()</code>関数は、エラーチェーン内のいずれかのエラーが特定の型に一致するかを確認し、一致する場合はその値をターゲット変数に設定します。これは、エラーの型に基づいて処理を分岐させたい場合や、エラー内の追加情報（この例では<code>ID</code>）にアクセスしたい場合に特に有用です。</p><h3 id="3-エラーのラッピングとアンラッピング"><a href="#3-エラーのラッピングとアンラッピング" class="headerlink" title="3. エラーのラッピングとアンラッピング"></a>3. エラーのラッピングとアンラッピング</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// エラーのラップと展開の例</span></span><br><span class="line">err = findUser(<span class="string">&quot;123&quot;</span>)</span><br><span class="line">fmt.Printf(<span class="string">&quot;元のエラー: %v\n&quot;</span>, err)</span><br><span class="line"></span><br><span class="line"><span class="comment">// ラップされたエラーを展開</span></span><br><span class="line">unwrapped := errors.Unwrap(err)</span><br><span class="line">fmt.Printf(<span class="string">&quot;展開されたエラー: %v\n&quot;</span>, unwrapped)</span><br></pre></td></tr></table></figure><p>Go 1.13では、<code>%w</code>動詞を使用して元のエラーをラップする機能が<code>fmt.Errorf</code>に追加されました。これにより、より詳細なコンテキスト情報を提供しながら、元のエラー値を保持できます。<code>errors.Unwrap()</code>関数を使用すると、ラップされたエラーから元のエラーを取り出すことができます。</p><h3 id="4-カスタムエラー型の作成"><a href="#4-カスタムエラー型の作成" class="headerlink" title="4. カスタムエラー型の作成"></a>4. カスタムエラー型の作成</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> NotFoundError <span class="keyword">struct</span> &#123;</span><br><span class="line">    ID <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *NotFoundError)</span></span> Error() <span class="type">string</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> fmt.Sprintf(<span class="string">&quot;user %s not found&quot;</span>, e.ID)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Goでは、<code>Error()</code>メソッドを実装した任意の型をエラーとして使用できます。カスタムエラー型を作成することで、エラーに追加情報（この例では<code>ID</code>）を含めることができ、より詳細なエラーハンドリングが可能になります。</p><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>Goのエラー判定では、&#x3D;&#x3D;演算子による比較はラップされたエラーに対して正しく動作しません。<br>そのため、Go1.13以降で導入された<code>errors.Is</code>や<code>errors.As</code>を活用することで、より安全かつ柔軟なエラーハンドリングが可能になります。<br>エラー処理の品質向上のため、ぜひこれらの手法を取り入れてみてください。</p>]]></content>
    
    
    <summary type="html">Goのエラーハンドリングのベストプラクティスを解説。errors.Is()、errors.As()、エラーのラッピングとアンラッピング、カスタムエラーの作成方法について詳しく説明します。</summary>
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
    <category term="Go" scheme="https://48n.jp/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>メルカリを退職してフリーランスになりました</title>
    <link href="https://48n.jp/blog/2025/02/09/resigned-from-mercari-to-become-a-freelancer/"/>
    <id>https://48n.jp/blog/2025/02/09/resigned-from-mercari-to-become-a-freelancer/</id>
    <published>2025-02-09T11:58:53.000Z</published>
    <updated>2025-02-25T01:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="自己紹介とメルカリでの経験"><a href="#自己紹介とメルカリでの経験" class="headerlink" title="自己紹介とメルカリでの経験"></a>自己紹介とメルカリでの経験</h3><p>私は 16 年ほど IT 企業でエンジニアとして働いてきました。GMO ペパボといった業界大手のサービスを運営している会社、ヤフーやメルカリといったメガベンチャー企業に勤めていました。直近はメルカリでおおよそ 3 年間働き、主に社内のカスタマーサービス部門が使うツールの開発に携わっていました。その中で GO 言語での開発経験や開発チームをマネジメントする経験を積むことができました。</p><h3 id="フリーランスになろうと思った理由"><a href="#フリーランスになろうと思った理由" class="headerlink" title="フリーランスになろうと思った理由"></a>フリーランスになろうと思った理由</h3><p>正社員としてのキャリアアップに魅力を感じなくなったからです。</p><p>私は物作りが好きで、現場で手を動かすのが性に合っていると思っています。また、仕事は好きですが、毎日深夜まで働いたり会社の評価を意識して振る舞うことなどは避けたいと思っています。</p><p>一方で正社員として求められる仕事はマネジメント業務にシフトしていきました。また、その会社の社員として期待される振る舞いを求められるようになりました。</p><p>上記のような自分の特性と会社が求めることのギャップがストレスとなり、正社員としてキャリアアップしていくことにモチベーションを感じられなくなってしまいました。</p><h3 id="退職前に準備したこと"><a href="#退職前に準備したこと" class="headerlink" title="退職前に準備したこと"></a>退職前に準備したこと</h3><p>フリーランスとして独立する前に、以下の準備を行いました。</p><ol><li><strong>スキルの棚卸し</strong><ul><li>職務経歴書の作成を通して、自分のスキルの棚卸しを行いました</li></ul></li><li><strong>副業</strong><ul><li>実際に IT 企業と業務委託契約を結び、平日の夜や週末に働いていました</li><li>個人事業主として登録し、青色確定申告の書類を作成していました</li></ul></li><li><strong>案件探しと情報収集</strong><ul><li>フリーランスの案件を扱うエージェントに登録して実際に案件獲得を行いました</li><li>自分の単価やどういった需要があるのかを確認することが目的です</li></ul></li></ol><h3 id="フリーランスになってからのリアル"><a href="#フリーランスになってからのリアル" class="headerlink" title="フリーランスになってからのリアル"></a>フリーランスになってからのリアル</h3><p>まだ 1 ヶ月程度ですが、実際にフリーランスとして活動し始めて、以下のようなことを感じました。</p><h3 id="アップサイド"><a href="#アップサイド" class="headerlink" title="アップサイド"></a>アップサイド</h3><ul><li><strong>時間と場所の自由</strong><ul><li>フルリモート、フルフレックスで働いているため、仕事と生活のバランスがとりやすい</li><li>案件はフルリモート前提の仕事のみに限定することができる</li><li>同じ現場のフリーランスの方は、週4勤務だったり15時30分で退勤するように業務時間を調整していたりと柔軟に働いている</li></ul></li><li><strong>開発に集中できストレスが減る</strong><ul><li>業務範囲が明確なので、本業である開発の仕事に集中することができる</li><li>雑務やマネジメント業務をする必要がないため、余計なストレスが減った</li><li>職場の飲み会に参加しなくてよい</li></ul></li><li><strong>環境を自分で選ぶことができる</strong><ul><li>勢いのあるスタートアップの案件に携われば、事業の成長を感じることができる</li><li>携わりたい事業があればその企業にアプローチすることができる</li><li>自分に合わないと感じるところからは撤退する判断ができる</li></ul></li></ul><h3 id="ダウンサイド"><a href="#ダウンサイド" class="headerlink" title="ダウンサイド"></a>ダウンサイド</h3><ul><li><strong>雇用と収入の不安定さ</strong><ul><li>会社員のように毎月決まった給料が入ることが確約されていない</li><li>常に契約解除のリスクがある</li><li>有給休暇がないので休んだ分だけ収入が減る</li></ul></li><li><strong>社会保障が脆弱</strong><ul><li>フリーランスは将来受け取れる年金額が少ない</li><li>病気になったり働けなくなったりした時の収入の保証がない</li></ul></li></ul><p><em>ダウンサイドについては、リスク対策が可能です。しかし、こういったリスク対策についても全て自分自身で行う必要があります。</em></p><h3 id="フリーランスの実態と心境"><a href="#フリーランスの実態と心境" class="headerlink" title="フリーランスの実態と心境"></a>フリーランスの実態と心境</h3><p>私の場合は正社員の頃とあまり働き方は変わっていません。平日の 8 時間は仕事、コミュニケーションは毎日発生しますし、仕事の内容や質も正社員に求められるものと変わりません。</p><p>しかし、契約形態としては独立しているので、自分自身のアイデンティティを大事にできる点が精神的なメリットになっている感じています。</p><p>その分、不安定さはありますが、常に安泰ではないという危機感が自分にとってよい刺激になっています。</p><p>これからフリーランスを続けていくと、様々なトラブルに遭遇すると思います。それも勉強だと思って邁進していく所存です。</p><h3 id="これからフリーランスを目指す人へのアドバイス"><a href="#これからフリーランスを目指す人へのアドバイス" class="headerlink" title="これからフリーランスを目指す人へのアドバイス"></a>これからフリーランスを目指す人へのアドバイス</h3><p>最後に私の経験から、フリーランスを目指す人に伝えたいことは以下の 3 つです。</p><ol><li><strong>準備と情報収集をする</strong><ul><li>いきなりフリーランスになることは不安も多いと思います。そういった場合は、副業から始めてみることをおすすめします</li><li>現在は副業案件が多いそうです。私もエージェントから副業案件についての提案をよく受けます</li><li>副業をすると確定申告が必要になるため、経理の知識もつきます。青色確定申告書類を作成できるようになりましょう。節税になります</li></ul></li><li><strong>自分の強みの把握とスキルの研鑽</strong><ul><li>自分の強みを明確にするために職務経歴書を作成することをおすすめします</li><li>職務経歴書のアピールになるような経験を積めるように、日頃から意識してスキルの研鑽に励みましょう</li></ul></li><li><strong>家族の理解が大事</strong><ul><li>家族がいる人は家族の理解が必要です。収入が安定している正社員から収入が確約されていないフリーランスに転向することは心理的な障壁が高いからです</li><li>いきなり説得することが難しい場合は、時間をかけて理解してもらうことが必要です。私は 3 年ほど時間がかかりました</li></ul></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;自己紹介とメルカリでの経験&quot;&gt;&lt;a href=&quot;#自己紹介とメルカリでの経験&quot; class=&quot;headerlink&quot; title=&quot;自己紹介とメルカリでの経験&quot;&gt;&lt;/a&gt;自己紹介とメルカリでの経験&lt;/h3&gt;&lt;p&gt;私は 16 年ほど IT 企業でエンジニアとして働い</summary>
      
    
    
    
    <category term="フリーランス" scheme="https://48n.jp/categories/%E3%83%95%E3%83%AA%E3%83%BC%E3%83%A9%E3%83%B3%E3%82%B9/"/>
    
    
  </entry>
  
  <entry>
    <title>エンジニアを目指す人が最初にすべきこと、するべきでないこと</title>
    <link href="https://48n.jp/blog/2021/08/31/how-to-get-started-learning-programming/"/>
    <id>https://48n.jp/blog/2021/08/31/how-to-get-started-learning-programming/</id>
    <published>2021-08-31T04:00:00.000Z</published>
    <updated>2021-08-31T04:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p class="top-message">近年、エンジニアを目指す人が増えてきています。今までパソコンを触ってきていない人もエンジニアを目指し始めており、史上空前のエンジニアブームといってもよいでしょう。この記事では、これからエンジニアを目指す人向けに<strong>何をすべきか、何をすべきでないか</strong>を説明していきたいと思います。</p><h2 id="エンジニアを目指す人が増えている背景"><a href="#エンジニアを目指す人が増えている背景" class="headerlink" title="エンジニアを目指す人が増えている背景"></a>エンジニアを目指す人が増えている背景</h2><p>近年、エンジニア需要の増加 <a href="#ref1">*1</a> でエンジニア（システムエンジニア、プログラマーのこと）を目指す人が増えてきています。文系の大学生、主婦、営業部で働いているサラリーマンなど、今までパソコンに触れてきていない人もエンジニアに興味を持ち目指している、といった状況です。</p><p>そのような状況はTwitterの「<a href="https://twitter.com/search?q=%23%E9%A7%86%E3%81%91%E5%87%BA%E3%81%97%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A8%E3%81%A4%E3%81%AA%E3%81%8C%E3%82%8A%E3%81%9F%E3%81%84&src=typed_query" target="_blank">#駆け出しエンジニアとつながりたい</a>」のようなタグからも垣間見ることができます。</p><p>また、SNSの発展に伴いインフルエンサーの影響も年々大きくなってきています。一部のインフルエンサーが発信する、好きな時に好きな場所で働く自由な働き方<a href="#ref2">*2</a>、高収入（フリーランスで年収1000万円）<a href="#ref3">＊3</a>などのキラキラしたイメージがエンジニア人気にさらに拍車をかけています。</p><div class="memo" style="margin-bottom: 40px;">プログラミングについては興味がないが、働き方や高収入に魅力を感じている人が多い印象です。</div><h2 id="穴の空いたバケツに水を注ぐな"><a href="#穴の空いたバケツに水を注ぐな" class="headerlink" title="穴の空いたバケツに水を注ぐな"></a>穴の空いたバケツに水を注ぐな</h2><p>これからエンジニアを目指す人へのアドバイスは「<strong>穴の空いたバケツに水を注ぐな</strong>」です。</p><p>プログラミングの学習を始めた人の話しを聞いてみると、インフルエンサーが発信している自由な働き方、高収入に魅力を感じ、フリーランスを目指してプログラミングの学習を始める方が多い印象です。</p><p>学習を始めることは大変素晴らしいことなのですが、プログラミングに関する学習をしてきていない人たちの多くはパソコンの操作がままなりませんので、ファイルを作成したり、画像をダウンロードしたりといった、パソコンに慣れている人だったら難なく行える操作ができません。</p><p>私はこのようなパソコンの操作がままならない状態を<strong>穴の空いたバケツ状態</strong>と呼んでいます。穴の空いたバケツにいくら水を入れても何も残らないように、この状態でいろいろと教えても多くの知識は定着しません。</p><p>残念ながらこの状態で高額な教材を購入したり、高額な学費を払ってプログラミングスクールに通っている人がいます。しかし、この投資は割りに合わない可能性が高いです。</p><div class="memo" style="margin-bottom: 40px;">穴の空いたバケツに多額の資金を投資することは避けましょう。</div><img src="/images/child-392971_640.jpg" alt="穴の空いたバケツ" loading="lazy"><p style="color: gray;">穴の空いたバケツに水を入れてもほとんどが流れていってしまう</p><h2 id="エンジニアを目指す人が最初にすべきこと"><a href="#エンジニアを目指す人が最初にすべきこと" class="headerlink" title="エンジニアを目指す人が最初にすべきこと"></a>エンジニアを目指す人が最初にすべきこと</h2><p>エンジニアを目指す人が最初にすべきことは、<strong>穴の空いたバケツをふさぐこと</strong>です。</p><p>まずは次の２点をおさえます。</p><ul><li><strong>学習する下地をつくること</strong></li><li><strong>プログラミングが自分にあっているかを確認すること</strong></li></ul><div class="memo">この２つが整うまではお金をかけてはいけません。無駄な投資になってしまうリスクがあるからです。</div><h3 id="学習する下地をつくる"><a href="#学習する下地をつくる" class="headerlink" title="学習する下地をつくる"></a>学習する下地をつくる</h3><p>どうやって学習する下地をつくるかですが、<strong>HTML&#x2F;CSSの学習</strong>からはじめましょう。</p><p>最初からPHPなどのプログラミング言語を学習した方が効率がいいというインフルエンサーもいますが<a href="#ref4">*4</a>、パソコンに慣れていない人はプログラミング言語をインストールしたり、実行したりすることが難しいので、そこで詰みます。</p><p>また、ターミナルやブラウザーに表示されているプログラムの実行結果をみて、楽しいと感じる人はあまりいないでしょう。それよりも、華やかなHTML&#x2F;CSSの画面をみる方がやりがいを感じられるものです。</p><div class="memo">パソコンの操作に慣れていない人はHTML/CSSから始めましょう。具体的な方法については、別の記事で説明したいと思います。</div><h3 id="プログラミングが自分にあっているのかを確認する"><a href="#プログラミングが自分にあっているのかを確認する" class="headerlink" title="プログラミングが自分にあっているのかを確認する"></a>プログラミングが自分にあっているのかを確認する</h3><p>最初の段階で必要なのは、パソコンの操作に慣れつつ<strong>プログラミングが自分にあっているのかを確認する</strong>ことです。特に<strong>プログラミングの学習が楽しい</strong>と感じられるかどうかが重要です。</p><p>高額な費用を払った後にプログラミングは自分にはあっていなかったと知る人もいます。自分にあっているかどうかは数十万円の学費を払わずとも、<strong>数千円の入門書を買って１カ月くらい学習をやってみればわかります</strong>。</p><div class="memo">プログラミング学習を楽しいと感じることができればOKです。</div><h2 id="参考リンク"><a href="#参考リンク" class="headerlink" title="参考リンク"></a>参考リンク</h2><ol><li id="ref1">    <a href="https://www.meti.go.jp/policy/it_policy/jinzai/houkokusyo.pdf" target="_blank">IT 人材需給に関する調査</a></li><li id="ref2">    <a href="https://www.youtube.com/watch?v=qt25fhF7fLo" target="_blank">【月収８００万】海外フリーランスの１日ルーティン【マレーシア】</a></li><li id="ref3">    <a href="https://www.youtube.com/watch?v=LoXe8iFAsVs" target="_blank">WEBフリーランスで年収1000万円超えないのはどう考えてもおかしい</a></li><li id="ref4">    <a href="https://www.youtube.com/watch?v=zfe-RkpShAg" target="_blank">【プログラミング初心者必見】HTML/CSSから勉強し始めてはいけない理由</a></li></ol>]]></content>
    
    
    <summary type="html">近年、エンジニアを目指す人が増えてきています。この記事では、これからエンジニアを目指す人向けに何をすべきか、何をすべきでないかを説明していきたいと思います。</summary>
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
  </entry>
  
  <entry>
    <title>ブログシステムをHexoにしました</title>
    <link href="https://48n.jp/blog/2019/10/31/replace-blog-system/"/>
    <id>https://48n.jp/blog/2019/10/31/replace-blog-system/</id>
    <published>2019-10-31T09:30:30.000Z</published>
    <updated>2019-10-31T09:30:30.000Z</updated>
    
    <content type="html"><![CDATA[<p>ブログシステムを<a href="https://hexo.io/" target="_blank" class="outbound">Hexo</a>にしました。ブログデザインも新規で作成しました。ブログシステムはそのうちリプレースしたいなと常々思っていたのですが、なかなかこれといったものがなく時間が経ってしまいましたが、ついにリプレースすることができました。</p><h2 id="以前のブログシステム"><a href="#以前のブログシステム" class="headerlink" title="以前のブログシステム"></a>以前のブログシステム</h2><p>以前のブログシステムはOctopressを使っていました。このOctopress、いくつか問題点がありました。</p><h3 id="動作が遅い"><a href="#動作が遅い" class="headerlink" title="動作が遅い"></a>動作が遅い</h3><p>Markdownで書いてそれをhtmlに変換する仕組みなのですが、その速度が遅いです。<br>記事数が少ない場合はそれほど気にならなかったのですが、100記事くらいになってくると明らかに遅くなってきます。<br>現在では、プレビューモードで変換するのに10秒程度かかってしまい、気持ちよく記事が書けない状態でした。</p><h3 id="メンテナンスされていない"><a href="#メンテナンスされていない" class="headerlink" title="メンテナンスされていない"></a>メンテナンスされていない</h3><p>2015年で開発が止まっています。新機能や改善もないですし、機能に問題がある場合も修正がされません。開発が止まっているものは使うべきではありません。今から使うのはやめたほうがいいでしょう。</p><h2 id="なぜHexoにしたのか"><a href="#なぜHexoにしたのか" class="headerlink" title="なぜHexoにしたのか"></a>なぜHexoにしたのか</h2><h3 id="Node-jsで書かれているから"><a href="#Node-jsで書かれているから" class="headerlink" title="Node.jsで書かれているから"></a>Node.jsで書かれているから</h3><p>HexoはNode.jsで書かれています。<br>最近はNode.jsを使うことが多いので、Node.jsで書かれているブログシステムを使いたかったのです。</p><h3 id="静的サイトジェネレーターだから"><a href="#静的サイトジェネレーターだから" class="headerlink" title="静的サイトジェネレーターだから"></a>静的サイトジェネレーターだから</h3><p>ブログシステムには大きく分けて静的サイトジェネレーターとデータベースを使ったブログシステムがあります。Hexoは静的サイトジェネレーターと言われるシステムです。静的サイトジェネレーターはMarkdown等の形式で書かれたファイルをhtmlに変換するソフトウェアです。</p><p>静的サイトジェネレーターのメリットは表示速度が早い、セキュリティリスクがほとんどない、他のシステムに移行しやすいなどのメリットがあります。個人ブログであれば静的サイトジェネレーターがおすすめです。</p><h3 id="人気のあるブログシステムだから"><a href="#人気のあるブログシステムだから" class="headerlink" title="人気のあるブログシステムだから"></a>人気のあるブログシステムだから</h3><p>Node.jsで書かれていて人気のあるブログシステムはHexoとGatsby、あとは最近VuePressも人気が出てきているようです。<br>他にどんなソフトウェアがあるかはこちらで確認することができます。</p><ul><li><a href="https://github.com/topics/static-site-generator" target="_blank" class="outbound">static-site-generator · GitHub Topics</a></li></ul><p>人気のあるソフトウェアは開発が活発で便利な機能も多く、使いやすいことが多いです。</p><h3 id="現在も開発が行われているから"><a href="#現在も開発が行われているから" class="headerlink" title="現在も開発が行われているから"></a>現在も開発が行われているから</h3><p>Hexoは現在も開発が行われています。2019-10-14にバージョン4.0のリリースが行われました。</p><ul><li><a href="https://hexo.io/news/2019/10/14/hexo-4-released/" target="_blank" class="outbound">Hexo 4.0.0 Released | Hexo</a></li></ul><h2 id="Hexo-vs-Gatsby-vs-VuePress"><a href="#Hexo-vs-Gatsby-vs-VuePress" class="headerlink" title="Hexo vs Gatsby vs VuePress"></a>Hexo vs Gatsby vs VuePress</h2><p>Node.js環境の静的サイトジェネレーターはHexo、Gatsby、VuePressの3つが人気です。GatsbyとVuePressは次の理由で採用しませんでした。</p><h3 id="Gatsbyを採用しなかった理由"><a href="#Gatsbyを採用しなかった理由" class="headerlink" title="Gatsbyを採用しなかった理由"></a>Gatsbyを採用しなかった理由</h3><p><a href="https://www.gatsbyjs.org/" target="_blank" class="outbound">Gatsby</a>はReactベースの静的サイトジェネレーターです。Reactに慣れている人であればいいかもしれませんが、私がReactに詳しくないのもあり、Gatsbyは学習コストが高いという印象です。あえてその学習コストを払ってGatsbyを使うメリットが見当たらないので採用を見送りました。</p><h3 id="VuePressを採用しなかった理由"><a href="#VuePressを採用しなかった理由" class="headerlink" title="VuePressを採用しなかった理由"></a>VuePressを採用しなかった理由</h3><p><a href="https://vuepress.vuejs.org/" target="_blank" class="outbound">VuePress</a>がメジャーバージョンになったということもあり、VuePressも試してみました。情報が少なくまだまだ使いづらいというのが正直なところで、VuePressを使うメリットが見当たりませんでした。<br>ブログではなく、ドキュメントなどのシステムとして使うならありかもしれません。<br>一応、サンプルコードをGitHubにあげているので興味のある方は参考にしてみてください。</p><ul><li><a href="https://github.com/shoyan/vuepress-sample" target="_blank" class="outbound">vuepress-sample</a></li></ul><h3 id="結論"><a href="#結論" class="headerlink" title="結論"></a>結論</h3><p>特にこだわりがなくブログを楽に構築したいということであれば、Hexo一択ではないかと思います。最後にHexoを使うメリットについて説明します。</p><h2 id="Hexoを使うメリット"><a href="#Hexoを使うメリット" class="headerlink" title="Hexoを使うメリット"></a>Hexoを使うメリット</h2><h3 id="ブログの基本機能が簡単に作成できる"><a href="#ブログの基本機能が簡単に作成できる" class="headerlink" title="ブログの基本機能が簡単に作成できる"></a>ブログの基本機能が簡単に作成できる</h3><p>Hexoでのブログ構築は5つのコマンドを実行するだけです。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ npm install hexo-cli -g</span><br><span class="line">$ hexo init blog</span><br><span class="line">$ <span class="built_in">cd</span> blog</span><br><span class="line">$ npm install</span><br><span class="line">$ hexo server</span><br></pre></td></tr></table></figure><p><code>hexo server</code> を実行後に<code>http://localhost:4000</code> にアクセスするとブログが表示されます。</p><h3 id="テーマの作成がしやすい"><a href="#テーマの作成がしやすい" class="headerlink" title="テーマの作成がしやすい"></a>テーマの作成がしやすい</h3><p>Hexoにはテーマ作成をサポートするツールが用意されています。<br><a href="https://www.npmjs.com/package/generator-hexo-theme" target="_blank" class="outbound">generator-hexo-theme</a>を使えば簡単にブログテーマの雛形を用意することができます。<br>ブログテーマは自作したのですが、その雛形はgenerator-hexo-themeで作成しました。</p><h3 id="CI-CDも簡単"><a href="#CI-CDも簡単" class="headerlink" title="CI&#x2F;CDも簡単"></a>CI&#x2F;CDも簡単</h3><p>masterにマージしたら自動的にデプロイする設定を入れています。そのあたりも公式のマニュアルが用意してあり、手順にそって設定していくだけでCI&#x2F;CD環境を作ることができます。私はGitHub Pagesを使っており、サーバー費用0円でブログを運営しています。コストパフォーマンスは最高ですね。</p><ul><li><a href="https://hexo.io/docs/github-pages.html" target="_blank" class="outbound">GitHub Pages | Hexo</a></li></ul><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>Hexoは学習コストが低く、テーマ作成もわりと簡単にできるので楽にブログを作りたい人におすすめです。Node.js環境でブログを作りたいときは検討してみてはいかがでしょうか。</p>]]></content>
    
    
    <summary type="html">ブログシステムをHexoにしました。Hexoのメリットや他のフレームワークであるGatsubyやVuePressと比較した結果などをまとめました。</summary>
    
    
    
    <category term="プログラミング" scheme="https://48n.jp/categories/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/"/>
    
    
  </entry>
  
</feed>
