「JS体操」のすゝめ 〜その①〜

JS体操のすゝめ〜その①〜

このエントリは【カヤック】面白法人グループ Advent Calendar 2023 の24日目の記事です。


こんにちは!意匠部のおばらです。
面白法人カヤックでは日々、様々な社内勉強会が開催されています。本記事では JS 好きな社内のエンジニア向けに私が企画&主催している「JS体操」についてご紹介します。

記事の最後にはみなさんも挑戦できるように最新の「JS体操」を紹介しています。ぜひ挑戦してみてください。



あ!JS は JavaScript の略ですよ。念のため。


1.「JS体操」とは?

JavaScript での開発環境は日々目まぐるしく変化しています。

最近では TypeScript での開発があたりまえとなり JavaScript を直接書くことはほぼ無いという人も多いのではないでしょうか?私もそうです。また、TypeScript / JavaScript によらず、各種フレームワークやライブラリを利用し比較的高レイヤな抽象化されたコードを書いていると思います。

しかしごくごくたまーに、フレームワークやライブラリでは実現できない機能を実現したり、細かなパフォーマンスチューニングをしたい場合があります。そうなると普段書いているコードと比べ低レイヤなコードを書く必要があります。フレームワークやライブラリがブラックボックス化しているロジックも自分で考えなくてはなりません。

例えば Three.jsPixiJS などのライブラリを使わずに生の WebGL で書く場合などです。 TypeScript で開発していたとしてもそれは例外ではありません。

そんな開発も可能な人材を社内で育成するべく企画した、JavaScript の言語仕様や各種 API の理解とプログラミング的思考力を鍛えるための日々の鍛錬、JS 脳&プログラミング脳をゆる〜く鍛えるためのエクササイズ、それが「JS体操」です。

2.「JS体操」の掟

「JS体操」には決して破ってはならない鉄の掟があります。それは、、

生の JavaScript、いわゆる Vanilla JavaScript のみ。フレームワークやライブラリを使用したら即出禁、永久追放

以上。メモ帳さえあれば開発ができた時代に逆戻りしましょう!

※ もちろん普段の案件ではフレームワークもライブラリも使っていますよ!

3.「JS体操」のメニュー

3.1. 何らかのロジックを解く

JavaScript の言語仕様、各種 API の仕様、数学、アルゴリズムなどの知識を駆使しロジックを解きます。 ロジックが解けたかどうかは大抵の場合「エラーが解消されたか」で判断します。 後述するグラフの問題などは目視での判断です。笑

3.2. コードゴルフで文字数を競う

初期の頃はコードゴルフは必須ではありませんでしたが、ゲーム性を上げるために取り入れました。最近はロジックを解き、且つコードゴルフをするのが基本メニューです。コードゴルフ付きの問題はコードの文字数がコンソールに表示されます。解けた人はそれを「JS体操」の社内 Slack チャンネル #club-js-taiso に貼っていくというアナログな自己申告システムです。長く続けるためには運営側のハードルを下げるのも大事ですよね。

ちなみに文字数は、簡易的に JS ファイルを fetch() で文字列で取得し、その length プロパティを見ているだけです。サロゲートペアとかは気にしてません。なので、これを逆手に取ることもできそうですね。

const { length } = await fetch('hoge.js').then(res => res.text());

console.log(
  `現在の文字数(≒length)\n%c${length}%c文字`,
  'color:#0af;font-weight:bold;font-size:800%;',
  'font-size:100%;',
);

ところでコードゴルフは一体なんの役に立つのでしょう?実際の案件でコードゴルフが求められることはまず無いですしコードゴルフで難読化されたら逆に困ります。

「JS体操」がコードゴルフで鍛えたいポイントは以下です。

  • ロジックをシンプルにする力
  • ロジックを一つではなく複数パターン考え比較する力
  • 言語(ここでは JavaScript)の仕様、各種 API の仕様の深い理解
  • 諦めない心

4.「JS体操」の流れ

普段の「JS体操」の様子を具体例を用いてご紹介します。

4.1. 出題方法

毎週1問、「JS体操」専用社内 Slack チャンネル #club-js-taiso に問題が投稿されます。 問題のコードは「JS体操」専用社内 GitHub リポジトリに push されます。

例えばこんな感じです。

上で例に挙げた第9回はグラフの問題。
アニメーションのイージングをいろいろ選びたいときは GSAP などのライブラリを使う方が多いと思いますが、ライブラリが用意しているイージングでは物足りないときはイージング関数を自作したりカスタマイズする必要があります。そんなときのための体操です。

イージング関数についてはちょうど3年前の今日、記事を書いていますので興味があれば御覧ください。 techblog.kayac.com

他にも様々なジャンルの問題が出題されます。
詳しくは後ほどご紹介します。

4.2. 解答方法

挑戦者はまず問題のコードを GitHub リポジトリから pull します。 ちなみに挑戦するかどうかは任意です。

そして手元で解いて、ブラウザのコンソールに表示された文字数のスクリーンショットを Slack チャンネルに貼ります。ちゃんと解けたかどうかは善意に基づく自己申告制。コンソールにエラーが表示されていなければ解けたことになります。自己申告制なのでたまに治安が悪化しますがその様子も後でご紹介しますね。

もしレビューを希望する場合は締切後に自分の回答を push し、プルリクを出します。

4.3. 実際の様子

まずはみんなで「JS体操」をしている様子をご覧ください。
(※ 時間にモザイクを掛けている理由はお察しくださいね☆)

まず問題が Slack に投稿されます。

早速挑戦する若者(20代男性)

煽る出題者(40代男性)

早速解いた若者(20代男性)

53文字という文字数で勝利を確信し、なぜか暗に寿司をねだる若者(20代男性)

53文字という文字数に感嘆しながらも焦る出題者(40代男性)

さらに1文字減らし皆を引き離す若者(20代男性)

たまにヒントも出します。

寿司をねだる後輩をじわじわ追い上げる先輩(20代男性)

寿司を逃す恐怖に怯えはじめる暫定1位の若者(20代男性)

寿司を奢らされる恐怖に怯える出題者(40代男性)

寿司をかろうじて阻止した出題者(40代男性)とそれを悲しむ若者(20代男性)

そして締め切り!!

締切後は Slack で適宜図なども交えながら解説をします。こういう複雑な波形はまず複数の波形の和に分解するとわかりやすいですね。

ちなみに、その後みんなで答え合わせをした結果、最終的には 47 文字まで短くできました。
みなさんもぜひトライしてみてください。

若者(20代男性)にたしなめられながらアレをアレすれば47文字まで短くできることに気づいた出題者(40代男性)

こんなやり取りが繰り返されています。

5.「JS体操」の過去問の紹介

グラフの問題以外にも、様々なジャンルの問題を出しているので少しご紹介します。

5.1. 第一回

2023年1月からスタートした「JS体操」。
記念すべき第一回は IIFE (Immediately Invoked Function Expression)、即時関数の問題でした。 最短文字数の答えはある1文字を足すだけですね。単に解く(≒エラーを回避する)だけなら一瞬でできそうですが、他の回答例をすべて挙げたり、そのメカニズムをきちんと説明できるでしょうか?

5.2. 第二回

面白インターフェイスを実装する問題。
アレが2つしか無いのがポイントですね!

5.3. 第三回

コードゴルフで競い合ったら面白いかも?ということで初のコードゴルフ問題。

インセンティブを設定すると盛り上がります。

焼肉に行けるの本当は3位までのつもりでしたが4位のひとだけ行けないのは悲しいのでみんなで行きました。 鎌倉オフィスから江ノ電で3駅の丸牛さん。おすすめです。 tabelog.com

5.4. 第八回

早くも問題のネタが思いつかなくなり、某有名な問題をアレンジした時も。
この頃から次第にロジックを解くだけでなくコードゴルフもする、という流れになっていきました。

コードゴルフ付きの問題が何問か続いた頃、ひねくれ者が出始めます。

何をしているか、想像できますか?
アレを上書いちゃってますね。

出題者もついひねくれてしまいました。治安悪化です。

5.5. 第十一回

面白インターフェイス再び。

5.6. 第十四回

ちょっと難しめの問題の時も。
このときは挑戦者の数が一気に減ってしまいました。。

ここで再び治安が悪化。

5.7. 第二四回

Deno は Node のアナグラムなのか!というところから思いついた問題。

とこんな感じでいろんなジャンルの問題を毎週出題しています。
問題を出題する側も、JavaScript の良い復習になります。

6.「JS体操」最新の問題に挑戦してみよう

最後に、この記事を書いている2023年12月22日現在の最新の問題をご紹介します。
(若干この記事用にカスタマイズしたうえで)GitHub にもコードを上げてあります。JS 脳をほぐしたい方はぜひ一緒に「JS体操」してみましょう♪

この問題を出したときの実際の社内 Slack のスクリーンショット

余計なホワイトスペースがたくさん入ったインデントされていない汚い JSON 文字列(本記事では JSON として解析する文字列、JSON の記法で書かれた文字列を便宜上「JSON 文字列」と呼ぶことにします)を綺麗に整形するという問題です。実際の業務では JSON.parse()JSON.stringify() でやるべき処理ですが、仮に自力でやろうとするとどうなるでしょう?そしてコードゴルフをするとしたら何文字になるでしょう?

汎用的なコードにする必要はありません。つまり問題の JSON 文字列さえ綺麗に整形できるコードであれば OK です。

社内では徐々に JSON が複雑になっていく、という形で何回かにわたり出題しました。
今回ご紹介するのはその最終問題です。

6.1. 「JS体操」サンプル問題のはじめかた

① コードをダウンロードする

コードは以下に置いてあります。
github.com

ファイルは以下の4点です。

  • index.html
  • ugly.json
  • beautify.js
  • main.js

② コードを実行する

index.html をブラウザで開きます。ブラウザは Google Chrome 推奨です。

JS のモジュールを import したり、JSON ファイルを fetch() している関係でローカルサーバ上で実行するなどしてください。Visual Studio Code ユーザの方は Live Server プラグインを使うと便利かもしれません。

③ 開発者用コンソールを開く

Mac Chrome の場合は Option + Command + i を押すと開きます。

最初はわざとエラーが出る状態になっています(※ Chrome では)

④ beuatify.js を編集してエラーを解消する

まずはエラーを解消しましょう。編集して良いファイルは beautify.js のみです。

/**
 * このファイルを JSON.parse() と JSON.stringify() を使用せずに
 * なるべく短い文字数で書き直してください♪
 *
 * @param {string} jsonString
 * @return {string}
 */
export default function beautify(jsonString) {
  return JSON.stringify(
    JSON.parse(jsonString),
    null,
    2,
  );
}

初期状態では JSON.parse()JSON.stringify() を使って JSON 文字列を綺麗に整形していますが、これを JSON.parse()JSON.stringify() を使わずに実装してください。

ちなみに JSON.parse()JSON.stringify() を使っていないかの判断は、単に両者を上書くことで実現しています。

JSON.parse = JSON.stringify = () => {
  throw new Error('JSON.parse() も JSON.stringify() も使用禁止だよ!');
};

よって以下のように他の windowJSON.parse()JSON.stringify() を使うなどの裏技で回避できるかもしれませんがそれは NG です。笑

const iframe = document.body.appendChild(document.createElement('iframe'));
const window = iframe.contentWindow;
const JSON   = window.JSON;

export default function beautify(jsonString) {
  return JSON.stringify(JSON.parse(jsonString), null, 2);
}

※ 業務ではすでに提供されている機能の独自実装は避け、JSON.parse()JSON.stringify() を素直に使いましょう!
※ 汎用的なコードにする必要はありません。本問題の ugly.json さえ綺麗に整形できれば OK です。関数 beautify() に渡される引数 jsonText は常に Valid な JSON 文字列で、且つ ugly.json の文字列のみである想定で OK です。例えば空のオブジェクトや配列( {}[])、truefalsenull1224"Santa Claus" など値のみなどの JSON 文字列が jsonText に渡ってくることは想定しなくて良い、ということです。
※ ちなみに RFC 4627 では JSON のトップレベルはオブジェクトか配列のみと定義されていますが ECMAScriptの仕様 では、truefalsenull1224"Merry Christmas!" なども許されていますね。

⑤ beautify.js の文字数をなるべく少なくする(コードゴルフ)

エラーが解消されると、コンソールに文字数が表示されます。

さて、ここからが長く苦しい戦いになります。JavaScript の知識をフル活用し、文字数をなるべく少なくしてみてください。

ちなみに社内での最短文字数は

でした。

github.com

7. まとめ

さて、「JS体操」いかがだったでしょうか?

こういった社内施策・勉強会を長く続け、文化を醸成・浸透させるのは大変です。でも Slack にただスクショをただ貼るだけ!みたいなシンプルな仕組みにすることで運営もしやすくなりますね。あとはゲーム性を取り入れたりたまには美味しいインセンティブも。「JS体操」はまだ始めてからもうすぐ1年ですが、とりあえず100問を目指そうと考えています。毎週1問なので100問達成は早くてもおそらく再来年ですね。笑

JavaScript はどんどん仕様が追加されていくのでこういった施策で楽しく学ぶのも良いと思います。 次の気になる仕様はデコレータusing キーワードでしょうか?これらでどんな問題が作れるか、楽しみです。

8. 次回の予告

明日は154文字の解説をお届けします。記事を書くのは23新卒の期待の新人、おおぎりです。
お楽しみに!!

〈2023.12.25 追記〉 154文字の解説は以下をご覧ください! techblog.kayac.com

9. 参考文献




面白法人カヤックでは一緒に働く仲間を募集しています。JS 好きな方はぜひぜひご応募ください!
www.kayac.com

www.kayac.com