JavaScript入門

Webページが表示されるまでの流れ

Webページにアクセスすると、次のような手順でブラウザ上にWebページが表示される。

image.png

JavaScript では、DOMの各ノードに対して色々な操作を実行できる。

DOMの構築とは別に、スタイルシートデータからCSSOMが構築され、DOMとCSSSOMをマージして、レンダリングツリーが構築される。

一連の処理は、各ステップを順番に完了してから次のステップに進むのではなく、ある程度パースが完了したら、スタイルとマージして画面表示をしつつ、処理を継続するという流れになっている。

JavaScript がHTMLページの中に記述されている場合、パースを一旦停止して、<script> タグの中のコードを実行するため、<script> タグ以降の処理が遅くなる。

処理が遅くなる=画面表示が遅くなるに等しいため、ユーザ満足度が低くなるため望ましいものではない。

よって、<script> タグは HTML ページの最後(</body> の直前)に書くと良い。

ただ、全ての <script> タグをページの最後に書けばいいということでもない。画面表示前に処理したいことがあれば、<body> タグの最初や、<head>タグ内にコードを書く。

defer属性

defer 属性を使うと、<script> タグでのパース停止をしない。実行されるタイミングは DOM ツリーの構築完了後。

<script defer src="./jscode.js"></script>

image.png

async属性

async 属性を使うと、<script> タグでのパース停止をしない。defer 属性との違いは、<sript> タグで読み込間れたコードは、DOMツリーの構築を待たず、読み込み完了後すぐに実行される点。

<script async src="./jscode.js"></script>

image.png

基本事項

プリミティブ型とオブジェクト型

JavaScriptのデータ型は、プリミティブ型とオブジェクト型の2種類。

  • プリミティブ型
    • 数値(整数および浮動小数点。64bit。データ型はnumber
    • 長数(数値型で扱えない大きな整数を扱う。データ型はbigint
    • 文字列(データ型はstring
    • 論理値(データ型はboolean
    • undifined(値が代入されていないことを示す。データ型はundefined
    • null(値が代入されていないことを示す。明示的に代入することが可能。データ型はobject
    • シンボル (ユニークな値を返す。データ型はsymbol)
  • オブジェクト型

noscript要素

ブラウザで JavaScript が無効になっていると HTML ファイルの中に記述された JavaScript のコードは実行されない。その場合、利用者は何も表示されないため、状況がわからない。

そこで、 JavaScript が無効になっている時にだけ別のコンテンツを表示する機能がある。それが noscript 要素。

下記はJavaScript 無効にした時のWebページ。JavaScript が無効なので、何も表示されない。

image.png

次に、noscript 要素を使ってみると、次のようにメッセージが表示される。

image.png

strictモード(非推奨コードの制限)

JavaScript では、文法的にエラーではないが、推奨でないコード記述が可能。例えば、次のようなもの。

// 未宣言の変数に代入
let myage;
myoge = 20;

// 引数名の重複
function test(a, a, b){
  console.log('a = ' + a);
  console.log('b = ' + b);
}
test(1, 2, 3);
// >> a = 2
// >> b = 3

上記のような非推奨のコード記述をエラーとするために、strict モードがある。使う時には次のようにする。

// プログラム全体に適用するときは、プログラムの先頭に書く
'use strict';
・・・

// 特定の関数にのみ適用するときは、関数の先頭に書く
function myFunc(){
  'use strict';
  ・・・
}

falseとみなされる値

次の論理値は全て false とみなされる。

false, 0, -0, 0n, ""(空文字), null, undifined, NaN

そして、上記以外のものは、全て true とみなされる。

長数の書き方

長数は数値リテラルで表すことができる最大数値 253-1を超えるような数値を演算したい時に使う整数。超数を使う時には、下記のようにnをつける。

let num = 854n;

使える算術演算子は、+, -, *, /, %, ** で長数同士に対して利用できる。そのほか、ビット演算やシフト演算、等価演算など、数値リテラルと大体同じ操作ができる。

文字列

テンプレートリテラル

文字を入力するときは、シングルクォート、ダブルクォート、バッククォートが使える。シングルクォートとダブルクォートの違いはない。

バッククォートで括った文字列をテンプレートリテラルといい、テンプレートリテラルでは文字列中の改行をそのまま扱えたり、式を埋め込んで使うことができる。

// 文字列の改行をそのまま扱える
let msg = `こんにちは。
今日は天気がとてもいいですね。`;
console.log(msg);
// => こんにちは。
// => 今日は天気がとてもいいですね。

// 式を埋め込む
let name = 'オレンジ';
let cost = 100;
let msg = `今日の${name}の値段は${Math.trunc(cost*1.1)}円です。`;
console.log(msg); 
// => 今日のオレンジの値段は110円です。

文字列操作でできること

String オブジェクトでは、色々な文字列操作ができる。

  • 文字列を分割する

    js 文字列.split([区切り文字[, 最大分割回数]])

  • 文字列を置換する

    js 文字列.replace(置換する文字列, 新しい文字列)

  • 複数の文字列を結合する

    js 文字列.concat(文字列[, 文字列, ...])

  • 文字列を繰り返す

    js 文字列.repeat(回数)

  • 開始位置と終了位置を指定して部分文字列を取得する

    js // 開始インデックスから終了インデックスまでを切り取る 文字列.slice(開始インデックス[, 終了インデックス]) 文字列.substring(開始インデックス[, 終了インデックス])

    違いは、次の2つ。

    • インデックスに負の値を指定した時、slice が最後尾からの順番に対して、substring では0を指定したものとみなす
    • 終了インデックスが開始インデックスよりも小さい場合、slice空文字substring はインデクスを入れ替えて表示する
  • 開始位置と文字数を指定して部分文字列を取得する

    js 文字列.substr(開始インデックス[, 文字数])

  • 指定位置の文字を取得する

    js 文字列.charAt(インデックス)

  • 大文字を小文字にする

    js 文字列.toLowerCase()

  • 小文字を大文字にする

    js 文字列.toUpperCase(

  • 指定文字を検索し、見つかる場合は最初の位置を返す

    ```js // 文字列を先頭から検索 文字列.indexOf(検索文字列 [, 開始インデックス])

    // 文字列の最後から検索 文字列.lastIndexOf(検索文字列 [, 開始インデックス]) ```

  • 指定文字列が含まれているかを判定する

    js 文字列.includes(検索文字列 [, 開始インデックス])

  • 文字列の先頭、末尾が指定文字列かを判定する

    ```js // 文字列の先頭を調べる 文字列.startsWith(検索文字列 [, 開始位置])

    // 文字列の末尾を調べる 文字列.endsWith(検索文字列 [, 開始位置]) ```

  • 文字列の先頭または末尾から空白、タブ、改行を取り除く

    ```js // 文字列の先頭と末尾から空白、タブ、改行除去 文字列.trim()

    // 文字列の先頭から空白、タブ、改行除去 文字列.trimStart()

    // 文字列の末尾から空白、タブ、改行除去 文字列.trimEnd() ```

  • 文字列が指定長になるように先頭または末尾に文字を追加

    ```js // 先頭に文字列を追加して指定長にする 文字列.padStart(文字列の長さ [, 埋め込む文字列])

    // 末尾に文字列を追加して指定長にする 文字列.padEnd(文字列の長さ [, 埋め込む文字列]) ```

    変数宣言の種類

変数宣言の種類は、let, var, const がある。性質は次のもの。

  • let

    基本的には、これを用いれば良い。let を用いる場合は、再宣言が禁止されている。

    js let num = 9; num = 10; // 再代入は可能 let num; // 再宣言は不可 //=> VM18:3 Uncaught SyntaxError: Identifier 'num' has already been declared

    また、スコープについてはブロックとなっている。

    js function test(num){ if (num >= 20){ let msg = '成人です'; }else{ let msg = '未成年です'; } console.log(msg); // ifブロック外で参照不可 } test(10); //=> VM293:7 Uncaught ReferenceError: msg is not defined //=> at test (<anonymous>:7:17) //=> at <anonymous>:9:3

  • const

    const は変数の宣言時に必ず初期値を設定する必要があり、再宣言、再代入ともに不可。

    ```js const num = 9; num = 10; // 再代入不可 //=> VM189:3 Uncaught SyntaxError: Identifier 'num' has already been declared

    const num; const num; // 再宣言不可 //=> VM261:1 Uncaught SyntaxError: Missing initializer in const declaration ```

  • var

    特徴的な振る舞いをする。let が使えるならわざわざ使う必要ない印象。再宣言、再代入ともに可能。

    js var num = 9; var num = 10; // 再宣言可能 num; //=> 10 num = 8; // 再代入可能 num; //=> 8

    スコープは、関数スコープとなっている。

    js function test(num){ if (num >= 20){ var msg = '成人です'; }else{ var msg = '未成年です'; } console.log(msg); // ifブロック外でも参照可 } test(10); //=> 未成年です

繰り返し処理

複数の変数を変化させる

C++ で『できたらいいな〜』とたまに思うことがあった複数の初期化式を JavaScript では扱える。

for (let i = 0, j = 3; i < 3; i++, j--)
{
  console.log(i + "," + j );
}
//=>  0,3
//=>  1,2
//=>  2,1

素晴らしい😄

プロパティ名を取得

C++ だと範囲for文と同じ動きだと感じた。

// jsの場合
// sample1
  const fruit = {orange:170, apple:90, lemon:110};
  for (let i in fruit){
    console.log(i + ', ' + fruit[i]);
  }

// sample2
const fruit = ['orange', 'apple', 'lemon'];
for (let i in fruit){
  console.log(fruit[i]);
}

上のコードをC++で書いてみると下記のような感じになる。

// c++の場合
// sample1
  unordered_map<std::string, int> fruits{ {"orange", 170}, {"apple", 90}, {"lemmon", 110}, };
  for (auto& fruit : fruits) {
    std::cout << fruit.first << ", " << fruit.second << std::endl;
  }

// sample2
  std::string fruits2[3] = {"orange", "apple", "lemon"};
  for (auto& fruit : fruits2) {
    std::cout << fruit << std::endl;
  }

配列

length プロパティで要素数を操作

『変わってるな🧐』と思ったのは、length プロパティで要素数を操作できる点。

let data = [10, 42, 52, 28, 26];

console.log(data);
//=> [10, 42, 52, 28, 26]

data.length = 2; // 要素を2つにする
console.log(data);
//=> (2) [10, 42]

指定位置の要素を置き換える

便利だなと思ったのは、splice メソッドでこいつは指定位置から指定要素数分、要素を置き換える。フォーマットは次のようになっている。

配列名.splice(開始インデックス, 削除する要素数, 追加要素1, 追加要素2, ...)

例えば次のような感じで配列を操作できる。

let alpha = ['A', 'B', 'C', 'D', 'E'];

alpha.splice(1, 2, 'X', 'Y', 'Z');
console.log(alpha);
//=> ["A", "X", "Y", "Z", "D", "E"]

alpha.splice(1, 2);
console.log(alpha);
//=> ["A", "Z", "D", "E"]

alpha.splice(2, 0, 'a', 'b');
console.log(alpha);
//=> ["A", "Z", "a", "b", "D", "E"]

forEachfor , for...ob の違い

Array オブジェクトには繰り返し処理用に forEach メソッドがある。要素を取り出して、コールバック関数に渡して処理ができる。

fruit.forEach(function(element, index, array){
  console.log('Index:' + index);
  console.log('Element:' + element);
  console.log('Array:' + array);
});
//=> Index:0
//=> Element:Apple
//=> Array:Apple,Melon,Orange
//=> Index:1
//=> Element:Melon
//=> Array:Apple,Melon,Orange
//=> Index:2
//=> Element:Orange
//=> Array:Apple,Melon,Orange

for 文、 for...ob でも同じことができるが、違いとして forEach では存在しない要素に対して処理を行わない。

let alpha = ['A', 'B', , , 'C'];

alpha.forEach(function(element){
  console.log(element);
});
//=> A
//=> B
//=> C

for (let i = 0 ; i < alpha.length ; i++){
  console.log(alpha[i]);
}
//=> A
//=> B
//=> undefined
//=> undefined
//=> C

for (let element of alpha){
  console.log(element);
}
//=> A
//=> B
//=> undefined
//=> undefined
//=> C

二次元要素のconcatでの複製に注意

concat で一次元要素の配列の複製はクローンで、多次元要素を持つ配列の複製を行う時には参照になることに注意。

  • 一次元配列の場合

    ```js let fruit = ['Apple', 'Melon', 'Orange']; let copyFruit = fruit.concat();

    copyFruit[1] = 'Grapes'; console.log(copyFruit); //=> ["Apple", "Grapes", "Orange"]

    console.log(fruit); //=> ["Apple", "Melon", "Orange"] // オリジナルは変わらない ```

  • 多次元配列の場合

    ```js let result = [[78, 92], [68,76]]; let copyResult = result.concat();

    copyResult[0][1] = 84; console.log(copyResult[0]); //=> [78, 84]

    console.log(result[0]); //=> [78, 84] // 参照なのでオリジナルも変わる ```

配列要素の検索

配列では要素を検索するメソッドが用意されている。

メソッド名 概要 戻り値
findIndex 条件に最初に一致するインデックスを取得 インデックス
find 条件に最初に一致する値を取得する
some 配列内に条件に一致する要素があるかを判定 真偽値
every 配列の全要素が条件に一致するかを判定 真偽値

条件式は次のようにコールバック関数として書く。

let result = [75, 68, 92, 84, 90];

// findIndexメソッド
let target = result.findIndex(function(element){
  return element > 85;
});
console.log(target);
//=> 2

// findメソッド
let target = result.find(function(element){
  return element > 85;
});
console.log(target);
//=> 92

// someメソッド
let target = result.some(function(element){
  return element > 85;
});
console.log(target);
//=> true

// everyメソッド
let target = result.every(function(element){
  return element > 85;
});
console.log(target);
//=> false

要素の並び替え

配列の並び替えには2つのメソッドがある。両メソッドは、並び替えの結果を新しい配列として返すのではなく、自身を直接更新するので注意が必要。

メソッド名 概要
reverse 要素を逆順にする
sort コールバック関数がなければ要素を文字列変換し並び替える。ある場合、コールバック関数の戻り値により並び替えを変える。
・戻り値が負:1つ目の要素を2つ目よりも小さいインデックス
・戻り値が0:何もしない
・戻り値が正:2つ目の要素を1つ目よりも小さいインデックス

簡単に処理のまとめるとした図のような感じになる。

let number = ['100', '20', '3'];

number.reverse();
console.log(number);
//=> ["3", "20", "100"]

number.sort();
console.log(number);
//=> ["100", "20", "3"]

number.sort(function(first, second){
  return first - second;
});
console.log(number);
//=> ["3", "20", "100"]

要素の抽出

上記で示した検索の他に、条件を満たす要素を抽出し、新しい配列を生成することも可能。その場合、filter メソッドを用いる。

filter メソッドでは、コールバック関数の戻り値が true となるものを新しく配列に追加する。

let result = [48, 75, 92, 61, 54, 83, 76];
let filterResult = result.filter(function(element){
  return element > 70;
});

console.log(result);
//=> [48, 75, 92, 61, 54, 83, 76]
console.log(filterResult);
//=> [75, 92, 83, 76]

要素の総和を計算する

Array オブジェクトの reduce メソッドを用いると、配列内の要素の値の総和を計算できる。reduce メソッドには、コールバック計算結果を保管できる変数を持っている点がポイント。

reduce メソッドの第二引数で保管値の初期値を設定できる。設定しない場合は、最初の要素が設定されるが、下記のような場合にうまくいかなくなるので、基本的に初期値を設定しておくと良さそう。

let user = [
  { name:'Yamada', result:75 },
  { name:'Suzuki', result:91 },
  { name:'Kudou', result:80 }
];

let total = user.reduce(function(sum, element){
  return sum + element.result;
}, 0); // sum が0で初期化

console.log(total);
//=> 246

reduceなのに総和??

『reduce(減らす)なのに、総和なんかい🤯』となんでこの名前なのかが気になってしまい、少し調査。

Array.prototype.reduce() - JavaScript | MDN によると、reduce メソッドではコールバックを呼び出す際に、直前の要素の計算結果を引き継げる仕組みを持っており、引き継いだ結果を『まとめた』結果を返す。

英単語の『reduce』には『減らす』の他に、『まとめる』や『変える』という意味もあり、後者の意味で用いられているようでした。中学英語レベルの自分としては、『減らす』にどうしても意味が引っ張られてしまいます😅

よって、reduce メソッドで総和計算できるし、例えば上記リンクでは、2次元配列を1次元配列にするといった処理も紹介されていました。すっきりした😏

配列の分割代入

配列の要素を複数の変数にまとめて代入できる。

// 分割代入しない場合
let personal = ['Yamada', 10, 'Tokyo'];
let name = personal[0];
let old = personal[1];
let address = personal[2];

// 分割代入する場合
let personal = ['Yamada', 10, 'Tokyo'];
let [name, old, address] = personal;

関数

関数化しておくことで、再利用をすることができコードのDRY化につながる。

関数の種類

function キーワードを用いる

// テンプレート
function 関数名(引数1, 引数2, ...){
  実行される処理1;
  実行される処理2;
  ...

  return 戻り値;
}

// サンプル
function dispTotal(x, y){
  let sum = x + y;
  return sum;
}
let result = dispTotal(3, 4);
console.log(result);
//=> 7

Function コンストラクタを用いる

他の方法との違いもないため、そんな利用することはないらしい。

// テンプレート
let 変数名 = new Function('引数1', '引数2', ..., '実行する処理1';`実行する処理2`);

//サンプル
let dispTotal = new Function('x', 'y', 'let sum = x + y;return sum');
let result = dispTotal(3, 4);
console.log(result);
//=> 7

関数リテラルを用いる

関数リテラルで定義された関数は、変数代入や関数呼び出し時に引数として指定することができる。

// テンプレート
let 変数名 = function(引数1, 引数2, ...){
  実行される処理1;
  実行される処理2;
  ...

  return 戻り値;
};

// サンプル
let dispTotal = function(x, y){
  let sum = x + y;
  return sum;
};

let result = dispTotal(3, 4);
console.log(result);
//=> 7

関数リテラルでは名前をつける必要がなく、無名関数として利用することもできる。無名関数は、一度きりの使い捨て関数みたいなもので、他から呼び出すことのできない関数になっている。イベント処理やコールバック関数としての用途がある。

function dispNum(x, y, func){ // 第3引数が無名関数
  console.log(func(x, y));
}

dispNum(10, 8, function(x, y){ // 呼び出し側で第3引数に無名関数を渡している
  return (x + y) / 2;
});
>> 9

アロー関数式を用いる

アロー関数式では、関数定義を省略して記述することができる。

// テンプレート
let 変数名 = (引数1, 引数2, ...) => {
  実行される処理;
  ...

  return 戻り値;
};

// サンプル
let dispTotal = (x, y) => {
  return x + y;
};
let result = dispTotal(3, 4);
console.log(result);
//=> 7

// サンプル(省略形)
let dispTotal = (x, y) => x + y;
let result = dispTotal(3, 4);
console.log(result);
//=> 7

可変長引数

呼び出し側から渡されてきた要素全てを配列として格納する仮引数のことを可変長引数(Rest Paramter)と呼ぶ。

function calcSum(...num){
  return num.reduce((sum, element) => sum + element, 0);
}

calcSum(1, 2, 3);
//=> 6
calcSum(1, 2, 3, 4, 5);
//=> 15

通常の引数と合わせて使用することもできる。その場合、可変長引数は最後に記述する。

function 関数名(引数1, 引数2, ...引数3){
  ...
}

HTML内での関数記述位置

大雑把には、呼び出し側よりも前に関数が定義されていれば、呼び出し時にエラーとなることはないと覚えておけば良さそう。

関数の中に関数を定義

JavaScript では関数の中に関数を定義することができる。関数内で定義された関数のスコープは、関数内スコープなので関数外から呼び出すことはできない。

function dispHello(){
  console.log('Hello');

  function dispBye(message){
    console.log(message);
  }
  dispBye('関数内スコープ');
}

dispHello();
//=> Hello
//=> 関数内スコープ
dispBye('関数外スコープ');
//=> Uncaught ReferenceError: dispBye is not defined

関数とオブジェクトの関係

JavaScript では関数もオブジェクトの一つで、関数を定義すると、関数名の変数に関数オブジェクトが代入される。

function calcTotal(x, y){
  return x + y;
}

console.log(calcTotal);
//=> calcTotal(x, y){
//=>   return x + y;
//=> }

関数を変数に代入したり、他の関数の引数に使うときには、関数リテラルを使う方法が用いられる。

let calcTotal = function(x, y){
  console.log(x + y);
}

let calcAbs = function(x, y){
  console.log(Math.abs(x - y));
}

function echo(value1, value2, func){
  func(value1, value2);
}

echo(1, 2, calcTotal);
//=> 3
echo(3, 4, calcAbs);
//=> 1

例外処理

例外の種類

例外のエラー種類は8種類のグローバルオブジェクトで定義されている。

例外名 概要
Error 一般的なエラー
EvalError eval関数に関するエラー
InternalError JavaScriptの内部エラー
RangeError 数値が有効範囲を超えた場合のエラー
ReferenceError 不正な参照を行った場合のエラー
SyntaxError JavaScriptの構文エラー
TypeError 変数や引数の型が適切ではない場合のエラー
URIError encodeURIまたはdecodeURIに関するエラー

上記のオブジェクトを使うことで、各例外発生時の振る舞いを変えることができる。

function returnFixed(num, digits){
  try{
    console.log(num.toFixed(digits));
  } catch(e) {
    if (e instanceof RangeError){
      console.log('RangeError');
    } else if (e instanceof TypeError){
      console.log('TypeError');
    } else {
      console.log(e);
    }
  }
}

returnFixed(3.87654, 3);
//=> 3.877
returnFixed(3.87654, 1000);
//=> RangeError
returnFixed('3.87654', 1000);
//=> TypeError

例外の有無によらず処理をさせる

JavaScript には try...catch...finally 文がある。try ブロックではブロック内処理の例外を捕捉し、catch ブロックでは例外捕捉時の処理を行い、 finally では例外の有無によらず文の最後に処理を実行する。

// format
try{
  例外が発生する可能性がある文を記述
  ・・・
} catch(e) {
  例外をキャッチしたときに実行される処理
  ・・・
} finally {
  最後に実行される処理
  ・・・
}

// sample
function sum(a, b){
  try{
    console.log(a + b);
  } catch(e) {
    console.error(e);
  } finally {
    console.log('Finally!');
  }
}
sum(10, 8);
//=> 18
//=> Fnally!
sum(10, 8n);
//=> Fnally!

コールバック関数内の例外はキャッチできない

コールバック関数が実行されるのは、try の外であるため例外をキャッチできないという話。

// コールバック関数じゃない場合はキャッチできる
console.log('start');
//=> start
try{
  10 + 8n;
} catch(e) {
  console.log(e);
  //=> Cannot mix BigInt and other types, use explicit conversions
}
console.log('end');
//=> end

// コールバック関数の場合はキャッチできない
console.log('start');
//=> start
try{
  setTimeout(function sum(a, b){
    console.log(a + b);
  },2000, 10, 8n);
} catch(e) {
  console.log(e);
}
console.log('end');
//=> end

正規表現

JavaScript での正規表現の定義方法

  • 正規表現リテラルを用いる

    ```js // format /パターン/ /パターン/フラグ

    // sample let regexp1 = /[a-zA-Z]{4}/; console.log(regexp1.test("1AbCd2")); //=> true

    let regexp2 = /apple/; console.log(regexp2.test("1Apple2")); //=> false regexp2 = /apple/i; console.log(regexp2.test("1Apple2")); //=> true ```

  • RegExpコンストラクタを用いる

    ```js // format new RegExp(パターン[, フラグ])

    // sample let regexp1 = new RegExp('[a-zA-Z]{4}'); console.log(regexp1.test("1AbCd2")); //=> true let regexp2 = new RegExp('apple', 'i'); console.log(regexp2.test("1Apple2")); //=> true ```

JavaScript正規表現で利用できるフラグ

JavaScript では6種類のフラグが用意されている。

フラグ フラグ名 説明
g Global フラグなしだと最初にマッチした文字列のみで、フラグありだと検索文字列全体にする
i IgnoreCase 大文字小文字区別なし
m Multiline フラグなしだとメタ文字 ^, $ のマッチは文字列の先頭と末尾のみで、フラグありだと行頭と行末も含める
s DotAll フラグなしだと任意の一文字の . は改行を含めない、フラグありだと含める
u Unicode フラグなしだとUnicode形式で指定しない、フラグありだと指定する
y Sticky フラグなしだと検索対象は全体で、フラグありだと指定した開始位置からのみマッチするかを判定

正規表現の使い方

フォーマット 説明
正規表現オブジェクト.test(文字列) 対象となる文字列が正規表現とマッチするかのブール値を返す
正規表現オブジェクト.exec(文字列) マッチした文字列を取得する
文字列.search(正規表現オブジェクト) マッチした最初の文字列の先頭文字のインデックスを返す。マッチしない場合は-1を返す
文字列.match(正規表現オブジェクト) 文字列の正規表現とマッチする部分文字列を取得する。マッチしなかった場合は null になるので要素取得時には例外処理などを入れる必要がある。
文字列.split([正規表現[, 最大分割回数]]) 対象文字列を、正規表現オブジェクトで分割し、それぞれを要素とした配列を返す
文字列.replace(正規表現, 新しい文字列) 対象文字列を、正規表現オブジェクトにマッチする部分を置換する

Mathオブジェクト

小数点以下の加工

Math オブジェクトには小数点以下を加工する静的メソッドが用意されている。

メソッド名 概要
Math.round 四捨五入
Math.ceil 切り上げ
Math.floor 切り下げ
Math.trunc 切り捨て

floortrunc の違いが分かりにくいが、floor は文字通り切り下げる

  // 正の場合
  const a = 2.82;
  console.log(Math.floor(a)); //=> 2
  console.log(Math.trunc(a)); //=> 2

  // 負の場合
  const b = -2.82;
  console.log(Math.floor(b)); //=> -3
  console.log(Math.trunc(b)); //=> -2

コンソール出力

コンソール出力の違い

コンソール出力メソッドには、以下4種類がある。

メソッド名 コンソール出力
console.log image.png
console.info image.png
Google Chrome だと log と違いなしだが、Fire Fox だと 『i』 アイコンがつく
console.warn image.png
console.error image.png

上記のようにコンソール出力のレベルを変えることで、開発者ツールのコンソール上でフィルタ機能を活用しやすくなる(エラーだけ表示等)。

コンソール上でオブジェクトを展開

コンソール出力するメソッドにオブジェクトを渡すと、コンソール上で展開することが可能。デバッグの時などに重宝しそう。

image.png

呼び出された経路の表示

console.trace メソッドでは、console.traceメソッドが呼び出された経路をコンソール出力することができる。

function funcA(){
  console.trace();
}

function funcB(){
  funcA();
}

funcA();
//=> console.trace
//=> funcA
//=> (anonymous)

funcB();
//=> console.trace
//=> funcA
//=> funcB
//=> (anonymous)

Ajax

Ajaxとは

AjaxAsynchronous JavaScript + XML の略で、Webサーバとブラウザ間を非同期通信する方法のこと。

Ajax を利用することでページの切り替えなしで表示内容を更新できるようになる。例えば、クライアントから一部の情報をリクエスして、サーバからの応答が返ってきたら、その情報を反映する。

上記の『一部の情報』という点がポイントのようで、ブラウザ上の全ての情報を更新するのではなく、部分的なリクエストにすることで、更新がない部分は残したままブラウザ画面を更新できるらしい。

例えば、Google マップの縮小時に広域エリアが表示されてなくても、すぐに表示されるようになるのは裏で Ajax が動いているかららしい。

Image from Gyazo

Ajax を支える XMLHttpRequest オブジェクト

上記のような非同期通信のために使用するのが、 HMLHttpRequest オブジェクト。このオブジェクトがサーバと通信を行い、リクエストに対するデータ受信が完了したらコールバック関数にて受信データを処理する。

大雑把には、次のような流れで Ajax 通信が行われる。

  1. XMLHttpRequest オブジェクト生成
  2. XMLHttpRequest.open による通信方法の設定
  3. XMLHttpRequest.send でリクエストをサーバーへ送信

非同期通信では、サーバからのレスポンスの受信完了時にコールバックを呼び出すので、それを検知するために、HMLHttpRequest オブジェクトには状態を表すプロパティがある。

説明
0 未初期化(openが未実施)
1 ロード中(open設定済み、send未実施)
2 ロード済(send済、レスポンス待ち)
3 受信中
4 完了

上記の状態をチェックすることで、処理を切り替え非同期処理を実現する。簡単に流れを確認してみるために、次のようなコードを用意。

let request = new XMLHttpRequest();

request.onreadystatechange = function(){
  console.log(request.readyState);

  if (request.readyState == 4){
    if (request.status == 200){
      let data = request.responseText;
      console.log(data);
    }
  }
}

request.open('GET', 'https://www.example.com', true);
request.send(null);

https://www.example.com を開き、デベロッパーツールのコンソールに上記コードを貼り付けて実行すると、状態が切り替わっていくことが確認できた。

Image from Gyazo

参考

JavaScript is 何?

スラスラ読める JavaScript ふりがなプログラミング を読んだので、大事と思ったことをまとめておく。

概要

使いどころ

JavaScript は、ユーザインターフェースを作るために用いられる。いわゆるフロントエンドの部分。

変数の使い方

基本的な書き方は、CやC++にとても似ているが、変数は少し違う。JavaScript で変数を用いる際には、let を用いる。

let kakaku = 100;
let urine = kakaku * 1.08;

また、変数の命名には、次の慣習的なルールがある。

  • 半角アルファベット、アンダースコア(_)、ダラー($)、数字を用いる
  • NGなのは、先頭数字や数字のみ、予約語を用いる

NGとなるものは、JavaScriptインタープリタ*1によって、数値、演算子予約語としてみなされるため、変数として使用できない。

条件分岐

JavaScriptの条件式は、CやC++とほぼ同じであったけど、厳密等価(===)と厳密不等価(!===)は、ちゃんと理解できていなかったのでまとめておく。

等値演算子

厳密等価と厳密不等価

演算子 記号 説明
等価 == 2つのオペランドが等しいことを検査する。オペランドの型が異なる場合には、型変換を試みてから比較する。
不等価 != 2つのオペランドが等しくないことを検査する。オペランドの型が異なる場合には、型変換を試みてから比較する。
厳密等価 === 2つのオペランドが等しいことを検査する。オペランドの型が異なる場合、異なると判断する。
厳密不等価 !== 2つのオペランドが等しくないことを検査する。オペランドの型が異なる場合、異なると判断する。

具体的に、どんな感じかを認識するために、比較表を作ってみた。部分に各演算子が入った時の結果を載せる。

演算子 1◯1 'hello'◯'hello' '1'◯1 0◯false
等価('==') true true true true
不等価('!=') false false false false
厳密等価('===') true true false false
厳密不等価('!==') false false true true

厳密ではない演算子の方がルーズといえ、このルーズさが問題を引き起こすこともあるため、型を含めてチェックする厳密等価、厳密不等価を用いることが推奨されている。

ちなみにRubyにも === があるが、上記の意味合いとは異なる。(instance method Module#===

繰り返し文と配列

ほぼC、C++と同じ。ここまで同じとは驚きでした😲

配列のループ操作と配列操作メソッド

2つ違いがわかったのでまとめておく。

  • 配列のループ操作

    配列のループ操作では、for( let 変数 of 配列 ) と書く。 C++だと、 (const auto& 要素 : 配列) に相当するイメージ。

  • 配列操作メソッド

    JavaScriptの配列には、最初から配列操作用のメソッドがある。 C++の、std::vector に近いものだと考えればよさそう。

    配列型のメソッド 働き
    配列.length 配列の要素数を返す
    配列.push() 配列の末尾に要素を追加
    配列.pop() 配列の最後の要素を取り出す
    配列.splice(start, num) 配列のstart番からnum個削除
    配列.sort() 配列の要素を並び替える
    配列.shift() 配列の先頭要素を取り出す

関数とオブジェクト

関数の書き方

if 、配列とかの書き方がC、C++と似ていたので関数もそうかと思ったら、結構特徴的でした。JavaScirpt の関数の書き方は次のもの。

// 関数定義(ES2015の場合(今後の主流))
let 変数 = (引数) => {
  処理
};
// 呼び出し
変数(引数);

// 関数定義(ES5の場合)
function 変数(引数){
  処理
}
// 呼び出し
変数(引数);

戻り値を返すときは、return 値 でOK。

オブジェクト

複数のデータを記録する入れ物としてオブジェクトがある。書き方は次の通り。

// オブジェクトの書き方
変数 = { prop1 : value1, prop2 : value2 }

// 呼び出し
変数.pop1         //パターン1
変数['prop1']     //パターン2

参考

*1:Webブラウザ内でJavaScriptを解釈する機能

オブジェクト指向プログラミング(OOP)とは

OOP(Object Oriented Programming)は、ソフトウェアの保守や再利用性を高めるための技術。

保守や再利用のためには、『強い凝集度』や『弱い結合度』であることがよい。 OOPを用いることで、そうした観点に沿う実装をしやすくなる。『無駄を省いて整理するプログラミング技術』と言える。

OOPには、3つの仕組みがある。

仕組み名 目的 説明
クラス 整理整頓 関連性の強いサブルーチン(関数)とグローバル変数を1つにまとめて、粒度の大きいソフトウェア部品を作る
ポリモーフィズム 無駄を省く メソッドを呼び出す側を共通化する(インタフェースを統一する)
継承 無駄を省く クラス定義の重複を共通化する

参考

ソフトウェア開発者として生きる上での心構え

情熱プログラマーを読んだので、大事だなと思ったことをまとめる。

大雑把には、『ソフトウェア開発で注目に値するキャリアを築くための方法』を学べた。

第1章 市場を選ぶ

1 先んずるか、やられるか

市場価値の高いエンジニアを目指す。

『先んずる』場合は、これから流行るであろうテクノロジに取り組み、市場の先をゆく。もちろん、次世代で流行らないと言うリスクもある。

『やられるか』場合は、テクノロジを最後まで看取り、死にゆくシステムの保守という作業を繰り返す。市場的には、死にゆくテクノロジのため、市場に取り残されると言うリスクもある。

安定したテクノロジの場合、リスクは無いが市場価値という意味では見返りは少ない。

2 需要と供給

価格競争に勝ち目はない。市場の不均衡を狙う。需要の高い労働市場は、最終的にプログラマの価格は下がる。理由は、参入者(供給)も多くなるため。

企業の中には、需要の高い労働市場にエンジニアを派遣する企業もあり、どうしたって、そんなの相手には戦えない。

だったら、ニッチなテクノロジに注目するのも手と言える。

一番いいのは、オフショア*1の進出がなく、国内で需要が多そうに見えるスキルを身につけること。

3 コーディングはもう武器にならない

テクノロジは、金で手に入る日用品になっている。そう考えると、コーディング能力だけでは食べていけない。コーディングだけでいいなら、買えばいいので。

大事なのは、ビジネス(業界)の分野における経験。ビジネスの分野もテクノロジと同様に選択する。つまり、どんな企業と業界で働くかを選ぶ。

自分の時間を、どんなビジネスに投資するかを考える。そのために、ビジネス屋と話をしたり、業界誌を読んだりして、情報を集める。

4 一番下手くそでいよう

簡単に言えば、レベルの高い環境に身を置けということ。

自分の周りの人たちが、自分自身のパフォーマンスに影響する。自分と違う環境に身を置いたとき、その環境を自分に取り入れる。

周りの人間が自分よりも能力が高い場合、その人たちに近づこうという心理が働き、自身の能力を高めることができる。

5 自分の知性に投資しよう

注目すべきテクノロジを選ぼうとすると、一番仕事が多いテクノロジに目を奪われがち。その観点からすると、ニッチなテクノロジへの投資は、馬鹿げているといえる。

しかし、仕事が多いテクノロジのエンジニアでは、差別化しないと埋もれてしまう。ニッチなことも知っているというのは、大きな武器になる。

新しい言語を学び、新しい考え方を理解し、自身の幅を広げる。

6 親の言うことを聞くな

親世代と価値観が異なることを認識する。親世代のキャリア選択の決定的要因は安定だった。

今はそんな時代ではない。市場価値として、1つのやり方(1つの企業でしか働いたことのない)しか知らないエンジニアよりも、いろんな環境で多彩な成功と失敗してきたエンジニアの方が価値がある。

7 万能選手になろう

ソフトウェアは製造業の製造ラインのようにはいかない。理由は、ビジネスが流動性を持つように、ソフトウェアも柔軟性がある。変化が激しいので、型にはまったやり方はやりにくい。

よって、開発の特定のプロセスしかできない場合、要求の変化等があると、すぐに手待ちが発生してしまう。もっと言えば、特定しかできないエンジニアは、替えが効きやすい。

自分自身をどこでもやれる万能選手を目指すことで、市場価値が上がる。

8 スペシャリストになろう

スペシャリストは、『一つのことしかできない』という意味ではない。わからないことも自身や周りの力を借りて、解決する人のことを言う。

人に教える機会は、自分自身が学ぶための最善の方法。

9 自分の人生を他人任せにするな

ベンダーに依存しすぎない。よって、一つのテクノロジにのみ投資するのは賢明ではない。

10 愛せよ、さもなくば捨てよ

自分の仕事に情熱を注ぐ。理由は、それが結果に表れるから。情熱を持てる仕事を見つけることで、凡人から大きな一歩を踏み出すことができる。どうしても情熱を注げないのであれば、新たな一歩を踏み出す時なのかもしれない。

『とにかくやれ、やれないはずはない。』

第2章 製品に投資する

キャリアアップに役立つ、自身への下記のような投資戦略を学ぶ。

  • スキルや技術の選択の仕方
  • 投資方法

11 魚の釣り方を学ぶ

アクティブに学ぶ姿勢を作り、知識や技術をきっちりと身につけて、自分でできるようにする。

その際には、自分の仕事の中で、完全に理解できていないところを掘り下げてみる。そして、『なぜ』、『どうして』を自問し、理解を深めるようにする。

12 ビジネスの仕組みを学ぶ

『ビジネスを学べ』

ビジネスで利益を上げる方法を知らないと、想像力を使って付加価値を生み出せるような仕事はできない。ビジネスがどう成り立っているかを知ることで、攻め方を知ることができる。

そのために、MBA経営学修士)志望者向けの本を読んでみる。

13 師匠を探す

誰かに頼ってもいいが、相手を選ぶ。

手本となる人を何人か見つけて、その特徴を洗い出す。その特徴に重要度と、現時点での自身の能力値の差分をとり、最も大きな差となっているものを、最優先課題として能力向上に努める。

14 師匠になる

何かを教える時には、誰かに教えるのが1番の近道。教える際には、自身が理解しており、さらに頭の中で整理されてなければならない。教えることで、自身の理解が整理され、さらに理解が深まる。

15 一に練習、二に練習

練習の目的は、自分の能力を伸ばすこと。部活と同じように、自主トレをしないと大会で戦えない。つまり、自分の技術に対して投資しなければ、品質という世界では戦っていけない。

自主練では、自分の限界ギリギリを攻めることが大事。本では次のことを薦めている。

  • できる範囲を広げるために使ったことのないAPIや関数を使ってみる
  • コードを理解する力をつけるためにOSSのToDoにチャレンジしてみる
  • コーディング力を鍛えるためにプログラミングコンテンストに参加してみる

プログラミングコンテストは、AtCoderとかpaizaみたいなやつならやったことがあるので、今後も時間があればチャレンジしてみよう。

16 プロセスを大切にする

プロセスとは、方法論として体系化されたもの。ここでいう方法論は、上から押し付けられる意味のないものではなく、先人たちの知恵を体系したもの。

プロセスを身につけるためには、実践が一番。プロセスを色々学び、実践の中で自分達にとって有益となるように、プロセスを選び出し、プロセス自体を磨いていくことが重要。

17 巨人の肩の上で

先人たちの優れたコードから色々学ぶ。

広く普及したOSSで公開されたコードを読み、どんな書き方をしており、どんな意図があるかを考える。OSSを色々知ることで、車輪の再実装といったことも起きにくくなる。

18 自動化によって仕事を確保する

生産性向上に確実な方法は、自動化する能力。自動化さえできれば、人手不足にも対応できる。まずは、繰り返しコーディングしている処理などがあれば、コードジェネレータを作ってみるのも手。

MDA(Model Driven Architecture)*2について調べてみる。

第3章 実行に移す

すぐに実行に移せる人材になるために何をすべきかを学ぶ

19 今すぐに

コンテストのような気持ちでプロジェクトにあたる(決められた時間に決められた機能を実装するような)

20 読心術

読心術を磨いて、相手の信頼を得る。日頃のふたした会話などから、真の要求を読み取り、不言実行で進めてみる。UIような部分については読み取るのは難しいので、やめておく。

21 デイリーヒット

報告できる成果を毎日あげる。チームがいつも我慢しているような些細な問題(自動化できるようなもの)をメモして片付けてしまうというのも有効。

22 誰のために働いているのかを思い出す

日々の仕事がどうチームの目標に貢献できているかを確認する。そうすれば、目的意識が生まれ、働く意味も見えてくる。

23 今の職務を全力で

野心を抱け。でも前面には押し出さない。野心を押し出しすぎると、上からの印象が悪くなり、投資されなくなる。

今の職務に全力を尽くし、きっちりこなす。現在のタスクが、自身の評価の指標になる。

24 今日どれだけうまく仕事ができるか?

退屈な仕事を楽しくする工夫を取り入れる。楽しさが増えれば仕事もそれだけうまくいく。

退屈な仕事の多くは、非創造的で義務的なもので、日常業務に占める割合も多い。なので、ここの楽しさを増せれば、仕事にも退屈しなくなり情熱を持って取り組めるようになる。

例えば、退屈な業務をこなした数を見える化して、同僚と競争できるようなことをするのもあり。賞金などもあると、さらに良いかもしれない。

25 自分にどれだけの価値があるのか?

給料の2倍の価値を生み出さなければ、会社にとって利益になる人材にはなっていない。いかにその値に近づけられるかは、日頃から自身の価値について考える必要がある。

常に、今日の自分の仕事に価値(会社にとって利益を生み出したか)があったかを自問する。

26 バケツ一杯の水の中の小石の一つ

『謙虚であれ、成功に溺れるな』

いくら能力が優れた人であっても、その人が抜けても悲しいかな、会社は回る。

今まで順調である場合、自分に自惚れ、自身のやり方に固執しやすくなる。常に最良の方法があるはずと自身に問う。

誰もが代替可能な存在であることを認め、その状態(例えば、誰でも保守できるコードになっているとか、誰かに依存している作業をなくす等)になるようにチームをリードできる人材が特別な存在となる。

27 保守作業の真価を知る

保守業務も自由で創造的にできる。保守業務であってもコードのリファクタリングなどができる。また、アプリケーションの品質が測定可能なリスト(応答時間、稼働時間等)を作って、そこに対してパフォーマンス改善を実施することもできる。

そう考えると、保守作業では創造的にいろいろな開発ができるといえる。

28 8時間燃焼

プロジェクトはマラソンであり、短距離走ではない。労働時間を長くして、短期的な生産性を向上しても、結局は燃料切れを起こして、効率は激しく落ち込む。

決められた時間の中で、最大限の効果を出すように努力する。つまり、8時間燃焼は8時間で力を出し切ると言う意味になる。

そのためには、早め早めに段取って、作業をうまく凝縮させるようにして事に当たる必要がある。

29 失敗する方法を学ぶ

人間なんだから、誰でも仕事の中、愚かなミスをする。大事なのは、ミスに対処、対策すること。

間違いに気づいて、ミスに対処する上での原則は次のもの。

  • 直ちに問題として取り上げる
  • 責任を負う。罪を負わせようとしない。責任の追求も時間の無駄。問題を解決する。
  • 解決策を提示する。なければ、解決を見出すための方策を計画する。方策は、具体的かつ予測可能な期限を設定したものにする。
  • 助言を求める。プライドを捨て、解決に向けた協力を依頼する。

失敗こそ成功につなげる絶好の機会。なぜなら、ミスした時の自分の振る舞いで、熱烈な支持を得られるか、それともぶち壊すかが決まる。真摯に対応する。

30 できないことは『できない』とはっきり言う

何でもかんでもできると言って、結果できない場合、信頼されない。『できません』と言えるチームの『できる』という言葉には信用がある。言うべきことを言い、より良い提案ができるのが価値のある開発者。

注意としては、『できません』の乱用はダメ。試しにやってみたい場合には、『簡単ではないがチャレンジしてみたい』と言う感じで伝える。

31 あわてるな

問題発生時に1番大事なことは慌てないこと。慌てると判断できなくなり、最善を尽くすことができなくなる。

また、そうしたピンチがあったとしても自分のキャリアには大して影響を与えないので、深刻に考えなくても良い。

慌てないためのコツは、その状況を第三者の視点で俯瞰すること。第三者として、困っている人を助けるという意識で、自分の状況を見てみると冷静に行動しやすい。

パニック日記をつけて自分の性格を分析するのも有効。

32 言って、成して、示す

計画をする。まずは1日のタスクをリスト化し、計画を立てる。このとき、リストのものを全て完了できなくてもOK。見積もりには誤差が絶対に乗るから。大事なのは、優先度をつけてやることを整理し、見通しを立てること。

1日の計画になれてきたら。30〜90日程度の大局的な、つまり戦術的な計画を立てる訓練をする。訓練をすることでそうしたレベルの計画の精度も上がっていく。

計画したら、必ずそれを報告し、計画に対してどうなったかを記録する。計画が省みられていない場合、誰も自分の計画を信用してくれない。

第4章 マーケティング...スーツ族だけのものじゃない

どんな優れた製品も知ってもらえないと買われない。ソフトウエア開発者にも同じことが言える。

リーダに自分の能力を理解させる方法と、業界全体に視野を広げる方法を学ぶ。

33 視点が違えば認識も異なる

他人の評価を気にすること。また、他人の評価は、認識の主体に(環境)よって変わる。 環境に応じて何をアピールすべきかを、次のような要因リストとして洗い出し、各環境に適した見せ方をする。

グループ 認識の決定因子
チームメイト 技術的技能、社会技能、チームワーク
マネージャ 指導力、顧客重視、コミュニケーション能力、遂行力、チームワーク
顧客 顧客重視、コミュニケーション能力、遂行力
プロジェクトマネージャ コミュニケーション能力、遂行力、生産性、技術的技能

周りからの自分への認識=自身の評価。

34 アドベンチャーツアーガイド

マネージャーや顧客(この節では、以降顧客)が求めるものは、プロジェクトについて彼らに安心感を与えてくれる人間。

ソフトウエアエンジニアは、開発において顧客のツアーガイドのようなもの。大柄な態度は最悪。常に謙虚で。

そうして、顧客から信頼を勝ち取ることで、顧客から支持を得たエンジニアになれる。

35 オレ、作文的なのは得意っすよ

作文技術は大事。自身の認識を左右する要因として文章力の比重が大きくなっている。

自分自身を言葉で表現できなければ、プログラムだってうまく組み立てられるわけがない。

わかりやすく表現できるのは、明快な設計とシステム実装能力とそれほど変わらない。

36 そこに居ること

直接会う方が、高い生産性と意思疎通が見込める。コミュニケーションがより効果的だから。

つまり高い生産性にはコミュニケーション能力が重要である。

会社にとって、欠かせない人材になるためには、一緒に働く人と密にコミュケーションを取る努力が必要。

37 スーツ語

ビジネスを運営している人たちの興味は、ビジネスの結果にある。よって、自分の業績を売り込むときは、そのビジネスに合った言葉を使わないと効果がない。

自分の成果が、ビジネスにとってどんな利益をもたらしたかを語る。

常にエレベータースピーチを用意しておく。

38 世界を変えよう

社員同士で、それぞれの業績を知らないのはヤバい。

使命を持って仕事をする。自分自身や目先の仕事ではなく、チームや組織、会社の人たちに変化をもたらせる必要がある。

改善の余地があるなら、変化をもたらす人材になれ。

39 業界で名前を売ろう

人脈が多いほど、適職やキャリアブレイクにつながる可能性が高くなる。

ブログで自身の考えなどを発信する。コメントなどもしてみる。そうしたところから、繋がりを増やしていく。

地域コミュニティ(開発者グループ)でスピーチすることも有効。

40 自分のブランドを築こう

認知と尊厳を得たいと言っても、Webでうざったい奴になってはいけない。

自分のブランドが落ちてしまう。世間の印象はそうしたところから形成され、いったんできると中々変わらない。

41 自作のコードをリリースしよう

オープンソースに寄与することは、ソフトウエア開発に対する情熱や技能の証明になる。

無償でやる気があることの裏付けは、積極性のアピールとなる。

42 目立つこと

行動に移し、アウトプットする。そして、注目度を高められる状況を作る。

43 コネを作る

普通の人間とプロを隔てているのは恐怖心。『虎穴に入らずんば虎子を得ず』の精神で、プロのコミュニティに関わってみる。

第5章 研鑽を怠らない

一発屋にならない方法を学ぶ

44 既に時代遅れである

IT業界は常に新しいことを学ぶ必要がある。人はビジネスが成功すると、そのビジネスモデルに安住しがち。

誰かが革新的なアイディアで市場に参入してくると、その成功は簡単に崩れる。

今何を学ぶべきかを、先取りして考える必要がある。週に一回は最前線技術を調査する時間を設ける。くれぐれも怠けない。

45 君は既に職を失っている

職務にこだわらない。現代は特定の職務だけでこなしていればなんとかなるというキャリアはない。

テスターやプロジェクトマネージャーになったつもりで仕事をして、職務の幅を広げるのが大切。

46 終わりのない道

努力を怠らない。成果が重要ではなく、成果のために努力することが大事。

成果ばかりを追い求めると、プロセスの質を高めるのを怠りがち。

悪いプロセスは悪い製品を生み出す。目の前のことではなく、将来の発展を見据えて行動する。

47 自分のロードマップを作る

自分という製品の『機能』を具体的な目標として設定する。

目標がないとどこに進めばいいかわからないので、一時的にでもいいので何に投資するかを決定する。

そして、ときおりその選択の結果の集合(自分のスキル)として、どんな製品になれるかを考える。

これまできたキャリアのロードマップを立てて、どう更新していくかを考える。

48 市場に気を配る

アンテナを高くして、技術系ニュースに目を通す。市場がどんなエンジニアを求めているか、変化していくかに気にかける。

そうすることで時代に取り残されない。

アルファギーク*3を終え』

49 鏡の中の太った男

自分自身の能力を評価する。360度サーベイ*4などが有効。

50 南インドのサル捕獲トラップ

硬直した価値観が自分をダメにする。一つのキャリアや技術に固執しすぎない。色々な技術を試してみて、食わず嫌いにならない。

51 ウォーターフォール式のキャリア計画はやめよう

アジャイル開発は、変化を受け入れ反復的なアプローチによって開発を進める手法。複雑な問題を解決しようとする時に、苦労を少なく進めるのに効果的。

人生のキャリアもある意味で開発と一緒。自分自身を製品と考えると、ウォーターフォール式に決められるものではない。変化に柔軟に対応していくことが求められる。

人生のキャリアにおける顧客は自分自身。自分自身が満足できるように、経験し進みながら、目標(キャリア)を変更していく。

52 昨日よりよく

大きて困難な目標を目指すときは、大抵の人間はその複雑さやハードルの高さからやる気を失ってしまう。そんなときは、『その目標に近づけたかではなく、昨日よりも努力できていたか?』を考えるのが大切。

目に見える形で自分の行為を着実に改善し続ければ、『大きくて困難な目標』を目指す際に受ける罪悪感や先送りの連鎖に囚われなくなる。

53 独立する

タイトルの通り。目覚ましい業績を目指すのであれば、独立する。後ろ盾がない状況となれば、自分自身の力でしか勝負ができないので、必然的に売り込む力や技術を身につけなければ生きていけない。

いきなり独立はなかなかハードなので、少し小さい企業や新興企業に転職するのも手。

参考

*1:海外の低コストのIT技術

*2:ビジネス要件やソフトウエアの機能と構造などを分かりやすい図を使ったモデルで表現してシステム開発を進め、最終的にそれらのモデルからプログラムを自動生成する技術

*3:最前線の最先端情報を常に追いかけてる人

*4:上司だけでなく、同僚や部下、他部署など複数の関係者から評価を行う手法

生産性の高いソフトウェア開発に必要なもの

Peoplewareを読んだので、大事だなと思ったことをまとめておく。

大雑把には、『ソフトウェア開発は人材がすべて。なぜ、人なのか?どうすれば人の生産性を上げられるのか』を学べた。

要約

『プロジェクトを成功するための鍵』や『人のマネジメント方法』を学べる。

第I部 人材を活用する

人は交換不能である。

第1章 今日もどこかでトラブルが

プロジェクトの問題は技術的問題(設計、製造、開発技法など)では片付けられない。ソフトウェア開発上の問題の多くは社会学的(人に関する問題)なもの。

第2章 チーズバーガーの生産販売マニュアル

ソフトウェア開発作業は、チーズバーガー大量生産のようなやり方ではうまくいかない。例えば次のようなやり方。

  • 失敗は許さない
  • 失敗したら懲罰
  • 人の替えは効く
  • マニュアル通り動け
  • 標準化せよ
  • 新しいことを試みるな。それは本部のやること

上とは、真逆のことを基本的には目指す。

  • エラーは大歓迎。知的活動では自然のこと
  • 失敗を許して、その経験を活かす
  • 人の替えは効かない。人の個性がプロジェクトを活発にする
  • 担当者に任せて、自発的に取り組ませる
  • 担当者のやりやすいようにする
  • 新しいことを試みる

第3章 ウィーンはきみを待っている

残業をたくさん課しても、無業時間(サボり)が増えるだけ。また、うまくサボれない人はワーカホリックになってしまう。『残業なんてクソ喰らえ』の信念で、本当の意味での生産性とは何かを考える。

生産性向上のやり方で、次の方法には注意。みんなウィーンに行ってしまう(退職する)

  • 長い時間働くようにプレッシャーをかける
  • 製品開発を機械的なプロセスにする
  • 製品品質について妥協する
  • 手順を標準化する

上の方法はいずれも仕事を面白味のない、やりがいのないものにする恐れがある。結果として、生産性が上がっても退職率も上がる危険がある。そういう恐れがあるということを認識しておく。

第4章 品質第一主義・・・時間さえ許せば

大抵の人は自尊心を傷つけられると激情する。開発において自尊心は、自分の作った製品の品質と強く結び付けられる傾向がある。よって、あくびの出そうな仕事をいくらこなしても何の満足も得られない。

『マーケットは品質なんか気にしない』ということはよく聞く事実であるが、エンドユーザの要求を超えた品質水準は生産性を上げる一つの手段と言える。

理由は、自尊心の観点からも、開発者自身が満足する品質基準を決めることが生産性の向上につながるため。

第5章 パーキンソンの法則の改訂

『仕事は与えられた時間に見合うところまで膨張する』という説。この本では、この説は あまり当てはまらない といっている。理由は、仕事を片付けて得られる満足感を早く得たいと思うから。

生産性を損ねる要因は、『不合理かつ非現実的なスケジュール』がある。参考データとして目標値設定において、生産性がどのように影響を受けるかを調査した結果で、次のようなものがある。

目標値設定者 平均の生産性 完了プロジェクト数
マネージャー 6.6 23
プログラマとマネージャー 7.8 16
プログラマ 8.0 19
システムアナリスト 9.5 21
目標なし 12.0 24

上記の通り、日程的なプレッシャーがある条件下では、専門家や現場が設定した方が生産性は向上する。理由は、より正確な作業工数を設定できるため。絶望的に厳しい見積りは、プログラマのやる気を削いでしまう。

面白いのは、日程的なプレッシャーを与えなかった時が一番生産性が高いという点。

パーキンソンの法則を手直しするとすると、次の節になる。

会社のルーチンワークは、就業時間に見合うところまで膨張する傾向がある

成熟の頂点にある企業で働くのが楽しくないのは、上記の理由によるものと言えそう。

第6章 ガンによく効く?『ラエトライル』

生産性向上の良い実績は、効果的な人の扱い方、作業場所や企業文化の改善等であり、特効薬のようなものはない。

生産性を向上するツールの謳い文句はたくさんあるが、本質的には解決しない。

マネジャーの役割は、人を働かせることではなく、 人を働く気にさせる こと。

第II部 オフィス環境と生産性

社員にやる気を起こして仕事をさせるには、やる気を失わせる原因を理解することが先決。作業効率を低下させる原因を究明し、健全で仕事がしやすいオフィス環境を作る方法を学ぶ。

第7章 設備警察

騒々しく、無味乾燥で次々と邪魔が入る場所に、プログラマを押し込めている限り、どんな改善策も意味がない。

設備警察は、上記のような『クソな環境』を投下コスト最小化を盾に維持しようとする組織。

オフィス環境の改善のみが唯一解。

第8章 プログラマは夜できる

就業時間外の方が雑音が少なく作業に集中できるような環境は、馬鹿げた話で、変えなければならない。

プログラマの生産性は、当然、個人間でバラツキがあるものだが、同じ企業内でみてみるとバラツキが小さい。つまり、『優秀なプログラマはある特定の企業に偏在し、そうでないプログラマも別の企業に偏って分布する』ということ。

通常の勤務時間中に、頭を使わなければならない人たちのチームをマネジメントをするのであれば、オフィス環境の整備は必須の仕事。

第9章 オフィス投資を節約すると

オフィスの投資節約効果は、生産性低下に影響することを考慮する。

オフィス投資と生産性は1:20という比率になるというデータがある。つまり、オフィス投資を100円すると、エンジニアに対して2000円投資したということになる。オフィス投資をケチることが、以下に危険がわかる。

エンジニアが効率よく仕事をするには、広くて静かな場所 が必要。

第10章 頭脳労働時間 対 肉体労働時間

マネジメント業務は割り込みだらけの仕事だが、部下の仕事はフロー状態*1が不可欠。

精神集中を妨げるもの(電話とか、上司や同僚からの話しかけとか)は、プログラマの生産性を低下し、やる気を失わせるので注意。

『今は集中時間!!』ということを外から見てわかるようにするという手も有効。(赤いバンダナ)

第11章 電話、電話、また電話

電話の割り込みで生産性が低下する。最低でも、フロー状態の時には『声をかけられない』ようにするという、寛容*2で効率的*3な企業文化を作り出すのが理想。難しいが、、、

せめて、電話じゃなくてメールにするなどしてあげよう。。

第12章 ドアの復権

オフィスを華やかにする必要はない。ただ、『邪魔の入らないオフィス環境』を整備する。

  • 静かで十分なスペース
  • チーム単位で仕切りを作り、集中できるエリアを作る

第13章 オフィス環境進化論

社員がやる気を出して気持ちよく働け、生産性が高いオフィスは次のようなもの。

  • チームには共有のスペース
  • 何人かが一緒に仕事ができる準個人用スペース
  • 騒音や外からの邪魔から隔離された個人用スペース
  • 窓のある場所で作業できる環境(外が見えるってのは、生産性や退職者数減少ということにも寄与)
  • 屋外、屋内で作業場所を選べる(賃貸なら実現可能でしょ?)

オフィス空間は、その中で進行している作業内容と共に変化し、進化する。どんな職種であっても、オフィスに自分の『匂い』をつけるのは大切。無個性なところは、働きにくい。

マネージャーの役割は、上記のような整った環境をプログラマに用意してあげること。自社内で確保できないなら、社外に移せないかも提案してみる。それで退職率が下がるなら、マネージャーとしては大きな仕事をしたことになる。

第Ⅲ部 人材を揃える

マネージャーは戦略家でも戦術家でもない。次の原則に従おう。

  • 人材を揃える
  • 人々に満足感を与え、やめないようにする
  • 人々を束縛から解放する

第14章 ホーンブロワー因子

人はそれまでの時間で人間形成がなされる。チーム内で成長を促すのはなかなか大変。だから、最初に人材を揃えるのが、とても大事。

人材選びでは、画一性に注意。他の社員と同じような見かけ、考え方、喋り方の人物は良い印象に見えることが多いが、外見で判断してはダメ。

なぜなら、同じような話や考え方しかできなくなるから。

自部門のエントロピー*4 をかく乱し、社内標準からかけ離れたとしても、適切な人材を集め、その人物たちに本来の力を発揮させることが、マネージャーの仕事と言える。

第15章 リーダーシップについて話そう

リーダーシップは奉仕の精神。主要な役割は、チームの指揮官ではなく触媒。権威を与えられなくてもリードできる性格が重要。

触媒となるためには、次のことを満たす必要がある。

  • 自ら仕事を引き受ける
  • 明らかにその仕事に向いている
  • あらかじめ必要な準備を済ませ、万全の態勢で仕事に向かう
  • 全員に最大限の価値を与える
  • ユーモアと明らかな善意のものに事にあたる

、、、ハードル高ぇ😅

第16章 ジャグラーを雇う

採用において、本当の実力がわかるということはとても重要。だからポートフォリオが大切。

適性検査は社員の自己評価には適しているが、採用には向いていない。理由は、適正で測れるのは入社数年程度で発揮できる能力であることが多い(昇進後のマネジメント力などは測れない)。

採用面接では、組織の業務と密接に関係のあるテーマでトークする。トーク内容がなんでも良い場合、面接者の得意分野の話で、不必要に良い評価を下してしまう。

第17章 他者とうまくやっていく

人材のダイバーシティは大事。多様性のあるチームの方が、新しいことを吸収しやすい。(仕事のやり方、思考体系、コミュニケーション等)

と言っても、人数が多かったり(三十人を超える)、人材が流動だと馴染むのは困難を極める。

第18章 幼年期の終わり

若い新しい時代についていく。環境*5にいるのではなく、テクノロジー*6についてく。

現代でいうと、PC、スマホ、Web、プログラミング、SNS、ブログなどは『環境』であり、『テクノロジー』ではない。

第19章 ここにいるのが楽しい

会社にとって、退職は明らかに無駄な出費。

辞める理由の大体は次のもの。

名称 内容
腰掛けメンタリティ この仕事を続けようという雰囲気を同僚が示さない。そして、退職が伝染する
使い捨てにされる予感 経営者が社員を交換できる部品としか考えていない
会社への忠誠心なんて馬鹿ばしい、と言う意識 人を部品としか思わない組織に誰が愛着を持つのか

また、会社の移転などは最悪の典型。自己中な経営者ほど、会社移転の執念が高い。会社の移転は、家族環境にも影響を与えるため、本当に社員のことを思っている経営者なら実践しない。

退職率が低い会社では、『本気でベストになろう』と努力している。全員がそう思うことで、強力な結束効果が生まれる。

また、そうした会社に共通するのは、生涯教育プログラムの充実であり、社員に新たな能力が必要なら、会社がそうしたものを身につけさせてくれる。よって、こうした会社では、仕事に行き止まりがない。

第20章 人的資産

経費とは『使って消えた金』で、投資は『資産を買うために別の資産に使う金』のことをいう。支出を 経費 ではなく 投資 として扱うことを、「支出を資産勘定に計上する」と言う。

社員への教育は、投資と同じで消えてなくならない 人的資本 と言える。これが、企業の将来に最も影響を与える重要な要素になっている。

第Ⅳ部  生産性の高いチームを育てる

挑戦的な仕事は、チームのメンバーに一緒になって努力する目標を与えるから重要。挑戦は、チームを一つにまとめる道具と言える。

人は、チームが一体となった時により良い仕事をするし、いっそう楽しいと感じる。

第Ⅳ部では、そんなチームがどんなものか、どうすれば形成できるかを学ぶ。

第21章 全体は部分の和より大なり

個人プレーよりもチームで成し遂げる成果の方が大きい。そのためには結束したチームを作る必要がある。

結束したチームを作るために、チーム内での目標統一は大切。チームは目標を達成すべく最大限の力を発揮するから。

そして、組織の目標は、組織のために働く人(社員)によって絶え間なく吟味されているが、大抵適当なものだと見られている。それでも目標は大切。

チーム編成の目的は、 目標を達成することではなく、目標を一致させること であり、目標を達成した時、チームは最大限の力を発揮している。

第22章 ブラックチームの伝説

ある目的のもとに集められたチームは、そのためだけに行動し、成長していく。チームとして、同じ方向を向いて仕事ができるため、結束した強いチームが形成される。

第23章 チーム殺し、7つの秘訣

チーム育成は農業と同じ。土を肥やして、種を蒔いて、水をあげて見守る。チーム育成成功のは秘訣ないが、チーム殺しに秘訣がある。

項目 内容
守りのマネジメント 部下を信頼しタスクを投げ、部下のやり方で自主的に進めさせる。信頼されていないと感じさせることは、助け合ってチームを結束させることに関心を示さない。
官僚主義 ペーパーワーク*7は人間の能力の浪費。ペーパーワークでは目標が設定されていたとしても、自分が成功のために必死にやっているとは感じられない。
作業場所の分散 グループの文化を形成するために、チームメンバーは近い場所に配置する。同じチームであれば、同時に静かに仕事をすることが多いため、フロー状態の中断の数も少ない。
時間の細分化 時間の細分化はチーム形成に良くない。人が覚えていられる他人とのやりとりには限りがある。複数の結束したチームに同時に身を置くことはできない。一人の人間に割り当てる仕事は、同時に一つを意識する。
製品品質の削減 自分の能力以下の製品作りを強制することは、プログラマの自負や喜びを傷つける。
はったりの納期 厳しいが不可能でない納期は決起剤となるが、はったりの納期*8は、『はいはい、出ました。いつものですね。絶対間に合わないです』と言う感じでチームのモチベーションを落とす。
チーム解体の方針 ある仕事から別の仕事に移る時に、チームを解体する方針の会社もある。会社が自らチームを壊す。アホらしい。

第24章 続、チーム殺し

チームの動機付けは、チームで設定することであって、会社が設定するものではない。動機付けのためのアクセサリー(会社から、会社のスローガンが入った盾など)を受け取っても、社員のモチベーションは上がらない。

チームには特色があり、それによって各チームが高い標準を採用する。会社によって設定されるものが、チームに響くものではないし、改善を促すものではない。

また、チーム内の負担の偏りが、何ヶ月も続くような状況ではチームは崩壊する。例えば、育休メンバーをフォローするために、他のメンバーに負担(残業)が継続する場合、結局のところフォローした社員の余暇が失われ、不満が溜まる。そして、痛みを分担しない人たちはチームから疎遠になり、チームは崩壊する。

たくさんの残業は生産性を下げるやり方ではあるが、あくまでも納期に間に合わなかった時の保険(残業して頑張ったけどダメだった)となるために実行されることが多い。

第25章 競争

競争ではなく、共同作業の経験を与える。仕事の中で教え教えられの文化を作る。現在は仕事の幅がかなり広くなっており、全てを知っているエキスパートはいない。それぞれが教師側になれる。そうした関係は、チームの結束力を高める。

競争は上の関係を壊す。教えられる側が下にみられる関係となり、相互の関係が生まれにくくなるため。

マネージャーの下記の行動は、競争を煽りチーム殺しに直結するので注意。

  • 年次の給与見直し、メリットレビュー
  • 目標管理
  • 大きな業績を残した社員の表彰
  • 成績に密着した表彰、賞、ボーナス
  • あらゆる形態の能力評価

第26章 スパゲッティディナーの効果

小さくて簡単な共同作業の積み重ねが、チームの結束につながる。チームを全体として成功させるために、小さな機会(パイロットプロジェクト、デモ・シミュレーション)をちょこちょこ提供する。

成功の秘訣は、『マネジメント』などないかのように、チームが和やかに一致団結して働くようにする。最良の上司は、管理されていることを部下に気付かせず、そんなやり方を繰り返しできる人。

第27章 裃*9を脱ぐ

チームをうまく結束させるマネージャーに共通する特性は次のもの。

  • やる気のある電話

    自尊心の高まる仕事を割り当てる。そういう仕事の割り当ては、社員の能力を認め、社員に自主性と責任を与えることになる。

    社員が自尊心の高まる仕事に責任を持つと、チームの一人ひとりは仕事をこなすだけなく、チーム内に信頼関係が報われると言うことを感じることができ、チーム形成に最も効果がある。

  • 缶詰作戦

    パーキンソン式ロボット*10にならない。

    人並みの部下であれば、部下の仕事を邪魔してイライラさせない以上のことはない。彼らのきちんと働いたかどうかは、彼らの成果によって判断すればよい。

  • 規則は破るためにある

    冷静な判断に基づく不服従は許す。どの地位の人も、どんな不服従が許されるかは知っている。それを許すことで、部下はマネージャーに対して意見できる関係になる。

  • 唇のあるチキン

    担当者レベルの人もチーム選択に意見が言えるのは大事。新プロジェクトのメンバー募集をかけたり、採用オーディションで一緒に働くメンバを選んだりといったことができるとよい。チームには特色があり、それを自分たちで形成することで、結束しやすいチームになる。

  • 誰が本当の責任者?

    職人の師匠と弟子の関係のように、自然に備わった権威でマネジメントする。権威によらないものは、侮辱やヤル気を失わせたり、同僚の職人との結束を不可能にするものではない。

    それぞれの担当分野で自然の権威として、相互に信頼された関係でこそ、チームの結束力を固める機会が高まる。

第28章 チーム形成の不思議な化学反応

化学反応のような作用で、固く結束したチームが生まれる会社もある。そうした健全な会社にする化学反応を生み出すための要素は次のもの。

  • 品質至上主義を作り出す

    『完全な製品だけを求める』というチームの気風は、チームが1つにまとまる可能性が高くなる。目標が明確だから。

  • 満足感を与える打ち上げをたくさん用意する

    チームメンバーには、共に成功し、それを喜ぶ癖をつけることが必要。チームに勢いと弾みをつけるメカニズムの一つ。打ち上げの頻度を上げるために、内部確認用としてのリリースバージョンを細かくするのも手。

  • エリート感覚を醸成する

    メンバーがエリート意識を共有すると、チーム自体もエリート性を帯び始める。そのエリート性を誇りに持ち、チームは固く結束する。結束したチームは、人を生産的にし目的意識を高くする効果がある。

  • チームに異分子を混ぜることを奨励する

    異質な人は、チームメンバに『クローンのような人間でなく、型にはまったプラスチック人間のように会社の型にはまらなくていい』という重要な象徴性を示す。チームには色が必要。

  • 成功しているチームを守り、維持する

    ヤンキースを解散させないのと同じ。結束したチームは解散させずに、新しいプロジェクトを選択できるようにする余地を与えるべき。

  • 戦術ではなく戦略を与える

    チームはネットワーク構造であり、階層構造ではない。各人が力を発揮できる分野で、時に応じてリーダーシップを発揮するようなチームで、恒久的なリーダーは存在しない。

第Ⅳ部 肥沃な土壌

プロジェクトやチームは、企業文化というコンテキストの中に存在する。企業文化の中には、健全なものもあればクソなものもある。

本章では、優れた企業文化とはどういったものかを学ぶ。

第29章 自己修復システム

自己修復できるシステムにする。

働き方の多くはメソドロジー*11として管理されることが多い。

そうした場合、決定を下すのはメソドロジーとなり、人は何も判断しなくなる。そうすると自己修復機能を失い、作業者たちは自分達にとって全く意味をなさないことをやる方向に進んでいく傾向がある。

また、メソドロジーは作業を固定的な型に押し込めようとするため、次のような問題が起きる。

  • 書類書きの泥沼化
  • 手法の不足(最良の手法にアンテナを貼らなくなる)
  • 責任感念の欠如
  • 全般的な意欲の低下

メソドロジーの効果は「方法がひとつにまとまる」ことである。メソドロジー以外にも、その目的を果たせることがあるため、実際に成功した実証事例となるまでは、下記のような方法で探索するのも手。

  • 教育研修
  • ツール
  • ピアレビュー

生産性が向上するのはホーソン効果*12によるところが大きい。

ホーソン効果を活用するには、標準的でないアプローチを使う必要がある。

標準は、どんなものでも簡潔で穏やかなものにすべき。

第30章 リスクとダンスを

リスクから逃げない。

本当に価値があるものの、リスクが全くないプロジェクトはない。そうしたプロジェクトは、すでに何年も前にやり尽くされている。

したがって、今日の重要なプロジェクトはリスクを抱えている。

よってリスク管理は大切。その中でも、自分自身が失敗するリスクを考えるのがポイント。 「完成が間に合わない」と言うリスクを管理することで、そうなったときのリスク緩和策の立案をより早い段階に考えることができる。

第31章 会議、ひとりごと、対話

目的とポイントのある会議にする。

組織が成熟すると、業務時間の中の会議が占める割合が増える。それも,儀式的な会議*13で。

何かを目的とする会議は、意味のある会議で、それ以外は意味のない会議と言える。

意味のある会議では、参加者を利害関係者に絞る。

儀式的な会議では、可能な限り削減し、削減した分を一対一の対話にする*14

オープンスペースでのネットワーキング*15も有効な手。

第32章 マネジメントの究極の罪

究極の罪は、人の時間を浪費すること。

浪費にはいくつかタイプがある。

  • 儀式的な会議

    キーパーソンと話すだけなら一対一でいい。出席者全員が何らかの問題を一緒に討議するときが有効な会議と言える。

  • 早期の過剰人員

    ソフトウエア開発では、計画と基本設計から始まるが、このフェーズは少人数の方がうまく行く。人員投入のタイミングは、ある程度見通しが立ってからの方がよい。

    納期が短縮される場合、短縮をカバーしようと早期の投入が行われるが、手が空いてしまう人が多くなってしまう。

  • 続、細分化

    タスクを細分化し、いろんな種類の仕事を1人に任せるのば良くない。種類が異なるタスクを着手する際に、頭の切り替えのオーバーヘッドがでて、ゾーン状態が解除されやすくなる。結果、思考力の必要な作業が進みにくくなる。そして、エンジニアはイライラしはじめる。

第33章 E(悪い)メール

人生は短い。何かをするために全てのことを知らなければならないのなら、大したことはできない。

社内スパム(受け取った側が知る必要があるかと言うと言う問いにノーとなるもの)は、受け取りでの時間を浪費する。

なぜ社内スパムの洪水の原因は、『沈黙は同意』という不文律が働いていること。 同意を得たいがために、いろんな人をCCに入れる。返事がなければ同意とみなす。

こうした状況が、社内スパムを産む。

まずは、送るメールが社内スパムではないかを確認してから送信しよう。

第34章 変化を可能にする

image.png

変化には絶対に混乱が必要。変化は、失敗(少なくともちょっとした失敗)が許される場合のみ、成功の可能性がある。 失敗が許されないのであれば、混乱を経る変化は成し遂げられない。

変化への基本的な反応は、論理的なものではなく情緒的なものなので、変化を助ける方法は、古い方法を讃えることが有効。

第35章 組織の学習能力

学習は極めて重要な改善メカニズムであり、学習しないものに将来の繁栄はない。

企業の姿勢が、経験から学ぶ姿勢に変化するとき、経験は学習となり定着する。経験フェーズから学習フェーズに変化するのには、次の形態がある。

  • 組織内に新しいスキルやアプローチ技術を徐々に浸透させる
  • 現行業務を別の方式で処理するため、組織自体の再編成を行う

どちらも、自己変革中の組織には、次の絶対的に避けられないリスクがある。組織の学習能力は、組織がどの程度人を引き止められるかで決まる

第36章 コミュニティの形成

人間には、コミュニティを必要とするという属性が備わっていると考えられている。優れたマネージャーの得意なことに、コミュニティ作りがある。

満足のいくコミュニティがある組織は、退職者が出にくい。コミュニティ意識が強くなると、そこから出て行こうとしなくなるため。さらに、そこで会社が人的投資を増やすと、社員の能力が一層向上し、自分をよく感じ、会社に好感情を持つようになり、別の会社へ移る可能性がさらに低くなる。

じゃあ、どうやってコミュニティを作るかについてだが秘策はない。コミュニティの醸成には、能力、勇気、創造性、時間が必要。また、一人で成し遂げることも難しい。触媒的な働きをして、徐々に形成していくしかない。

第Ⅵ部 きっとそこは楽しいところ

仕事は楽しくあるべきだ

第37章 混乱と秩序

混乱状態から整然とした制御可能な方法を目指す進歩のほうが面白い。その進歩によって、混乱は整理されるが、そういう意味では秩序のあるものは面白みがないとも言える。

そこで、次のような、小さな混乱の建設的な導入を取りれるのも良い。

項目 内容
パイロットプログラム 新しく効果が証明されていないことを試す機会。新しいことをやることでホーソン効果も現れる。効果測定しにくくなるため、パイロットプログラムでは、一つだけ新技術を採用し、それ以外では標準を守る。
プログラミングコンテスト、実践さながらの訓練 チーム形式のコンテストにして、結束を高めるきっかけを提供する。コンテスト中は、コンテストに集中できるようにする。成功体験のため、誰もが賞を得られるようにする。
ブレーンストーミング イデアの質ではなく量で出させる。出が悪なったら、類似や逆、熟考などをする。
教育、旅行、学会、お祭り、冒険体験 仲間と一緒に同じ体験をする。冒険や馬鹿馬鹿しさ、少量の建設的混乱も望ましい要素と言える。

第38章 自由電子

自由電子*16を目指そう。

そのために、上から降りてくるどんな指示よりも、自分自身の指示が会社の最善の利益に叶うというレベルを目指す。

第39章 眠れる巨人よ、目を覚ませ

まずは、一つだけ実践して、変化を起こそう。変化を起こす際には、周囲で同じように感じている理性的だが、堪忍袋の尾が切れかかった人たちを焚きつけ、変化を渦を大きくしよう。

参考

*1:一つのことに没頭し、ほとんど瞑想状態になること

*2:かかってきた電話を無視できる

*3:そもそも電話がかかってこない

*4:一様、均質

*5:成長期に既にあるもの

*6:成長期になかったもの

*7:頭を使わずに機械的にドキュメントを作ること

*8:不可能な納期

*9:四角ばった態度をやめて、相手に対して打ち解ける

*10:部下が働いているところを歩き回り、サボったりしていないかに眼を光らせる

*11:あらゆる頭脳集約型作業をどのように進めるかについての一般的なシステム理論。あるゆる作業を標準化したもの

*12:人は何か新しいことをやろうとするの時に、より良い成績を収める

*13:時計によって終わり、特定の何かを決定する会議ではない

*14:儀式の中の対話の多くは一対一であることがほとんど

*15:会議はなく、指定時間に指定場所で、コーヒーを片手に自由に対話したい人と対話する

*16:やる気を持って、自分自身のために働いた結果が、会社の利益につながる仕事師

アジャイル開発って何?

いちばんやさしいアジャイル開発の教本を読んだので、大事だなと思ったことをまとめておく。

大雑把には、『アジャイルの概要』を学べる。いくつか読んだアジャイル本(アジャイルサムライ, SCRUM BOOT CAMP THE BOOK)の中では、一番読みやすい本な気がした。

アジャイル開発とは

短い期間でリリースし、フィートバックをして、改善するというサイクルを基本として進める開発手法。

一度リリースして終わりではなく、リリースしたソフトウェアに対するユーザのフィードバック自体が開発サイクルに含まれる点が、アジャイル開発の原則であり特徴。

要求と要件

ソフトウェア開発は、『要求』からスタートする。要求は次のような方法で決めていくことが多い。

  • 顧客から提示される
  • 開発インサイト(ユーザ自身が気づいていない本音や動機を引き出し、サービス利用に引き込む)により開発側で要求を見つけ出す

要件は、要求を実現するために必要な機能、性能のこという。要件には大きく次の2種類がある。

項目 意味
機能要件 主目的に関する要件
非機能要件 性能や品質、セキュリティ等の主目的以外の要件

アジャイル開発においても、要件定義は重要な工程になる。

なぜアジャイル開発なのか

ウォーターフォールアジャイルでも、要件定義や設計は行い、プロセスの共通部分は多い。

違いとしては、ウォーターフォールは、各工程が滝が流れるように進み、各工程の成果物を完成品とみなし、あと工程での手戻りが発生しないように進める。

一方、ジャイルでは反復的に開発が進み、スプリントごとに動くソフトウェアが作られ、次のスプリのではそのソフトウェアから得られた気づきを基に要件レベルから見直される。

ユーザが本当に欲しいものは『潜在的なニーズ』であることが多いため、実際に製品を使ってみるまでは、本音が得られることが少ない。

そうした状況において、ウォーターフォールでは全部できてからでないと製品を使えず最後になって、『なんか、これじゃない、、、😱』ということになってしまう。

アジャイルでは、小さいサイクルで動くものを少しずつ作っていき、都度ユーザにフィードバックしてもらい開発を進めるため、最終的にユーザの求めた機能が提供できる。

また、いくつかの要件を形にした後で、新しい要件に気づいたり、実装を進める中でより良い設計を思いついたりした場合にも、アジャイルではフィードバックで取り入れることが可能。

アジャイル開発のコア

3つのコンセプト

コンセプト 意味
チーム 自己組織化*1された職能横断的*2なチーム
インクリメンタル 少しずつ作る
イテレーティブ 反復的に作る

アジャイル開発では、プロダクトを少しずつ繰り返し的に進めるために、『作成すべきもの』を一覧で管理する。一覧はその並び順を開発の優先順とする。

image.png 例:プロダクトバックログ項目の明確化の必要性 | Ryuzee.com

上記の一覧で管理する場合、各項目一つひとつが明確で、かつ他の項目と依存しないことが望ましい。理由は、項目同士の依存度が高いと、順番が入れにくくなるため。よって、各項目は、十分に小さい単位にすることが望ましい。

各項目を小さくすることの弊害としては、全体として何を目指していたかが見えにくくなる点。そこへの対応としては、ユーザ視点で全体像を把握する、次の方法がある。

参考

*1:自分達自身で物事を決定し、自律的に動ける

*2:複数の専門性を有し、ソフトウェアを完成させられる能力を有す

Rubyで学ぶデザインパターン

Factory メソッド

インスタンス生成をサブクラスに任せるデザインパターンインスタンスの生成部分を切り離すことで、結合度を下げて追加、変更、保守を容易にする。

例えば、次の楽器クラスがあるとする。

image.png

今後も楽器の種類が増えることが考えら、楽器インスタンスを生成するインタフェースが統一されていると、このクラスを利用するユーザは使いやすいし、保守もしやすくなる。

Factoryメソッドを次のように導入する。インスタンス化する際には、派生クラスで initialize メソッドを呼び出し、スーパクラスの initialize メソッドでは派生クラスの new メソッドを呼び出す。

そうすることで、Factoryクラスの各インスタンスに応じて生成するクラスを切り替えられる。

image.png

実行するときには、次のようにする

# サックスのファクトリーメソッド
factory = SaxophoneFactory.new(3)
saxophones = factory.ship_out

# トランペットのファクトリーメソッド
factory = TrumpetFactory.new(2)
trumpets = factory.ship_out

Abstract Factory メソッド

異なる組み合わせ を持った複数のオブジェクトをひと塊に生成するために用いる。

Abstract Factory メソッドの旨味は次の2点。

  • 関連し合うオブジェクトの集まりを生成できる
  • 整合性が必要なオブジェクト群を集めて生成できる

これだけだとよくわからんので、次のようなクラスがあるとする。

image.png

上記クラスを用いて開発することになったとする。その時の制約として、次のものがあるとする。

上記を守るように実装する際に、各クラスをそれぞれインスタンス化して、お互いが関係するように実装することもできるが、中々面倒。

そこで、AbstractFactory メソッドで、複数のオブジェクトをひと塊にして生成できるようにする。メソッドのイメージとしては次のような感じ。

image.png

インスタンス生成時には、スーパクラスの initialise メソッドを呼び出し、スーパクラスの initialize メソッドの中でインスタンス化を行う。そして、インスタンス化で呼び出される new メソッドでは、派生クラスの中で定義したものを用いる。

そうすることで、組み合わせを守ったオブジェクトを生成できる。

実行するときには、次のようなコードになる。

# カエルと藻のAbstractFactory
factory = FrogAndAlgaeFactory.new(4,1)
animals = factory.get_animals
animals.each { |animal| animal.eat }
plants = factory.get_plants
plants.each { |plant| plant.grow }

# アヒルとスイレンのAbstractFactory
factory = DuckAndWaterLilyFactory.new(3,2)
animals = factory.get_animals
animals.each { |animal| animal.eat }
plants = factory.get_plants
plants.each { |plant| plant.grow }

ビルダ(Builder) パターン

ビルダパターンは次の場面で使われる。

  • オブジェクト生成に大量のコードが必要(生成するインスタンスのプロパティが多い)
  • オブジェクトの生成が難しい
  • オブジェクト生成時に必要なチェックを行う

上記のために、ビルダパターンでは、『Director:作業過程を決定』と『Builder:作業インタフェースを持つ』を組み合わせて、柔軟にオブジェクトを生成するデザインパターンとなっている。

ビルダパターンには3つの構成要素がある。

役割名 内容
Director(ディレクタ) ビルダで提供されているインタフェースのみを使用して処理を行う
Builder(ビルダ) 各メソッドのインタフェースを定める
ConcreateBuilder(具体ビルダ) Builderが定めたインタフェースの実装

上だけだとよくわからないので、ここでもサンプルを考える。

加工水(塩水と砂糖水)を作る処理を考える。まずは実際の工程を考えてみると、必要な要素と作業に分割できそう。

項目 内容
要素 水、素材(砂糖 or 塩)
作業 水を加える、素材を加える

加工水を作るのに必要なモノと作業は揃った。あとはこれを実施する作業者がいれば加工水の生成が可能になる。つまり、オブジェクトが作れるようになったということ。

上記の例を、ビルダパターンに当てはめると、『要素:具体ビルダ』、『作業:ビルダ』、『作業者:ディレクタ』という関係になる。

要するに、オブジェクトの生成に必要な作業は、ビルダにインタフェースとして用意し、ディレクタで何をするかを決定していく。

そして、出来上がるオブジェクトは具体ビルダによって生成されるオブジェクトであり、それ自体の構成は、ディレクタが決定した作業(ビルダのインタフェースの組み合わせ)により決まる。

こうしておくことのメリットは、 ディレクタはインタフェース(作業)だけ知っていればよく、Builderではインタフェースを担保していれば良いので、すげ替えなどが容易 に行える。

クラス図に置き換えてみる。

image.png

ディレクタはビルダーのインタフェースを参照し、具体ビルダではビルダのインタフェースを実装する。

ソースコードで示すと次のような感じになる。

  • 具体ビルダ
# 塩水クラス (ConcreteBuilder:ビルダーの実装部分)
class SaltWater
  attr_accessor :water, :salt
  def initialize(water, salt)
    @water = water
    @salt = salt
  end

# 素材(塩)を加える
  def add_material(salt_amount)
    @salt += salt_amount
  end
end

# 砂糖水クラス (ConcreteBuilder:ビルダーの実装部分)
class SugarWater
  attr_accessor :water, :sugar
  def initialize(water, sugar)
    @water = water
    @sugar = sugar
  end

  # 素材(砂糖)を加える
  def add_material(sugar_amount)
    @sugar += sugar_amount
  end
end
  • ビルダ
# 加工した水を生成するためのインターフェイス(Builder)
class WaterWithMaterialBuilder
  def initialize(class_name)
    @water_with_material = class_name.new(0,0)
  end

  # 素材を入れる
  def add_material(material_amount)
    @water_with_material.add_material(material_amount)
  end

  # 水を加える
  def add_water(water_amount)
    @water_with_material.water += water_amount
  end

  # 加工水の状態を返す
  def result
    @water_with_material
  end
end
  • ディレクタ
# 加工水の作成過程を取り決める
class Director
  def initialize(builder)
    @builder = builder
  end

  def cook
    @builder.add_water(150)
    @builder.add_material(90)
    @builder.add_water(300)
    @builder.add_material(35)
  end
end

実際にオブジェクトを生成するときには、次のようにする。

# 砂糖水を作る
builder = WaterWithMaterialBuilder.new(SugarWater)
director = Director.new(builder)
director.cook

# 塩水を作る
builder = WaterWithMaterialBuilder.new(SaltWater)
director = Director.new(builder)
director.cook

シングルトンパターン

シングルトンパターンは、あるクラスのインスタンスが一つしかない状態を保証する方法。

例えば、次のようなリソース管理で用いるのが適切と考えられている。

  • ログの書き込み処理を行うメソッドでのファイルアクセス
  • システム内の共通のキャッシュテーブルを参照する場合
  • etc.

シングルトンパターンに則ったクラスの条件は次の3点。

Ruby でシングルトンパターンを実装する時には、 クラスに Singleton モジュールを Mix-in して利用する。

sigleton モジュールを Mix-in すると、new メソッドは プライベートになる。クラス図にすると次のような感じ。

image.png

上記のクラスを実装してみる。

# SingletonはMix-inしたクラスの同一のインスタンスを返す
require 'singleton'
# シングルトン
class SingletonObject
  # instanceメソッドが定義され、newメソッドがprivateに
  include Singleton
  attr_accessor :counter

  def initialize
    @counter = 0
  end
end

上のクラスを使ってみる。

obj1 = SingletonObject.instance
obj1.counter += 1
puts(obj1.counter) #=> 1

obj2 = SingletonObject.instance # 同一のインスタンスが返る
obj2.counter += 1
puts(obj2.counter)
# => 2 ⇦ 前回の結果が引き継がれている

また、シングルトンの条件として、『パブリックのコンストラクタを持たない』があるので、試しに new メソッドでインスタンス生成してみるとエラーになる。

obj3 = SingletonObject.new
# private method `new' called for SingletonObject:Class (NoMethodError)

アダプタ

アダプタは、現実世界の変換コネクタのようなもの。直接繋がらないコネクタと差し込み口は、変換コネクタを仲介として結びつける。つまり、クラスの型を整える『ラッパー』のようなもの。

アダプタが利用されるシーンは次のようなもの。

  • 関連性・互換性のないオブジェクト同士を結びつけたい
  • 他のコンポーネントへの変更ができるようにしたい

アダプタの構成要素は次の4種類。

項目 役割
利用者 ターゲットのメソッドを呼び出す
ターゲット インタフェースを規定
アダプタ アダプティのインタフェースを変換して、ターゲット向けのインタフェースを提供
アダプティ 実際に動作する既存クラス

ここも例の如く、クラス図っぽく書いてみる。

image.png

Client から OldPrinter のメソッドを用いるようにしたい時には、アダプタを用意してそこから呼び出したいメソッドを呼び出す。

まずは、ターゲットを実装。Ruby に Interface はないが、下記のようにオブジェクトを渡して、当該オブジェクトのメソッドを呼び出すという形で Interface を再現。

# 利用者(Client)へのインターフェイス (Target)
class Printer
  def initialize(obj)
    @obj = obj
  end

  def print_weak
    @obj.print_weak
  end

  def print_strong
    @obj.print_strong
  end
end

次に、アダプティを実装。

# Targetにはないインターフェイスを持つ (Adaptee)
class OldPrinter
  def initialize(string)
    @string = string.dup
  end

  # カッコに囲って文字列を表示する
  def show_with_paren
    puts "(#{@string})"
  end

  # アスタリスクで囲って文字列を表示する
  def show_with_aster
    puts "*#{@string}*"
  end
end

最後に、アダプタを実装。アダプタでは、インタフェースのメソッド内に呼び出したいメソッドを実装する。

# Targetが利用できるインターフェイスに変換 (Adapter)
class Adapter
  def initialize(string)
    @old_printer = OldPrinter.new(string)
  end

  def print_weak
    @old_printer.show_with_paren
  end

  def print_strong
    @old_printer.show_with_aster
  end
end

利用者では、Printer クラスのインスタンス生成時に、アダプタを用いてオブジェクト生成して渡す。

アダプタのメソッド内では、アダプティのメソッドが呼ばれるようになっているため、ターゲットのインタフェースで実行するとアダプティのメソッドが呼ばれる。

# 利用者(Client)
p = Printer.new(Adapter.new("Hello"))

# アダプタを通してアダプティのメソッドが呼ばれる
p.print_weaek # => (Hello)
p.print_strong # => *Hello*

コンポジットパターン

コンポジットパターンは、『全体と中身』を同一のものとして捉えることで、再起的な構造をクラスで表現するデザインパターンのこと。

コンポジットは3つの要素からなる。

要素名 役割
コンポーネント 全てのオブジェクトの基底となるクラス
リーフ プロセスの単純な構成要素で再帰しない
コンポジット コンポーネントの一つで、サブコンポーネントを構成

具体的な使い方としては、ディレクトリとフォルダを同様のコンポーネントとして扱うことで、削除処理などを再起的に行えるようにするなどがある。

他にも、ファイルシステムなどの木構造を伴う再起的なデータ構造を表現できたり、階層構造で表現されるオブジェクトの扱いが楽になる。

例として、ファイルシステムを考えてみる。まず、クラス図で全体像を眺める。ディレクトリは、再帰できる(自身の中にファイルとディレクトリをもてる)ので、集約でそれを表現する。

image.png

上記のコードを書く。

まずは、コンポーネントから。コンポーネントで共通メソッドを規定する。

# FileEntry, DirEntryクラスの共通メソッドを規定
class Entry
  def get_name
  end

  def ls_entry(prefix)
  end

  def remove
  end
end

次に、FileEntry クラス。

# Leaf
class FileEntry < Entry
  def initialize(name)
    @name = name
  end

  def get_name
    @name
  end

  def ls_entry(prefix)
    puts(prefix + "/" + get_name)
  end

  def remove
    puts @name + "を削除しました"
  end
end

最後に、DirEntry クラス。ディレクトリ内には、ファイルが複数あることもあるため、メンバ変数に配列がある。

# Composite
class DirEntry < Entry
  def initialize(name)
    @name = name
    @directory = Array.new
  end

  def get_name
    @name
  end

  def add(entry)
    @directory.push(entry)
  end

  def ls_entry(prefix)
    puts(prefix + "/" + get_name)
    @directory.each do |e|
      e.ls_entry(prefix + "/" + @name)
    end
  end

  def remove
    @directory.each do |i|
      i.remove
    end
    puts @name + "を削除しました"
  end
end

上記のコードを使ってみる。

root = DirEntry.new("root")
tmp = DirEntry.new("tmp")      # tmp ディレクトリ生成
tmp.add(FileEntry.new("conf")) # tmp ディレクトリ内にファイル追加①
tmp.add(FileEntry.new("data")) # tmp ディレクトリ内にファイル追加②
root.add(tmp)                  # root ディレクトリに tmp ディレクトリを追加

root.ls_entry("")
# => /root
# => /root/tmp
# => /root/tmp/conf
# => /root/tmp/data

root.remove
# => confを削除しました
# => dataを削除しました
# => tmpを削除しました
# => rootを削除しました

デコレータ

デコレータは、既存オブジェクトに簡単に機能を追加するためのパターン。デコレータパターンを適応することで、レイヤ状に機能を積み重ねて、必要な機能を持つオブジェクトを作れる。

レイヤ状に機能を重ねるので、既存オブジェクトの中身を変更なく機能を追加(デコレート)できる。機能の積み重ねの組み合わせの仕方で、様々な機能を実現できる。

継承に似ているが、継承よりも変更の影響を受けにくい。

デコレータは2つの要素からなる。

要素名 役割
具体コンポーネント ベースとなる処理を持つオブジェクト
デコレータ 追加する機能を持つクラス

『ファイルへの出力機能』を持つクラスで、デコレータの実装を学ぶ。まずは、クラス図を書いてみる。

image.png

上記の通り、基となる ConcreteComponent の機能を、デコレータで表現する。

SimpleWriter クラスを次ようなものとする。

class SimpleWriter
  def initialize(path)
    @file = File.open(path, "w")
  end

  def write_line(line)
    @file.print(line)
    @file.print("\n")
  end

  def pos
    @file.pos
  end

  def rewind
    @file.rewind
  end

def close
    @file.close
  end
end

上記のコードをデコレータで表現する。

class WriterDecorator
  def initialize(real_writer)
    @real_writer = real_writer
  end

  def write_line(line)
    @real_writer.write_line(line)
  end

  def pos
    @real_writer.pos
  end

  def rewind
    @real_writer.rewind
  end

  def close
    @real_writer.close
  end
end

次に修飾を考える。

  • 業務番号出力機能を実現する。

      class NumberingWriter < WriterDecorator
    
        def initialize(real_writer)
          super(real_writer)
          @line_number = 1
        end
    
        def write_line(line)
          @real_writer.write_line("#{@line_number} : #{line}")
        end
      end
    
  • タイムスタンプ出力機能を実装する

      class TimestampingWriter < WriterDecorator
        def write_line(line)
          @real_writer.write_line("#{Time.new} : #{line}")
        end
      end
    

上記を使って、コーディングしてみる。

f = NumberingWriter.new(SimpleWriter.new("file1.txt")) 
f.write_line("Hello out there")
f.close

f = TimestampingWriter.new(SimpleWriter.new("file2.txt"))
f.write_line("Hello out there")
f.close #=> 2012-12-09 12:55:38 +0900 : Hello out there

f = TimestampingWriter.new(NumberingWriter.new(SimpleWriter.new("file3.txt")))
f.write_line("Hello out there")
f.close
# => 2012-12-09 12:55:38 +0900 : Hello out there

上記のように、既存クラス(ConcreteComponent)の実装を変更することなく、機能を自由に組み合わせることが可能。

プロキシ

プロキシパターンは、1つオブジェクトに複数の関心事がある場合に、それを分離したい時に使う。

例えば、オブジェクトの本質的な目的とは異なる非機能用件(セキュリティ要件とか)を切り離して実装できる。

プロキシは2つの要素からなる。

要素名 役割
対象オブジェクト(subject) 本物のオブジェクト
代理サブジェクト(proxy) 特定の「関心事」を担当、それ以外を対象サブジェクトに渡す

イメージ的には、代理人が本人でなくてもできることを処理し、代理人ができない本人が処理するといった、いわゆる『代理人』のように振る舞う。

代理オブジェクトは、対象オブジェクトと同じインターフェースを持ち、利用時は代理オブジェクトを通して対象となるオブジェクトを操作する

サンプルとして、銀行の窓口業務を担当するクラスとユーザ認証を担当するクラスで、『関心事の分離』をするプロキシパターンを学ぶ。

イメージ的には、ユーザ認証担当は、代理人として自身にできること(ユーザ認証)は自分で処理して、できないこと(入金/出金)は対象の窓口担当に回すって感じ。

まずは、クラス図を書いてみる。

image.png

上記のように、クライアントは代理オブジェクトにリクエストを出すことで、銀行内のオブジェクトを操作できる。代理オブジェクトはできることは自分で行い、できないことは対象オブジェクトのメソッドを呼び出し対象オブジェクトに処理させる。

コードは次のような感じ。

# 銀行の入出金業務を行う(対象オブジェクト/subject)
class BankAccount
  attr_reader :balance

  def initialize(balance)
    @balance = balance
  end

  # 出金
  def deposit(amount)
    @balance += amount
  end

  # 入金
  def withdraw(amount)
    @balance -= amount
  end
end

次に代理人オブジェクト。

ポイントは、 initialize で対象オブジェクトを渡している点。

# etcはRubyの標準ライブラリで、/etc に存在するデータベースから情報を得る
# この場合は、ログインユーザー名を取得するために使う
require "etc"

# ユーザーログインを担当する防御Proxy
class BankAccountProxy
  def initialize(real_object, owner_name)
    @real_object = real_object
    @owner_name = owner_name
  end

  def balance
    check_access
    @real_object.balance
  end

  def deposit(amount)
    check_access
    @real_object.deposit(amount)
  end

  def withdraw(amount)
    check_access
    @real_object.withdraw(amount)
  end

  def check_access
    if(Etc.getlogin != @owner_name)
      raise "Illegal access: #{@owner_name} cannot access account."
    end
  end
end

BankAccountProxy にとって、 check_access 以外は自身の主目的でないので、対象オブジェクトのメソッドを呼び出す設計になっている。

で、使う時には、次のように書く。

# ログインユーザーの場合
account = BankAccount.new(100)
proxy = BankAccountProxy.new(account, "goruchan")
puts proxy.deposit(50)  # => 150    <- proxyを通してオブジェウトを操作
puts proxy.withdraw(10) # => 140

# ログインユーザーではない場合
account = BankAccount.new(100)
proxy = BankAccountProxy.new(account, "no_login_user")
puts proxy.deposit(50)
#`check_access': Illegal access: no_login_user cannot access account. (RuntimeError)

proxynew するときに、対象オブジェクトと所有者を引数として渡し、組としている。

コマンド

コマンドは、あるオブジェクトに対してコマンドを送ることで、そのオブジェクトのメソッドを呼び出すこと。

例えば、Linuxコマンドのように、ユーザはファイルシステムの実装を知らなくても、ファイルの追加・削除を実行できるのも、コマンドパターンの一つ。

コマンドパターンは、2つの要素からなる。

要素名 役割
コマンド コマンドのインタフェース
具体コマンド コマンドの具体的な処理

ファイルの作成・削除・コピーができるモデルで、コマンドパターンを考える。

まずはクラス図で書いてみる。

image.png

基本的には、継承先で基底クラスのメソッドをオーバライドする。

コマンドのコードは次のような感じ。

class Command
  attr_reader :description
  def initialize(description)
    @description = description
  end

  def execute
  end

  def undo_execute
  end
end

具体コマンドを実装していく。

require "fileutils"

class CreateFile < Command
  def initialize(path, contents)
    super("Create file : #{path}")
    @path = path
    @contents = contents
  end

  def execute
    f = File.open(@path, "w")
    f.write(@contents)
    f.close
  end

  def undo_execute
    File.delete(@path)
  end
end

class DeleteFile < Command
  def initialize(path)
    super("Delete file : #{path}")
    @path = path
  end

  def execute
    if File.exists?(@path)
      @content = File.read(@path)
    end
    File.delete(@path)
  end

  def undo_execute
    f = File.open(@path, "w")
    f.write(@contents)
    f.close
  end
end

class CopyFile < Command
  def initialize(source, target)
    super("Copy file : #{source} to #{target}")
    @source = source
    @target = target
  end

  def execute
    FileUtils.copy(@source, @target)
  end

  def undo_execute
    File.delete(@target)
    if(@contents)
      f = File.open(@target, "w")
      f.write(@contents)
      f.close
    end
  end
end

class CompositeCommand < Command
  def initialize
    @commands = []
  end

  def add_command(cmd)
    @commands << cmd
  end

  def execute
    @commands.each { |cmd| cmd.execute }
  end

  def undo_execute
    @commands.reverse.each { |cmd| cmd.undo_execute }
  end

  def description
    description = ""
    @commands.each { |cmd| description += cmd.description + "\n"}
    description
  end
end

クラスの準備はこれで完了。あとはこいつを使ってみる。

command_list = CompositeCommand.new
command_list.add_command(CreateFile.new("file1.txt", "hello world\n"))
command_list.add_command(CopyFile.new("file1.txt", "file2.txt"))
command_list.add_command(DeleteFile.new("file1.txt"))

command_list.execute
puts(command_list.description)
# => Create file : file1.txt
# => Copy file : file1.txt to file2.txt
# => Delete file : file1.txt

# 処理を取り消すコマンド
command_list.undo_execute
# => file1のみになっている

インタプリタ

インタプリタは、ひとつひとつの問題はシンプルだが、組み合わさって複雑になるような場合に効果を発揮する。

インタプリタでは、各問題を処理し、その結果を手順に基づいて処理を実行していく。

上だけだと何言ってるかわからないけれど、例えば次のような機能を実装したいとする。

  • ファイルネームが〇〇、かつ、サイズが△△以上のファイル一覧を表示せよ
  • ファイルネームが〇〇、または、サイズが△△以上のファイル一覧を表示せよ
  • ファイルネームが〇〇以外、かつ、サイズが△△以上のファイル一覧を表示せよ

上記の場合、具体的な機能(ファイルネーム検索、サイズ確認)と、それを組み合わせる処理(And,Or,Not)があることで、少し複雑になっていて、まさにインタプリタの効果を発揮するパターンといえる。

インタプリタパターンは、4つの要素からなる。

要素名 役割
抽象表現(AbstractExpression) 共通のインタフェースを定義
終端(TerminalExpression) 終端を表現するクラス(具体的な機能)
終端以外(NonterminalExpression) 非終端を表現するクラス(組み合わせ)
状況、文脈(Context) 構文の解析を手助けする

ファイル検索機能でインタプリタを学んでいく。

とりあえず、クラス図で書いてみる。

image.png

まずは、すべてのファイル検索のベースとなる単純なクラスを用意する。

class Expression
  def |(other)
    Or.new(self, other)
  end

  def &(other)
    And.new(self, other)
  end
end

次に、具体的な機能のクラスを用意

  • すべてのファイル名を取得する All クラス
require "find"

class All < Expression
  def evaluate(dir)
    results= []
    Find.find(dir) do |p|
      next unless File.file?(p)
      results << p
    end
    results
  end
end
  • 与えられたパターンとマッチするすべてのファイル名を返す FileName クラス
class FileName < Expression
  def initialize(pattern)
    @pattern = pattern
  end

  def evaluate(dir)
    results= []
    Find.find(dir) do |p|
      next unless File.file?(p)
      name = File.basename(p)
      results << p if File.fnmatch(@pattern, name)
    end
    results
  end
  • 指定したファイルサイズより大きいファイルを返す Bigger クラス
class Bigger < Expression
  def initialize(size)
    @size = size
  end

  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      results << p if( File.size(p) > @size)
    end
    results
  end
end
  • 書込可能なファイルを返す Writable クラス
class Writable < Expression
  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      results << p if( File.writable?(p) )
    end
    results
  end
end

そして、組み合わせ部分のクラスを用意

  • 書き込みができないファイル検索用の Not クラス
class Not < Expression
  def initialize(expression)
    @expression = expression
  end

  def evaluate(dir)
    All.new.evaluate(dir) - @expression.evaluate(dir)
  end
end
  • ファイル検索式結合用の Or, And クラス
class Or < Expression
  def initialize(expression1, expression2)
    @expression1 = expression1
    @expression2 = expression2
  end

  def evaluate(dir)
    result1 = @expression1.evaluate(dir)
    result2 = @expression2.evaluate(dir)
    (result1 + result2).sort.uniq
  end
end

class And < Expression
  def initialize(expression1, expression2)
    @expression1 = expression1
    @expression2 = expression2
  end

  def evaluate(dir)
    result1 = @expression1.evaluate(dir)
    result2 = @expression2.evaluate(dir)
    (result1 & result2)
  end
end

上のクラスを用いて、ファイル検索をすると、次のような出力になる。

# 具体的な機能(名前検索)を組み合わせ(And)で結合
complex_expression1 = And.new(FileName.new('*.mp3'), FileName.new('big*'))
puts complex_expression1.evaluate('13_test_data')
#=> 13_test_data/big.mp3
#=> 13_test_data/big2.mp3

# 具体的な機能(サイズ検索)だけ
complex_expression2 = Bigger.new(1024)
puts complex_expression2.evaluate('13_test_data')
#=> 13_test_data/big.mp3
#=> 13_test_data/big2.mp3
#=> 13_test_data/subdir/other.mp3

# 具体的な機能(名前検索)で条件に当てはまるオブジェクト生成し、Andで抽出。
complex_expression3 = FileName.new('*.mp3') & FileName.new('big*')
puts complex_expression3.evaluate('13_test_data')
#=> 13_test_data/big.mp3
#=> 13_test_data/big2.mp3

# 具体的な機能(全抽出)だけ
complex_expression4 = All.new
puts complex_expression4.evaluate('13_test_data')
#=> 13_test_data/big.mp3
#=> 13_test_data/big2.mp3
#=> 13_test_data/small.mp3
#=> 13_test_data/small1.txt
#=> 13_test_data/small2.txt
#=> 13_test_data/subdir/other.mp3
#=> 13_test_data/subdir/small.jpg

イテレータ

イテレータは次のような場合に用いる。

  • 要素の集まったオブジェクト(配列等)にアクセス
  • 集合の要素に順番にアクセスする必要がある

つまり、イテレータパターンでは、要素の集まりを保有するオブジェクトの各要素に、順番にアクセスする方法を提供するパターンといえる。

イテレータパターンには、内部イテレータと外部イテレータがある。

イテレータ 概要
内部イテレータ コードブロックベースのイテレータeach メソッドがこれに相当する
外部イテレータ コードブロック外のクラスとして用意したイテレータ

ブログ内の記事にアクセスする方法で、外部イテレータについて学んでいく。

とりあえず、クラス図で書いてみる。

image.png

外部イテレータは、ブログ(集約オブジェクト)の要素(記事)にアクセスするためのパターン。

まずは、要素の記事のクラスを書く。

class Article
  def initialize(title)
    @title = title
  end

  attr_reader :title
end

次に、要素のまとまりであるブログクラスを書く。

class Blog
  def initialize
    @articles = []
  end

  def get_article_at(index)
    @articles[index]
  end

  def add_article(article)
    @articles << article
  end

  def length
    @articles.length
  end

  def iterator
    BlogIterator.new(self)
  end
end

最後に、このまとまりを操作するイテレータを用意する。

class BlogIterator
  def initialize(blog)
    @blog = blog
    @index = 0
  end

  def has_next?
    @index < @blog.length
  end

  def next_article
    article = self.has_next? ? @blog.get_article_at(@index) : nil
    @index = @index + 1
    article
  end
end

上記の動作を確認する。

blog = Blog.new
blog.add_article(Article.new("デザインパターン1"))
blog.add_article(Article.new("デザインパターン2"))
blog.add_article(Article.new("デザインパターン3"))

iterator = blog.iterator
while iterator.has_next?
  article = iterator.next_article
  puts article.title
end
#=> デザインパターン1
#=> デザインパターン2
#=> デザインパターン3

オブザーバ

オブザーバは、次の条件を満たす場合に用いる。

  • オブジェクトの状態が変化する可能性がある
  • 変化したことを他のオブジェクトに通知する必要がある

例えば、新幹線の予約者が予約を解除したいときに、予約キャンセルを鉄道会社に連絡するみたいな状況。

上の例の通り、オブザーバは、あるオブジェクトの状態が変化した際に、オブジェクト自身が『観察者』に『通知』する仕組み。これにより、観察者は、常に観察しなければならない状態から解放される。

オブザーバパターンは、3つのオブジェクトからなる。

オブジェクト名 役割
サブジェクト(subject) 変化する側のオブジェクト(変化を報告する側)
オブザーバ(Observer) 状態の変化を関連するオブジェクトに通知するインタフェース
具象オブザーバ(ConcreteObserver) 状態の変化に関連して具体的な処理を行う(報告を受ける側)

従業員の小切手、税金請求書発行の処理で、オブザーバについて学んでいく。

とりあえず、クラス図で書いてみる。

image.png

まずは、サブジェクトのコードを書く。

require 'observer'
class Employee
  include Observable
  attr_reader :name, :title, :salary

  def initialize(name, title, salary)
    @name = name
    @title = title
    @salary = salary
    add_observer(Payroll.new)
    add_observer(TaxMan.new)
  end

  def salary=(new_salary)
    @salary = new_salary
    changed
    notify_observers(self)
  end
end

ruby には、observable と呼ばれるオブザーブとしての機能を持つ標準モジュールがあるため、組み込む。

changed メソッドで更新フラグを立て、add_observer で追加したオブザーブに対して、notify_observers メソッドで更新通知を出す。

次に、小切手発行と、税金請求書発行を行うクラスのコードを書く。

class Payroll
  def update(changed_employee)
    puts "彼の給料は#{changed_employee.salary}になりました!#{changed_employee.title}のために新しい小切手を切ります。"
  end
end

class TaxMan
  def update(changed_employee)
    puts "#{changed_employee.name}に新しい税金の請求書を送ります"
  end
end

これを実行する。

john = Employee.new('John', 'Senior Vice President', 5000)
john.salary = 6000
#=> 彼の給料は6000になりました!Senior Vice Presidentのために新しい小切手を切ります。
#=> Johnに新しい税金の請求書を送ります
john.salary = 7000
#=> 彼の給料は7000になりました!Senior Vice Presidentのために新しい小切手を切ります。
#=> Johnに新しい税金の請求書を送ります

salary を更新したタイミングで、具象オブザーバが呼ばれていることが確認できる。

ストラテジパターン

ストラテジパターンは、メソッドの中である一部分の処理を切り替えたい時に使えるパターン。状況に応じて、アルゴリズムを変えなければならないような時に使える。例えば、ゲームの難易度設定に応じて、戦略アルゴリズムが変わるなど。

ストラテジパターンは、3つのオブジェクトからなる。

オブジェクト名 役割
コンテキスト(Context) ストラテジの利用者
抽象戦略(Strategy) 同じ目的をもった一連のオブジェクトを抽象化したもの
具象戦略(ConcreteStrategy) 具体的なアルゴリズム

ストラテジのアイディアは、コンテキスト*1が委譲*2によってアルゴリズムを交換できるようにすること。

レポート生成を、HTML形式とプレーンテキスト形式で切り替えられるようなプログラムを題材に、ストラテジについて学んでいく。

とりあえず、クラス図で書いてみる。

image.png

まずは、両形式のインタフェースとなる、抽象戦略のコードを書く。

class Formatter
  def output_report(title, text)
    raise 'Called abstract method !!'
  end
end

次に、具象戦略のコードを書く。

class HTMLFormatter < Formatter
  def output_report(report)
    puts ''
    report.text.each { |line| puts "#{line}" }
    puts ''
  end
end

class PlaneTextFormatter < Formatter
  def output_report(report)
    puts "***** #{report.title} *****"
    report.text.each { |line| puts(line) }
  end
end

最後に、レポートのコードを書く。

class Report
  attr_reader :title, :text
  attr_accessor :formatter

  def initialize(formatter)
    @title = 'report title'
    @text = %w(text1 text2 text3)
    @formatter = formatter
  end

  def output_report
    @formatter.output_report(self)
  end
end

initialize メソッドで @formatterインスタンス生成時に渡した具象戦略を設定して、インスタンスのレポート出力は、具象戦略の output_report に自分自身(Report)を渡して処理させている。

結果を確認する。

report = Report.new(HTMLFormatter.new)
report.output_report
# =>
# => text1
# => text2
# => text3
# => 

# formatterの出力形式を切り替える(戦略変更)
report.formatter = PlaneTextFormatter.new
report.output_report
# => ***** report title *****
# => text1
# => text2
# => text3

テンプレートメソッド

テンプレートメソッドは、2つのコードのやりたいこと(アルゴリズム)がほぼ同じで、部分的に変更したい時に有効なパターン。

スーパークラスで処理の枠組みを固め、サブクラスで具体的内容を実装する。

スーパークラスでは、アルゴリズムの流れの中で利用される抽象的なメソッドと、抽象的なメソッドを用いて処理のアルゴリズムを定義するテンプレートメソッドを定義する。

テンプレートメソッドを使うことのメリットは次のもの。

テンプレートメソッドは、2つのオブジェクトからなる。

オブジェクト名 役割
スーパークラス 変わらない基本的なアルゴリズム
サブクラス 具体的な処理

レポート生成を、HTML形式とプレーンテキスト形式で切り替えられるようなプログラムを題材に、テンプレートメソッドについて学んでいく。

とりあえず、クラス図で書いてみる。

image.png

まずは、スーパークラスで高レベルの処理を書く。

# レポートを出力する
class Report
  def initialize
    @title = "html report title"
    @text = ["report line 1", "report line 2", "report line 3"]
  end

  # レポートの出力手順を規定
  def output_report
    output_start
    output_body
    output_end
  end

  # レポートの先頭に出力
  def output_start
  end

  # レポートの本文の管理
  def output_body
    @text.each do |line|
      output_line(line)
    end
  end

  # 本文内のLINE出力部分
  def output_line(line)
    raise 'Called abstract method !!'
  end

  # レポートの末尾に出力
  def output_end
  end
end

次に、サブクラスで詳細な処理を記述する。

# HTML形式でのレポート出力を行う
class HTMLReport < Report
  def output_start
    puts ''
  end

  def output_line(line)
    puts "#{line}"
  end

  def output_end
    puts ''
  end
end

# PlaneText形式(`*****`で囲う)でレポートを出力
class PlaneTextReport < Report
  def output_start
    puts "**** #{@title} ****"
  end

  def output_line(line)
    puts line
  end
end

使用する際には、サブクラスをインスタンス化するだけ。

html_report = HTMLReport.new
html_report.output_report
# => 
# => report line 1
# => report line 2
# => report line 3

plane_text_report = PlaneTextReport.new
plane_text_report.output_report
# => **** html report title ****
# => report line 1
# => report line 2
# => report line 3

参考

*1:同じコード記述やプログラム上の要素が、その置かれているプログラム内での位置や、実行される際の内部状態などによって異なる振る舞いをしたり、異なる制約を受けたりすること

*2:ある機能を持つオブジェクトを生成して、オブジェクトに処理を依頼する

*3:抽象度の高い処理、ロジック的な部分、処理のフレーム

*4:レポート行を書き出すと言うような具体的な処理