Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save think49/2f02c7371dded1ce5aab5da3fac122cf to your computer and use it in GitHub Desktop.
Save think49/2f02c7371dded1ce5aab5da3fac122cf to your computer and use it in GitHub Desktop.
[JavaScript] 独力で一からJavaScriptコードを書くには.md - GitHub Gist

独力で一からJavaScriptコードを書くには?

概要

Q&Aサイトの頻出質問に以下があります。

「他人のコードを改変することなら出来るのですが、自分で一からコードを書くことが出来ません。どうすれば自分ひとりの力で書けるようになりますか。」

その回答を体系的にまとめたのが本記事になります。

[必要条件] 論理的思考

独力で「出来る人」と「出来ない人」の性質を比較表にしました。

比較項目 出来る人 出来ない人
現象認識の単位 分解して、最小単位で認識(分割統治法) そのままの状態でおおざっぱに認識
主観/客観 客観的 主観的
考察の手法 論理的 イメージ
用語の使い方 正式用語以外はNG 何となく伝わればOK
コーディング前の準備 要件/アルゴリズム等の必要情報を予め調べる 準備不要。とりあえず、コードを書く。
コードの完成度 コードの動きを100%理解しなければNG 動けばOK

プログラミング言語を学習する過程で論理的思考は必ず必要となりますが、その過程で、思考が機械的になる傾向があります。

プログラミング言語が相手にするには「コンピュータ」という名の機械であり、機械はイメージを理解できませんし、何となく書いた言語も解釈出来ません。

  • 機械が解釈できる「厳密で論理的なコード」を書く為には「厳密で論理的な思考」が必要となります
  • 用語も、正式用語を使わなければ、正確に理解できず、コードを組む前の考え方に組み込めません

思考を機械に近づける事(※1)がプログラミング上達のポイントなのかもしれません。

(※1) その為か、プログラマを「冷たい人間だ」と評される事があります。 気持ちやイメージを徹底的に排除し、論理だけを重んじる姿勢を「冷たい」と評していると思われます。 が、プログラミングに必要だからそうしているのであって、人間的に冷たいわけではないと私は思います。

(1) 問題を分解する

  • 問題が起きたとき
  • 文章を読むとき
  • コードを読むとき

全てにおいて、分解して「最小限の情報量」におさめます。 複雑な内容でも分解して、単純情報になれば、自分一人の力で理解できるからです。

1 + 4 * 3 - 2;

この四則演算式を人間が解く場合、「二項演算式を部分的に解いて、少しずつ計算していく手法」を取る方がほとんどだと思います。

1 + 4 * 3 - 2;
1 + 12 - 2;
13 - 2;
11;

同じ作業を、全ての情報において、実践します。 この手法を分割統治法といいます。

(2) 再現可能な最小限のコード

質問に対して、「問題を再現可能な最小限のコードを掲示して下さい」という指摘がされることがありますが、これも「分割統治法」です。

「再現可能な最小限のコード」を書く為に、少しずつ小さなコードに変化させていく過程で、問題点が単純化され、自力で原因を発見して、解決に至るケースは珍しくありません。 私は何度も経験しています。

(3) 仕様書を読む

ある内容をWeb検索で調べ、複数の情報ソースから情報Aと情報Bを発見したとします。 情報を精査したとき、情報Aと情報Bで矛盾が出ることがあります。 人が書く情報には勘違いや記憶違いで誤りを発信してしまうこともあります。

そんなときに100%の信頼性を持つのが仕様書です。 JavaScriptは特異な言語で「JavaScript」という名の仕様書はありません。 「ECMAScript」「DOM」「HTML Standard」等の複数の仕様書から成り立っており、調査前にどの仕様書を調べるのかを確かめておく必要があります。

慣れるまでは、MDNを入り口に「仕様書」の場所を確かめると良いと思います。 例えば、下記ページの「仕様書」のセクションで「ECMAScript (ECMA-262)」にリンクされています。

(4) ブラウザのDevelopper Toolsで調べる

仕様を調べても、ブラウザに実装されていなかったり、バグが混入されている事があります。 実際に、ブラウザで実行してみるのは重要です。

いまどきのブラウザはDevelopperTools(開発者ツール)でブラウザの動きを追跡できます。

JavaScriptコードを書いた時に「動作対象ブラウザ」は決めているはずなので、対象ブラウザの開発者ツールの機能は把握しておく必要があります。


基本的にはリファレンスを読みながら、使い方を覚えていくことになりますが、コンソールを使うこなす上で評価値という概念が重要になります。 例えば、コンソールに 1 + 2 を入力した場合、

1 + 2
3

このように、1 + 2 の演算結果が次の行で返ってきます。 これを 1 + 2評価値を返す、と表現します。

式文(ExpressionStatement)は全て「評価値」が返ってくるので、覚えておくと便利でしょう。 コンソールにコードを入力すれば、console.log() を使わなくても、演算結果を得る事が出来ます。

なお、コンソールに console.log(1 + 2) を入力すると、

console.log(1 + 2);
3
undefined

3console.log(1 + 2) の実行結果によるものですが、undefinedconsole.log(1 + 2) の評価値です。 関数呼び出しコードにおいては、評価値は「関数の返り値」と等価となります。 「Console API」は標準化されている為、仕様書を読んでおくと、より深く理解できます。

(5) コーディング

プログラマは次の3つのstepを経て、コードを書いています。

  1. 要件定義
  2. アルゴリズム定義
  3. コーディング

「要件定義書」のようなきちんとした文書は残していないかもしれませんが、出来るプログラマは「要件は何か」「どのようなアルゴリズムを組むか」という思考を常にしているものです。

ここでは正道で「1 -> 2 -> 3」の順序で説明していますが、コードを書き始めてから「要件」を見直すパターンもあります。 しかし、要件を何も決めずにコードを書くことは出来ませんので、おおまかにでも要件を決めてコードを書いているはずです。 いずれにしても、「要件」があることは頭に入れておく必要があります。

(5-1) 要件定義

function sum (num1, num2) {
  return num1 + num2;
}

console.log(sum(1, 2));     // 3
console.log(sum("1", "2")); // "12"

後者を「仕様」と見なすか、「バグ」として受け入れるか、が「要件」の違いです。 「仕様」とみなす場合、このコードは

  • 「引数1」「引数2」に String 型を指定した場合、加算ではなく、文字列連結結果を返します

という「要件」を持つことになり、要件通りの挙動なので、不具合報告に対しても、「仕様」として「修正しない方針」で返答します。 対して、「バグ」として受け入れる場合、修正が必要で、例えば、以下の修正コードが考えられます。

function sum (num1, num2) {
  return Number(num1) + Number(num2);
}

console.log(sum(1, 2));     // 3
console.log(sum("1", "2")); // 3

この場合の「要件」は、

  • 「引数1」「引数2」はNumber 型に変換してから、加算を行います

になり、それがそのまま「仕様」となります。

「要件」と「仕様」は似ていますが、「要件」には

  • 期待される動作
  • 要求される動作

の意味があり、「コードを書く前に決定する仕様」のような意味になります。

(5-2) アルゴリズム定義

アルゴリズム(英: algorithm [ˈælgəˌrɪðəm])とは、「計算可能」なことを計算する、形式的な(formalな)手続きのこと、あるいはそれを形式的に表現したもの。コンピュータにアルゴリズムをソフトウェア的に実装するものがコンピュータプログラムである。人間より速く大量に計算ができるのがコンピュータの強みであるが、その計算が正しく効率的であるためには、正しく効率的なアルゴリズムに基づいたものでなければならない。

アルゴリズムは人間が理解しやすいようにフローチャートで表される事があります。 JavaScriptコードからフローチャートを生成する「js2flowchart.js」を見てみましょう。

例えば、demoで初期表示されたコードに関数呼び出しコードを加えた場合、

function indexSearch(list, element) {
    let currentIndex,
        currentElement,
        minIndex = 0,
        maxIndex = list.length - 1;

    while (minIndex <= maxIndex) {
        currentIndex = Math.floor((maxIndex + maxIndex) / 2);
        currentElement = list[currentIndex];

        if (currentElement === element) {
            return currentIndex;
        }

        if (currentElement < element) {
            minIndex = currentIndex + 1;
        }

        if (currentElement > element) {
            maxIndex = currentIndex - 1;
        }
    }

    return -1;
}

indexSearch([1,2,3,4,5], 3);

フローチャートをwhileループ回数分だけ印刷し、チャート上のそれぞれの動きを順番に追いかけて、各変数値を記入していけば、人間の目でも動きを追跡できますので、やってみてください。

indexSearch([1,2,3,4,5], 3); // 2

関数の返り値は 2 が正解ですが、フローチャートを使って、同じ答えを導きだせたでしょうか。

ここでは、フローチャートを使いましたが、フローチャートを作るのが目的ではありません。 (4) で「アルゴリズム定義後にコーディング」する過程を書きましたが、それは

  1. アルゴリズムを頭の中で作って動かす
  2. そのアルゴリズムで目的の動作が実現できるか頭の中で確認する
  3. コードを書く

の作業を表しています。

この作業は無意識下かもしれませんが、プログラマは皆やっています。 それをしないと、動くか動かないか分からないコードをあてずっぽうに書くことになってしまいます。 コードを書く前に「動く」と確信を持つには、事前に頭の中で動かす必要があります。

(5-3) コーディング

予め用意されている「ビルトイン関数」にもアルゴリズムはあります。 ここでは、比較的単純な「ECMAScript 5.1当時の Array.isArray」を例題にします。 (学習の為には、最新仕様の「ECMAScript 2020」を題材にすべきですが、アルゴリズムが複雑になったので、ここでは構造が単純な古い仕様を採用しています)

  1. If Type(arg) is not Object, return false.
  2. If the value of the [[Class]] internal property of arg is "Array", then return true.
  3. Return false.

このように、アルゴリズムは序列リストで表すことも出来ます。 (whileループも「2. に戻る」を使えば、表現可能です) 仕様書は序列リストでアルゴリズムを表すので、この表現に慣れておくと、仕様を解読しやすいでしょう。

このアルゴリズムをコードに変換してみます。

function isArray (arg) {
  // 1. If Type(arg) is not Object, return false.
  if (Object(arg) !== arg) {
    return false;
  }

  // 2. If the value of the [[Class]] internal property of arg is "Array", then return true.
  if (Object.prototype.toString.call(arg) === '[object Array]') { // [[Class]] 内部プロパティは Object.prototype.toString で確認する事が可能です
    return true;
  }

  // 3. Return false.
  return false;
}

これが、「アルゴリズム定義」から「コーディング」する流れ、です。

まとめ

前提条件の論理的思考を除くと、実作業は次の流れになります。

  1. (1)~(3) 概念/原理を理解する
  2. (4) 単純化した単機能からテストする
  3. (5-1) 要件定義
  4. (5-2) アルゴリズム定義
  5. (5-3) コーディング

一般のイメージとして「プログラマはキーボードをたくさん叩いてコードを書いている」と感じている方が多そうですが、実際には考察/調査している時間が大半を占めています。

コーディングの段階になれば、

  • これから書くコードの一つ一つを100%理解している
  • 「このコードを書けば、期待通りに動作する」という確信を持っている

という前提でコードを書きます。

コードが期待通りに動かず、前stepに戻る場合は、(1)からやり直し、次こそは「100%の理解(※2)」にしてからコーディングにのぞみます。

(※2) 「100%の理解」とはプログラマが持つ意気込みのようなものです。 「動くか動かないか分からないけど、コードを書いてみよう」な状態で、コードを書いてはいけません。 「このコードをかけば、こういう動作になる」という原理を理解した上でコードを書かなければ、不測の事態に対応できません。

「理解 -> 設計 -> コーディング -> 不具合発生 -> 始めに戻る」のサイクルはPDCAとよく似ています。

[補足] 分割統治法で再利用性を上げる

「分割統治法」で小さな単位にしたとき、

上記記事で while 文を「最も基本となる文」と紹介しましたが、while 文を使いこなせれば、他に紹介した全ての繰り返し処理を実現可能です。 そして、他に紹介した繰り返し処理

  • for
  • for-of
  • Array.prototype.forEach
  • for-in
  • Map.prototype.forEach

の「アルゴリズム」を理解できたとき、これらの処理の相互変換が可能となります。 アルゴリズムの理解は「メリット/デメリットの列挙」を可能とするので、取捨選択も自ずとできるようになります。

[補足] 式文(ExpressionStateMent)

(4) で触れた式文(ExpressionStateMent)の概念は以下の記事で説明しています。

@think49
Copy link
Author

think49 commented Jul 12, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment