Node.js と SendGrid API を使った文字化け対策

Node.js と SendGrid API を使った文字化け対策
Node.js と SendGrid API を使った文字化け対策

Communication illustrations by Storyset

こんにちは、開発チームでエンジニアをしている和田です。
最近はいろんな開発ツールに AI が導入され、馴染みあるツールの進化に驚かされる毎日です。

話は変わりますが、ついこの前に自分が担当しているプロダクトで、ユーザーが SendGrid 経由で送信したメールの文章が文字化けするという現象に遭遇しました。 MacWindows 端末でファイルのやり取りをしていたりすると遭遇しやすい文字化け...。

今回は文字化けさせないように処理を追加するタスクを担当したので、その作業記録として記事を書かせていただきます。

背景・目的

SendGrid のリクエストに含まれていたメール本文の文字化け対応の方法解説。

対象読者

Node.js(Express)と SendGrid API を使用した環境における文字化けの変換方法を知りたい開発者。


メールの文章が文字化けした状況

自身が担当しているプロダクトでは、メールの送受信に SendGrid を使用しています。 SendGrid からのリクエストに含まれるメール本文が文字化けしており、メール本文が判読できない状態になっていました。 発生頻度としては月に数回程度、こちらの現象が確認できました。

文字化けの対策方針

実際にメール本文を処理しているコードを確認したところ、字数制限などに対するバリデーション処理はありましたが、文字コードに関する処理がなかったため、SendGrid からのリクエストを受け取った直後に文字コードの変換を行います。

SendGrid のリクエストについて

SendGrid からのリクエストには、下記のように送信元・受信元・メールタイトル・メール本文および各項目それぞれに対して扱われた文字コードが含まれています。

{
  "to": "test@test.test",
  "subject": "メールタイトル",
  "from": "test@test.test",
  "text": "メール本文",
  "charsets": {
    "to": "UTF-8",
    "subject": "UTF-8",
    "from": "UTF-8",
    "text": "iso-2022-jp"
  }
}

今回はこちらの値を使って文字コードの変換を行っていきます。


SendGrid のメール本文が文字化していた場合の対処方法

1. ログの確認・文字化けの再現

まずはログの確認を行って、実際にどの種類の文字コードとして送信されたのか確認します。
先程、示した SendGrid のリクエストを確認すると、req.body.charsets.text: "iso-2022-jp" として送信されており、メール本文のみが文字コード ISO-2022-JP として送信されており文字化けが発生していました。

メール本文が文字化けすることを確認するため、メーラーで送信時の文字コードを変更して再現してみます。

こちらの記事を参考にメール送信時に文字コードを変更したところ、ログの通りにメール本文が文字化けしました 👍

2. 文字コード変換関数の作成

ログを確認して再現ができたところで、実際に文字コードを変換する処理を記述していきます。 今回、発生していたのは ISO-2022-JP による文字化けでした。

Node.js であれば TextDecoder().decode() を使えば ISO-2022-JP の文字化けを解消することができますが、今回は日本語のメールでよく使用される Shift_JIS, EUC-JP にも対応したいのでこれらの文字コード変換ができるライブラリを使用して関数を作成します。

有名どころとしては、iconv-lite ですが、こちらは ISO-2022-JP に対応していなかったので encoding.js を使用することにしました。

では、文字コード変換関数を作成します。

const Encoding = require("encoding-japanese");

/**
 * 文字列のエンコーディングを変換する
 * @param { string } str 変換前の文字列
 * @param { string } fromEncoding 変換前のエンコーディング
 * @param { string } toEncoding 変換後のエンコーディング
 * @return { string } 変換後の文字列
 */
const convertEncoding = (str, fromEncoding, toEncoding = "unicode") => {
  // 変換前と変換後のエンコーディングが同じ場合は受け取った文字列をそのまま返す
  if (fromEncoding === toEncoding) return str;

  // 変更前後のエンコーディングを encoding.js で扱える文字列表記に変換する関数
  const convertToDefinedEncoding = (encoding) => {
    const enc = encoding.toLowerCase();
    if (enc === "utf-8" || enc === "utf8") return "UTF8";
    if (enc === "iso-2022-jp") return "JIS";
    if (enc === "shift-jis" || enc === "shift_jis") return "SJIS";
    if (enc === "euc-jp") return "EUCJP";
    return "UNICODE";
  };
  // 変換前の文字列を Buffer に変換する
  // 実際は Buffer ではなく Buffer を模した配列に変換される
  const buffer = Encoding.stringToCode(str);
  const to = convertToDefinedEncoding(toEncoding);
  const from = convertToDefinedEncoding(fromEncoding);

  // Buffer と変更前後のエンコーディングを渡して文字列として出力する
  return Encoding.convert(buffer, { to, from, type: "string" });
};

encoding.js で文字コードの指定をするには UTF8, JIS, SJIS のように全て大文字の表記にする必要があるので、SendGrid のリクエストの charsets文字コードを渡して変換する convertToDefinedEncoding を通して、encoding.js convert 関数に渡して文字化けしたテキストを読める文字として返します。

3. 文字コード変換関数のテスト作成

convertEncoding 関数が実際に期待どおりの動作をするか確認します。
テストには Jest を使用しているので下記のコードでテストを行います。

it("Test convertEncoding", () => {
  // UTF8, ISO-2022-JP, Shift_JIS, EUC-JP の4つのテストケースを用意
  const testCases = ["utf-8", "iso-2022-jp", "shift-jis", "euc-jp"];
  // 各文字コードを encoding.js で扱える形式に変換
  const convertEncTypeName = (enc) => {
    if (enc === "utf-8") return "UTF8";
    if (enc === "iso-2022-jp") return "JIS";
    if (enc === "shift-jis") return "SJIS";
    if (enc === "euc-jp") return "EUCJP";
    return "UTF8";
  };
  // テストケースに対して forEach でテストを実行する
  testCases.forEach((testCase) => {
    // 日本語を含む文字列を encoding.js で変換して文字化けさせる
    const sourceText = `${testCase}エンコーディング`;
    const encodingText = Encoding.convert(sourceText, {
      to: convertEncTypeName(testCase),
      type: "string",
    });
    // convertEncoding に文字化けした文字列を渡して出力が期待値通りか確認する
    const convertedText = convertEncoding(encodingText, testCase);
    expect(convertedText).toBe(sourceText);
  });
});

ポイントとしては、検証するテキスト(sourceText)には日本語の文字列を必ず含めることです。半角英数字のみだと文字化けは生じず、テストの意味がなくなってしまいます。
無事にテストが通過したので、リクエスト処理部分にこちらの関数を通すように書き換えました。


まとめ

最後まで読んでくださりありがとうございました 🙇‍♂️
今回は文字コードの Node.js, SendGrid API を使用した環境での文字コードの変換について解説させていただきました。

SendGrid のリクエストから文字コードを取り出して処理を行ったため、encoding.js で扱えるようにする変換処理を各部に入れる必要がありましたが、encoding.js の convert 関数は変換元の文字コードを自動判定できるようです。
場合によっては、もっとシンプルに実装できたかもしれませんね。

コミュニティ・リソース・参考文献

Vue3 のプロジェクトに TypeScript を導入してみた話。

Vue3 のプロジェクトに TypeScript を導入してみた話。
Vue3 のプロジェクトに TypeScript を導入してみた話。

こんにちは、開発チームでエンジニアをしている和田です。

最近はフロントエンドのプロジェクトを Vue2 から Vue3 に変更するという大掛かりな作業を終えてホッとしております 😌
とはいえ Web の世界は日進月歩、あらゆるライブラリのバージョンアップが常日頃行われています。我々、開発者は安全に開発を行うためにプロダクトの保守改善をしなくてはなりません。

ということで今回は Vue3 への移行を終えて TypeScript を導入することになったため、その作業手順を記事として残しておこうと思います。

背景・目的

この記事では Vue3(Vue CLI)で構築されたフロントエンドのプロジェクトに TypeScript を導入する方法について解説します。

対象読者

Vue のプロジェクトに TypeScript を導入する方法を知りたい開発者。

開発ツール・環境の概要

筆者は下記の環境下で TypeScript の導入を行いました。

  • Vue v3.3.8
  • Vue CLI v5.0.8
  • eslint v8.53.0
  • eslint-plugin-vue v9.21.1

Vue3 プロジェクトに後から TypeScript を導入する方法

それでは導入方法について解説を行っていきます。下記の手順に沿って導入を進めていきます。

  1. Vue CLI コマンドで TypeScript 関連ライブラリをインストール
  2. 自動生成された不要なファイルの削除
  3. eslint 系ライブラリのバージョンアップデート
  4. 設定ファイルの修正
  5. エントリーポイントのエラー修正
  6. ビルドできるかの確認

1. Vue CLI コマンドで TypeScript 関連ライブラリをインストール

Vue CLI には後から TypeScript を導入するための便利なコマンドがあります。
コマンドラインvue add typescript というコマンドを実行すれば TypeScript および TypeScript に関連した必要なライブラリを一括でインストールしてくれます。

コマンド実行時にはいくつか設定のための質問に Yes or No で答える必要があります。
筆者の場合は下記のように設定しました。

質問 Yes/No 設定理由
Use class-style component syntax? No Vue3 では Object Style によりコンポーネントを記述するため。
Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes プロジェクトにて babel を使用しているため。
Convert all .js files to .ts? No 今回は全ての .js ファイルを .ts ファイルに変更しないため。
Allow .js files to be compiled? Yes 既存の .js ファイルでコンパイル可能にするため。
Skip type checking of all declaration files (recommended for apps)? Yes 全ての型宣言ファイルの確認を行う必要はないため。

コマンド実行後にはライブラリのインストールと下記に示す諸々のファイルの追加・変更が行われます。

  • エントリーポイントが .js ファイルから .ts ファイルに変更される
  • tsconfig.json ファイルが新規作成される
  • .eslintrc.js ファイルに TypeScript 用の設定が追記される
  • HelloWorld.vue などのサンプルコンポーネントのファイルが新規作成される

コマンド実行直後では npm run build コマンドを実行するとエラーが表示されてビルドはできませんでした。


2. 自動生成された不要なファイルの削除

ビルド実行時に表示されるエラーを解消される前に雑草を抜いていきます。
まず、自動生成された下記の不要なファイルを削除します。

  • HelloWorld.vue
  • HomeView.vue

3. eslint 系ライブラリのバージョンアップデート

プロジェクトに既存でインストールされていた eslint-plugin-vue と今回、新しくインストールされた @vue/eslint-config-typescript が競合することによって、エラーが発生するようになってしまいました。 それぞれをバージョンアップすれば解消可能であることが分かったので、下記のコマンドを実行しました。

npm uninstall @vue/eslint-config-typescript eslint-plugin-vue
npm i -D @vue/eslint-config-typescript eslint-plugin-vue

4. 設定ファイルの修正

.eslintrc.js ファイルに加えられた変更でエラーが発生したため、解消していきます。
今回のエラーは既存の extends と新たに追加された extends の項目が同時に存在しており、項目名が重複していました。

module.exports = {
  env: {
    node: true,
  },

  // FIXME: キーが重複しているため一つにまとめる
  extends: ["plugin:vue/recommended", "eslint:recommended", "@vue/prettier"],

  parserOptions: {
    // 省略
  },

  rules: {
    // 省略
  },

  extends: [
    "plugin:vue/recommended",
    "eslint:recommended",
    "@vue/prettier",
    "@vue/typescript",
  ],
};

extends を一つにまとめればエラーは解消されます。


5. エントリーポイントのエラー修正

最後にエントリーポイントが .js ファイルから .ts ファイルに変更されたことで、発生した TypeScript のエラー修正を行います。 ここはプロジェクトの構成次第で様々かと思いますが、筆者の環境では下記の旨を示すエラーが表示されました。

  • 拡張子のないファイルのインポートをしているためファイルが見つからない
  • null を取りうる値に対してメソッドを呼び出している
  • ライブラリから参照している型宣言ファイルが見つからない

ここではそれぞれのエラーに対する具体的な解決策については割愛させていただきます。
すべてのエラーが解消できたらビルドできるか確認していきます。


6. ビルドできるか確認

満を持してビルドです。
npm run build を実行してビルドが完了すれば無事に TypeScript が導入できたことになります。


TypeScript の導入にあたって思うこと

無事 TypeScript を導入して嬉しく思います。
しかしながらビルドの実行が少し長くなったように感じました。
Vue CLI は Vue から提供されている npm パッケージですが現在はメンテナンスモードに入っており、より高速なビルドツールである Vite への移行の風潮があります。
今回、部分的に TypeScript を導入したことにより、今後の開発でさらにビルド時間が伸びることが予想されるため Vite の導入なども検討していきたいです。


まとめ

最後まで読んでくださってありがとうございました。
今回 TypeScript を導入したことで、今後はより安全な開発が行えることに期待がもてます 🙌
TypeScript に書き換えることで、これまで潜在的だった不具合を発見しやすくなることも嬉しいですね。 この記事が同様の環境をもつ開発者のためになれば幸いです。

コミュニティ・リソース・参考文献

New Relic 主催の SLO, SLI ワークショップに参加した話

NewRelicのSLO/SLIワークショップに参加した話
NewRelicのSLO/SLIワークショップに参加した話

Work illustrations by Storyset

N2i はディップ株式会社様と共同で面接コボットというプロダクトを開発しています。 面接コボットでは New Relic というモニタリングプラットフォームを使用しているのですが、今回 New Relic の担当者様主催の SLI ワークショップに参加することになりました。 この記事ではワークショップにて学んだ内容を技術ブログにまとめさせていただきます。

※以降、引用で表記したものはワークショップ内で説明があった表現を利用させていただいております。

SLA, SLO, SLIについて

最初に SLA, SLO, SLI それぞれの定義について確認です!

SLA とは?

SLA とは Service Level Agreement という意味合い通り、サービス事業者が利用者に対して、「どの程度の品質を保証できるのか」を明示したものです。 筆者はとある受託案件にて、SLA を参考に目標が達成されなかった場合の対応について、PM としての策定経験があります。

SLO, SLI とは?

SLO は Service Level Objective つまりサービスレベル目標、SLI は Service Level Indicator つまりサービスレベル指標を指します。 セットで記載されることが多く サービス事業者が「目標として設定されるもの」 であるものです。

SLOを定めることによって、それに逸脱しないという明確な基準を持って、新機能のリリースを推進することができる SLOは運用チーム、開発チーム、プロダクトチームの共通言語として活用することができる

ワークショップでは上記のように説明していただきました。


それぞれの項目の決定・策定

次に SLI, SLO を何にするか決めていきます。

SLI の策定

先に SLI の策定から行います。SLI は上記の考え方をベース利用者がコボットで行う最もメジャーな操作の流れを書き出しました。 その中でサービスにとって重要なことを SLI として計測すべき対象として定義しました。

SLI の計測

次に New Relic を操作して SLI の計測を行います。 今回、設定の詳細は伏せさせて頂きますが、実際の計測結果は下記のようになりました。 ワークショップにて筆者のグループでは、コボット上に取り込んだ応募者が正しく表示されることを SLI の計測対象として設定したため APM で計測されている対象の API が正しくレスポンスを返せている割合を計測しています。

SLO の設定

SLI の計測ができたので SLO を決めていきます。

サービス事業者は監視ツールのエラー率を高めるわけにサービス提供をしているわけではありません。 ワークショップにて SLO について下記のように教えていただきました。

SLOを超えていれば、ほとんど全てのユーザーはサービスに満足している

この考えに従って、今回は下記画像のように SLO を定めました。

※このSLOはワークショップの演習として設定したもので、正式なものは別途検討予定です

ちなみに SLO はサービス事業者が設定する目標になるため SLA で設定する値よりは高く設定されるケースが多いです。

なぜ SLO を定めるのか?

SLO を定めることでその達成状況に合わせてプロダクトの方針を決めることができます。

  • SLO が未達成であれば、新機能のリリースよりもエラー原因の解消に注力する
  • SLO 達成済みであれば、新機能のリリース計画を立てる

このように SLO の達成率に従った意思決定が可能となるわけです。 また、SLO を定義することでチーム内でプロダクトの状況を関係者が共通言語として活用することができます。

SLOは運用チーム、開発チーム、プロダクトチームの共通言語として活用することができる

また、SLO が達成できていることはユーザの満足度につながっているはずです。 もし SLO が達成していてもユーザの満足度があがっていないのであれば、SLO を見直したり、計測する SLI を見直すことでより SLO を活用することができます。

ワークショップに参加してみて...

初めは New Relic 主催のワークショップということで New Relic の APM の使い方を中心に学ぶ会と考えていました。(こちらも今後開催があれば参加してみたい) 実際は SLO, SLI の座学とワークを通して SLO を運用するための知識を得られる有益な場でした。 参加者はビジネス・アプリ・インフラの領域の人でチームが構成されていたため、SLI の議論の際には様々な重要だと思う機能の話が出たりして面白かったです ! この度はワークショップを開催いただきありがとうございました!

Vue3 へのアップデートでハマったこと3選

Vue3 へのアップデートでハマったこと3選
Vue3 へのアップデートでハマったこと3選

People illustrations by Storyset

こんにちは、開発チームでエンジニアをしている和田です。

最近は担当しているプロダクトのフロントエンドを Vue2 から Vue3 にバージョンアップを行うタスクを任され、なんとか本番リリースができました🙌

自分自身、ライブラリやフレームワーク全体のバージョンアップは初めてでもあり、チームとしてもかつてない規模の変更だったため、大仕事でした😭

そのような背景もあり、今回は Vue3 へのバージョンアップでハマったことを 3 つほど紹介させていただきます。

背景・目的

Vue3 へのバージョンアップ作業でハマったことの備忘録です。
Vue3 へのバージョンアップに取り組む方の参考になれば嬉しいです。

前提条件・コンテキスト

自身が担当していたプロジェクトは Vue2 で作られており、Options API と Composition API のそれぞれで書かれたコンポーネントが混在している状況でした。 Composition API の導入が決まってから少しずつ Options API からの書き換えを行ってきた途中で Vue3 へのバージョンアップを行ったという背景があります。


Vue3 のバージョンアップでハマったこと3選

1. リアクティブな値が取得できない

原因: 構文書き換え時の .value のつけ忘れ

Vue3 へ書き換えが完了した直後、期待しない動作を示すほとんどの原因が .value のつけ忘れでした。 Vue3 から Vue を学習されている方は馴染みが無いかもしれませんが、Vue2 の Options API ではリアクティブな値を次のように定義します。

<script>
  export default {
    name: "TestComponent",
    data() {
      return {
        fruits: [],
      }
    },
    computed: {
      apples() {
        return this.fruits.filter(fruit => fruit === "apple");
      }
    }
  }
</script>

一方で、Vue3 Composition API では次のように記述します。

<script>
  export default defineComponent({
    name: "TestComponent",
    setup() {
      const fruits = ref([]);
      const apples = computed(() => fruits.value.filter(fruit => fruit === "apple"));

      return { fruits, apples };
    }
  })
</script>

Vue3 ではリアクティブな値には .value をつけて値の参照を行うため.value をつけずに処理すると当然、不具合が発生します。 computed を使用している値も .value が必要となるため、記述忘れによる不具合が開発中によく見られました。

Vue3 書き換えの際は TypeScript を導入しておらず、これらの記述ミスがあったとしても一見、問題なく動作するように見えます。 結合テストでしっかりチェックしないと気づかない場合もあるので侮れないですね😬


2. ref 属性から子コンポーネントのメソッドが実行できない

原因: ref 属性に記述したリアクティブな値を return していない

次に子コンポーネントのメソッドがなぜか実行できないという問題に遭遇しました。
結論として、子コンポーネントのメソッド実行のために ref 属性に記述するリアクティブな値を return していないことが原因でした。

Vue2 で Composition API を使用する場合、下記のように記述して子コンポーネントのメソッドを実行していました。

<template>
  <ChildComponent ref="refChildComponent" />
</template>

<script>
  export default defineComponent({
    name: "ParentComponent",
    setup(props, context) {
      const doChildMethod = () => {
        context.refs.refChildComponent.childMethod();
      }

      return { doChildMethod };
    }
  })
</script>

一方、Vue3 Composition API では次のように記述します。

<template>
  <ChildComponent ref="refChildComponent" />
</template>

<script>
  export default defineComponent({
    name: "ParentComponent",
    setup() {
      const refChildComponent = ref(null);
      const doChildMethod = () => {
        refChildComponent.value.childMethod();
      }

      return { refChildComponent, doChildMethod };
    }
  })
</script>

context.refs を使って refChildComponent にアクセスできなくなったため、子コンポーネントref 属性に記述した名称と同じリアクティブな変数を定義してあげる必要があります。
さらに子コンポーネントのメソッドを実行するには refChildComponentreturn させないといけません。

Vue2 Composition API では return の記述が必要なかったため盲点でした。
新しく Vue3 でコンポーネントを作るような状況であれば、このような見落としは生じづらいかもしれませんが、大量の書き換え作業の中ではやはり抜け漏れがあると疑った方が良いですね。

この問題が発見しづらい要因として、ref 属性に記述した値は文字列であるということも挙げられます。 例えば下記のように子コンポーネントprops に変数を渡す場合、その変数が return に記述されていないとエラーが出ます。

<template>
  <ChildComponent :title="refChildComponent" />
</template>

<script>
  export default defineComponent({
    name: "ParentComponent",
    setup() {
      const title = ref("")

      return { title }; // ここに title を入れないとエラー
    }
  })
</script>

先程の ref 属性の例ではこのようなエラーは表示されないので、一見問題ないように見えてしまうわけですね... 恐ろしい...😱


3. this の残骸によって処理が走らない

原因: Options API の this を消し忘れている

最初の Vue2 Options API の例にも示したように、Options API ではコンポーネントで定義されたメソッドやリアクティブな値を使用すときは頭に this を付ける必要がありました。
Composition API になってからは this の記述は不要になったのですが、Composition API に書き換えるときに Options API の残骸の this が、残っていても警告が出ないパターンが存在します。

<script>
  export default defineComponent({
    name: "TestComponent",
    setup() {
      const doFoo = () => {
        doHogeHoge();
        this.doBar(); // 意図しない this が紛れ込んでもエラーが見えない
        doFugaFuga();
      }
    }
  })
</script>

幸いなことに自分が担当しているプロダクトでは this を使用している箇所は限定的なため見つけるのは簡単でしたが、開発中にエラーとして出てくれないのはやはり怖いです。


この問題に今後どう向き合うか?

今回、紹介したハマった箇所3つはいずれも開発中に見落としがちになるものばかりでした。
結合テストでほとんどの問題を発見するに至りましたが、できればテストを実施する前の段階で見つけたいです。型チェックによって防げるものも多いので TypeScript を導入すれば、より安全に開発を進めることができるかもしれないですね。

とはいえ、Vue2 から Vue3 への大規模なアップデートのタイミングで TypeScript を入れるのもハードルが高いかもしれないので導入のタイミングは検討する必要があります。


まとめ

最後まで読んでくださりありがとうございました 🙇‍♂️
紹介させていただいたハマった点はいずれも初歩的なものです。普段なら間違えないような作業も、大量の書き換え作業の中で埋もれてしまって気づかないということを教訓として得ました。

改めてこの記事が Vue3 へのバージョンアップに挑む開発者の参考になれば嬉しいです!

コミュニティ・リソース・参考文献

【初心者】JavaScript のカウンターの書き方。色々な書き方から無限の学びを得る。

【初心者】JavaScript のカウンターの書き方。色々な書き方から無限の学びを得る。
【初心者】JavaScript のカウンターの書き方。色々な書き方から無限の学びを得る。

Work illustrations by Storyset

こんにちは、開発チームでエンジニアをしている和田です。
最近は担当しているプロダクトの開発メンバーが入れ替わったりとバタバタしている毎日です🏃‍♂️

いろんな開発メンバーがいると十人十色のコードが生まれます。また、自分自身に焦点を当ててみても過去の自分と今の自分では書き方に差があるはず... なんて哲学的なことを考えていると、一つの処理を様々な書き方で書いてみたくなりました(どうゆうことやねん笑)

というわけで今回は JavaScript 初心者の方向けにカウンターのいろんな書き方を簡単に紹介させていただきます。

JavaScript でカウンターをいろんな書き方で書いてみる

変数を使ったカウンターの書き方

まずは最も簡単な変数を使った書き方です。

let counter = 0;

for (let i = 0; i < 10; i++) {
  console.log(counter++);
}

console.log(`counter: ${counter}`);
console.log(`counter: ${counter}`);
0
1
2
3
4
5
6
7
8
9
counter: 10
counter: 10

プログラミングを最初に学んだときのループ処理などでよく見る例ですね。
ループの中では最初に counter 変数に入っている値をコンソールに表示した後にインクリメントを行います。 当然、ループを抜けた後の counter 変数の中身は常に 10 となります。

関数のプロパティを使ったカウンターの書き方

プロパティと聞くとオブジェクトをイメージされる方が多いかもしれません。
JavaScript では関数は特殊なオブジェクトとして定義されているので、関数の宣言を行っていればオブジェクトのように . でプロパティの値を操作することができます。

const uniqueInteger = () => uniqueInteger.counter++;
uniqueInteger.counter = 0;

for (let i = 0; i < 10; i++) {
  console.log(uniqueInteger());
}

console.log(`counter: ${uniqueInteger.counter}`);
console.log(`counter: ${uniqueInteger.counter}`);
0
1
2
3
4
5
6
7
8
9
counter: 10
counter: 10

uniqueInteger 関数の返り値に uniqueInteger.counter++ とすることで、uniqueInteger 関数の counter プロパティの値を返してからインクリメントを行います。
関数を宣言した時点では counter プロパティには何も値が入っていないので、宣言の前後で uniqueInteger.counter = 0; という初期化が必要となります。 ループの中のみで関数を実行しているので、ループの外では現在格納されている値を呼び出すことができます。
カウンターの値を初期化するのが少し手間ではありますが、「どの関数を何回実行したか?」が強調された書き方です。

即時実行関数とクロージャを使った書き方 その1

即時実行関数やクロージャと聞いて馴染みのない方もいるかも知れません。
まずはサンプルコードを確認しましょう。

const uniqueInteger = (() => {
  let counter = 0;
  return () => counter++;
})();

for (let i = 0; i < 10; i++) {
  console.log(uniqueInteger());
}

console.log(`counter: ${uniqueInteger()}`);
console.log(`counter: ${uniqueInteger()}`);
0
1
2
3
4
5
6
7
8
9
counter: 10
counter: 11

uniqueInteger は即時実行関数、すなわち宣言した直後に実行される関数なのでuniqueInter にはcounter++ という処理をもつ関数が格納されています。
したがってuniqueInteger() として実行すると counter をインクリメントした結果を返してくれるわけです。 ポイントとなるのはuniqueInteger 関数の counter 変数の値を直接、確認することができない点です。
今回の書き方は、これまでとは異なり、ループ後の値を保存しておきたい場合は専用の変数を設けるなどする必要があります。

【補足】即時実行関数で書かないとどのような結果になるか?

これまでのサンプルに対する理解を深めるためにあえて uniqueInteger を即時実行関数ではなく、ただの関数として実行してみます。

const uniqueInteger = () => {
  let counter = 0;
  return () => counter++;
};

for (let i = 0; i < 10; i++) {
  console.log(uniqueInteger()());
}

console.log(`counter: ${uniqueInteger()()}`);
console.log(`counter: ${uniqueInteger()()}`);
0
0
0
0
0
0
0
0
0
0
counter: 0
counter: 0

uniqueInteger 関数からは () => counter++ という関数が返されるので、実行時は uniqueInteger()() と少し変わった書き方をしてあげる必要があります。この場合、実行結果はループがあってもなくても 0 となります。これは関数が呼び出されるたびに counter 変数が counter = 0 として初期化されるためですね。一方で即時実行関数は宣言した後すぐに実行されるため、その後も uniqueInteger 関数内の counter 変数を初期化せずに参照し続けます。このような違いがあるため即時実行関数とクロージャを使うとカウンターとして機能するわけです。

即時実行関数とクロージャを使った書き方その2

関数のプロパティを使った書き方では、関数名とプロパティ名を . で繋ぐことで「どの関数を何回実行したか?」がわかりやすく書かれていました。 即時実行関数・クロージャでも同様の表現で記述することができます。

const uniqueInteger = (() => {
  let counter = 0;
  return {
    current: () => counter,
    counter: () => counter++,
    reset: () => (counter = 0),
  };
})();

for (let i = 0; i < 10; i++) {
  console.log(uniqueInteger.counter());
}

console.log(`counter: ${uniqueInteger.current()}`);
console.log(`counter: ${uniqueInteger.current()}`);
console.log(`counter: ${uniqueInteger.counter()}`);
console.log(`counter: ${uniqueInteger.counter()}`);
console.log(`counter: ${uniqueInteger.reset()}`);
console.log(`counter: ${uniqueInteger.counter()}`);
console.log(`counter: ${uniqueInteger.counter()}`);
0
1
2
3
4
5
6
7
8
9
counter: 10
counter: 10
counter: 10
counter: 11
counter: 0
counter: 0
counter: 1

先程の記述方法と異なっているのは uniqueInteger 関数の返り値がオブジェクト形式になっている点です。

さらにオブジェクトのそれぞれのプロパティの値は関数となっているため uniqueInteger.current() とすれば現在のカウンターの値を取得することができ、uniqueInteger.counter() でこれまで同様にインクリメントすることもできます。
また uniqueInteger.reset() とすることでカウンターの値をリセットすることもできます🙌

これまでのサンプルコードのいいところを全部詰め込んだコードですね。

どのカウンターがベストプラクティスか?

カウンターの処理1つとってもこれだけたくさんの書き方ができます。
では、どの書き方がベストプラクティスと言えるでしょうか?

答えは「時と場合による」です😇

とても簡単な処理では、最後の多彩な機能が盛り込まれたカウンターよりも最初の単純な変数によるカウンターの方が簡潔で理解もしやすいです。 チームや必要とされる機能などによって、どのような書き方が最適なのか?というのは常に変わるものなのでいろんな書き方を覚えておくのが良いかもしれませんね。


まとめ

最後まで読んでくださりありがとうございました🙇‍♂️
ただのカウンターの処理をいろんな表現で書かかせていただきました。
ライブラリから提供されている関数やメソッドにも今回紹介した書き方で実行するようなものが見られます。カウンターに関する知識だけでなく、今回紹介した内容がコードを読み進めるときに役に立つことを願っています🙏

技術ブログを運営してきた半年間の試行錯誤

技術ブログを運営してきた半年間の試行錯誤
技術ブログを運営してきた半年間の試行錯誤

こんちには、N2i Tech Blog が半年続いていることに感動している編集長の和田です。
いやぁ年末ですね🎄 この N2i Tech Blog は 2023年5月に正式に始動して以来、毎週欠かさず投稿を続けて来ました。 少ないながらも本記事の執筆時点では既に30記事以上を皆で投稿しています。

今回は普段の記事とは趣向を変えて、Tech 系の企業の技術ブログ運営をされている方向けに Tech Blog を継続してきた工夫や反省について述べさせていただきたいと思います。

技術ブログをこれから運営する予定の方にも参考になればと思いますので、最後まで読んでいただけると嬉しいです🙏

N2i の技術ブログの運営体制

工夫や反省について述べる前に、N2i の技術ブログの運営体制について簡単に紹介させていただきます。

運営メンバーについて

まず、運営としてコアメンバーとして動いているのは和田(筆者)と岡部の2名で記事の管理や N2i Tech Blog の運営方針などを決めています。両者とも普段はエンジニアとして作業しており、業務の合間の時間で当ブログの運営を行っています。

執筆メンバーについて

執筆者は運営メンバーはもちろん社内のエンジニア全員(PMやEMも含む)を対象としており、各人に決められたノルマなどは設けておらず自由に投稿していただいています!

執筆していただいた記事は専門領域の開発メンバーと我々、運営メンバーが校閲し、記事を公開するような流れです。

技術ブログ運営の目的

N2i Tech Blog のチームとしての目標は「エンジニアが自発的に技術発信をする文化を作ること」です。

学んだことを自分からアウトプットして各々が切磋琢磨しているチームは素敵ですよね。N2i をそんな組織にしたくて技術ブログを運営しています。

ここまでが N2i Tech Blog の運営体制・目的の簡単な説明です。次に半年ほど毎週投稿を行うに当たって工夫したことについて説明させていただきます。


技術ブログの運営に当たって工夫したこと

技術ブログの運営に当たって工夫したこと
技術ブログの運営に当たって工夫したこと

People illustrations by Storyset

様々な方々に支えられながら継続できている N2i Tech Blog ですが、継続のために色々と工夫をして参りました。 その中で特に継続の工夫として効果があったと感じることを3つ紹介させていただきます。

1. 投稿の目標宣言をする

幸いなことに毎週の事業部全体ミーティングの中で当ブログの活動を発表するための時間を設けていただいています。 その中で「毎週1記事必ず投稿する」ということを口酸っぱく言い続けてきました。

宣言し続けたがゆえに逃げ道のない状況となり、筆者自身の編集長としての責任も相まって本当に毎週1記事投稿できています。 非常に単純なことではありますが、発足以来この指針をぶらさず徹底してきたのは我々 N2i Tech Blog チームの誇りです👍

工夫というより精神論的なものかもしれませんが、個人的に最も継続のために効果があった行動だと思っています。

2. 頑張り過ぎない低燃費スタイル

「毎週1記事必ず投稿する」の裏返しでもあるのですが「週1記事以上は投稿しない」ということも大切にしてきました。 たくさんの方が執筆してくれた週は嬉しくなって投稿を早まる気持ちもありました。しかし、すぐに投稿はせずに現状のペースを保つことを心がけました。

週1記事すなわち毎月4記事の投稿が目標となるため、その分達成のためのハードルも低くなって無理なく継続できたと思います。 ちょっとした時間に一気に2記事も書いてしまえば、後は書いてくれそうな人を二人くらい見つければ目標達成といった具合です。

あえて目標を小さく設定したのは方針として良かったと感じています。

3. 記事のジャンルにこだわらない

N2i Tech Blog の記事を見ていただくと分かるように、サンプルコードが記載されたいかにも技術ブログらしい記事から社員へのインタビュー・書籍の紹介記事など、さまざまな記事を投稿してきました。

techlog.n2i.jp

techlog.n2i.jp

techlog.n2i.jp

技術ブログとして運営する以上は、技術に関する専門的な内容を主軸として投稿をしたいのが正直なところではあります。しかしながら、ブログ運営の初期の指針としてはジャンルよりも継続と量が大切であると考えたため、あまり制限を設けずに投稿してきました。

技術に関連している・業務に関連した学びがあるという条件に少しでも当てはまっていれば投稿してきたのは良い選択だったと思っています。


ここまでが運営にあたって工夫してきたことです。特別な仕組みやシステムを設けているわけではありません😅
一方で運営を継続するにあたってあまり効果を感じられなかった施策もあります。当ブログの運営にあたって失敗したと思われる施策についても紹介させていただきます。


技術ブログの運営で効果のなかった施策

技術ブログの運営で効果のなかった施策
技術ブログの運営で効果のなかった施策

Sport illustrations by Storyset

  • 記事ネタがない
  • 書き手が見つからない

これら2点はどこの企業ブログでも抱える問題ではないでしょうか?
N2i Tech Blog でも同じ問題を未だに抱えているわけです...
それらを打開するために試行錯誤したもののあまり効果の感じられなかった施策を3つほど紹介させていただきます。

1. 記事のテンプレートを用意するも使う人がいない

開発メンバーのほとんどが技術記事の執筆経験がなく、最初の記事を書くハードルが高い状況でした。
そこで下記のような記事の骨組みとなるようなテンプレートを設け、できる限り楽に記事が書けるような環境を整えることを考えました。

<!-- 
記事のタイトル例
- 「新しいエディター〇〇を試してみました」
- 「なぜ Mac で開発するのか?」
- 「最近流行りの〇〇ターミナル使ってみた件」
-->


## 背景・目的

<!-- 記事の目的や背景を説明し、読者に対して何が提供されるかを明確にする。 -->

### 対象読者

<!-- 記事の対象となる読者層や必要な事前知識を明示する。 -->

### 開発ツール・環境の概要

<!-- 関連する開発ツールや環境の主要な機能や特徴を紹介。 -->

### 主な機能と特徴

<!-- 関連するプログラミング言語やフレームワークの主要な機能や特徴を紹介。 -->

### インストールと環境設定:

<!-- 開発ツールや環境のインストール方法と環境設定手順について説明。 -->

---

## 実践内容

<!-- サンプルコードや実践例を用いて、プログラミング言語やフレームワークの使用方法を紹介。 -->


### 自分の意見・ベストプラクティス

<!-- 実践内容を通じて思ったことや意見。効率的で安全な開発のためのベストプラクティスや推奨されるコーディングスタイルを紹介。同じ目的を持つ代替ツールとの比較を行い、選定したツールのメリットやデメリットを説明。 -->

---

## まとめ

<!-- 記事の内容を要約し、読者が得た知識をどのように活用できるかを強調。 -->


### コミュニティ・リソース・参考文献

<!-- 関連するドキュメント、チュートリアル、ブログ、フォーラム、コミュニティなど、さらなる情報を得るためのリソースを紹介。 -->

毎週の定例ミーティングの中で上記のテンプレについて周知するものの執筆者が増えるわけでもなく、執筆してくれた方はテンプレを使わずに書いてくださったので効果が感じられなかったです😅

自主的に書ける人はスラスラと筆が進むし、そうでない人は別のところに障壁がありそうです。

今は書き方以前に「何を書くか?」に困っているのでは?というところに着目し、「私の作業環境シリーズ」などといった誰でも書きやすいようなテーマを設けて検証しています。

2. 記事の代筆アピールも効果なし...

開発チームのメンバーは各々がタスクに取り組んでいるため、単純に記事を書く時間がないというのが現実です。 当然、ブログの運営メンバーもエンジニアとしてタスクに取り組んでいるため、業務に支障のない範囲で活動をしています。

開発チームが執筆してくれるメンバーのタスク量をコントロールすることは難しいので

「普段の業務中の学びや作業メモを箇条書きにしたものを投げてくれればこちらで記事にしますよ〜」

...などとアピールしましたが、特に効果は得られませんでした。


この記事の執筆時点では「N2i Tech Blog に記事を投稿して何が得られるか?」という書き手側のメリットを示せていないため、執筆するメンバーが記事を書きたくなるような仕組みづくりが必要なのかもしれません。

3. 月に4記事という頑張ったら一人でもなんとかなる目標設定

冒頭でもお話した通り、週に1記事つまりは月に4記事程度書くことを当ブログの目標にしています。
この施策自体は失敗とは思っていませんが裏目に出たことがあります。

それは投稿するメンバーが固定化されてしまうということです。

正直なところ月に4記事だったら一人でもなんとか書けてしまうため、数名のメンバーが執筆してくれれば目標を達成するのは難しくありません。結果として誰かが書かなくても目標が達成されるという状況が生まれてしまい、メンバーが固定化されるという事態に陥りました。

「エンジニアが自発的に技術発信をする文化を作ること」が目標なので、可能なかぎりいろんな人に記事を書いてもらいたいと思っています。しかしながらメンバーが固定化されているのは、目的から遠ざかっているようで悲しいですね🥲


まとめ

最後まで読んでくださりありがとうございました🙏

効果の感じられなかった施策に対する代替案は今のところありませんが、このままのペースで記事数を増やしていこうというのが今の方針です。

この記事がこれから技術ブログを運営する方や技術ブログを運営されたばかりの方の参考になることを願っています。

この記事を読んで「エンジニアが自発的に技術発信をする文化を作ること」を実現するためのアイデアが思いついた方はぜひ N2i Tech Blog チームにご連絡ください🙏

【私の作業環境】N2i のエンジニアリングマネージャー吉野さんのデスクを覗いてみた!

私の作業環境

こんにちは、N2i Tech Blog 編集長の和田です。
エンジニアのみんなの作業環境について紹介していくシリーズ「私の作業環境」😆

物理的な環境・お気に入りのツール・ワークフローなんでも構わず社員のみんなのこだわりポイントを紹介させていただきます。
今回はエンジニアリングマネージャーの吉野さんです!


今回、紹介する吉野さんのデスクはこんな感じ👀

吉野さんの作業環境
吉野さんの作業環境

美しいですね。
筆者自身、音響機器には決して詳しくないですが、オーディオにこだわりを感じるデスクですね✨
使用されている機材も教えていただきました!

  • モニター: BenQ EX270QM
  • キーボード: HHKB Professional Type-S
  • マウス: Kensington Expert Mouse
  • マイク: RODE NT-USB Mini
  • カメラ: LOGICOOL C980GR
  • ドッキングステーション: CalDigit TS3 Plus
  • USB-DAC: FiiO K3ES & Fostex PC100USB
  • アンプ: Marantz HD-AMP1
  • スピーカー: Wharfedale DIAMOND 210

キーボードはエンジニア御用達の HHKB のハイエンドモデル👍
たくさんの機材を使用していながら、この写真にキレイに収まるくらい整頓されているのがびっくりです😁

吉野さんからはこだわりのポイントなども教えていただきました!

私の作業環境のこだわりポイント

-- デスクの中で一番こだわりをもってるのはどこですか?

有線ですべて繋がっていることです!

一時期、無線化を進めていましたが、イヤホンの充電を忘れることがあったり、カメラ・マイク・キーボード・マウスは接続状況が安定せず、無線機器の機嫌に左右されてストレスを感じることがあったんですよね...😅 もう無線の機嫌問題を考えたくないと思って無線化は諦めました。

また、ほとんどが USB 切り替え機(UGREEN)を経由して繋がっていて、仕事用 PC とプライベートの PC との USB 機器の切り替えと共有がスムーズにできるのも便利に感じています。

-- おすすめの機材を1つ教えていただきたいです!

マイクがコンパクトだけど性能が良くて気に入っています。
前面の物理ボタンがミュートボタンではないところはちょっと微妙です。

-- デスクの「ここを改善したい」があれば教えていただきたいです!

CalDigit TS3 Plus の Display Port が 1.2 で HDR に対応していないため、別途 USB-C - Display Port の変換アダプタ (Cable Matters) を使っています。ここは改善したいですね。
CalDigit TS4 (CalDigit の新しいドッキングステーション) に買い換えればいいんですが、値段分の満足度を得られるんだっけ?というところでずっと迷っています...

まとめ

最後まで読んでくださりありがとうございました!
以前の田中さんのデスクはモニターが印象的でしたが、今回の吉野さんは音響機器が印象的でしたね。
配線の工夫やこだわりにも驚かされました✨

まだ前回の「私の作業環境」シリーズを読まれてない方はぜひ下記からチェックして下さい!

techlog.n2i.jp