JavaScriptを完全未経験者に講義した際の振り返りと反省点

# 追記
本記事中に登場する会社を2021年10月付けで退職し、現在は別会社にてエンジニアをしております。
したがって本記事は以前務めていた会社で行った講義のまとめになりますので、"弊社"という表現がでてきたらそれは以前の職場なんだなと置き換えてください。

弊社に入社したばかりの完全未経験エンジニアに向けて「JavaScriptで何ができるの？」というテーマで講義を行いました。
所感と備忘録、反省点などをまとめて、今後振り返ったときや人に物事を教えるといったときに参考になればと思います。

## 背景
弊社では完全未経験のエンジニアが毎月3~10人ほど入社します。
出向先はコールセンターや配線工事、Excelへのスクショを主とするような、プログラミングスキルを必要としないところが中心です。
しかしながら、そのために**「JavaBronze程度の資格は本で勉強したけどプログラミング全然わかりません」**といったなんちゃってエンジニアが大量生産されていました。

私自身も待機になっていたこと、このままプログラミングに関心を抱かないまま現場に出荷されるエンジニアが増えてしまうことを危惧し、ここ数か月程ですが毎月入社してくるエンジニアに対して簡単な講義をしてきました。

対象者のレベル感としては

「Progateをちょっとだけ触った」
「HTMLのコーディングはやったことがない」
「プログラミング以前にエディタという言葉を知らない」

レベルをイメージしていただければと思います。

# 講義内容について
受講生にはJavaScriptで簡単なDOM操作を体験してもらいます。

HTML + CSS + JavaScript(ES6)の簡単な構成。
CSSはあらかじめ用意したものを使い、受講生はHTMLとJavaScriptだけを弄るという形にしました。

## 講義のゴール
実際に手を動かしてもらい、プログラミングとはどういう感覚なのか、どういう面白さを感じられるのかといったことを理解してもらおう。というのが目的になっています。

「JavaScriptを使えばこんなことができるんだ！」
「プログラミングって面白いんだ！」

と感じてもらうことがゴールとして設定しています。

したがって、JavaScriptを完璧に理解してもらおうとか、効率の良いコードの書き方とか、thisについて語ろうとかそういったことは考えていません。
(いつか受講生と「thisについて語ろう」が出来る日が来てほしい)

## 講義で作るもの
「ドラクエの戦闘画面」のようなものを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1079033/6e73de8b-c7fb-7bfb-d13f-956b00eb7136.png)

コマンド欄からは「たたかう」または「にげる」が選択できるようになっています。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1079033/df5ac308-b370-fd11-6e10-3cd1904f02d1.png)

今回は厳密にHPを減らしたり、攻撃の成功判定を行ったりはせず、「たたかう」を選択した時点で敵モンスター1体が消滅するようにしています。
また、「にげる」を選択した場合はゲームのリロードを行うようにしています。

```HTML:index.html
<body>
    <div class="container">
        <!-- 敵の画像と名前が表示される -->
        <div class="enemy_container">
            <ul id="enemies">
            </ul>
        </div>

        <!-- コマンドが並ぶ -->
        <div class="command_container">
            <ul class="commands">
                <li id="attack">たたかう</li>
                <li id="run">にげる<li>
            </ul>
        </div>
    </div>

    <!-- JSを読み込む -->
    <script src="main.js"></script>
</body>
```
HTMLは必要最低限です。

```JavaScript:main.js
'use strict';

{
    class Enemy {
        // 初期化
        constructor(name, img_url) {
            this.name = name;
            this.img_url = img_url;
        }

        // 敵情報エリアの要素を取得
        enemies = document.getElementById('enemies');

        // 画像と敵情報を作成する。
        create() {
            // <li>要素の作成
            const li = document.createElement('li');

            // <img>要素の作成
            const img = document.createElement('img');
            img.src = this.img_url;
            img.alt = this.name;
            // 作成した<li>の中に<img>を入れ込む
            li.appendChild(img);

            // <p>要素の作成
            const p = document.createElement('p');
            p.textContent = this.name;
            // 作成した<li>の中に<p>を入れ込む
            li.appendChild(p);

            // 最後に、作成した<ul>の中に<li>を入れ込む
            enemies.appendChild(li);
        }
    }

    // === 戦闘開始時の処理 ===
    // 敵のインスタンスを作成
    const boss = new Enemy("親玉キング", "img/boss.png");
    const slime = new Enemy("スライム", "img/slime.png");

    // 敵の数をランダムで決定する
    const num = Math.floor(Math.random() * 5) + 3;
    console.log(num + '体の敵があらわれた！');

    // ボスを作成する
    boss.create();
    // numの数と同じになるようにスライムを作成
    for(let i = 1; i < num; i++) {
        slime.create();
    }

    // ==== コマンド(イベントリスナー) ===
    // 「たたかう」を選択したとき
    const attack = document.getElementById('attack');
    attack.addEventListener('click', function() {
        alert("攻撃成功！敵を1体倒した！");
        document.getElementById('enemies').lastChild.remove();
        clearCheck();
    });

    // 「にげる」を選択したとき
    const run = document.getElementById('run');
    run.addEventListener('click', function() {
        alert('逃げ出した...ゲームをリセットします。');
        location.reload();
    })

    // === 戦闘の判定 ===
    // 敵を全員倒せたかの判定
    function clearCheck() {
        const num = document.getElementById('enemies').childElementCount;
        if(num === 0) {
            alert('ゲームクリアー　おめでとう！');
            // 現在表示されているページをリロードする
            location.reload();
        }
    }
}
```

作成してもらうJSは大まかには

1. Enemyクラスの処理
1. コマンドを押したときのイベントリスナーによる処理
1. 戦闘後に必ず行う判定

の3部に分かれています。

### クラスについて
prototypeなんて説明しだしたら未経験者の頭は爆発してしまいます。ありがとうES6。

Enemyクラスは敵の情報を分類するクラスです。名前と画像のURLを引数に取り、インスタンスを作成します。
createメソッドにより、`<li>`要素を作成し、`<ul id="enemies">`内に放り込みます。

### イベントリスナー
コマンドにはidを振っているので、それぞれのidを持つ要素を取得し、クリックされた場合のイベントを追加しています。
「たたかう」を選択した場合、アラートの後にenemies内に存在する子要素のうち、一番最後にある要素を削除します。
これにより、**ザコ敵が1匹分消滅した**、ということを表現できます。

### 戦闘後に行う判定
現状はクリアチェックのみを行っています。
`childElementCount`を使用して、enemies内の子要素の数をカウントします。
enemies内の子要素カウントが0(= 敵を全滅させた)のときはゲームクリアとし、リロードを行います。

この判定は攻撃後毎回行われます。


## Emmetは使うべきか
Emmet機能は取り入れていない人のほうが少ないかもわかりません。
これを使うことでコーディングの大幅な時間短縮を図ることが可能だからです。

ただ、今回行った講義では**Emmet機能の紹介は一番最後に回しました**。
賛否両論あると思いますが、今回私の中には以下のような狙いがありました。

***「一度とんでもなく苦労してもらうことで、新しくエディタを入れたりするときにEmmetを忘れずに使ってもらえるようにしたい」***

受講生の大部分はコーディング自体が初めてです。
そのため、タグを閉じ忘れるとどうなるのか、閉じタグのスラッシュの位置を間違えるとどうなるのか、といった「基本的なやらかし」すらも経験できていない状態です。

一度痛い目に合っていただいた上で、初めてEmmetの価値が大きなものになると考え、全て手入力していただきました。
(もちろんEmmetについて知っている受講生には使っていただきました。)

私自身は閉じタグの有無のようなところで時間を浪費するなど勿体ないことだと思いますし、時間は短縮できるなら短縮した方が良いというのが大前提だという考えでいます。
受講生に対しても「痛い目に合うのは一度だけで良い」とは伝えています。

# 講義をしてみて気付いたこと
実際に数か月間、何回かに渡って講義を行った際に気付いたことです。
これらは未経験者だから・新人だから、といった括りだけに留まらず、普段人と話していても意識すべき点かもしれません。

## 未経験者は「本当にここにコードを書いていいのか？」でフリーズする 
未経験者は、for文やif文といった仕組みや構造、概念を頭で理解はしていたとしても、
**「今ここにそれを本当に書いていいのだろうか？」**
と悩んでしまい、書くことができない場合があります。

「ここに書いてしまったらバグが発生してしまうのではないか？」
「もしバグが起きたときに戻せなくなったらどうしよう」

と不安になってしまい、手を付けるのが怖くなってフリーズしてしまうのです。

だんだん経験を積んでいくにつれてそういう疑問はなくなっていくのですが、初心者のうちは本当に不安になってしまうものです。そのため有識者は、そこを解消できるように導いてあげなければいけません。 

## 未経験者から質問を投げかけるハードルは高い
***「いつでも質問を投げても大丈夫です」***
というようにしたとしても、実際に受講生から**質問が飛んでくることは滅多にありません。**

もちろん理想としては未経験者であっても質問の必要がないくらい入り込みやすい講義をすることですが、実際にはそうでない場合もあると思います。

考えられる理由としては、

1. 自分が何についてわからないのかがわからない
1. 講義の流れを止めてしまわないかで不安になっている
1. 「質問をする」こと自体に抵抗がある

といったものが考えられます。

### 自分が何についてわからないのかがわからない
誰しもがもがき苦しむところです。
何がわからないのかがうまく説明できない。でも飲み込めてはいない。すっきりしない。といった状態です。

何十、何百、何千行とコードが続いている中で、ピンポイントにわからない箇所を指摘して…という方が難しいのではないでしょうか？

私自身もこれに関しての解決方法として正しい手段は見つけられていません。

ただ、前後との繋がりをいまいち理解しきれていなかったり、今何をしているのか、で迷っている場合がよく見受けられました。
そのため、「自分の現在地」をよく確認したうえで、**頭だけで整理せずに手を動かしてもらう**ようにしました。

### 講義の流れを止めてしまわないかで不安になっている
受講生が気を使ってしまっている場合です。
気を遣わなくていいよ！といったところで、当人の身体には気を遣うように染みついてしまっているので難しいです。

特に、「新人」という立場上、発言することに抵抗を持ってしまう人もいます。

「順調に講義が進んでいるのに止めてしまったら評価が下がるのではないか？」
「大勢の前で講義を止めるのは恥ずかしいことだ」
「怒られるかもしれない」

評価が下がることなんてないのですが、やはりこのように思ってしまう人はいます。
このような雰囲気を醸し出してしまっている講師にももちろん問題はありますが、だからこそ対策をとる必要はあります。

### 「質問をする」こと自体に抵抗がある
前述の内容と被る部分がありますが、こちらは「質問行為」自体に抵抗を持つパターンです。

「✖✖ってこういうことかな？でも、講師がさっき話していた内容かもしれない…**既に講師が話していた内容を質問してしまったら、私は人の話を聞かないやつだと思われるかもしれない**」

初心者とか未経験とか関係なしに、講義や講演で質問が出てこない場合、だいたいこれなのではないか？とまで思っています。

人間の脳は一度聞いたくらいで完璧に理解することはまずできません。
特に初めて聞く内容であれば、内容を聞き、意味を整理して、飲み込むといった工程を一度言葉で聞いただけでは不可能です。

間違っても**「一度しか説明しないからよく聞きなさい」なんてやらない**ことです。

# 反省点
## 全てを「理解させる」のは不可能
講義を行う以上は、教える内容の全てを理解して頂きたいというのは講師の本音ではあります。

しかしながら、たった1時間程度の講義で、未経験者がその内容の全容を理解するというのは現実的に考えてとても難しいことです。

例えば、
「たった一週間で実践的なプログラミングやインフラ・gitなど、エンジニアとしてのすべてのツールを身につけることができます！」

というのが誇大広告であることはそれなりに勉強をされている方であれば誰もが感じていることだと思います。

未経験者が1週間程度の勉強で完璧に使いこなすことができるというのは、とてつもなく優れた頭脳を持った人でなければまず不可能ですし、そんな浅はかなものではないからです。
それをたった1時間ちょっとの講義で**完全に理解させる**というのはそもそも前提として間違っているのです。

「どこまで理解できていればOKとするか」を事前に定めておき、そのラインを越えられるように接していくことが求められるでしょう。
そしてそのハードルが高くないものであれば、調子に乗ってあれもこれも詰め込みすぎない、その匙加減も必要です。

講義開始直後は熱がこもってあれもこれも押し込もうとしてしまうことがありましたが、あまり効果はありませんでした。
あくまでも「第一歩を踏み出させる」という点を重視して、ある程度許容することも大事なのだということを学びました。

## 「今は何をしているのか」を意識させる
未経験者は特に、**「今、自分は何をしているのか」**が途中で分からなくなってしまうことがあります。これは説明を受けていても普通に起こってしまうので、単に理解力が無いと切り捨てることはできません。

未経験者にとっては新しいことの連続です。初めて知るような話を聞きながら、手を動かしていくというのはとても大変な作業です。
ある程度知識があれば「〇〇の状況の✖✖と似ているな」と考えることもできるかもしれませんが、そのベースすらない状態では置き換えて理解することはできないのです。

5分前まで作業していた内容であっても、次の工程の作業に移る段階では忘れてしまったり、前後の繋がりが分からなくなってしまうのです。

大事なのは、
**最初に作りたいものの全容を簡単に説明する**こと。
そして
**「今の作業箇所は、全体の中のどこの部分か」を説明してあげること**が大切です。

実際に確認してみましょう。

```JavaScript:main.js
    // 「たたかう」を選択したとき
    const attack = document.getElementById('attack');

    attack.addEventListener('click', function() {
        alert("攻撃成功！敵を1体倒した！");
        document.getElementById('enemies').lastChild.remove();
        clearCheck();
    });
```

「たたかう」コマンドによる流れは以下の通りです。

1. プレイヤーが「たたかう」をクリックする
1. アラートを呼び出す
1. 敵のエリアから要素(敵)を一つ消す
1. ゲームクリアしたかどうかをチェックする

1つの操作をさせるとき、**目的を達成するためにどんな処理が行われるか**を考えます。
次に、**その処理を完了させるために必要なことは何か**を考えます。

今回であれば

| 目的を達成するための処理 | 処理を完了させるために必要なこと |
|:-:|:-:|
| プレイヤーが「たたかう」をクリックする  | 「たたかう」をクリックしたときのイベントを仕込む  |
|  アラートを呼び出す | アラートを呼ぶための関数を書く  |
| 敵のエリアから要素(敵)を一つ消す  | 敵のエリア情報を取得して、要素を1つ消す  |
| ゲームクリアーしたかどうかをチェックする  | チェックをするための関数を作る  |

このように導き出すことができます。
 
### 元々用意されている関数なのか、自分で作らなければならない関数か
ポイントとなるのは、「JSが用意してくれている関数」か、自分で作らなければならない関数か、です。

「JSが用意してくれている関数」は、例えば`getElementById()`や`alert()`など、様々なアプリケーションで汎用的に利用されるものが多いです。
`.remove`演算子も関数とは違いますが仲間といえるでしょう。

一方、自分で作らなければならない関数は「ゲームクリアしたかのチェック」など、そのアプリケーション独自の動きをさせたいようなものです。

この独自の動きをさせたいひとまとまりこそが、**その処理を達成させるために必要なことは何か**の部分になるわけです。
もし関数を作成している最中に受講生が「今何をしているんだっけ？」となってしまっていたら、全体像と、今していることを照らし合わせて示してあげる必要があるでしょう。

## フィードバックは貴重
講義が終わった後のフィードバックには目を通しましょう。
しかしながら新人という立場上からなのか、大抵いいことしか返ってきません。

もし悪い点・よくない点が沢山書かれていた場合、それはとても貴重な意見ですのでしっかり目を通しましょう。

出来る限りフィードバックは匿名にすると、集まりやすいかもしれません。

# まとめ
私がやってきたことは、直接会社の儲けに繋がるものではありません。
しかしながら、一人でも多くのエンジニアがエンジニアとしてのスタートを切るための足掛かりになればと思った次第ではあります。

裏を返せば、上記で行ったレベルの講義はスタートを切る**足掛かりにしかなりません**(もしかしたらそれすらも遠いかもしれません)。
ちょっとDOM操作が出来るだけでは仕事にはならないですし、戦力には程遠いです。

この講義を刺激として次にステップアップしていくためには、まだ何が必要かを考える必要があるかもしれません。