レベルエンター山本大のブログ

面白いプログラミング教育を若い人たちに

BLOCKVROCKリファレンス目次はこちら

44歳にもなってプログラミングが楽しくてしょうがない

あと10日ほどで44歳になります。

で、いまはプログラミングが楽しくって、いやーどうしよう。ここ1−2年は人生で一番プログラムを書いたかもしれない。

あたらしいことをやるのも楽しい、VR/ARとかAIとか、ひと昔前ならSFだったことが実現できるのはとってもエキサイティング!

学ぶ中で、昔はわからなかった数学の難しい理論がちょっとづつわかってくるのも嬉しい。ゲームの腕前があがっていくのと同じで、着実に力がついてきてることがわかるのがいい。新たに学ぶことが、実利に繋がるのとても嬉しいしやる気がでる。「勉強」という強いられている感じがなく、探究な感じなのがとても楽しい。

 

今はプログラミングを使ってできることが急激に進化しているから、学べばありとあらゆることを仕事にできる。

 

プログラミングが若手のものだった時代

ちょっと前なら、プログラミングは若手がやる肉体労働で早々に卒業してマネジメントとかをやるのが大人、みたいな雰囲気がありました。自分も30代の頃よくそう言われたけどキャッチアップを続けていてよかった。ほんと。

何度、「そんなことはお前のやることじゃない」と言われたことか、、、

 

「プログラミングは若手のやること」論はわからんでもないです。基本細かい作業なので。

マネジメント力をつけて、たくさんの人を動かして大きな仕事をすることや組織や事業を大きくして社会へインパクトを与えることも面白いとは思います。営業という仕事の、価値を顧客に手渡しできてる感じも面白いですね。ビジネスのいろんな役割、どれもそれぞれ面白いと思える部分はあります。それはいいんだけど、直接手を下せない部分も増えるからもどかしい。大きな仕事の面白さと、手のひらにおさまる仕事の面白さはそれぞれにあると思う。

全部掌握できて、その上で一部を人に任せるのが理想で。いや理想は現実にできるとはいいませんが、目指すなかで落とし所を見つけたいな。

 

周りにもプログラミングする40代多い

10人ぐらい知り合いを思いおこしてみると、同年代のプログラマーも全然活躍しているように思います。CTOとか偉くなってる人も多いんですが、それでもバリバリコード書いてる人が多いです。

近年のソフトウェアは複雑で、一つの専門知識だけでは成り立たないですし、必然的に活躍する世代が上がっているんでしょうね。

35歳定年説とかもありましたね。きっとこれはちょっと前の大企業での話だったんでしょう。たしかに大企業だとマネジメントで大きな仕事を回すことが面白いと思います。でも、大企業含めてどんな企業でも変化を積極的に起こしていかなくてはいけない時代になってきてて、0−1みたいな仕事も多くなっているし時代が変わってる。

今まで、ITはゼネコンに喩えられていて、エンジニアは土方や建築士に例えられていたけど、今は料理人に例えるほうがしっくりくると思う。

腕のある料理人が、途中でマネージャーにならないといけない理屈はないし、自分で店を持つのが良いキャリアパスだなと思う。栄養士やレシピ屋さんになるのもいい。マネジメントは上手い人にまかせるか。

起業とプログラミングの相性

ということでプログラミングは、起業との相性もよい。ソフトウェアを作って売るって話じゃなくても、サービスに付加価値を作るためにも役立つし、業務のプロセスを効率化するためにも役立つ。アイデアを思いついたときに、実行するまでが早いし、そのたびに外部へ投資しなくて済む。起業+プログラミングで仕事をしてると、家族の介護みたいな状況もなんとかこなせてるし、いいことづくめ。

 

唯一の問題は、肩こりに悩まされることだけれど、腕があがるうちはやりたいです。

探究に終わりはないですね。よぼよぼのジジイになってもやっていたい。

 

 

 

a-frame raycasterを使って壁との衝突判定を実装する

a-frame raycasterを使って高低差のある地形に沿って移動する - レベルエンター山本大のブログの続き  

目的

a-frameではWASDキーおよび矢印キーでカメラの位置を移動させることができるものの、壁や物体への衝突判定は自分で作り込む必要があります。

以下の動画のように前進、右移動、左移動、後退のどの方向でも障害物に当たったら跳ね返されるようにすることがこの記事の目的です。

f:id:iad_otomamay:20210504014123g:plain

障害物に当たったら、その場にストップするという仕様も考えられますが、そうすると壁にめり込んだりというトラブルが発生しがちです。 めりこみを避けるためにぶつかったら、ちょっと跳ね返るという処理にしておきます。

準備

前回投稿した以下のプロジェクトを土台として、そこに付け足す形で実装します。 a-frame raycasterを使って高低差のある地形に沿って移動する - レベルエンター山本大のブログ

カメラに四方のレイキャスターを追加

前回、a-cameraにはstand-onというカスタムコンポーネントを設定し、下方向のレイキャスターを設定しました。

同じ仕組みで前方・後方・右側・左側のレイキャスターを設定します。

今回のカスタムコンポーネントは、wall-colliderという名前です。

a-cameraにwall-collider属性を複数設定しているため、__以下に識別子をつけた名前にしています。

この multiple instance の仕組みはComponent – A-Frameを参照してください。

raycasterは、directionによって前後左右に向くように設定します。

<a-camera
    wall-collider__f="raycaster:#raycaster-f"
    wall-collider__b="raycaster:#raycaster-b"
    wall-collider__r="raycaster:#raycaster-r"
    wall-collider__l="raycaster:#raycaster-l"
    stand-on="raycaster:#stand-on-ray"
>
    <a-entity id="stand-on-ray" raycaster="objects: .ground; direction:0 -1 0"></a-entity>

    <!-- 四方のレイキャスターを定義 -->
    <a-entity id="raycaster-f" raycaster="objects: .collidable; far: 1; direction:0 0 -1"></a-entity>
    <a-entity id="raycaster-b" raycaster="objects: .collidable; far: 1; direction:0 0 1"></a-entity>
    <a-entity id="raycaster-l" raycaster="objects: .collidable; far: 1; direction:1 0 0"></a-entity>
    <a-entity id="raycaster-r" raycaster="objects: .collidable; far: 1; direction:-1 0 0"></a-entity>
</a-camera>

カスタムコンポーネント

カメラに設定したwall-colliderコンポーネントを定義します。

a-frameのコンポーネントは、a-sceneより先に読み込まれている必要があります。

multiple:trueという属性を指定することで wall-collider__XXXのように、1要素に対して__区切りで複数のインスタンスを設定することができるようになります。

schemaやdependencyは前の記事を参照してください。

AFRAME.registerComponent('wall-collider', {
      schema: {
          raycaster: { type: 'selector' },
      },
      multiple: true, // 複数指定可能
      dependency: ['raycaster'],
}

初期化処理 initの実装

コンポーネントの初期化処理も、前回記事の地面に立つコンポーネントとよく似ています。

  1. カメラに設定したraycasterのいずれかが対象に交差したときにraycaster-intersectionイベントが発生します。

  2. そのタイミングで反応するべきraycasterであることを調べます。

  3. 交差先オブジェクトとの接面を保存しておきます。(次のtickのタイミングで使うため)

  4. 交差が外れたら保存したオブジェクトをクリアしておきます。

evt.detail.intersections[0].face.normalの記述部分だけが、前回と異なります。 交差オブジェクトとの接面(face)を取得して、nomalizeして保存しておくというコードです。

nomalizeすることで、ベクトルから距離の情報を取り除き、向きを表す単位ベクトルに変換することができます。 つまり、"0 1 0" や"0 0 -1"のようにxyzを0,1,-1だけで表すベクトルに変換されます。

init: function () {
    this.el.addEventListener('raycaster-intersection', (evt) => {
        console.log(evt.target);
        if (evt.target !== this.data.raycaster) return; // 対応するレイキャスターのみ反応
        this.face = evt.detail.intersections[0].face.normal;
    });

    this.el.addEventListener('raycaster-intersection-cleared', (evt) => {
        if (evt.target.id !== this.data.id) return;
        this.face = null;
    });
},

tickの実装

tickでは、faceが保存されていれば対象オブジェクトと衝突しているので、跳ね返すのですがthree.jsのメソッドを使えば、大変簡単な記述で実現できます。以下の処理だけ。

tick: function () {
    if (!this.face) return;
    this.el.object3D.position.add(this.face.multiplyScalar(0.5));
},

一つ一つ説明します。 this.faceが登録されていなければ、衝突していないので処理しないとします。

次にthis.faceが登録されている場合です。 カメラの要素に設定したコンポーネント内であるため、this.elはカメラのaframe要素となります。そのカメラ要素からobject3Dプロパティでthreejsのオブジェクトを取り出します。

three.jsのadd()メソッドは、ベクトル同士の和(足し算)を計算してくれるものです。

this.faceに保存された接面のベクトルは、衝突方向と反転したベクトルを持っているため、カメラの位置(position)に反転ベクトルを足し算します。add メソッドは呼び出し元のベクトルに計算結果を反映させるため、跳ね返してくれるというわけです。

mulitplyScalarはベクトルに対して単一の値を掛け算できるメソッドで、これで跳ね返りの距離を調整します。

(あまり小さい数にすると、衝突後もオブジェクトをすり抜けてしまうので注意が必要です)

three.jsのおかげでとっても短いコードで実現できましたね。

全てのコード

 

<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
    <title>Aframe example Stand on Ground</title>
  </head>

  <script type="text/javascript">
    /**
     * 地面に立つためのコンポーネント(前回記事の範囲)
     */
    AFRAME.registerComponent('stand-on', {
      schema: {
        raycaster: { type: 'selector' },
      },
      dependency: ['raycaster'],
      init: function () {
        this.el.addEventListener('raycaster-intersection', (evt) => {
          if (evt.target !== this.data.raycaster) return; // 対応するレイキャスターのみ反応
          this.raycaster = evt.target.components.raycaster;
          this.target_el = evt.detail.els[0];
        });

        this.el.addEventListener('raycaster-intersection-cleared', (evt) => {
          if (evt.target !== this.data.raycaster) return;
          this.raycaster = null;
          this.target_el = null;
        });
      },

      tick: function () {
        if (!this.raycaster || !this.target_el) return;
        const item = this.raycaster.getIntersection(this.target_el);
        this.el.object3D.position.y = item.point.y + 1.7;
      },
    });

    /**
     * 前後左右のヒット(今回記事の範囲)
     */
    AFRAME.registerComponent('wall-collider', {
      schema: {
        raycaster: { type: 'selector' },
      },
      multiple: true,
      dependency: ['raycaster'],
      init: function () {
        this.el.addEventListener('raycaster-intersection', (evt) => {
          console.log(evt.target);
          if (evt.target !== this.data.raycaster) return; // 対応するレイキャスターのみ反応
          this.face = evt.detail.intersections[0].face.normal;
        });

        this.el.addEventListener('raycaster-intersection-cleared', (evt) => {
          if (evt.target.id !== this.data.id) return;
          this.face = null;
        });
      },

      tick: function () {
        if (!this.face) return;
        this.el.object3D.position.add(this.face.multiplyScalar(0.5));
      },
    });
  </script>
  <body>
    <a-scene>
      <a-assets>
        <a-asset-item id="envGlb" src="./Crater.glb"></a-asset-item>
      </a-assets>

      <a-camera
        wall-collider__f="raycaster:#raycaster-f"
        wall-collider__b="raycaster:#raycaster-b"
        wall-collider__r="raycaster:#raycaster-r"
        wall-collider__l="raycaster:#raycaster-l"
        stand-on="raycaster:#stand-on-ray"
      >
        <a-entity id="stand-on-ray" raycaster="objects: .ground; direction:0 -1 0"></a-entity>

        <!-- 四方のレイキャスターを定義 -->
        <a-entity id="raycaster-f" raycaster="objects: .collidable; far: 1; direction:0 0 -1"></a-entity>
        <a-entity id="raycaster-b" raycaster="objects: .collidable; far: 1; direction:0 0 1"></a-entity>
        <a-entity id="raycaster-l" raycaster="objects: .collidable; far: 1; direction:1 0 0"></a-entity>
        <a-entity id="raycaster-r" raycaster="objects: .collidable; far: 1; direction:-1 0 0"></a-entity>
      </a-camera>

      <a-gltf-model position="0 0 0" src="#envGlb" class="ground" animation-mixer=""></a-gltf-model>

      <a-sky color="skyblue"></a-sky>
      <a-box color="red" class="collidable" position="0 1 -4" scale="2 2 2"></a-box>
    </a-scene>
  </body>
</html>

a-frame raycasterを使って高低差のある地形に沿って移動する

目的

AframeのカメラはデフォルトでWASDキーや矢印キーによる操作ができますが、高低差のある地形に沿って移動はしてくれません。
また、階段のようなモデルも登っていきたいところですね。
Aframeで地面に指定したモデルの地形に添って移動できるようにします。

下記の動画では、WASDでカメラを動かしてクレーターを登っていき、最終的に高い位置から見下ろします。

f:id:iad_otomamay:20210503123232g:plain

これを実現できるようにすることがこの記事の目的です。

シーンの設定

とりあえず、モデルや空や、目印になる赤いボックスを置きます。
モデルはMozilla の Spoke https://hubs.mozilla.com/spoke からダウンロードしたクレーターです。

<a-scene>
  <a-assets>
    <a-asset-item id="envGlb" src="./Crater.glb"></a-asset-item>
  </a-assets>

  <a-gltf-model position="0 0 0" src="#envGlb" class="ground" ></a-gltf-model>

  <a-box color="red" position="0 1 -4" scale="2 2 2"></a-box>

  <a-sky color="skyblue"></a-sky>
</a-scene>

カメラの設定

カメラにraycasterを設定。raycasterは「objects:」属性にCSSクエリセレクタを設定することで、交差イベントを発火するオブジェクトを決められます。
このサンプルではCraterモデルのclassに「ground」を設定したので、Craterの3Dモデルに交差するとイベントを発火します。
またraycasterのdirectionプロパティの中の yの値を -1 にすることで下方向のraycasterとなります。
directionは、Vector3型となっていて"0 0 0"のようにスペース区切りの3つの値を設定することで x y zを指定できます。

<a-camera stand-on="raycaster:#stand-on-ray">
  <a-entity id="stand-on-ray" raycaster="objects: .ground; direction:0 -1 0"></a-entity>
</a-camera>

aframeのカスタムコンポーネントを作成

次にAframeのコンポーネントを作成します。「stand-on」と名付けました。schemaでは、このコンポーネントに対する外部入力プロパティを設定することができます。raycaster という名前は任意でありraycasterコンポーネントとは無関係なので別の名前にした方がわかりやすいかもしれません。selecter型にしておくことで、CSSクエリセレクタを入力として受付て、内部的にはDOMの参照として利用することができます。

カメラに設定するraycasterは複数つくることがあり得るので、このコンポーネントが使うraycasterは、どれなのかを指定できるようにしておきます。(今後記事で壁との衝突判定も記載しようと思います)

AFRAME.registerComponent('stand-on', {
  schema: {
    raycaster: { type: 'selector' },
  },
  dependency: ['raycaster'],
}

init の処理

Aframeコンポーネントの初期化(init関数)時に、raycasterが交差したことを表すイベント(raycaster-intersection)をリッスンします。
カメラに複数のraycaster(例えば壁との衝突判定など)を設定していた場合、raycaster-intersectionは全てのraycasterで反応します。
そのため今発生しているイベントがstand-onに対応した下向きのraycasterの交差であることを確認します。

schemaで指定されたraycasterで発生した時だけ、raycasterと交差先のオブジェクトを保存しておきます。*1

同じく交差イベントがクリアされたタイミング(raycasterの交差が外れたタイミング)で、保存していたオブジェクトを削除します。

init: function () {
  this.el.addEventListener('raycaster-intersection', (evt) => {
    if (evt.target !== this.data.raycaster) return; // 対応するレイキャスターのみ反応
    this.raycaster = evt.target.components.raycaster;
    this.target_el = evt.detail.els[0];
  });

  this.el.addEventListener('raycaster-intersection-cleared', (evt) => {
    if (evt.target !== this.data.raycaster) return;
    this.raycaster = null;
    this.target_el = null;
  });
},

raycaster-intersectionのEvent

raycaster-intersectionのEventの主なプロパティ

event
	.detail
		.intersections[n]	交差を表すオブジェクト配列
				.face 交差面
				.distance 距離
				.object	交差先Treejsオブジェクト
				.point	交差した点のVector3座標
				.uv	交差した点のVector2座標
		.els	交差先オブジェクトのDOM
	.target	反応したレイキャスタDOM

tickの処理

tickでは、raycasterと交差先オブジェクトが保存されている(交差イベントが発生して交差が外れるまで)の間ずっと、getIntersection()を使って最新の交差位置を取得します。これでカメラの配下にある地形を常に読み取ることになります。
最新の交差位置のy座標をカメラの高さに合わせます。1.7を地面からの視点の高さとしました。この1.7もschemaで読み込んでもいいですね。

tick: function () {
  if (!this.raycaster || !this.target_el) return;
  const item = this.raycaster.getIntersection(this.target_el);
  this.el.object3D.position.y = item.point.y + 1.7;
},


最終的なコードは以下です。

<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
    <title>Aframe example Stand on Ground</title>
  </head>

  <script type="text/javascript">
    /**
     * 地面に立つためのコンポーネント
     */
    AFRAME.registerComponent('stand-on', {
      schema: {
        raycaster: { type: 'selector' },
      },
      dependency: ['raycaster'],
      init: function () {
        this.el.addEventListener('raycaster-intersection', (evt) => {
          if (evt.target !== this.data.raycaster) return; // 対応するレイキャスターのみ反応
          this.raycaster = evt.target.components.raycaster;
          this.target_el = evt.detail.els[0];
        });

        this.el.addEventListener('raycaster-intersection-cleared', (evt) => {
          if (evt.target !== this.data.raycaster) return;
          this.raycaster = null;
          this.target_el = null;
        });
      },

      tick: function () {
        if (!this.raycaster || !this.target_el) return;
        const item = this.raycaster.getIntersection(this.target_el);
        this.el.object3D.position.y = item.point.y + 1.7;
      },
    });
  </script>
  <body>
    <a-scene>
      <a-assets>
        <a-asset-item id="envGlb" src="./Crater.glb"></a-asset-item>
      </a-assets>

      <a-camera stand-on="raycaster:#stand-on-ray">
        <a-entity id="stand-on-ray" raycaster="objects: .ground; direction:0 -1 0"></a-entity>
      </a-camera>

      <a-gltf-model position="0 0 0" src="#envGlb" class="ground" ></a-gltf-model>

      <a-box color="red" class="collidable" position="0 1 -4" scale="2 2 2"></a-box>

      <a-sky color="skyblue"></a-sky>
    </a-scene>
  </body>
</html>

*1:Aframe の1.2.0の時点では、交差イベント(raycaster-intersection)は、交差が開始したタイミングでしか発行されません。状態のリフレッシュはtickで実装します。tickで使うためにraycasterと交差先オブジェクトを保存しておきます。

2000年代オブジェクト指向は絶対の正義だった。つまり僕は洗脳を経験している

私がIT業界の片隅に所属をし始めた2000年ごろ、Javaエンジニアはスーパースターだった。Javaエンジニアを名乗るということは、秘奥義オブジェクト指向を習得していることに他ならないからだ。

 

オブジェクト指向こそ正義」だった。

 

Javaオブジェクト指向を身につければ、20年は食っていけると言われたものだった。

あれから20年。たしかにJavaオブジェクト指向で20年は食えた。が、もはやオブジェクト指向は絶対でも正義でもない。

 

僕は、IT講師として新入社員にJavaを教える仕事もしているが「オブジェクト指向こそ正義」と無垢な生徒達に教えなければならない時に、苦痛を覚えるようにすらなってしまった。

 

2000年代から、新人教育のテキストは変わっていない。継承は積極的に使っていくべきで、オブジェクトは現実世界を模した仮想現実世界をコンピューター内に生み出す技術とされている。Strutsだけはようやく姿を消した。

 

 

2000年前半、

これからはオブジェクト指向だよ。当時の上司は言っていた。

 

 

オブジェクト指向を初めて学んだ時、理解不能だったのは、変数やループといったプログラミングらしい構文から一転して、様々な現実の例え話が登場することだった。オブザーバーは軍師で諸葛孔明なのだと書かれた雑誌を読んで先輩に質問したときに「そんなクソ本読むな。GOF(GangOfFour)を読め」と叱責された。しかし、GoFデザインパターンという本を知り、読み、さらに訳のわからない泥沼にハマったきもちになった。

 

オブジェクト指向を習得したと実感するためには、結局プログラムを書いて読むしかなかった。Sessionの状態管理をラップするクラスを書いたときだった。訳もわからずこねくり回した労力の割りに成果は少ないのだが、これぞオブジェクト指向だ!と実感が得られた。

 

 

それから、オブジェクト指向は自分の中でソフトウェア開発における「正義」だった。

 

 

当時SESでいろんな現場を行き来する末端のプログラマーだったが、「この現場はクソだ。コードにオブジェクト指向らしさが全くない。真理を知らないアホがリーダーをやってるんだ。」とぼやいていた。

自分たちだけは、日本のSE業界をオブジェクト指向を中心としたイケてる世界に変えていこう。

所属していたのは零細なSES企業だったが、オブジェクト指向的な技術力で盛り立てて、大手のイケてない現場でブイブイ言わせてやろう!と心のそこから思っていた。

 

 

薄々は感じていた。

 

 

インターフェイスの分離やデザインパターンを使わない現場に入った時、IDEがコードのジャンプを適切に実行してくれることの楽さを感じた。

しかし、その現実に目をつぶって、わざわざ全部をインターフェイスで分離して、追いかけにくいコードに書き換えた。これこそがオブジェクト指向ですよ、すばらしいでしょう。と。。

 

ダサい手続き型は残らずこの手で駆逐してやる

オブジェクト指向的にリファクタリングすることが、正義だと思っていた。 

 

 

自分がプロジェクトのリーダーや技術のリーダーになる頃には、徐々に気付き始めていた。

Webのサーバーサイドで隆盛を誇っていたJavaだが、実のところWebサーバーサイドの現場のコードにオブジェクト指向が必要な部分などそう多くはなかった。

 

フレームワーク部分では、多少オブジェクト指向的なテクニックは有効だったが、大半のコードは業務の流れを記述し、DBアクセスのフローを制御するだけのもので、無駄に継承やデザインパターンを駆使しているコードはメンテナンスしにくいだけのものだった。

  

 

その頃には、駆け出しエンジニアの後輩から

「現場ではオブジェクト指向のテクニックを十分に使わせてくれません、せっかく学んだオブジェクト指向のテクニックを駆使するにはどうしたらいいですか?」的なことを聞かれて、どうにも返答に困るようになっていた。

 

なぜなら要らんからだ。

 

現場のリテラシーが低いだけとは言い切れない。と思うようになってきていた。

 

 

「世の中を変えたければ偉くなれ」程度のアドバイスでお茶を濁した。

正義は時代とともに形を変える。

 

 

オブジェクト指向は、使えると便利なシーンは確かにある。

グローバル変数を細かくしてくれた。

型を自分で作れるのはとても嬉しい。

文字列配列のindex 0はIdで、index 1 は氏名で、、、というプログラムを書いていた世界には戻る必要はない。

しかし、絶対の正義というほどのことではない。指向にするほどのものでもない、ただの便利な言語機能だ。

 

 

しかし、2000年代は絶対の正義と言えるレベルの宗教だった。

オブジェクト指向が、分析や設計にまでおよぶ広大な宗教だったから、部分的な不都合などは些末なことだった。

 

 

僕は、自分がようやく壁の中に逃げ込んだだけの人類に過ぎず、壁の外には広大な世界が広がっていることを知った。

 

 

生まれてこのかた、自分は正常なリテラシーをもった現実的な考え方の人間であり、たくさんの情報源にふれているので何者かに洗脳されることはない、と思っていた。

 

しかし、事実として僕は、オブジェクト指向絶対教に洗脳されたことがあり、

 

いま、新しい宗教に洗脳されている人を見たとしても 

それが悪だとか、自分が洗脳されていないとか言い切ることはできない。

 

 

 

 

ビジョナリーカンパニー弾み車の法則

「ビジョナリーカンパニー弾み車の法則」(TURNING THE FLYWHEEL)は90ページほどしかない書籍だけれど、重要な指針を書いてくれている良い本だと思います。90ページなので紹介もしやすいですね。

 

ビジョナリー・カンパニー 弾み車の法則

ビジョナリー・カンパニー 弾み車の法則

 

 

ビジネスモデルを構築する作業の中で、とても本質的な内容だと思います。

「弾み車効果(THE FLYWHEEL)」とは、アマゾンの戦略コンサルタントも努めた本書の著者ジム・コリンズさんが見出したものです。

 

良い会社から偉大な会社への飛躍とは、たった一つの決定的行動、壮大な計画、驚異的イノベーション、幸運、あるいは奇跡の瞬間がもたらすものではない。それはむしろ、巨大な思い弾み車をまわしつづけるようなものだ、と。

力いっぱい押すと、弾み車はほんの数センチ前へ動く。さらに力を込めて押しつづけると、ようやく1回転する。そこで手を止めない。押し続ける。

弾み車は少しだけ速く動くようになる。2回転、3回転押し続ける。・・・

1万回転、10万回転。すると、ある時点でブレイクスルーが起きる。およそ止めようのない勢いのついた弾み車は、飛ぶように転がっていく。 

 

そしてアマゾンでは、この弾み車を事業の中核に据えてモデルを考えていました。

 

ベゾスとその側近は、独自の好循環を図で表している。それこそがアマゾンの推進力になってきたというのだ。内容はこんな具合だ。

f:id:iad_otomamay:20210119175851p:plain

『低価格が訪問客数の増加につながる。顧客増加が売り上げ増加につながり、手数料を支払うサードパーティーの売り手をサイトに引き寄せる。それによってウェブサイトを運営するのに必要な配送センターやサーバーといった固定費に対してより多くの売り上げをあげられる。効率性が高まることで、さらに価格を下げることができる。』

この弾み車の構成要素のどれをおしても、ループは加速するはずだと彼らは考えた。

こうして弾み車が回転し、勢いがつく。さらに弾み車を押せば、勢いは加速する。それを繰り返す。

ベゾスは、アマゾン成功の決め手は弾み車効果を活用したことだと考えている。とストーンは書いている。

  自社の独自性について考える|藤本正雄(ふじもと まさお)|note

 抽象化されていて、一見当たり前のように思えるかもしれませんが。大事なことは、一貫して好循環を回し続ける努力をすることですね。

 業績が急落したときにこの好循環を回すことに集中できるでしょうか。

 

ドットコムバブルが崩壊した混乱期に、ベゾス以下の経営陣がパニックを起こして弾み車を放り出して悪循環に陥ってもおかしくはなかった。

・・・

悪循環に陥った企業は、不本意な結果に直面すると、規律を失い、場当たり的な対応をするようになる。新たな救世主、流行や目新しい出来事、方向性に飛びつく。それはさらに不本意な結果を引き起こすだけだ。

・・・

一方アマゾンは、弾み車を堅持し、勢いをつけるためにその枠組みの中で積極的に確信を続けた。
 

 特に悪い環境になったときに、弾み車を回し続けることができるかどうかがポイントですね。 

 自分のビジネスでも、好循環のサイクルをどのように構築するかを考えて行きます。

 

 

プログラマ育成者に知っておいて欲しい初心者の5段階

 プログラミングスクールといえばグレーなビジネスモデルというようなイメージが蔓延ってしまって悲しいですが、プログラミングのスキルを多くの人が手にすることはとても有用なことではあるので今後も頑張っていきたいと思います。

 

 プログラミングを習得しよう!と学習を始めた人たちをこれまで、数千人ほど観察してきました。その中で気づいたことは、プログラミング初学者は同様の場所で詰まってしまう傾向がみられるということです。多くの人にとって、同様のハードルというかステップがあるのだと思います。そういうことを書こうと思います。

 

 ステップは0−4としました。しかし、Webや組み込み、ゲーム、データサイエンスなど、目指す世界によってその先にさらにたくさんのハードルがあると思います。ここでは、その後にどのような世界に進むかに限らず、プログラミング学習者に共通して現れるハードルに絞って記載しようと思います。

 

ステップ0:コンピューターに対するリテラシー

0-1. タイピングが間に合わない、誤字、タイプミス心が折れる

0-2. 新たに登場する英単語の多さについていけない。学んだ次の日には用語を忘れてしまう。

0-3. コンピューターの一般用語に対する勘が働かない、例えばログイン/ログアウトとサインイン/サインアウトが同義語だと区別がつかない。サーバーという用語が1つのコンピューター端末を指すと認識できないなど

 

このハードルについて

 ステップ0はプログラミングというよりもコンピューターに対する慣れ、リテラシーというようなステップです。そのため、ゲームやネットの世界を徘徊しているような人であればアドバンテージになる部分だと思います。

 プログラミング用語に限らない「デバイス」や「メモリ」や「OS」などの用語がコンピューター初心者を苦しめます。これらのコンピューター用語に明るければ負担を少し減らせます。

 プログラミングが必修化した今どきの子供にとってはゲームやネットも教養の一部になってるといえます。子供には恐れずにゲームやインターネットに触れさせると良いと私は考えています。

 教養の差は後で効いてくるのでプログラミングを学び始めたら、プログラミングの学習だけではなく、より広いコンピューターリテラシーを持つように日々の積み重ねをスタートして欲しいです。育成者であればリテラシーを蓄積する課題を科してあげると生きた教育になると思います。

 日経コンピューターなどの雑誌の過去ログを読んだり、タイピング練習を始めたり、パソコンを組み立てたり分解したりがおすすめです。就職するときやプロになってから本当に効いてくると思います。

 

ステップ1:エラーや失敗から学ぶスタンス

1-1. エラーが出るとパニックになる

1-2. エラー内容を読まない

1-3. 試行錯誤にトライしようとしない

 

このハードルについて

 ステップ1は、初心者あるあるですが意外に深いテーマでもあります。エラーとの向き合い方は、極論すればコンピューターとの向き合い方だからです。

 エラーは、コンピューターとの会話であり、融通の効かない世界に立ち向かう強い武器だと思います。エラーは、仲間であるコンピューター(というか言語設計者)からの優しさなのですが、初心者は「真っ赤になって怒られた!」と思ってしまいます。

 

「コンピューターは怒ってなどおらず。一緒に問題を解決したいと思っているんだ。」

 そう教えるようにしています。

 

 エラーメッセージを読み、原因について分析して仮説を立て、検証をして正解を導き出す姿勢こそが、プログラマにとって最も重要な姿勢です。そして、初学者をその姿勢に導けるかどうかが育成者としての手腕だと思ります。

 

 コンピューターよりもさらに不確実な「現実世界」にはエラーメッセージのような親切な仕組みはありません。コンピューター、エラーメッセージと向き合って原因分析や仮説の検証、改善というプロセスを前向きに捉えられると、不確実で複雑な現実への向き合い方も変わってくると思います。

 子供らにプログラミングを教える意義はここだとさえ思います。ビジネスの世界を含む、現実世界の様々な未知の課題解決においても役に立つ姿勢なのです。

 

ステップ2:制御とフローとデータのイメージ

2−1.  ループのイメージがつかない。上から下に書いている文章が繰り返す意味が理解できない

2-2.  頭の中でデータの変遷が追えない。データのイメージが頭の中に作り出せない

2−3. 多次元配列やオブジェクトのイメージが頭の中に作り出せない

2-4. 多くのことをいっぺんに考えすぎていて手に負えないと思ってしまう

 

このハードルについて

 ステップ2からは、プログラミングらしいハードルです。そのためプログラミング初心者は多かれ少なかれ経験するハードルです。ただ、その乗り越え方や乗り越える速さには個人差があります。いくつかの練習問題に取り組むだけで乗り越えられる人もいますが、他の人の何倍も理解に時間がかかる人もいます。

 しかし、遅かれ早かれ諦めなければ必ずクリアできるハードルなのです。私はそう確信しています。頭の使い方の慣れの問題であって、いままで見てきた学習者の中で最終的にここがクリアできなかった人はいません。

 ちなみに今では偉そうにプログラミングの先生を語る私も、ここで苦労しました。コンピューターリテラシーがなく、独学で取り組むしかなかった22歳の私は、8時間×3日間、1つの課題に悩み続けてようやくループとはどういうものかを体得したのです。

 

ステップ3:定義と実行のイメージ

3-1. 関数の定義と呼び出しについてイメージできない

3-2. 実行時の処理が移動することがイメージできない

3-3. 呼び出す側と呼び出される側が存在することがイメージできない

3−4. データを知っている、知らせる、戻すなどのイメージや、データの管理場所をイメージできない

 

このハードルについて

 プログラミング学習を始めてからしばらくのうちは1つの関数定義で完結するプログラミング練習問題をクリアしていくことでしょう。

 そこからステップアップしていく時に詰まってしまうハードルとして構造化関数化があります。そして、その延長にはオブジェクトやクラス・型が難関として待ち構えます。

 特に、異なるファイルに書かれた関数やオブジェクトを呼び出すことは、どのような言語でも学習者にとっては、慣れるのに時間を要するポイントです。ただ、ここも慣れの問題なので時間をかければ必ずクリアできます。諦めなければ。

 

 ステップ3のハードルに際しては、できるだけたくさんのプログラミングの課題演習に触れるのが良いと感じます。しかし、市販の書籍やスクールのテキストでは、このパートをさらっと進めてしまうものがあります。

 もっというと、このステップ3に限らず、スクールテキストなどで「慣れ」に目を向けないものは多いと感じます。1回説明したらわかるだろうというスタンスのテキストだ。関数やオブジェクトといった概念は、説明を聞いて理解するというよりも、体で慣れなくては使えるようになりません。説明は1回でもいいが、演習はたくさん欲しいと感じます。初心者の気持ちによりそうテキストかどうかは、こういったパートでの演習量をみてみると良いでしょう。

 慣れに目を向けないテキストが生み出されるのは、エンジニアらしい部分でもあるといえます。プロのエンジニアにとっては、同じことを繰り返しドキュメントに記載するのは悪だとされています。できるだけ1箇所に事実を書いて、同じ概念が出てきたら何ページを参照というようにリンクづけすることが、プログラミング的な考え方では正解なのです。

 

ステップ4: アルゴリズムデザイン

4-1. 自分が発想した解決するべき課題に対して、処理の流れを組み立てることができない

4-2. 自分でデータの構造を作りだせない

4-3. 正しさの検証手順を考えられない

4-4. 大きな物事を大きなままで考えている

 

このハードルについて

 答えのある演習課題を使ってプログラミングを学び始めるのが一般的です。しかし、ある時点で自分でオリジナルのアルゴリズムを組み立てることが必要となります。漠然とした課題に対する解決を自分で考案しなくてはならなくなると、アルゴリズムデザインの力が求められます。

 アルゴリズムというと、なぜかネット上の情報や本では「ソート」や「サーチ」しか扱われません。そのため「アルゴリズムとはバブルソートクイックソートのような高度で洗練された手順である」などと思い込んでいる人もいて、難しく捉えすぎているケースが多いです。

 アルゴリズムデザインは、解決する手順を組み立てられる力です。不格好でも、非効率でも問題が解決される手順が示せることが大事で、最適化はあとでスキルが上がった時にやればいいと割り切るのが大事です。

 アルゴリズムデザインで大事なことは、3つ。

 ・大きな物事を手に負えるサイズまで分解して考えること

 ・順序を組み立てて過不足なくつなげること

 ・小さくテストしていくこと

 

 アルゴリズムのデザインを含めた「デザイン」つまりソフトウェアの設計こそ、プログラミングのクリエイティブな部分であり、面白いところです。

 問題に対する解決策を提案して実装し、それがバチっとハマったときの快感や、より効率的なアルゴリズムによって、いままでの処理が劇的に効率化したときの快感がえられたら、あとはほっておいても学びたくて仕方なくなるものです。

 

 冒頭にも記載したとおりに、この先にもたくさんのハードルがあるのですが、ステップ4までのハードルの意味が理解でき、乗り越えた実感がえられたら、あとは突き進むだけだと思います。 

 プログラミング学習が、育成者にとっても学習者にとっても幸せな時間になればいいですね。

 

VSCode「デコレーターの実験的なサポートは将来のリリースで変更になる可能性がある機能です」を消す

VSCodeでtypescriptを書いていてデコレーターを使うと、

デコレーターの実験的なサポートは将来のリリースで変更になる可能性がある機能です。'experimentalDecorators' オプションを設定してこの警告を削除します

というワーニングがでる。

tsconfig.json

"experimentalDecorators": true
をやるだけでは取れなかったが、
 

 以下の記事をみて設定から、Experimental Decoratorsにチェックを入れたらワーニングが消えた。

ihatetomatoes.net