犯人はWindows Update……か?

ここまであからさまに「Windows Updateが原因でPCガー」的な場面に遭遇したのは初めてである。

5~6年ほど前に購入したノートPC Dell Vostro 2521が2台転がっていて、実験用に使おうとしたのだ。

2台ともWindows 7からのアップグレードでWindows 10化していて、現時点ではWindows 10 Ver.1803が入っている。ここ数ヶ月放置していて、何か月分かのWindows Updateが未適用の状態だった。

Windows Updateで2019年7月の累積パッチがインストールされたので、適用のために再起動したところ、起動はするけど操作しようとするとブルースクリーン(INVALID PROCESS ATTACH ATTEMPT)になってしまう状態に陥った。

2台とも同じ症状だ。

ちょっと観察してみたところ、Vostro 2521本体のタッチパッドで操作しようとするとブルースクリーンになることが分かった。試しにUSBマウスを使うようにしたら、ブルースクリーンを回避できた。

タッチパッドは、Windows Update前には普通に使えていた。ブルースクリーンが起こるようになる前との違いは「Windows Updateをかけたか否か」だけ。

何というか、ここまで堂々とWindows Updateが疑わしく見えるシチュエーションに遭遇すると、一周回って感心してしまうものだ。

なおブルースクリーンについては、遅れてKB4346084(今月再リリースされた版)が自動適用されたら、発生しなくなった。2台とも同じ。

KB4346084は「Intel製CPUのマイクロコード更新する」という少々特殊なパッチなのだが……適用順でブルースクリーンなる、という情報は見つけられていない。

今までどのくらいプログラミング言語を触ってきたか(3秒で挫折したものものも含む) Ver.11

2019-06-30現在のステータス。id:eel3:20180630:1530319168 から1年経て、こうなっている。

なおCSS、HTML、XMLはひとまず除外する。人工言語ではあるけれども「プログラミング言語」という括りに含められるか否かは議論が分かれる気がする。*1

よく使っている

AWK (Gawk)
単純なテキストレコードの処理はAWKで十分間に合う。今の時代、自作ツールをnawkやGNU awk単体で実装するのは苦行すぎて*2皆無なものの、シェルスクリプトMakefileAWKのコードを埋め込むなどして他のコマンドと組み合わせて使う機会は依然として多い。シェル上でワンライナーでテキスト処理する時にも重宝している。これはこれで十分AWKらしい使い方ではないだろうか?
C++
ちょくちょくお仕事で使うが、本職のC++使いではない。C++11やC++14は非常に便利で、better Cでも使う価値がある。C言語使いからすると、C++03時代よりも充実度が進んだ標準ライブラリ・ラムダ式・autoによる型推論など、便利で羨ましい限りだ*3。あと、Swift時代のクロスプラットフォームC++ライブラリ作者は、どうあがいてもARCから逃れられないので、C++11以降のスマートポインタは必須だ*4正規表現とスレッドも標準ライブラリに加わったので、あとはソケット(低水準ネットワークAPI)をサポートしてくれないだろうか。低水準の処理を行いつつも便利な機能で実装時間を短縮できる点は便利で、少なくともシステムプログラム言語としての利点だと思う。だけど機能多すぎ/複雑すぎなところはなんとかならないものか。強力な反面、使い手を選ぶ言語だ。
C言語
お仕事での主力言語。シンプルかつ低水準の世界が垣間見れるところが割と好き。とはいえ最近の他の言語と比較すると、シンプルすぎて安全機構が欠けていたり標準の便利機能が少なかったりするので、入門用の言語としては薦められない。にもかかわらず、プログラミング未経験者向けのC言語の本は毎年出版されている――謎だ。クロスプラットフォームなモジュール屋としては、今までC89を採用してきたものの、いい加減そろそろC99とかC11とか次世代の言語とか使いたい。でも古いVisual Studioもサポートしたいから、C99すら2023年~2024年ぐらいまで使えない。悲しいなぁ。
DOSバッチファイル
プログラミング言語に含まれるかどうか不明だが、含めてしまう。ちょっとした自動化や、複数ツールを組み合わせて使うときのラッパーとしてよく使う。コマンドプロンプトはシバン(shebang)に対応していないので、スクリプト言語で書いたツールを起動するラッパーとしても多用している。意外と色々なコマンドが用意されているので、単純にそれらを叩く分には十分だが――言語機能がショボいので、バッチファイルでifやforのような制御構文系コマンドが必要になってきたら、何かを決定的に間違えているのではないかと考え直すようにしている。
Kotlin
本格的にAndroidアプリ開発に関わるようになったのがGoogle I/O 2017直後の過渡期なので、JavaよりもKotlinでの経験値の方が多い。モダンな「強い静的型付け」の、割とええ感じの言語やね。ただ、Swift 3をつまみ食いして半年以上経ってからKotlinをつまみ食いしたこともあり、未だに両者の概念・機能が頭の中でごった煮になっている。それと、NDK絡みの作業が多いので、C++11/14・Java 7/8・Kotlinを行ったり来たり。泣けるぜ。Swiftもそうだが、「強い静的型付け」の言語は「開発環境込み」で高い生産性とコードの安全性を両立させる方向に進んでいる気がする。
make (Makefile)
プログラミング言語に含まれるかどうか不明だが、DSL扱いで……いやGNU Makeはそこそこプログラミング言語的か。GNU Make 4.0はさらにプログラミング言語的だな、特にGNU Guileなところが。GNU MakeとNMAKEが主力で、稀にNetBSD Make(pmake)を使うが、いずれも独自拡張アリアリだ。もう素のmakeではMakefileを書けない身体だ :)
Python
Python 2.xで実装した小ツール群をPython 3.xに移植した。Pythonではlazyなスタイルでのコーディングが許されず、整然とコードを記述する必要がある。その辺は、Perl 5やRubyとは随分と雰囲気が異なる。気になるのは、インデントが必須な言語仕様であるために、シェルスクリプトに埋め込んで使うのが苦痛な点だ。Pythonだけでコードを書く分には気にならないのだけど。
sed
プログラミング言語に含まれるかどうか不明だが、DSL扱いで*5。テキスト処理用。シェルスクリプトMakefileにて他のコマンドと組み合わせて使う。というか正規表現でのテキスト置換以外の機能を使った記憶が……あったな、dとiとpと=とブレースによるグループ化ぐらいだが。私のレベルではsedFizzBuzzを書けないので、sedで難しい処理を記述しないようにしている。
Windows PowerShell
時代はPowerShell Coreらしいが、現行のWindows 10でデフォルトで利用できるv5.1に留まったままである。スクリプト言語としてのPowerShellは、オブジェクト指向.NET Frameworkを叩けてダイナミックスコープでスクリプトブロック(という名の無名関数)と、無茶でピーキーで完全にプログラマ向けな代物だ。Microsoftもよくもこんなエライ代物を出したものだ。残念なことに、コマンドプロンプトの代替という観点では、外部ツールとの親和性が微妙にイマイチだ(特に文字コードとか)。でもPowerShell内で閉じている分には問題ないので、私の手元では「Windows専用のGUI付き小ツールを作るためのスクリプト言語」か「Excel COMとか叩く用のスクリプト言語」か「Windows Serverの管理スクリプトを書くためのスクリプト言語」扱いしている。ところで、いい加減『Windows PowerShell イン アクション』級の最新バージョン対応の言語解説書が出版されてくれないだろうか。
シェルスクリプト (/bin/sh)
プログラミング言語に含まれるかどうか不明だが……いや、私的にはシェルスクリプトは立派なプログラミング言語だ。基本的な用途は、バッチファイルと同じくちょっとした自動化や複数コマンドを組み合わせて使うときのラッパーだが、実現できる内容は遥かに多い。言語本体(?)がバッチファイルよりも高機能だし、Unixユーザランドはコマンドが充実している。その意味では、WindowsではMSYSよりもCygwinで――いやむしろWSL(Windows Subsystem for Linux)で環境構築すべきだろう。Cygwinでは、主要な処理をシェルスクリプトで記述しておき、bashからはシェルスクリプトを利用し、コマンドプロンプトではラッパーのバッチファイル経由でシェルスクリプトを叩く使い方をしている。ただWindows上では処理速度が妙に遅くなる点が不満だ。

あまり使っていない

Free Pascal
お試しで触っているのだが、微妙にDelphi/Free Pascal初心者(ただし他言語の経験者)向けの良い資料が少なくて難儀している。玉石混交なのだ。いっそのこと『OBJECT PASCAL HANDBOOK―マルチデバイス開発ツールDelphiのためのプログラミング言語完全ガイド』を買ってしまおうかしら。
Java
生まれて初めて触れたプログラミング言語その2。実のところ、職業プログラマとして本格的に使用することは一生ないと思っていた。Androidアプリ開発も、Kotlin採用後に本腰入れて関わるようになったので、Kotlinメインだ。だが、なぜかぬるい感じに時々Javaのコードを触っている。ただし、先にコレクションの操作方法が充実した他の言語を学んでからJavaを本格的に触るようになったので、Java 8以降のStream APIが使えないと身体が拒否反応を示す。少なくとも、構文の見た目こそ保守的なオブジェクト指向プログラミングをしているC++に似ているけど、中身はC++とは似ても似つかない代物だということは体感している。
Objective-C, Objective-C++
時代はSwiftだと言われているけど、どっこいObjective-CObjective-C++は生きている。というかSwiftのコードにC++で書かれたライブラリを直接組み込むことができない以上、両者を繋げるグルー言語として生き残ることになるよね。最近流行の言語と比べると良くも悪くも80年代的だが、アプリケーションプログラミング用としてはC言語よりマシだし、C++ほど複雑怪奇*6ではない。そしてC言語C++で書かれた既存のライブラリをそのまま使える。Objective-Cのハイブリッドな所は好きだが、Objective-C++はハイブリッドすぎて微妙。C++のクラスとObjective-Cのクラスを、C++ラムダ式Objective-Cのブロック構文を同時に使うのは大変だ。便利ではあるんだけどね。
Perl 5
時々、やむをえない事情で触ることがある。だが基本的によく分からない。何というか、あの記号の羅列っぽさに中々慣れないというか、自分は余りに自由度が高すぎる言語は苦手だと気づいたというか。(言語仕様に慣れているなら)半ば使い捨てなテキストフィルタとかをさっと書くには、悪くない言語だと思うのだが。
QML
宣伝文句のとおり、QMLはGUIの記述に非常に向いている。それも、単に標準のUI部品(エレメント)を使うだけでなく、少し改造して使うとか、オリジナルのUI部品を作って使うとか、それらを別のアプリケーションに使いまわすとか、そういう時に威力を発揮する。あと、プロパティバインディングやレイアウトのアンカー指定など、画面サイズの変更に追随するUIを作りやすい機能も揃っている。JavaScriptでちょっとした処理も記述できる――とはいえ、やりすぎるとパフォーマンスの罠が……。少なくとも、JavaScriptでゴリゴリコードを書くのはQML的ではない。QMLは宣言的に、シンプルに書くものだ。力技でロジックでゴリ押しすると、色々と罠に嵌る言語だ。
Ruby
自作ツール実装にて、AWKの代替言語の最有力候補だった(でも最近はPythonが多いんだな、これが)。テキスト処理でも割と重宝するが、バイナリデータへの変換が絡んでくるとAWKよりもRubyを使った方が効果的だ*7。そろそろirbを電卓代わりに使うスタイルも板に付いてきた気がする。to_s(16)やto_s(2)で基数変換して表示できるところが好き。
SQL
生まれて初めて触れたプログラミング言語その3ぐらいに位置する。組み込みの人なのでSQLとは無縁だと思っていたが、まさかTransact-SQLを少しだけ触ることになるとは。
Swift
コンパイラによる強力な型推論と型安全性のチェック」がお仕事用のメジャーな言語にまで降りてきたという点で、Swiftは静的型付け言語界のJavaScript*8だと思っている。でもユーザ数的には、Kotlinが「静的型付け言語界のJavaScript」ポジションなのかもしれない。割と好感が持てる言語だ。そろそろ互換性のない言語仕様の変更が発生し難くなってきた感じかなあ。
Visual Basic .NET
Visual Basic .NET 2003で書かれたコードを時々メンテ中。流石に開発環境はVisual Studio 2013移行したけど。

最近使ってないが、縁は切れてない

bash
最近はデフォルトシェルがbashな環境も多いので、自分用のツールぐらいは素の/bin/shではなくbashで書いても大丈夫な気がしてきた。shよりbashの方が遥かに便利だからなあ――PerlRuby等には負けるけど。bashスクリプトを書くときの唯一の欠点は、メジャーバージョンごとの差異や各ディストリでのビルドオプションの違いにより、同じbashという名前でも実は千差万別なところだと思う。PerlRubyのバージョンは気にするけど、これがシェルになると意外とバージョンに無頓着になってしまう。なんでだろう?
bison (yacc)
プログラミング言語に含まれるかどうか不明だが、DSL扱いで。やっぱり構文解析系統のコードを自作するのは割に合わない――だなんてうそぶきつつ、LALR法とか全く知らないままに、既存のyaccのコードを切り貼りして遊んでいるところ。まだ簡易電卓レベルだが便利さを体感しつつ、さっそくtypo 1文字で痛い目(shift/reduce)に遭った。とりあえず、flexと組み合わせた上でのエラー処理(エラーメッセージの改善)が課題だ。
C#
かつて、勉強を兼ねてC# 2.0を少し触ろうとするも未完に終わった過去をもつ私。あらためてVisual Studio 2013をインストールして、また少しだけ触ることに。言語仕様的にはC# 5.0の環境だが、使用する機能はC# 4.0相当だろうか。変数の型推論ラムダ式LINQ、デフォルト引数は便利だ。.NET Frameworkの機能数は反則ものだが、所々に微妙に抽象化が行き過ぎたAPIが見られるのは気のせいだろうか? それにしても、クラスが必須ではないC言語C++に慣れてしまった弊害か、アプリケーション・メインエントリすらclass内に定義しなくてはならないC#には、なかなか慣れない。
CASL II
生まれて初めて触れたプログラミング言語その1。何だかんだで、後でCプログラマになってからも低水準での思考ツールとして微妙に役に立っている。まあ考えるための言語であって実用言語ではない。仮に実用的な処理系*9があったとしても余りに命令がシンプル過ぎて悶絶するなあ、なんてFizzBuzzしてみて思った。
flex (lex)
プログラミング言語に含まれるかどうか不明だが、DSL扱いで。字句解析用のツールという印象が強かったのだが、よく考えてみたら、flexは「sed(1)のよくある使い方」と同様に「正規表現でパターンマッチング --> 何らかのアクション」という内容を記述するためのツールだった。ただ単に、「何らかのアクション」をC言語で書けることと、flex自体ではパターンマッチングをせずに「パターンマッチングするC言語のコード」を生成することが少々風変わりなだけ。grep(1)やsed(1)その他で小ツールを実装して運用しつつ、性能が求められたらflexで専用ツール化する――とか考えたけど、普通にgrep(1)やsed(1)を使う方が高速だった。
Go
寡作ながらもいくつか小ツールを書いてみたが、標準ライブラリが充実しているコンパイラ型言語っていいっすね。C言語に比べればC++の標準ライブラリも充実しているが、どちらかといえばプリミティブな機能が中心だ。PythonRubyばりの標準ライブラリを抱えているGoには及ばない。その辺は、やはりCプログラマ(特にCでフィルタやデーモンの類を書く層)には受けそうな言語だと思う。並列処理周り(goroutines)とかARM対応とかが気になる。ソフトリアルタイム限定だが「組み込みLinux + Goで書いたデーモン」とかどうだろう? ただメモリを食うらしいという噂がどうなったか気になる――64bit環境では解消されるという話だったようだが、32bit環境でも解消されるようになったのだろうか? 組み込みでは現時点では逆立ちしたって64bit CPUはありえないからなあ、スマホタブレット以外では。
Io
プロトタイプベースである点を除けば、何となくSmalltalk的であるような――公式ドキュメントらしきIo Programming Guideでも影響を受けた言語として真っ先にSmalltalkが挙げられているから、あながち思い違いでもないだろう。今更ながら『7つの言語 7つの世界』のIoの章を読み終えたので、ちょっとしたコード片を書いているが……Windows版のバイナリが古いためか、リファレンス通りなのに動作しないコードに直面している。
JavaScript(クライアントサイド)
クライアントサイドJavaScriptのコードを書いたこともあったけど、スキル的にはもう何もかも賞味期限切れだよなあ。ECMAScript 2015以降を使うなら、それ以前とはコードのスタイルから変えないと不味そう。
JavaScript(サーバサイド?)
Node.jsやPhantomJSが出てきた頃は、クライアントサイド以外でもJavaScriptが使われる文化が広まる可能性があるのか気になったものだが……結局どうなっているの? 何か処理系もNode.jsに収束してきた印象が。
Lua
Wiresharkのパケット解析スクリプトを書いてから数年。今度はC言語で書かれたUnixデーモンの設定ファイル用に組み込むことに。これはこれで「職業柄」の範疇、なのだろうか? Windowsのことを考えなければ、自前でライブラリをビルドしてアプリに組み込むのは結構簡単だった。
Scheme
GaucheWindowsネイティブ環境用バイナリは実験版だが、私が触る分には何の支障もない*10ことに気づいて久しい今日この頃。『Scheme手習い』と『Scheme修行』を購入したので、とりあえずCommon LispではなくGaucheScheme)の勉強をする方向に転換しようか検討*11しているうちに何年たったのやら。Gaucheはフィルタ・ライクな小ツールの実装用としても良い感じだ。しかし最も多い利用方法はREPLを電卓代わりにすることだ*12。うーん、作業環境がmacOSLinuxに移ったなら、大手を振ってGaucheでフィルタを書くのだが。
Tcl/Tk
Tclは書き方を忘れた頃にテキスト処理ツールを書いている気がする。Tclは結構独特な言語だ。構文がシェルスクリプトばりに全てコマンドだったり、値が全て文字列だったり、実はリスト構造だったり、意外とTCPソケット通信が得意だったり……。それでも慣れれば結構使いやすい。意外とプロトタイピングに向いている気がする。8.6以降ではオブジェクト指向プログラミングもOKだが、それよりも例外処理用のtry末尾呼び出しの最適化用のtailcallの方が興味深い。しかし、これからメジャーになる可能性は低そうだ。Tkは……小規模なGUIツールをさくっと構築できるところは便利だが、Webアプリ全盛の時代にどれだけ訴求力があるのやら。
Vim script
少し触ってみた分には、exコマンドの拡張(=コマンドの羅列)とは気づかない程度にはプログラミング言語らしいと思う。とはいえ妙なところで嵌ったり微妙に一貫性のない部分があったりするので、その辺りで好き嫌いが別れる気がする。
XSLT
縁が切れたと思いきや、仕事でXHTMLから特定要素を抜き出す作業に使うことに。XMLからテキストレコードに変換してしまえば、後はUnix流テキストフィルタの世界が待っている。餅は餅屋というもので、定型的なXMLの変換はXSLTで記述すると楽だ。唯一気に入らないのは、xsl:sortでアルファベットの大文字と小文字を区別してソートすることができないこと。ぐぬぬぬ。

これから(また)使うかもしれない

Alloy
形式手法の中では比較的カジュアルに使えそうなので期待中。入門書も処理系も入手した。私の場合、先に何か論理型の言語をかじった方がよいのかも。
Common Lisp
2009年に勉強しようと思い立ったものの、未だに進んでいない。階乗とかハノイの塔とかiotaぐらいは書いたが、目標は「ちょっとしたツールを自作する」だ。まだ道は遠い。最近は時々CLISPを簡易電卓代わりにしている。
Coq
ソフトウェアの基礎が気になるので、処理系だけ入手。
Emacs Lisp
.emacsにコピペ」限定で。Common LispSchemeを触ったためか、何となく内容を追えるようになってきた気がしていたが、勘違いだった。
F#
OCamlは「Windows上で日本語を扱う」という視点では処理系がちょっと微妙なので、いっそのことF#に乗り換えようかと……。『実践F#』が積読状態になっている。
Forth
pForthをMinGWでビルドしたので処理系は手元にある。スタック指向の言語はいつか勉強したい。
LOGO
そういえばLOGOを触ったことがない。とりあえずUCBLogo(Berkeley Logo)だろうか? Windows上でUCBLogoばりにGUI無しで動作する処理系はないだろうか?
Object REXX
思うところがあって処理系とIBM謹製のドキュメントを入手したものの、そこから先の進展は無いまま。ReginaでClassic REXXっぽい感じで触っているからなあ。
OCaml
Common Lispを勉強するはずが、いつの間にか触っていた言語。一応、階乗ぐらいは書いた。時間が取れたらもうちょっとしっかりと勉強したいが、面倒なのでF#に移行しようか検討中。
Oz
ふと思い立ってUbuntuにMozartを入れた。『Scheme手習い』の次はCTMCP片手にOzで勉強かなあ。道は遠いな……。
PostScript
これかForthか、どちらに手を出すべきか? 悩ましい。
Processing
入門書も処理系も入手して、あとは弄る時間をつくるだけ。
Prolog
少し前に触っていた言語。『7つの言語、7つの世界』の地図の色分けプログラムには衝撃を受けた。何というか「正しい問い」を見つけられるか否かが肝なのか。この辺は、根底の部分でAlloyに通じる何かがあるように思う。ひとまず、Prologで論理プログラミングと宣言的なスタイルに慣れておけば、形式手法にて「論理で宣言的に」記述するときに戸惑いが減るのではないかと期待している。
Rust
仕事柄「C/C++の次のシステムプログラミング言語」はそれなりに興味の対象で、Go言語やD言語ほどではないが、Rustも……まあ、気にならなくはない。ちなみに、これら3言語と同列にObjective-CやSwiftが挙げられることもあるようだが、個人的見解としては、システムプログラミング言語としてのこの2言語には全く期待していない。あれは、Appleというしがらみからは逃れられないでしょうな。
Smalltalk (Squeak, Pharo)
Smalltalkは有名な古典的プログラミング言語だというのに、触ったことがない。ということでSqueakとPharoの処理系のみ準備完了。うーん、「環境」付きなのが気になる――言語を弄くる基準が「コンソール上でテキストフィルタ」という変な人種な私だからなあ。
Smalltalk (GNU Smalltalk)
個人の思想信条による理由よりSqueakとPharoにわだかまりを感じてしまう変人なので、邪道だと思いつつもコンソールでテキスト処理もOKなGNU Smalltalkも用意してみた。これで言語としてのSmalltalkの勉強に集中できる……か?
VBA (Visual Basic for Applications)
今までVBAから逃げ回っていたのだが、ついに使うことになりそうな予感。たぶん、Access VBA 8割にExcel VBA 2割ぐらい。

今は全く使っていない

Active Basic
VBScripを触りだした影響で、時々思い出しては弄くっていた。ほんの少しだけ触って放置して、すっかり忘れてからまた触る――これを繰り返していた気がする。なので毎度初めて触るのと同じ状態だった。String型をバシバシ使用 :)
bc
その昔、Windows標準の電卓アプリの代わりに使おうとして色々あって挫折した。今はirbclisp/goshで計算しているからなあ。
Clojure, Scala
JDKがなくてもJava APIを叩くスクリプトを書けるので非常に便利。Scala型推論とか、便利っすね。言語仕様はJavaよりも好みだ。とはいえ、IoT時代にJava VMベースでどこまでメインストリームに居残ることができるのか? ちょっと興味深い。サーバサイドに活路を見出すのだろうか?
COBOL
FizzBuzzするためだけにOpenCOBOL 1.0をWindows上に用意して触ってみた。なんというか、COBOLの名前と生まれた時代が示すように基幹業務(というかお金や帳簿が絡んでくるところ)向けの言語だよなあ、といった感じ。COBOL 2002のフリーフォーマットを採用するだけでも使い勝手が変わる気がしたが、世の中にはまだ広まらないのだろうか。
CoffeeScript
仕事で使う予定はない。RubyPythonその他の影響を受けているだけあり、その手のスクリプト言語っぽい感じでコードを書けるので、慣れれば素のJavaScriptで直接コーディングするよりは楽だ。しかし標準ライブラリ回りや処理系絡みの機能やサードパーティのライブラリなど、結局はJavaScriptを知らないとCoffeeScriptでコードを書けないと思う。それに生成されたJavaScriptのコードを見て「うわぁ、これあまり効率的でないなあ」と感じる時もあって、高速化が必要な部分では生成されるコードを気にしながら記述したりCoffeeScriptを諦めてJavaScriptで書くことになるので、やはりJavaScriptを知らないとマズイ。とはいえ便利なのは確かだ。CoffeeScriptのコードは即Node.jsで実行できるので、その辺りから「CoffeeScriptでテキストフィルタ」的な文化が生まれると面白いかも。気になるのはECMAScript 6の存在で、今までCoffeeScript独自の機能だった部分の多くがES6で取り込まれるので、今後ES6対応が進むにつれてCoffeeScriptの立場がどうなっていくのか、少々興味深い。
D言語 2.x
仕事柄「C/C++の次のシステムプログラミング言語」はそれなりに興味の対象で、Go言語ほどではないが、D言語も気になる存在だ。D言語シンタックスがC・C++に近いだけでなく、コーディングしている時のアプローチ・判断についても、CやC++での流儀がそこそこ通用しやすい気がする。少なくとも、Go言語でコーディングするよりは、文化的背景の違いによるモヤモヤは感じにくい。あと、標準ライブラリを使ってテキストフィルタを書いたところ、エラー処理を1~2ヶ所のtry - catchにスッキリまとめることができて、ちょっと驚いた。throwされる例外のメッセージ文字列が、ちょうどよい塩梅の内容だったため、メッセージを変更する(いったんcatchして、再throwする)必要がなかった。ちょっと残念なのは、マルチバイト対応だが……。
Fortran
Fortran 90やFortran 95あたりは結構近代的な言語だと思う。用途次第ではC言語よりもFortranの方が遥かにマシな選択だろう。配列がらみの処理はFortranの方が得意だし、言語機能としてのモジュール化の方法はC言語には存在しない。可変長な文字列の扱いに微妙な制限がある点はマイナスな気もするが、まあ基本的に数値計算プログラム用の言語だからなあ。
GDB (GNU Debugger)
……いやGDBはデバッガとして使っているが、GDBスクリプトを書く機会は(FizzBuzz以外に)ない。勉強不足なだけかもしれない。
Groovy
JDKがなくてもJava APIを叩くスクリプトを書けるので非常に便利。動的型付け言語っぽくいくもよし、@CompileStaticや@TypeCheckedで型推論するもよし。言語仕様はJavaよりも好みだ。コンソールアプリを書く人としては、オプション引数解析用の機能を標準で持っている点で、GroovyはClojureScalaよりもポイントが高い*13。個人的には、IoT時代に「Java VMベース」の言語としてどこに活路を見出すのが、興味深く見守りたいところ。やはりサーバサイドだろうか?
HSP (Hot Soup Processor)
FizzBuzzで楽しんでみたが、何というか他言語経験者には受けが悪そうな命令体系だと思う。もっとも初心者がプログラミングという行為に深入りせずにWindows用のGUIな何かを作る分には、あの命令体系でも十分な気がしないでもない。ところで元々は「HSPで職業プログラマ的な良いコードを書くと、どんな感じになるか?」というネタを思いついて処理系を用意したのだけど、そちらは全く進展がないまま。
JScript on WSH
他人が使うテキスト処理ツールの実装に使って以来、時々触ってきた。Windows用の配布可能な小ツールを実装する時の定番言語だった。でもそろそろ潮時だろう。HTAと組み合わせてクライアントサイドJavaScriptなノリで簡易なGUIツールを実装できる点も、PowerShell + WPF + XAMLで代替できそうだ。他のメリットは「JavaScriptECMAScript)でフィルタを書ける」だったが、WSHのなかなか目的にたどり着けないオブジェクト階層にイライラするよりも、Node.jsやPhantomJSを使ったほうが精神衛生的にマシだ。
m4
その昔テキスト処理用に触ろうとして、Windows用のどの処理系も日本語の置換に何かしらの問題を抱えていたので泣く泣く諦めた。思うところがあって改めて少し触ってみたが――なるほど、確かに中毒性のある言語*14だ。
REXX
Open Object REXXの処理系を入手したのに、何故かReginaを入れてClassic REXXっぽい方向に走っていた。何というか、COMコンポーネント.NET Frameworkと無関係でいられるのなら、バッチファイルの代替としてはREXXあたりがほどよい塩梅だと感じる。しかし最近流行の言語とは随分と勝手が違うし、日本語の情報も少ない。メインフレーム以外の世界で流行る可能性は少ないだろう。
T4 Text Template
「へえ、こんなものがVisual Studioに入っていたのか。機能多すぎで色々と便利なツールを見逃しているんだな、やっぱり」と思いつつ触ってみた。テンプレート変換の用途ではピカ一だと思う。ただ処理系を手に入れる方法が「Visual Studioをインストールする」or「MonoDevelopをインストールする」なので、何となく「単体で手軽に使えるツール」ではないというイメージが……。まあC#VBで処理を記述するので、それらの処理系が必要だという面での制約なのだろう。
VBScript on WSH
JScriptほどではないが「Windows上で他人も使えるツールを書くためのLL」扱いしていた言語。Windows Server管理の関係で触っていた。というかWebで入手可能なWSHのサンプルの大半がVBScriptで書かれていたり、ADSI関連のコレクションをJScriptで舐めれなかったりして、結局は必要に駆られて使用することに。明快に記述できる文法は評価に値するが、スクリプト言語としては少々冗長だ。配列は自動拡張しないし、組み込み関数はプリミティブ気味だし、冗長気味な文法との合わせ技でコードがさらに冗長になっていく……。文法や言語仕様の詳細なドキュメントが見つからないのだが、どこにあるのだろうか?*15
秀丸マクロ
7年ほど秀丸エディタを使っていたが、マクロを書く機会はなかった。一念発起してFizzBuzzしてみて感じたのは、最近の便利な言語に慣れた身としては色々とモヤモヤ感がある言語仕様だということ(歴史的経緯的に仕方ないのだが)。とはいえちょっとした拡張ツール的なものを手軽に作れそうではあった。

*1:HTML5 + CSS」の組み合わせなら、チューリング完全の疑惑があったり、JavaScript使わずにCSSでWebチャットを作った猛者がいたりと、色々と怪しいのだけど。

*2:「独立性の高い単体のツールを実装する」という視点では、現代ではAWKよりも便利な言語が山ほどある。

*3:しかし標準ライブラリの充実度をJavaC#.NET Framework含む)と比較したり、型推論まわりをKotlinやSwiftと比較してはいけない。以前よりも随分と便利になったのだけど、だけど、隣の芝生を見てしまうと、うーん。

*4:SwiftではC++の機能を直接呼び出すことができないので、Objective-Cでラッピングした上で利用することになる(インタフェースはObjective-Cで、内部実装はObjective-C++)。この時、Objective-Cクラスのインスタンス変数として「C++クラスのインスタンスを保持するスマートポインタ」を持つ構成にしておくと、Objective-Cクラスのインスタンスがdeallocされる時に、スマートポインタ経由でC++クラスのインスタンスもdeleteされるのである。

*5:これでもsedチューリング完全な言語だと証明されているらしい。

*6:少なくともC++の言語仕様は私の手には余る。自分が把握している範囲の機能でコードを書くのは好きなのだけど。

*7:とはいえ、ついついC++を使ってしまうのだよなあ。

*8:私の認識では、JavaScriptは、第一級関数やクロージャがお仕事用のメジャーな言語に組み込まれて、少なくない人が使う契機となった言語だ。

*9:――といってもシミュレータだけど。

*10:支障がある部分を触るほど深入りするには、あと20年ぐらい掛かるのではないか?

*11:Schemeの勉強というよりも、再帰の勉強なのか?

*12:現状はirbclispかgoshの3択だ。

*13:ClojureScalaに関しては、同様の機能を私が見逃している可能性も高いのだが。

*14:m4はマクロプロセッサなのでプログラミング言語ではないはずだけど……。

*15:MSDNの資料では物足りない。もうちょっと掘り下げた内容のものが欲しい。

コマンドプロンプトからUSB記憶装置を取り外そうとして失敗し続けている記録

WindowsUSBメモリなどを取り外す際にタスクトレイから行う「ハードウェアの安全な取り外し」の操作をコマンドプロンプトから実現しようとして、依然として失敗し続けている。

備忘録として、時系列順に記録を残しておこうと思う。何か進展があったら追記するつもりだ。

おま環の問題かもしれないので、もう少し具体的に背景を書く:

  1. Windows Storage Server 2016 Workgroupで運用しているNASがある。
  2. このNASには、NTFSでフォーマットしたUSB-HDDが取り付けてある。
  3. USB-HDDは、Windowsバックアップツールを利用した定例バックアップ先として使用している。
  4. USB-HDDは、定例バックアップの時だけ接続している。バックアップ終了後には、取り外し可能な状態にした上で、USB-HDD本体の電源をOFFにしている。
  5. 諸々の事情で、定例バックアップはWbadmin.exeを叩くパッチファイルをタスクスケジューラで実行することで実現している。
  6. 定例バックアップ完了後に、毎回NASにログオンして「ハードウェアの安全な取り外し」の操作をするのが面倒である。なので同様の操作をパッチファイルから実行することで自動化したい。
  7. NASは業務用として運用しているので、Windows Storage Server 2016の標準の機能か、もしくはMicrosoftが公開しているツールなどで実現したい。サードパーティのツールは導入したくない。

以上の理由より、コマンドプロンプト(正確にはパッチファイル)から標準/準標準の機能で「ハードウェアの安全な取り外し」と同等の処理を実現しようと試みているのだが、未だに成功する気配がない。

今のところ得られている結果は「そもそもアンマウントすらできない」か「運用上問題となる副作用が発生する」のどちらかである。

1. PowerShell : Shell.Application 経由で Eject

PowerShellを使ってCD-ROMドライをイジェクトするネタがあるのだが、同じ方法でUSBデバイスもいけるという情報を得たので、試してみた。

# 実行例 - F: を取り外す場合
(New-Object -ComObject Shell.Application).Namespace(17).ParseName('F:').InvokeVerb('Eject')

結果:アンマウントすらされない。

2. Windows Sysinternals : sync のオプション -e を使う

Windows Sysinternalsのsync.exe/sync64.exeのオプション-eでイジェクトできるとの情報を得たので、試してみた。

:: 実行例 - F: を取り外す場合
sync64.exe -nobanner -e F

結果:アンマウントすらされない。

3. 標準コマンド : mountvol を使う

標準コマンドmountvolのヘルプを見たところ、オプション/pにそれっぽい内容の記述があったので、試してみた。

:: 実行例 - F: を取り外す場合
mountvol F: /p

結果:ディスクがアンマウントされ、オフラインになる。その後にタスクトレイでの取り外し操作は必要だが、既にオフライン状態なので、直ぐに取り外し可能状態になる。

問題点:この後ディスクの電源をONにした時、ドライブレターの割り当てが外れている。例えば「mountvol F: /p」でFドライブのUSB-HDDを取り外した場合、そのUSB-HDDを再度接続した時、ドライブレターが全く割り当てられない(なのでエクスプローラーの「デバイスとドライブ」からはUSB-HDDにアクセスできない)。

4. PowerShell : Set-Disk でオフラインにする

PowerShellのSet-Diskでゴニョゴニョできる、という情報を得たので、試してみた。

# 実行例 - F: を取り外す場合
Get-Disk | ?{ $_.FriendlyName -eq 'I-O DATA HDJA-UT' } | Set-Disk -IsOffline:$true

結果:ディスクがアンマウントされ、オフラインになる。その後にタスクトレイでの取り外し操作は必要だが、既にオフライン状態なので、直ぐに取り外し可能状態になる。

問題点:この後ディスクの電源をONにした時、ディスクがオフラインのままになる。なのでSet-Diskを使うなどして明示的にオンライン状態にしないとUSB-HDDが使えない。

書籍購入:『Kotlinプログラミング』

あとで書く。

Kotlinプログラミング

Kotlinプログラミング

もうひとつのTDD

5年前の今頃は、なにやらTDD熱かったらしい。

TDDには全く何も取り組んでいない身なのだが、しかし傍から見聞きしているだけでも、TDDには参考となる面が色々とある。

ただし、それらの「参考となる面」は、TDDそのものというよりも、TDDによる副産物による部分が多い気がしてならない。

本エントリでは、TDDの部外者がTDDを見聞きした上で有用であると感じた面を挙げることで、TDDの別の側面にフォーカスを当ててみたいと思う。

プログラマ ミーツ ソフトウェア・テスト:第三次接近遭遇

よくあるTDDの例では、少なくとも(伝統的な開発工程でいう)詳細設計および実装のフェーズと単体テストが一体化している。この結果として、プログラマはどうあがいてもソフトウェア・テストの一部工程(下位Vモデルでのテスト工程の一部)と主体的に関わらざるをえなくなる。

よく考えれば、これは意外と革新的なことだったのではないかと思う。

ものすごく伝統的な職業プログラマの世界では、プログラマとテスターは分かれているし、双方の間に厳然たる身分差があることも多い。プログラマはコードを書き、書いたコードがビルド可能かつ何となく動いているっぽいことを漫然と観察するだけ。テスターはアプリ/システム全体を対象としたテストケースを書き、テストを行い、不具合報告書をあげるだけ。両者は異なる身分で、時にいがみ合っていることもある。

そのような世界においては、一般的な「テストの知識がある/テスト経験がある」プログラマとは、基本情報技術者試験あたりでテストの基礎知識をおざなりに学んだか、初めてプロジェクトに配属された際の役割がテスターだった程度で、コードを書くようになってからはテストとは無縁だったりする。「そんなこたぁテスターにやらしときゃええのよ」。

私自身はそこまで「ものすごく伝統的な職業プログラマの世界」な場面に直面したことはないが、組み込み開発の「プログラマが責任もって単体・結合テストを担当」なプロジェクトと、PCアプリの「単体・結合テストは行わず、アプリ全体のシステムテストで済ます」プロジェクトの双方を経験している。

その経験を元に、誤解を恐れずに書くなら、ソフトウェア・テストに通じているプログラマと通じていないプログラマとでは、明らかにコードの書き方が異なる。特に、単体テスト結合テストでコードの正当性を検証すること」を強く意識しているコードと、その辺りを全く意識していないコード(単体テスト結合テスト形式的にしか実施していないコードを含む)の間には、その書き方に差異が見られる。

ソフトウェア・テストと真剣に向き合っているプログラマは、よくある単純な不具合事例を回避するために防御的にコードを書くし、問題が起きやすい部分(例えば境界値がらみとか、マルチスレッドでの排他など)を実装する際には細心の注意を払う。境界値を気にかけるプログラマは、境界条件における極端なふるまいを考慮し、ときに設計・仕様の不備や矛盾に起因する問題を実装前に発見する。真剣に単体テストを行うプログラマは、段々とテスタビリティを考慮したコーディングをするようになる。「単体テストによる正当性の検証」を視野に入れたプログラマは、できるだけ結合度が低く単機能の関数/メソッドを志すようになる。

ソフトウェア・テストの知見の有無は、書いたコードの品質に良い影響を与える。つまり、ソフトウェア・テストを強く意識することで、テスト実施の有無にかかわらずバグの数が低減する可能性がある。

その意味において、プログラマがテストのことをあまり意識する必要がなかった伝統的な環境から、プログラマが低水準のテスト(単体テスト結合テスト)を通じてテストのことを強く意識する切欠を与える環境への移行は、ソフトウェアの品質を向上させるチャンスではないだろうか?

無論、従来の延長線上に「単体テストをちゃんと実施しましょう」という工程を付け加えるだけでも効果があるかもしれないが、TDDの場合、(「そんな単体テストで大丈夫か?」的問題は発生しうるものの)実装と単体テストが不可分であるので単体テストをサボりにくい、という利点があるといえる。テストをやらずに済むならなにも意識せずにスルーできるが、テストせざるをえないなら否応なく意識することになるのだ。

単体テストによるコスト削減の可能性

TDDの話は、結構な割合で関数/メソッド単位の単体テストとセットになっている。これはつまり、ソフトウェアの品質向上の面では、いわゆる「テスト担当者がシステムを触ってバグを出す」というレベルのテストとは別に、よりソースコードに密着した開発者テストを実施することも有効である、ということだ。

(実施するテストが増えている点に注意。TDDを行ったからといって、従来の「テスト担当者によるテスト」が不要になるわけではない)

このことは、実のところ今更書くようなことではない。遥か昔から「単体テスト結合テスト・統合テスト・システムテスト」と言われてきたではないか。手垢にまみれて古くさく役に立たない(と一般的に言われてそうな)基本情報技術者試験に出てくるほどに鉄板のネタだ。

組み込み開発のように高品質が求められる分野では、関数/メソッド単位での単体テストを実施することが多い。形式的に、ではなくキッチリと実施していれば、それなりにソフトウェアの品質向上に結びつく効果がある。でなければ、単体テストが行われなくなって久しいはずだ。

組み込み業界にいる私からすれば、関数/メソッド単位の単体テスト結合テストは「プログラマの嗜み」というか、「開発者テスト」として責任を持って行うべきものだ。本職のテスター*1には劣るが、ソフトウェア・テストの基礎知識を学んだ上で実務への適用方法を試行錯誤するのは当然のことだ。また、単体テスト結合テストで得られた知見から、コードの書き方を見直すこともある。

しかし一方で、その組み込み業界においても、例えばデバイスと連携するPCアプリやモバイルアプリの開発では、様々な事情で関数/メソッド単位での単体テスト結合テストを行わずにアプリ全体を対象としたテスト(システムテストなど)で済ましてしまうことがある。

そういうアプリは、一見して正しく動作しているようで、実は隠れた爆弾を抱えていたりすることがある。そんなアプリの改修に関わって痛い目に遭った経験がある私としては、最低でも関数/メソッド単位の単体テストぐらいはキッチリと行ってほしいのだが……。

ソフトウェア・テストは、ある意味においてコンクリートのひび割れや空洞の検査に似ている。アプリケーション全体をブラックボックスと見立てた普通のシステムテストは、コンクリートを目視で検査するようなものだ。優秀なテスターなら、打音調査や超音波による検査を行うだろう。

しかし、目視や打音調査などよりも確実な手法がある。コンクリートをスライスして断面を調べることだ。コンクリートの検査では不可能だが、ソフトウェア・テストでは可能だ。コードリーディングによるレビュー、静的解析ツールによるインスペクション、関数/メソッド単位の単体テスト結合テストが該当する。

関数/メソッド単位の単体テストが行われないのは、効果が分かりにくいからだ。あるアプリケーションを実装まで行い、その後の工程を「同じ環境、同じ人員」にて「単体テストあり」と「単体テストなし」に分けて行うことが可能ならば、両者の比較から有意な差の有無が判明するだろう(と私は妄想している)。しかしそれは不可能な話だ。

この不可能な話が可能だと仮定すると、関数/メソッド単位の単体テストによって得られると予想される効果は、ソフトウェアの品質向上と、結合テスト以降(リリース後の保守フェーズを含む)の「不具合発見→設計・実装への手戻り」によるコストの低減だろう。

前者の「品質向上」は、関数/メソッド単位の単体テストを実施すべき論拠としてはやや弱めだろう。顧客からみて「一定以上の品質が保たれている」と感じられるのなら、そこからどれだけ品質を向上しても、直接的な収益に結びつかないからだ。職業プログラマの場合、論理的には「費用対効果」より求められる品質が決まるはず*2なので、仮に「関数/メソッド単位の単体テストをせずとも要求された品質をクリアしている」という状態*3ならば、関数/メソッド単位の単体テストの実施は「過剰品質」となる。

しかし結合テスト以降の工程のコストが低減するのなら話が変わってくる。開発期間の短縮や、場合によっては売上原価の圧縮に結びつくからだ。

また、システムのライフサイクルという観点では、経験的に、関数/メソッド単位の単体テストを行っていないコードは「保守・改修」の工程に禍根を残すことも多い気がする。この直感が正しいならば、関数/メソッド単位の単体テストを実施することによりライフサイクル全体におけるコストが低減する可能性がある、とも考えられる。

こういった、計測の難しい部分のコストへの影響を明らかにすることが可能ならば、単体テストの重要性について開発者以外に説明する際に、有力な論拠となりうるだろう。計測が可能か否か、またどう計測するか、という問題はあるが……。

テスタビリティの高さ≒コードの品質の良さ

先に書いたが、「単体テスト結合テストでコードの正当性を検証すること」を強く意識しているコードと、その辺を全く意識していないコードには、大きな差異がある。

まずは簡単なところから。

良いコードを書こうとするプログラマは、よくある単純な不具合事例を回避するために、防御的にコードを書くようになる。例えば、C言語系統の言語で度々話題となる「{}を付けるか否か」問題だ。防御的にコーディングする人は必ず{}を付けるし、{}を省く人でも明らかに「この程度なら大丈夫だろう」というケース(例えば単文ifでの即時returnによる引数エラーチェックの羅列とか)にとどめることが多い。この派生で、結果を返さずに単に副作用だけを期待する*4関数形式マクロをC言語で実装する際、処理全体を「do {} while (0)」で囲むこともある。

防御的にコードを書こうとするプログラマは、同時に、よく問題が起きやすい部分を実装する際には細心の注意を払うようになる。例えば境界値絡みとか、マルチスレッドでの排他・競合などだ。

特に境界値・境界条件を気にかけるプログラマは、境界条件においてプログラムがどう振る舞うか、またどう振る舞うべきか、時間を割いて検討することが多い。境界条件では「極端な振る舞い」が発生する可能性がある。その辺を考察することで、時に設計・仕様の不備や矛盾に起因する問題を実装前に発見することがある。

基礎的な例だと、入力が「指定された範囲内の整数値」なら範囲の直前・直後及び範囲の最小値・最大値のケースを、文字列なら「0文字の文字列」のケースを、ファイルなら「0 byteのファイル」のケースを、「1行1レコード」のデータファイルなら「改行コードのみの行を含むファイル」や「ファイル末尾に改行コードが存在しないファイル」のケースを考慮するものだ。

まあ、ここまでは、テストによる正当性の検証にこだわっていなくても、良いコードを書こうとしている人なら取り組んでいることが多い。

では単体テストに取り組むようになると、ここからどう変化するのだろうか?

真剣に単体テストを行うプログラマは、段々とテスタビリティを考慮したコーディングを行うようになる。

例えば複雑な条件式は、組み合わせを網羅するテストケースを考えるのが難しいし、また実際にテストする際に「どこまでテストしたか?」という点が曖昧になってしまうものだ。だからテスタビリティを考慮して、複数の「単純な条件式」に分解するようになる。

C言語系の言語にある三項演算子は、便利な反面、複雑で怪しげなコードを書いてしまいやすいという欠点がある。そこで代替案として、短いけど入れ子になって複雑な三項演算子の塊を、あえて複数のif文を用いて「冗長だけど分かりやすい」コードに書き直すことを提案される場合がある。これなども、実のところテスタビリティの観点から同様な結論に達することがままあるものだ。「多少冗長だけど分かりやすい」コードの方がテストケースを考えやすいからだ。

また「単体テストによる正当性の検証」を視野に入れているプログラマは、可能な限り結合度が低く且つ単機能の関数/メソッドを書こうとする。理由は簡単で、その方が単体テストのテストケースを考えやすいからだ。

単純な話、数千行あるような最長不倒関数には大抵色々な機能が詰め込まれているものだ。そんな関数のテストケースを考えるのも、テストケースを充足できるに足るような自動テストスイートを用意するのも、ライフワークとなりかねない危険で複雑な作業だ。

そのような危険を避けようとすると、自然と各関数は単機能になるし、引数の数は少なめになるし、行数は短めになるし、グローバル変数インスタンス変数の参照を抑えた結合度の低い実装になる。結果として、実装された関数は、自然とモジュール強度(凝集度)が高く且つモジュール結合度が低いものが多くなる。

不思議なもので、テスタビリティを考慮してコードを書くことで、自ずとコードの品質が良い方向に変化するのである。

この辺りは、テスタビリティなんて考えず、形式的にしか単体テストを行わない開発者には無理な話だろう。

「テストケース作成」による「仕様」の網羅、明確化

(先に誤解されないように書いておく。TDDにおけるテストは、単体テストとは限らない。TDDで行うのは「そのプロジェクト/プロダクトに見合ったテスト」だ)

仕様策定時に見逃されていた矛盾や曖昧な点に、実装フェーズ終盤に差し掛かってから気づいたことはないだろうか? その矛盾や曖昧な点をどうするか確認した結果、今までの実装の少なくない部分を破棄してやり直す破目になったことはないだろうか?

TDDは「Test」の名を関する手法な訳だが、ソフトウェア・テストには少々興味深い特性がある。テストする前にバグやバグがありそうな場所を発見できることがあるのだ。

ホワイトボックス・テストやグレーボックス・テストを除けば――要はブラックボックス・テストなのだが――テストケースは仕様書を読み解いて作成することになる。ここで、テストの期待結果を明確に記述できないテストケースを発見したなら、おそらく関連する仕様に抜けや漏れがあるか、仕様が曖昧であるはずだ。その周辺にバグが潜んでいる可能性がある。もしくは、不具合とまでは言えないが、お客様に確認していただいたら「そこって、そうじゃないんだよねぇ」とちゃぶ台返しとなるかもしれない。

別の例として状態遷移が挙げられる。仕様書に状態遷移図が書かれていたり、もしくは状態遷移図は書かれていないものの状態遷移っぽい部分が存在することがある。これを状態遷移表に書き直してみると、抜けや漏れ(=怪しい部分)があったり、仕様書には書かれていない「状態」を付け加えないとうまく状態遷移表にならない部分(=漏れ or 考慮し忘れ)があったり、そもそも状態遷移表に書き直せない(=矛盾がある)ことがある。そういった部分には、バグが潜んでいる可能性が高い。

ソフトウェア・テストは、テストを実施すること以上に、実施するテストを考えることが重要だ。テストケースを考えている時点で、未だテストを実施していないのにもかかわらず、バグがありそうな場所が見えてくることがある。

テストケース作成で炙り出せるバグは、仕様策定や設計の段階に遡ることができる問題であることも多い。これはつまり、言い方を変えるならば、「テストケース作成」という形で仕様や設計をレビューしているに等しい。通常のレビューとは別に、再度レビューを行うことで、仕様がより明確となるし、仕様書を隅から隅までチェックすることによる網羅も期待できる。

TDDでは割と早い段階でテストケースを作成する。それは、TDDの入門書のように単体テストかもしれないし、単体テストよりも上位のテスト(システムテストなど)かもしれないが、いずれもテストケース作成を通じて仕様や設計を網羅し、不明点を明確にし、漏れや矛盾をさらけ出す効果を期待できる。

もちろん、何も考えずに形式的にテストケースを作成してしまっては、何の効果も得られないだろう。仕様を網羅するように、且つ怪しそうな部分をピンポイントで狙い撃つ。よいテストケースの作成は難しい。作れるようになるまで時間がかかるものだ。

それでも、実装の前に良質なテストケースを作成できれば、仕様に遡るような問題を減らした状態でコードを書き始めることができる。実装フェーズや、その後のテストフェーズにて、仕様策定や設計の段階への手戻りが発生する可能性は低くなるはずだ。

自動テストスイートによる「気軽にテスト再実施」

TDDの話では、「関数/メソッド単位の単体テスト」と同じくテスト自動化もセットになっていることが多い。

テスト自動化は、初期投資のコストがかかる。例えば、関数/メソッド単位の単体テストの自動化は比較的コストが低い部類に入るのだが、それでも手間がかかる。忙しいと、ついつい単体テストともども省略してしまいたくなるものだ。UIのテストなど、システム全体にまたがるテストを自動化しようものなら、仕組みを作る/導入するコストは更に大きくなる。

しかし、一度仕組みを用意してしまえば、「これほど心強い味方はいない」と言わしめるほど役に立つ。ソフトウェアのバグ修正、既存機能の改良、新機能の追加の度に、修正を加えた部分のテストケースのみ手を加えた上で全体をテストし直すことで、追加・変更点の再テストと同時に既存部分のリグレッションテストを済ますことができる。

ソフトウェアに変更を加えた後の再テストは意外と大変だ。直接変更を加えた部分だけでなく、変更が波及するだろう部分もテストし、デグレードがないことを確認しなくてはならない。

デグレードを見つけるには、変更の波及が想定される部分を推定してそこだけテストするか、ソフトウェア全体を再テストするしかない。変更の波及箇所を推定する方法、実施範囲を抑えることができる反面、見落としによるテスト漏れが発生する可能性がある。安全なのは「全体を再テスト」だが、テスト方法が全て手動ならばコストが高くつくし、プレッシャーの下で手作業でテストしているならば、どこかで手順間違いなどのミスが起こりうる。

だがテストが自動化されていれば、ソフトウェア全体を再テストする際のコストが低くなる。コストが低ければ、気軽にテストすることができる。見落とされがちだが、「気軽にテストできる」ということは重要だ。

一旦リリースしたソフトウェアが、保守・改良されつつ長く長く使われ続けるほどに、自動テストは開発期間の短縮と一定品質の保持という効果をもたらす。パッケージソフトや組み込み製品開発など、競合が多数存在する分野においては、初版リリース後のバグ修正や新機能追加におけるフットワークの軽さは武器の一つとなりうる。

その意味においては、自動化の対象を単体テストに絞る必要はない。いや、もちろん単体テストの自動化も効果があるのだが、より広範囲の、システム全体にまたがるテストの自動化も重要となる。それこそ、Webアプリ界隈ではブラウザの互換性の問題もあり、GUIに関わるテストを自動化しようという動きが見られる。こまめに修正・改良とデプロイを繰り返したいのなら、テストの自動化は必須なのだ。

この特徴ゆえに、テスト自動化は反復型開発と親和性が高く、反復型であるアジャイル開発(特に変化を前提とするXP)にて取り上げられるのだろう。

テスト自動化については、保守コストが発生する点も考慮する必要がある。継続的に開発を続けていくプロジェクトでは、刻一刻と変化していく環境に合わせて自動テスト・システムも改良されていくし、プロジェクトが継続している以上は自動テスト・システムも存在し続けても大丈夫だろう。しかし「Ver.1完成→チーム解散→数年後にVer.2開発開始」というスタイルの開発では、例えばVer.1の自動テスト・システムをVer.2開発で使用しようと試みた際、改めて自動テスト・システムが動作する環境を構築し直す作業が発生するし、OSや開発ツールや開発マシンの変化に一気に追随する大きな手間もかかるだろう。

仮に今後、テスト自動化を広めていきたいのならば、この種の保守コストとどう向き合うべきか考える必要があるだろう。場合によっては、「プロジェクト」のあり方を議論する必要があるかもしれない。

まとめ

TDD部外者から見て、TDDには次のような有用な要素が含まれている。

  1. 開発者にソフトウェア・テストを強く意識させる(他人事だと思わせない)。
  2. よりソースコードに近いレイヤーのテスト(単体テストなど)の有用性を明示している。
  3. テストを意識することで、テスタビリティの高い、ソースコードの読者からみて見通しのよいコードを書くようになる契機となりうる。
  4. テストケース作成にあたり、開発対象の仕様を明確化し、漏れのないよう網羅しようという意識が(プログラマにも)働く。
  5. 自動テストにより、リグレッションテストのコストを引き下げる。リリース後のバージョンアップ等における開発期間を短くする効果がある。

上記の各要素、特に2~4は、よく見てみれば昔から指摘されてきた。決して新しい内容ではない。

単体テストは重要だ。見通しのよいコードを書くことも重要だ。仕様を明瞭にして漏れのないようにすることだって重要だ。

どれも重要だが、しかし現実のソフトウェア開発において軽視させることが多かった。明確な仕様もなく(それどころか断片的な仕様さえ1週間も経たずにころころ中身が変わる)、ろくに単体テストもされていない、見通しの悪い○○なコードで構築されたソフトウェアの何と多いことか。

TDDが巧妙なのは、ソフトウェアの実装フェーズにテスト(最低でも単体テスト)を組み込み、一体化させることで、プログラマを否応なくソフトウェア・テストの世界に引きずり込もうとしている点だ。しかも、プログラマにソフトウェア・テストを意識させることで、暗黙のうちに前述の各要素を強制させている。

もちろん、テストの追加を「単なる重荷」ととらえて、形式的にしかTDDしない開発者もいるだろう。そのような開発者の手にかかれば、TDDだろうと何だろうと効果はゼロだ。しかしTDDを契機にソフトウェア・テストに積極的に取り組み、単体テストを行い、テスタビリティの観点からコードの書き方を改善し、仕様の網羅・明確化という観点で問題を洗い出すようになる開発者だっているだろう。

個人的には、上記の点がTDDのメリットであり、これらのメリットが得られるのならテスト・ファーストに徹する必要は無いと考えている。しかし、私を含めて悪い意味での「怠惰なプログラマ」がサボってしまいがちなこれらの要素を、TDDは暗黙のうちに強制しようと働く。その意味でTDDは巧妙で、実に有用な興味深い手法だと言えるだろう。

追記

筆者は研究開発系のプロジェクトに関わることが多く、「そもそも、このアルゴリズムで大丈夫なのか?」という不明点が多々ある段階で設計・コーディングを行うことが大半である。

だから、試作品を何度も揉んで、ある程度「この方法で実現できそう」という感触が得られてから、関数単位での単体テストや、ライブラリ単位での統合テストに着手している。それより前に単体テストに着手すると、試作品を揉んだ結果として「データ構造を含む大規模な見直し」が発生したので単体テストも大幅に書き直す――なんてことが発生して、時間的コストがかかり過ぎるからだ。

そういう事情があるので、厳密なTDDは行っていない。ただし、単数単位の単体テストは実施しているし、単体テストの自動化もしている。

おそらく私の環境においては「試作品を何度も揉んで、ある程度『この方法で実現できそう』という感触が得られ」るという粒度が、TDDにおける「自分のシステムにおいて期待する振る舞いを記述するテストを書き、それをパスさせる」という手法に適した大きさなのだろう。

単体テストは重要な要素ではあるものの、単体テストが「おま環」でのTDDに適した粒度であるとは限らない――という点を考慮した上で、TDDについて評価すべきだろうし、評価結果を提示する時は「おま環」について併記すべきだろう。でないと議論が噛み合わないはずだ。

*1:本職のテスト担当の人は、優秀な人は本当に優秀だ。このバグ、どうやって見つけたのだろう……。

*2:実際には、まあ、色々とありまして……。

*3:現実的に、このような状況がありうるか疑問ではある。

*4:Pascalのprocedureのような感じ。

argparseで引数を1つとるオプションをいい感じで扱えるようにしたい

ここ2~3年ほどPythonで小さなコマンドライン・ツールを書く機会が何度かあったのだが、argparseでコマンドライン引数を解析する時、引数を1つだけとるオプションの扱いに難儀している。

ArgumentParser.add_argument()のキーワード引数nargsに1を設定すればよいのだが、解析結果がリストで返される。なので「args.option_foo[0]」みたいにインデックスを指定する必要がある。APIの思想としては、この挙動は理解できる。しかしAPIを使う側としては面倒くさいのだ。

特に面倒なのが、省略可能なオプションでかつ省略時に適当な既定値が無いケースだ。この場合、「args.option_foo」は空のリストである可能性がある(必須オプションや、キーワード引数defaultを使って適当な既定値を用意できるケースでは、リストが空になることはない)。だから「if args.option_foo:」のように空リストか否か確認するコードと、「args.option_foo[0]」で値を参照するコードを書き分ける必要がある。そうしないと空リストなのに「args.option_foo[0]」で値を参照しようとして例外となる。このコンテキストの切り替えが面倒なのである。

大体「複数の値を取りうるオプション(結果として値が0個になる可能性もあるオプション)」の場合はイテレータを使うので、例外が発生することはまず無い。argparseのnargsの仕様からすれば、引数を1つだけとるオプションが例外的なのである。そして私が作るツールでは「引数を1つだけとるオプション」が大多数を占めているので、例外的状況ばかりなのである。

上記挙動を嫌って、少し前まではキーワード引数nargs'?'を指定していた。この方法なら解析結果を「args.option_foo」という書き方で参照できる。しかし難点として、コマンドライン引数にて「引数無しでオプションを指定する」ということが許容されてしまう。本来は「--option-foo value_foo」みたいに指定して欲しいところを「--option-foo」とだけ指定された時に、argparseが許容してしまうのである(というか、それが'?'の時の仕様通りの挙動なのだが)。

もっと、こう、何か「いい感じ」にできないか考えた結果、現時点ではキーワード引数nargsに1を設定した上で、ArgumentParser.parse_args()ArgumentParser.parse_known_args()の戻り値に以下の関数を適用して「いい感じ」に変換することで対応している。

def simplify_options(opts, as_list=[]):
    def ispublicattr(attr_name):
        return attr_name and attr_name[0] != '_'

    def simplify_attr(attr_name):
        attr = getattr(opts, attr_name)

        if not isinstance(attr, list):
            return attr
        elif attr_name in as_list:
            return attr
        elif not attr:
            return None
        elif len(attr) > 1:
            return attr
        else:
            return attr[0]

    attrs = {name: simplify_attr(name) for name in dir(opts) if ispublicattr(name)}
    Options = collections.namedtuple('Options', list(attrs.keys()))
    return Options(**attrs)

関数simplify_options()のキーワード引数as_listに登録された名前以外のリスト型のattributeを展開した上で、namedtuple化して返している。

使い方はこんな感じ。

parsed, rest = parser.parse_known_args()

# '--foo' と '--bar' の引数はリストのままにしておく。
opts = simplify_options(parsed, ['foo', 'bar'])

リストのままにしておきたいものを明示する必要がある点が面倒と言えば面倒だ。しかし実際のところ、コマンドライン・ツールにて「複数の引数を受け取るオプション」を設ける機会は意外と少ない。だから「毎度面倒な手順を踏む」という状況にはならない。

しかし、もっと良い方法は無いだろうか? 私が見落としているだけで、こんな関数を用意しなくとも、標準の機能でうまい具合に扱える気がするのだが……。

「C言語書けます」のボーダーラインはどこか?

C言語を覚えると組込み系の採用に有利」みたいな就職・転職向けの文言を見る度に、世の中そんなに甘くないと言いたくなるのです。

多分、組込み開発に限らず、例えばLinuxコンソールアプリ等でC言語を散々使った人も同意するのではないかと思うのだけど、Cプログラマは他のCプログラマに厳しいものだ。

そもそもC言語自体が鋭い刃物に例えられる代物で、メスにもドスにもなりうる取り扱い注意な言語だ*1。だからCプログラマは誰しも、C言語について一言居士となる。

そんな連中からすれば、C言語の基本文法を知っていて簡単な課題を解ける程度の人は、「C言語を書ける人」には含まれない。

では、どのレベルの人が「C言語を書ける人」に該当するのか?

よくよく考えてみると、次の点をクリアしているか否かがポイントではないかと思う。

  1. C言語の各種文法の「効率的で理にかなった使い方」をある程度知っている。
  2. C言語とメモリモデル(スレッドじゃなくてハードウェア・アーキテクチャの方)にまつわるアレコレをある程度把握している。

(1) は、例えば普通のCプログラマは関数の引数として構造体の実体ではなくポインタを指定するように実装するものだが、そういう癖を身に着けていているか、そうする理由を知っているか否か――ということだ。

この辺は、師匠となる本や人に揉まれながら、C言語でそこそこの大きさの実用的な何かを実装した経験によって成長する部分だろう。

ぶっちゃけた話、(1) はC言語に限らない話で、他のどの言語でも同じだ。C言語が特異なのは (2) の存在だ。

(2) は、要するに『Cプログラミング専門課程』で語られているような、C言語とメモリに関する諸々をどこまで体得しているか、という話だ。

プログラミング言語はハードウェアに対するある種の抽象化層として機能するのだけど、C言語はその抽象化層が薄くて且つ容易に穴をあけることができる*2。で、穴の中にはメモリ空間が広がっている*3

穴は簡単にあいて容易に拡がる代物であるし、Cプログラマもまたよせばいいのに好き好んで穴をあけようとする連中なもので、どうしても穴の中のメモリとの付き合い方を覚える必要がある。うまく付き合えないCプログラマは翌朝顔にひっかき傷をこさえて出勤する定めにある。

そもそも組込み以外のソフトウェアであっても、ファイルやTCP/IP経由で外部とバイナリデータ*4でやり取りすることになった時点で「メモリ上のデータの形式」というメモリとのお付き合いが始まるものだ。組込みでなくともC言語を使うなら、メモリとの上手な付き合い方は必須の教養だ。

抽象化層の薄さと穴のあけ易さはC言語(とC++)の特異な部分だ。他の言語はもう少し厚い抽象化層を持っているし、穴があきにくいように工夫されている。だから大抵の言語では抽象化層(言語機能とその有効な使い方)を学べば大半の物事が済むのだが、C言語では言語機能という抽象化層を学ぶだけでは不十分で、穴の中のメモリを知る必要がある。

で、メモリとの付き合い方を毎年指導するのがしんどいので他人任せにしたいのだが、唯一使える本だと思っている『Cプログラミング専門課程』は入手困難なのである。

Cプログラミング専門課程

Cプログラミング専門課程

代わりの本、何か無いかなあ?

*1:この例えの元ネタは藤原博文氏の『Cプログラミング診断室』である。

*2:だからこそドライバのような「ハードウェアを直接叩く」系のソフトを書くことができるのだが。

*3:組込みソフトウェアの場合は物理メモリだし、WindowsLinuxなどのアプリの場合は論理メモリだ。

*4:ここでは「テキスト形式ではないデータ」ぐらいの意味で使っている。

書籍購入:『基本からしっかり身につくAndroidアプリ開発入門 Android Studio 3対応』

軽く中身を見た。

差し詰め本書は「お仕事でAndroidアプリを書くことになったAndroidアプリ以外アプリ開発経験がある職業プログラマ」向けの入門書、といったところだろうか。

実際に、まえがきの「本書の特色」に以下の記載がある:

そのため、本書では特に業務でのアプリ開発でまず押さえておかなければならないポイントに絞って解説を行っています。

例えばListViewを使ったアプリの例題の場合、類書だと単純にArrayAdapter<String>で文字列を表示するだけで終わることが多いのだけど、本書ではlistitemのレイアウトファイルを作成して独自のアダプタを実装するところまで説明されている。で、仕事では独自のアダプタを実装する構成のアプリが多い訳だ。

本書について、ネット上では「構成が直感的じゃない」という指摘があって、まあ確かに一般的なAndroidアプリ開発の入門書と比較すれば直感的ではないのだけど、でも順を追って読み進めれば問題にならない。そして職業プログラマは、そういう読み進め方に慣れているものである :)

だから急にAndroidアプリ開発の仕事が降ってきた人が入門書を買う際には、本書を候補に入れることを推奨する。

書籍購入:『Kotlinイン・アクション』

軽く中身を見た。

Kotlinイン・アクション

Kotlinイン・アクション

本書は、Javaでの開発経験がある人が、『Kotlin入門までの助走読本』やKotlinの公式リファレンスなどで基本を学びつつ、同時に本書を参照して言語としてのKotlinの理解を深めていく――という使い方に向いているように思う。

裏を返せば、本書を読み進めるには「一定以上のJavaの経験」が必要であるし、「Kotlinの基礎文法」の学習に関しては他の文書を併用した方が良い。

それなりに人を選ぶ本だ。

「組込み開発=C言語」というイメージはどこまで正しいか?

組込み開発でのプログラミング言語というとC言語(時にアセンブラ)が真っ先に挙げられる気がするのだが、実務的にどこまで正しいのか書いておこうと思う。

結論から言うと、組込みプログラマの立場としては「組込みプログラミング=C言語」は概ね正しいのだが、組込み業界という視点では「C言語以外も結構使われている」ということになる。

そもそも組込み業界ではどんなソフトウェアが作られているだろうか?

組込み業界と言っても結構広いので、分野によって色々と異なるものだが、私が関わっているのは「ガジェット・スマート家電」寄りの分野だ。作っているソフトウェアは、組込みシステム向け三層アーキテクチャ(ドライバ・ミドルウェア・アプリケーション)が適用される程度には大きい*1。OSはRTOSの類が大半だ。

で、外注も含めて、書かれているソフトウェアは以下のような感じだ。

  1. ハードウェア制御用のドライバ・ファームウェア
  2. 移植性のあるミドルウェア
  3. 組込み機器のアプリケーション層
  4. 組込み機器をパソコンと接続するためのWindowsデバイスドライバ
  5. 組込み機器と連携するWindowsアプリケーション
  6. 組込み機器と連携するmacOSアプリケーション
  7. 組込み機器と連携するAndroidアプリケーション
  8. 組込み機器と連携するiOSアプリケーション
  9. 組込み機器と連携するWebサービスのクライアントサイド
  10. 組込み機器と連携するWebサービスのサーバサイド

今の時代、リッチな組込み機器はUSBやネットワーク経由で他のサービスと連携する仕組みを備えている。で、連携先の数だけ必要なソフトウェアが増えている。

接続方式が一般的なプロトコルで十分であるとか、外部デバイス側のフレームワークで提供されている通信機能で十分な性能が得られるような場合は、外部デバイスとのやり取りの部分を仕様で定義した上で、外部デバイス側のソフトウェアの開発を外注することが多い。

例えばWebサービスのサーバサイドが一般的なRESTアーキテクチャのWebアプリケーションで十分ならば、Webアプリケーション側のWeb APIのURLや通信データの形式を決めた上で、Webアプリ専業の会社に発注してしまうのだ。

しかしちょっと特殊なことを実現しようとなると、サーバサイド側の実装も自分たちでコントロールしたくなるので、サーバサイドの初期実装まで自分たちで行うことになる。その後はシステムの性格次第で、運用だけ外注してしまうか、運用と改修を共に外注してしまうことになる。

同じことはモバイルアプリにも言える。機器との接続方法がSDKに用意されている一般的な機能で済むとか、アプリのUIが凝ったものでなくて普通のUI部品で済むようなケースでは、外注することが多い。しかし特殊なことを実現したいとか、UIを凝りに凝ったものにしたいとか、そういう事情がある場合は内製することが多い。

先の一覧でいうなら、(1)〜(3) はほぼ確実に自分たちで開発するが、(4) 以降は開発するシステムの性格次第だ。アプリのデザインや性能要求が絡んでくる場合は、自前のPCアプリ・モバイルアプリ開発部隊を持っていたりする。

実際に組込み機器内で動作するコンポーネントである (1)〜(3) のうち、ステレオタイプの組込みプログラミングに最も違いのは (1) だ。ドライバやファームウェアを担当している人は名実ともに組込みプログラマである。彼らの主要言語はC言語だ。その意味で、組込みプログラマの感覚では「組込みプログラミング=C言語」となる。

一方で (2) のミドルウェア屋は「ハードウェアから分離させた移植性のあるモジュール」を開発するのが主要業務なので、C言語使いではあるものの、組込みプログラマという意識はやや薄い。(3) の組込み機器のアプリケーション層担当ともなると、ミドルウェアやドライバのAPIを叩くのでハードウェアを直接制御することは無いし、LCD表示用のGUIツールキットとの絡みでC++を使うことも多々ある。

なので、組込み業界の中のどの分野かに依存する話ではあるが、組込み系の企業に就職した人が使うプログラミング言語C言語であるとは限らないし、誰もがハードウェア制御用のコードを書ける訳でもない、という現状がある。

サービス展開も含めて規模の大きなシステムの場合、そのシステムを開発している企業の中で分業化が進んでいるものだ。だから、若手の頃から組込み機器のアプリケーション層の開発をバリバリやってきて、C++ベースのオブジェクト指向プログラミングの手練れになった反面、ハードウェアを直接叩くドライバの開発経験がない――みたいな人が実在したりする。

私自身、キャリアの大半が三層アーキテクチャミドルウェア層の開発なので、組込みらしいハードウェア制御プログラムを書いたのは2度だけだし、その開発も業界に入ってから10年以上経ってから偶然遭遇したものだった。

ということで、分野を選ぶ必要はあるものの、C言語が使えなくても組込み業界に入ることは可能だ。結構、機器と連携するモバイルアプリ開発とかで、AndroidiOS向けアプリ開発の手練れを探している企業は多いと思う。

ただし、少し特殊な要件がある場合は、例えばモバイルアプリ開発でもC言語C++の知識が求められることがある。リアルタイム性に関する性能要件がやや厳しいので核となる部分をCやC++で実装する必要があるとか、機器とのTCP通信にて独自のプロトコルを使う必要があってクライアント側としてCやC++で実装されたモジュールが提供されるので組み込む必要があるとか、そんな感じ。なのでAndroidならNDKの、iOSならC++11のスマートポインタとObjective-C++の知見*2が要求されることになる。

(だから先の一覧のmacOSiOSAndroidアプリの開発言語にC++を含めている)

結論として、繰り返しになるが、組込みプログラマ的には「組込みプログラミング=C言語」という構図は概ね正しい。しかし組込み業界という俯瞰した視点では「C言語以外も結構使われている」ということになる。業界にはデバイス制御プログラム以外のソフトウェアを書いている人もそれなりにいるのだ。

なお、上記の議論は「製品としてリリースするソフトウェア」を前提としている。開発用の小ツール――内製ツールやプログラマの個人用ツールとしては、テキスト処理に長けたスクリプト言語Excel制御用の言語(VBAPowerShell)がごく普通に採用されているものだ。ただ、全般的にC言語C++(better C)畑の人が多いので、Windows向けデスクトップアプリ開発では(CやC++のモジュールをそのまま組み込むことができる)MFCやQtが選択されやすい傾向にはあると思う。

*1:規模の小さなマイコン・プログラミングだと、せいぜい「ドライバ・アプリケーション」の二層のアーキテクチャであるし、モノによっては明確な分離がされていないごった煮の実装となっている場合もある。

*2:CやC++のモジュールをObjective-C++でラッピングして、インタフェースをObjective-Cとして見せることで、Swiftから楽に使えるようにするのである。その際に、Objective-CのARCと歩調を合わせてリソース解放するように、C++11のスマートポインタを使う。