JavaScriptの超基本#01 効率的に習得するための3つの事(その2)

JavaScript

JavaScriptだけでなく、いろんな言語で応用できる方法です。
言語固有の概念や知識は後から習得すればよいです。

どの言語でも、ここに書いてあることを先に習得する事で、スタートダッシュできます。

少しパワープレイもありますが、そこは言語を習得するために必要なコストと思って頑張っていきましょう!

JavaScript言語の説明はこの記事の目的ではないので必要最小限にとどめます。

関数=プログラムを再利用する事を知る

これも、どの言語でも同じように存在する概念です。
JavaScriptでの関数の定義方法を確認します。

初学者の方には少しだけハードルが高くなります。
私も最初「関数って、、数学かよ」って、その名前にアレルギー反応が出てしまいましたw

一度分かっちゃえば大したことではありませんが、正しく理解する事により非常に多くの恩恵を受けることが出来ますのでとても重要です。

初学者の方は、どう使うかよりも、なぜ使う必要があるのかを常に意識する事が重要 です。

「関数」という言葉よりも、「プログラム」という言葉の方が、より聞き慣れた言葉だと思うので、まずは「プログラム」を語ってみます。

プログラムの最も抽象的な基本要素は、入力データ, 処理, 処理結果の3つ

どのプログラム言語でも同じように、プログラムは基本この3要素で出来ています。

  • 「入力データ」は、処理対するデータの事です。
  • 「処理」は、入力データを加工する事です。
  • 「処理結果」は、処理によって加工されたデータの事です。

例えば、これを「足し算をする」というプログラムで考えてみると、

  • 「入力データ」として「足す数」と「足される数」の2つのデータが必要です。
  • 「処理」は2つのデータを足します。
  • 「処理結果」は足し算の計算結果です。

実際にJavaScript(Node.js)で書いてみましょう。

// add.js
var x, y;
x = parseInt(process.argv[2]);
y = parseInt(process.argv[3]);
console.log(x + y);

これが、指定された2つの数字を数として認識し、2つの数の足し算結果を表示するという処理のプログラムです。

実行してみましょう。

$> node add.js 1 2
3

素晴らしい!
最初のプログラムができちゃいましたね。

これ、ソフトウェアの基本概念として先ほど解説した「入力データ」「処理」「処理結果」で構成されてます。

ここでは足し算プログラムを作りましたが、これを「関数」に置き換えてみます。
少し乱暴な言い方をすると、「足し算という処理」を再利用できるように再定義したものが「関数」だと思ってもらえれば理解が速いと思います。

今後、プログラムを作るときは、3つの必須要素「入力データ」「処理」「処理結果」を必ず意識してください。
そして「処理」については、「何を何する」という簡単な言葉で表現できるものにしてください。
それを意識する事で、どんなに複雑な処理でも、あなたのプログラムはとてつもなくシンプルで、誰にでも説明ができ、再利用可能な、完成度の高いプログラムになります。
1点だけ補足すると、「何を何する」は一番大きな抽象表現であり、そのプログラムを構成する様々な小さな機能(バリエーション)を持たせることが出来ます。どのようなバリエーションを持たせるのか、バリエーションをどのように表現するか、これがあなたの腕の見せ所になります。

プログラムを再利用できるようにする=関数にする

今回作ったプログラムは「足し算をする」という処理を実行するものなので、数行程度のコード量で簡単に書く事ができます。
でも、これが数十行や数百行のコード量を必要とする処理を行うプログラムで、何度も足し算処理が必要となる場合、同じ処理を書くのは非効率です。

なので、先ほど作った足し算プログラムの「足し算をする」という処理を別のプログラムからも再利用できるように作ります。
関数の出番です。

// Add.js
const add = function(a, b) {
    var x, y;
    x = parseInt(a);
    y = parseInt(b);
    return x + y;
}
console.log(add(process.argv[2], process.argv[3]));

とても単純ですが、「a」と「b」という2つの値(入力データ)を受け取り、その合計を計算(処理)し、合計値(処理結果)を返すという動作を行う「add」というfunction = 「関数」を作りました。

JavaScriptの関数は、大きく3つの書き方ができます。上記の例では名前を持たない関数式(匿名関数)を定義しました。

// その1:関数定義(固有名詞を持つ)
function 関数名(入力データ) {
   処理;
};
//その2:関数式(名前を持たない関数を定義し、変数に格納)
const 変数 = function(入力データ) {
    処理;
}
//その3:アロー関数(関数式の省略形)
const 変数 = (入力データ) => {
     処理;
}

上記3つのJavaScriptの関数は、変数に入れて利用する事ができるという特徴があります。
上記のうち関数式はイキナリ変数に入れて利用するタイプの関数です。

固有名詞を持つ関数は、グローバルスコープであればいつでもどこからでも呼び出すことが出来ます。
関数式は直接変数に定義する匿名関数(名前が無い)のため、その変数がグローバルスコープにあればいつでもどこからでも呼び出すことが出来ますが、変数が書き換えられると利用できなくなります。

さて、元のプログラムの動作をより正確に表現すると

プログラムに指定された2つの「文字」を「数」に変換し、加算した結果をコンソール(画面)に出力する

となります。
では「add」関数をより正確に表現するとどうなるでしょうか。

関数に指定された2つの「文字」を「数」に変換し、加算した結果を関数を呼び出したプログラムに返す

となります。

ほぼ同じですね。
処理結果を画面に表示するのか、関数の呼び出しもとに返すのかの違いだけです。

ですが、決定的な違い があります。
それは 関数のほうは再利用できる という事です。

例えば、1+2と3+4と5+6を実行するプログラムを作りたい場合、関数ではないプログラムの場合どうするかというと、こうなります。

例題を書くのが面倒なので同じコードをコピペします。。。
これはプログラミングのセオリーとして、やってはいけない事の1つです。(これを「コピープログラミング」というらしいですが、呼び方はどーでもいいかな。。)
再利用性がないプログラムを書くとこんなに面倒で、変数名も分かりづらくなり、後々も超絶大変になるという一つの反面教師としてみてくださいw

// add.js
var x, y;
x = parseInt(process.argv[2]);
y = parseInt(process.argv[3]);
console.log(x + y);
x = parseInt(process.argv[4]);
y = parseInt(process.argv[5]);
console.log(x + y);
x = parseInt(process.argv[6]);
y = parseInt(process.argv[7]);
console.log(x + y);

それでは実行してみましょう。

$> node add.js 1 2 3 4 5 6
3
7
11

正しい結果が表示されました。

コラム:「外部設計」と「内部設計」
ソフトウェア開発では、「外部設計」と「内部設計」という2つの設計に関する概念が存在します。

「外部設計」というのは「ユーザーから見た場合にソフトウェアがどのように動くか」というソフトウェアの挙動をデザインする事です。

「内部設計」は「ソフトウェアがどのようにその機能を実現しているか」というソフトウェアのアーキテクチャデザインの事です。

上記のプログラムは「外部設計は満たしているが(ユーザーが求めている挙動を正しく反映したもの)、内部設計(アーキテクチャ)が酷いプログラムである」という事が言えます。

次に関数を使った場合にどうなるかを見てみましょう。

// Add.js
const add = function(a, b) {
    var x, y;
    x = parseInt(a);
    y = parseInt(b);
    return x + y;
}
console.log(add(process.argv[2], process.argv[3]));
console.log(add(process.argv[4], process.argv[5]));
console.log(add(process.argv[6], process.argv[7]));

それでは実行しましょう。

$> node add.js 1 2 3 4 5 6
3
7
11

プログラムの挙動は同じですが、足し算をするという処理を関数にした事で、他の処理で足し算が必要となったときにadd関数を使用する事でどこからでも足し算ができるようになった (再利用できるようになった) という大きなメリットが発生したことになります。

次に、少し改良する事でもう一つの関数のメリットを説明します。

関数は修正箇所を限定できる

このプログラム、本来必要となる処理が含まれていません。
それは、バリデーション というデータチェック処理と、エラー処理 です。

プログラムは、通常は「データ」が期待した値かどうかを「チェック」しなければなりません。想定していない値、例えば、足し算で使用するデータに文字が指定された場合や、そもそも値が無い場合、足し算できないですよね。

なので、チェック処理を足し算の前で実行できるように定義します。
まずは、関数にしていないプログラムのほうから行きます。

ここまでくると、さすがに超絶面倒だな。。

// add.js
let x, y;
if (isNaN(process.argv[2]))
    throw {name:'Argument error', message:'データが不正です'};
x = Number(process.argv[2]);
if (isNaN(process.argv[3]))
    throw {name:'Argument error', message:'データが不正です'};
y = Number(process.argv[3]);
console.log(x + y);
if (isNaN(process.argv[4]))
    throw {name:'Argument error', message:'データが不正です'};
x = Number(process.argv[4]);
if (isNaN(process.argv[5]))
    throw {name:'Argument error', message:'データが不正です'};
y = Number(process.argv[5]);
console.log(x + y);
if (isNaN(process.argv[6]))
    throw {name:'Argument error', message:'データが不正です'};
x = Number(process.argv[6]);
if (isNaN(process.argv[7]))
    throw {name:'Argument error', message:'データが不正です'};
y = Number(process.argv[7]);
console.log(x + y);

ふう。。
では、関数を利用しているプログラムで同じことをやってみましょう。
新しくバリデーション処理を関数として定義します。

// Add.js
const validateNumeric = function(a) {
    if (isNaN(a))
        throw {name:'Argument Error', message:'データが不正です'};
}
const add = function(a, b) {
    let x, y;
    validateNumeric(a);
    validateNumeric(b);
    x = Number(a);
    y = Number(b);
    return x + y;
}
console.log(add(process.argv[2], process.argv[3]));
console.log(add(process.argv[4], process.argv[5]));
console.log(add(process.argv[6], process.argv[7]));

validateNumericという関数を追加し、数字かどうかのチェックとエラー処理を一か所にまとめました。
入力データが2つあるので、validateNumeric関数を2か所で流用しています。

関数を使用しない場合、プログラムが大きくなるにつれてどんどん自分の首を絞め始めるというのが分かったと思います。

例えば、さらに機能を追加し、数字かどうかのチェック以外に値の範囲を制限する処理を追加したくなった場合、関数にしていないプログラムはもう触りたくなくなります。。。

かなり極端な例でしたが、関数を利用する事で、いかにプログラムが効率化するかお分かりになったと思います。

しかも、ここまで読んだ方は、なんとなく「関数とは」が分かったんじゃないですか?

関数まとめ

関数というのは、「小さいプログラム」とほぼ同義なのですが、あなたのプログラミング作業を効率化するために重要なものだという事が分かったと思います。

プログラムの再利用性 という観点からみると、関数を使用したプログラムでは、データが数字/数値化どうかを判断するために、毎回if文でチェックするのではなく、validateNumeric関数を利用できますし、足し算処理をしたければ、add関数を利用すれば、呼び出した後のデータチェックまでマルっと実行してくれます。
同じコードを書かなくてもいいわけです。

プログラムの修正容易性 という観点からみても、関数を利用したプログラムでは、修正箇所が関数内部にある場合、そのほかの箇所への影響はなく、非常に局所的な修正で済むわけです。

関数を使いこなせるかどうか、これがあなたの プログラムの内部品質を向上させるキーポイントの1つ になります。

コメント

タイトルとURLをコピーしました