著者アーカイブ 杉浦

著者:杉浦

ホワイトテストと網羅率

 テストはプログラムの構成要素を参考にするもの、参考にしないもので二種類に分けられます。前者がホワイトテスト、後者がブラックテストとされます。ホワイトテストは設計の通りにプログラムが動作するかを確かめます。ホワイトテストによるテストではテスト内容を条件分岐の網羅などソースコードを元に定められます。ホワイトテストで考えられるテストは、ソースコードの全ての部分を実行する、ソースコードの全ての部分を様々な状態で実行する、ソースコードの全ての部分をあり得る全ての状態で実行する、といった様なテストです。
 ホワイトテストにはテスト達が所定の網羅条件をどれだけ達成しているかを表すカバレッジ(網羅率)という単位が定められています。hogehoge coverage xx%やCx xx%の様に表されることが多いです。

命令網羅 (statement coverage) (C0) 実行可能な命令の網羅率です。条件分岐の仕方に関わらずコード全体のうちどれだけが実行されたかを表します。
分岐網羅 (branch coverage) (C1) 実行可能な分岐の網羅率です。全体のうちどれだけの分岐を通ったかを表します。
条件網羅 (condition coverage) (C2) 実行可能な条件の網羅率です。全体のうちどれだけの条件を通ったかを表します。

真にプログラム上でありうる状態を網羅するためには条件網羅であっても力不足ですが、現実の時間は有限です。組み合わせ爆発にあるように、真に網羅を行うテストは実行時間が現実的でありません。後述しますが、網羅を目標にするテストのみではバグを見逃すことが多々あります。
 次のコードを例に命令網羅、分岐網羅、条件網羅それぞれを説明します。

function (a,b,c){
	if((a == 1 && b == 2) || c==3){
		//ifの中の処理。分岐なし
	}
	return;
}

このコードで入力引数がa=0,b=0,c=3のテストのみを実行した場合、C0:100%、C1:50%、C2:33%となります。まずC0:100%について説明します。これは実行可能な命令の網羅率です。引数がa=0,b=0,c=3ならばifの中に入りコード全体の命令を実行したことになり網羅率100%となります。次いでC1:50%について説明します。これは実行可能な分岐の網羅率です。このコードはifの中に入る入らないで2通りの実行ルートがあります。引数がa=0,b=0,c=3のテストのみを実行した場合、片方のルートのみを通ったということであり網羅率50%です。最後にC2:33%です。これは実行可能な条件の網羅率です。このコードにおいて実行可能な条件の組み合わせは(a == 1 && b == 2)=true,(a == 1 && b == 2)=falseかつ(c==3)=true,(a == 1 && b == 2)=falseかつ(c==3)=falseの3種類です。(a == 1 && b == 2)=trueの場合、二つ目の条件c==3の結果を評価せずとも条件の真偽値が定まるため二つ目の条件を評価しない言語が大多数です。そのためカバレッジでも二つ目の条件を評価しません。1/3を網羅したためC2:33%です。
 カバレッジはテストケースを考えるのに有用な基準ですが先に述べた通り100%を追求することは割に合わないことが多いです。実際のケースは上手く記述しようとも巨大で複雑になります。そのような場合、完全に網羅を行い、現実的な実行時間に収まるテスト達を考えることは困難です。加えて、カバレッジの追求のみでは実行パスに関わらないエラー要素、例えばa=b/cという処理におけるc=0の場合の様なバグが漏れやすいです。あくまで経験ですが高級言語の様な通常の記述において暗黙的な処理を多分に含む言語ほど命令、条件の網羅によるテストから漏れるバグが増えます。多方面からテストを行うことによって効率よくソフトウェアの品質を検証できます。

著者:杉浦

覚えておくべき変数の少ないソースコード

 この記事の話はリーダブルコード9章のあたりにより詳しく書いてあります。
 人間が一度に覚えて置ける項目の数の話は色々ありますが、それらの話のうちに大量の物事を詳細に覚えていられると述べるものは滅多にありません。覚えておくべき変数の少ないコードは人間が変数に格納されるであろう値を覚えながら読むことのできる良いコードです。そのようなコードは保守性が高く、バグが起こりにくいです。
 覚えておくべき変数を少なくするためには、そもそも変数を増やさないことが最良です。もしあるソースコード中に出現する変数が3つのみであれば、そのソースコードがどれだけ長大であっても、記憶しておくべき変数は3つより多くなりません。よく消える変数は一時的な値の置き場となっている変数、アルゴリズムの制御を担っている変数です。どちらもとりあえず書いて動かしてみる、という手法を行ったコードによく現れ、見直しによるコードの整理によって除去されます。次のコードの場合、bは用いず、aにまとめられるというになります。

var a = 'hogehoge';
/* aを用いるがaの値を変更しない処理 */
var b = 'hogehoge';
/* bを用いた処理 */

 変数が参照される範囲を小さくするという次善策もあります。変数には参照できる範囲、スコープが定められています。このスコープを少ない行数の範囲に留めることで、一度に覚えておくべき変数の数が少ないコードを実現します。言語にもよりますがスコープはおおよそ変数の宣言場所から、宣言を行った塊であるモジュール、クラス、関数などが終わるまでと定められます。宣言時の塊の最小単位の終わりがスコープの終わりとなることを基本とする言語が多いです。このスコープが小さい変数ばかりであれば、覚えておくべき変数が常に少ないコードの実現に近づきます。
 小さいスコープを実現するためには少なくとも二つの方針があります。
 一つは変数の宣言を変数を使用する直前にすることです。ある変数が使用されるはるか前に宣言されていた場合、使われない変数に頭を取られたり、使用された場所でこれ以前この変数はどのような操作をされたのか考えることになったりします。次のコードは塊の最初に変数a,b,cを宣言しており、全ての変数を最後まで記憶する必要があります。

var a,b,c;
/* aのみを用いた処理 */
/* bのみを用いた処理 */
/* cのみを用いた処理 */

一方で使用直前に宣言した場合、次の様になります。

var a;
/* aのみを用いた処理 */
var b;
/* bのみを用いた処理 */
var c;
/* cのみを用いた処理 */

このコードは変数b,cを記憶する時間が全ての変数を最初に宣言するコードより短くなっています。
 もう一つは小さい塊を多数用意し、処理を細分化し、各々その中で変数を宣言することです。この小さい塊を多数用意する手法には問題の細分化による単純化、容易な単体テスト、抽出された汎用処理の再利用、などといった利点もあります。関数やメソッドの様な多用される塊は余裕をもって一画面に収まる様にされます。一画面に収まる程度ならば処理は複雑になりにくく、一度に使用される変数も少ないです。この手法においてソースコード全体で使用される変数の数は十分多いですが、コードを読む際には塊単位で読めばよく、一度に覚えておくべき変数は少なくなりやすいです。次のコードは関数hoge_aを読む時は変数aのみを記憶すればよく、b、cも同様です。

function hoge_a(){
	var a;
	/* aのみを用いた処理 */
}
function hoge_b(){
	var b;
	/* bのみを用いた処理 */
}
function hoge_c(){
	var c;
	/* cのみを用いた処理 */
}
著者:杉浦

画像リサイズwebアプリbulkresizephotosの紹介

bulkresizephotosは複数の画像のサイズをまとめて変更するwebアプリです。
 このwebアプリは手軽という点で優秀です。複数の画像をあっという間にリサイズしてくれます。ページを開いて画像をまとめてDrag & Dropして操作準備完了です。
このアプリが無料でできる動作はScale、LongestSide、Width、Height、ExactSize。それとAddWatermarkのオプションです。
 Scaleは画像を一律の割合で拡大、縮小します。下図のExamplesにある様に、各動作はそれが画像をどのようにリサイズするかを教えてくれます。

 Width、Height、ExactSizeは幅を一定、高さを一定、幅と高さを一定、とリサイズとExmaplesを見ればすぐにわかります。LongestSideのExamplesは少々不親切です。LongestSideは長辺(Longest)の側(Side)の長さの指定です。600×400の画像と100×200の画像を指定1200でLongestSideを実行した場合、それぞれ1200×800、600×1200のサイズの画像になります。
 AddWatermarkは透かしの追加のことです。見本マークや紙幣の透かしの様に、AddWatermarkで指定した画像が他の画像全てに合成されます。

著者:杉浦

電話番号の慣例と仕様の橋渡しをする正規表現

 電話番号は国内の固定電話番号は10ケタ、携帯電話の電話番号は11ケタと定められています。しかしながら表記は慣例というのみで明確な定義がありません。***-****-****や***-***-****や(****)**-****などはよく見ます。これらや区切りなしに対応していれば大体カバーできますが、やっかいなのが企業等の広告用電話番号です。お客さんに覚えてもらうために彼らは語呂合わせなどで電話番号を自由に区切って表記します。こういった番号が対象に含まれる電話番号認識が必要となった場合、一つ一つのケースに対応はとてもしてられません。慣例で表記される()-を無視して仕様中にある数の数を数える正規表現で電話番号を認識しましょう。?によるあってもなくてもいい、が有効です。

雑なパターン:(?:\d[\(\)-]?){10,11}

いくらか丁寧なパターン:\((?:\d\)?-?\(?){10,11}

著者:杉浦

読むのが早いソースコード

 ソースコードは早く理解されるコードであることが要求されます。早く理解されるためには、書かれている文字が何なのか読み取られるという方法があります。同じ動作を関数化することはよく使われる手法です。

function natcmp_name($a, $b) {
	if (empty($a['nickname']) || $a['nickname'] == "") {
		$a['nickname'] = $a['fullname'];
	}
	if (empty($b['nickname']) || $b['nickname'] == "") {
		$b['nickname'] = $b['fullname'];
	}
}

 これは連想配列aと連想配列bを比較する関数です。比較の方法はニックネームの自然な辞書順の比較、もしニックネームがなければ代わりにフルネームを使う、です。この関数はabならば>0を返します。自然な辞書順というのは主に数字に関連した部分です。単純な辞書順では一文字ずつ文字を読み取り、違いが出た時点で順序を決定します。この単純な辞書順において’1abc’という文字列は’10abc’という文字列よりも後に並びます。これは2文字目のaと0の比較で0の方が辞書順で前に来るのが適当であるためです。自然な辞書順において’1abc’という文字列は’10abc’という文字列よりも後に並びます。
 この関数を用いてある3人の名前の並び順を決定する。コードを書くと次の様になります。

function natcmp_name($a, $b) {
	if (empty($a['nickname']) || $a['nickname'] == "") {
		$a['nickname'] = $a['fullname'];
	}
	if (empty($b['nickname']) || $b['nickname'] == "") {
		$b['nickname'] = $b['fullname'];
	}
	strnatcmp($a["nickname"], $b["nickname"]);
}
natcmp_name($person[1],$parson[2]);
natcmp_name($person[2],$parson[3]);
natcmp_name($person[3],$parson[1]);

 3回の比較が12行程度で記述できました。これを関数抜きで記述した場合、次のようになります

if (empty($person[1]['nickname']) || $person[1]['nickname'] == "") {
	$person[1]['nickname'] = $person[1]['fullname'];
}
if (empty($person[2]['nickname']) || $person[2]['nickname'] == "") {
	$person[2]['nickname'] = $person[2]['fullname'];
}
strnatcmp($person[1]["nickname"], $person[2]["nickname"]);
if (empty($person[2]['nickname']) || $person[2]['nickname'] == "") {
	$person[2]['nickname'] = $person[2]['fullname'];
}
if (empty($person[3]['nickname']) || $person[3]['nickname'] == "") {
	$person[3]['nickname'] = $person[3]['fullname'];
}
strnatcmp($person[2]["nickname"], $person[3]["nickname"]);
if (empty($person[3]['nickname']) || $person[3]['nickname'] == "") {
	$person[3]['nickname'] = $person[3]['fullname'];
}
if (empty($person[1]['nickname']) || $person[1]['nickname'] == "") {
	$person[1]['nickname'] = $person[1]['fullname'];
}
strnatcmp($person[3]["nickname"], $person[1]["nickname"]);

 21行であり、比較回数が増えればもっともっと増えます。これに目を通すのは前の例より時間がかかります。さらに置換ミスやタイプミスによる想定外の動作に気づきにくいという問題もあります。
 もっとも、一番読むのが早いのは何も書かれていないコードです。上に並べたコードは比較をしましたが比較から何も返ってきません、データを参照して、操作しているだけです。最後のコードに至ってはデータを上書きで壊すバグの元ですらあります。再利用の可能性のある場合でもコメントアウト、再利用の可能性のない場合は完全に消去。読む必要のある部分は0行になります。

著者:杉浦

正規表現の(?:)

 (?:)は(?:)で括られたグループは後方参照されないという記号です。後方参照は便利ですが、()もそれ以上に便利で多用しがちです。()が増えるとどこに何が格納されるかわかりにくくなりとても面倒です。(?:)ですっきりする例を挙げます。

 <([a-z])(?:(\s[^>]+)?)>(.*)<\/\1>

 HTMLやXMLの様なタグに使えそうな正規表現です。使えそうというのは、この表現は記事を書いている時の思い付きであり、私自身使ってないからです。.*はバグの温床です。これを(?:)抜きで状態遷移図っぽく可視化すると
 
 (?:)を使って可視化すると

 となります。前者のgroup2はいらない感じがしますが、後者では見事に消えています。

著者:杉浦

導入の簡単なwebページテストツール、Selenium IDEの紹介

 Selenium IDEは使うまでの導入が非常に楽なwebページテストツールです。多量のテストコードを書くのは手間ですし、Chrome、Firefox限定ですが、アドオンを入れるだけ、自分の操作を記録してコードに起こしてくれると扱いが簡単です。この記事はfirefox版でのインストールと操作の記録、再生の操作説明になります。
 アドオンというだけでインストールはSelenium IDE – Firefox 向けアドオンのページに飛んで”firefoxへ追加”からアドオン追加をするだけです。追加が完了した場合、右上のアイコンが現れます。

 アイコンを押したら下図の画面が出ます。赤丸ボタンで操作を記録開始します。

 記録停止は同じ場所に現れる赤い四角ボタンです。下図は、シーポイントラボのホームページを開いて、chrome開発者ツ…という記事を開いて、画面下へスクロールして、本文をクリックした、という操作を記録した場合になります。記録の保存、読込もできます。

 操作はGUI上から記録抜きに記述できます。操作コードには任意のjavascriptコードを実行というものもあり、ブックマークレットの応用でそれなりに拡張できます。困ったことにSeleniumIDEはfirefoxがfirefox quantumになったあたりで一度サポートが途切れて生まれ変わってます。検索して出てくる情報が古いとあてにならないことが多々あります。

著者:杉浦

chrome開発者ツールによる要素に付与されたイベントの調査

 開発者ツールを開いて(F12キーによるショートカットかオプション→その他のツール)、下図の赤丸部、ElementsEventListenersと開けば対象の要素を参照した時に、その要素に付与されているイベントを特定して見ることが出来ます。

 またAncestors Allにチェックを入れると下図の様に今そのページに存在するイベント全てを見ることが出来ます。

 余談ですがfirefoxでは下図の様に要素にイベントがついている場合、要素の横にEVとマークがつき、そこから参照することになります。display:flexのような大本が知りたい対象も表記されます。ただchromeの様にタブを開くような画面にたどり着けませんでした。いいところどりのツールを得るにはいささか手間なようです。

著者:杉浦

読みやすいソースコード

 この記事を読むよりリーダブルコードを読んだ方がよほど良いと思います。Amazonで売っている電子版は見つかりませんでした。リーダブルコードはQiitaの記事でよく引用されたり技術書ランキング | テック・ブック・ランク”、5万部売れたり(技術書にしては多いです)でよく読まれています。
 O’Reilly Japan – リーダブルコード
 コードの記述は想定外の動作を起こさず、正しく動作する記述であることが最も重要視され、次いで理解しやすさ、時々実行速度が優先されます。実行速度が三番手なのは雑なコードでも要求に対して十分高速な時が少なからずあるからです。理解しやすいコードはそのコードを読む、未来の自分、共同開発者、ユーザの助けになります。未来の自分と書くと語弊がありますが、この未来というのはわりと短期です。一月もすれば読まない部分のコードの記憶は怪しくなります。
 理解しやすいコードに必要な条件に誤解が起きないというものがあります。誤解が起きない書き方というのは、接続詞である演算子や括弧を少なくして多重コードを避ける、変数名や関数名の意味を一意に決まる限定的な言葉にする、などの書き方です。
 読みにくい多重コードは三項演算子、関数呼び出しが特にそうです。

hoge = a?b?c:d:e
hoge = a?b?c:d?e:f:g?h:i
hoge = a(b(c,d(e()),f(g(h(),i))))

 二重だけでもちょっと躊躇います。下二つはもう大惨事です。
 変数名の意味を一意にすることの効果は、コードの理解に必要な文章量を少なくする効果でもあります。良い命名がされたコードはコードの一部を読むのみでプログラムが分かります。悪い命名というのは例えば、次のような命名です。

results = Database.all_objects.filter("year <= 2011") 

この results には何が含まれているだろうか?

  • 「year <= 2011」のオブジェクト
  • 「year <= 2011」ではないオブジェクト
  • Dustin Boswell (著), Trevor Foucher (著), 須藤 功平 (解説), 角 征典 (翻訳). リーダブルコード (P.30).
     このような場合、言葉の意味を考えるなり調べるなりしてより限定的な言葉を選ぶことで解決します。調べる時は類語や色々な人のコード中に頻出する名前を調べるといいです。この例の場合はfilterの代わりにselectやexcludeが推奨されます。

    著者:杉浦

    javascriptの比較演算子==、!=と===、!===

    ==、!=と===、!===は曖昧な比較と厳密な比較です。==、!=と===、!===を使い分ける事で以前書いた暗黙の型変換に悩まされない比較を行うことができます。
     javascriptは暗黙の型変換を行う言語です。暗黙の型変換とは、黙って値の型(文字列、整数、真偽値等)を変える、という動作です。この暗黙の型変換は便利な一方で予期せぬ挙動を引き起こすことがあります。例えば、次の関数を考えます。この関数が行いたいことは、引数の値を+1して返す、ということです。

    (inc = function(v){
    	return v+1;
    })();
    

     この関数は次の結果を引き起こします。

    a=inc(2);
    //aは3
    a=inc("2");
    //aは"21"
    

     javascriptは親切にも文字列型である引数に合わせて、数値として扱いたい1を文字列型に変換してくれました。この様に暗黙的な型変換をして欲しくない時があります。if文では次の様なコードが例に挙げられます。

    (hoge = function(msg){
    	if(msg == ""){
    		//msgが空なら返す
    		return;
    	}
    	//msgを使ったして欲しい処理
    })();
    

     javascriptは数値0を文字列””に暗黙的に変換します。そのためmsgに格納されている値が数値0の場合、動作はifの中に入り込み、msgを使ったして欲しい処理を行わずにhoge(msg)を抜け出すものになります。==、!=は比較の際に暗黙的な型変換を行う比較演算子なのに対し===、!==は比較の際に型変換をさせない比較演算子です。これを用いて次の様に記述した場合、msgに格納されている値が数値0であってもif文の中に入り込まずに、msgを使ったして欲しい処理を行います。

    (hoge = function(msg){
    	if(msg === ""){
    		//msgが空なら返す
    		return;
    	}
    	//msgを使ったして欲しい処理
    })();