アーキテクト

ソフトウェアアーキテクチャ/システムアーキテクチャ(以下、アーキテクチャ)が何がという問いに対して、IT業界は、混乱しているか議論を避けているように見える。私は、個人的にはアーキテクチャが何がついておおよその見解を持っているが、記事として公開するには客観性が足りない。

アーキテクチャが何かわからないが、しかし業界には「アーキテクト」という肩書きが存在する。要約すると、CTOと「チーフデザイナ」「プログラムリード」の間に位置する職位というのが定義に見える。「アーキテクト」を名乗るべき人は、このような曖昧な肩書きを名乗ることに違和感を覚えないのだろうか。分析やオブジェクト指向の基本は、物事に正しい名前を与えることではないのか。

 

アーキテクチャと同様に、ソフトウェアアーキテクト/システムアーキテクト(以下、アーキテクト)についても、やはり私はおおよその見解しか持っていない。しかし、間違いなく、次のような仕事はアーキテクトの仕事であると考えている。

 

あなたがアーキテクトであるとする。そして、いまひとつのシステムの開発について検討している。

そのシステムは、大規模だが、単一の目的・責務のためのもので、技術的な障害は何もなく、外部とのI/Fもなかった。要は、工数がかかる以外は、なにも難しいことはなかった。もしあなたがひとりでやれば、時間はかかるが簡単に開発できるだろう。

あなたは、このシステムの技術的にもっとも良いデザインは、それを単一のプログラムとして普通に設計することだと判断した。しかし、最終的に開発チームへ展開した計画は、システムを二つのサブシステムに分割し、多少の手間をもってしてもその間のI/Fを早期に確定させるための作業を行うことだった。

あなたは、なぜ、技術的に最適な設計を捨て、システムをふたつに分割したのだろう。

 

この思考実験への答えの一つは、「開発チームが二つのグループに分かれていた=設計を組織形態に合わせた」というものだと思う。そして、その場合は、私は先に示したようにアーキテクチャという技術的視点で組織の問題を吸収することがよい選択だと思う。

 

さて、この判断は、アーキテクトにしかできないだろう。

なぜならこの判断は、要求、仕様、分析からは導出できず(つまりトップダウンではなく)、実装やクラス設計からも導出できない(ボトムアップでもない)。そうすると、プログラマや、プログラマを統括しているリーダでは問題を発見したり解きようがない。一方、プログラムマネジャーなどの非技術スタッフでは、「チームをふたつにしないとどうなるか」などわかるべくもない。

 

アーキテクトは、ユーザのために価値を作り出すエンジニア(プログラマ)、のために価値を作り出すエンジニアである。したがって、彼らがどういう動機でこの仕事に携わっているのかや、開発中にどういう心理状態であるかを理解していなくてはならない。それは、製品におけるUX: User Experience が重視されてきた昨今の事情とまったく一緒である。当然、物作りをしたことのない人物にアーキテクトは務まらない。

もちろん、真のアーキテクトが行う行動をパターン/テンプレートとして、それを模倣すればアーキテクトらしい振る舞いはできるだろう。しかし、彼が何者であるかと、彼の職は何かというのは別であるから、とりあえず本稿では「本物のアーキテクトとは何か」にフォーカスして述べている。

 

世の中で見かける「アーキテクト」は、厳密には「システムデザイナ」というべきと思う。彼らは、システムレベルでの設計を行っているのであって、設計以外の何かをしているようには見えない。もちろん、彼らの行為にはアーキテクチャと呼ぶべき要素も含まれているが、設計とアーキテクチャの区別を「規模」でしているように見える。そんなアホなことはない。

また、「ITアーキテクト」という肩書きで、会社の経営とITとの統合を支援する仕事もあるが、彼らの扱う「アーキテクチャ」は組織のアーキテクチャであって、ソフトウェアのアーキテクチャではない。であるから、わざわざITなどとつけて混同を誘うのは誠実ではないと感じる(そもそも組織という言葉が「人の集合のアーキテクチャ」の意なのに)。

 

なんだかまとまりがなくなってしまった。

アーキテクチャがわからないのに、アーキテクトがたくさんいるのはおかしい。

・私はアーキテクチャを定義し切れていないので、アーキテクトが何かもいえない。

・しかし、アーキテクトの仕事のひとつはわかる。それは、顧客のための開発者に対して、技術的に貢献することだ。

アーキテクチャは、重要だが、根本的に難しい問題領域であると感じる。すでに、人のポジショニングの道具になってしまっているし、そういうフォースが働きやすい概念だ。技術的に正しく定義づけられることは未来永劫ないかもしれない。

生産性の「向上」?

なにげなく生産性向上などと書いてしまうが、生産性は改善するものじゃなかろうか。

なぜなら、生産する主体は人だから。人に対して向上というのは、なにか違和感がある。まるで機械みたいだ。確かに、ソフトウェア工学という似非学問の大部分は人を機械として扱うことから出発しているから、それで正しいのかもしれないけど、自分までそれに与したいとは思わない。

「向上」が適切なのは、たとえば統合開発環境Visual Studio など)について、生産性向上というのなら分かる。これなら要は「VSが使いやすくなった」ということだからだ。ほかにも、信頼性向上というのも分かる。信頼性の対象は製品だから。

 

これからは、主体が人なのか製品なのかは区別しよう。

そして、生産性の改善に貢献していきたいと思う。

リファクタリングは手術?

以前、「リファクタリングは手術に似てる」という人に会って、そうだなあと納得したのだけれど、どうも違う気がしてきた。そもそも手術は、人体が損傷・劣化するという前提があり、その変化した分を元に戻すか代替するということだと思う。対して、ソフトウェアは決して劣化しないことが非常に重大な特徴であることは明らかで、この前提の違いは無視できないだろう。

また、手術は、「何が健全なのか」について明らかな指針がある。それは健康体であって、つまり究極的なTO-BEが明確にある。対して、ソフトウェアはそもそも劣化しないのだから、リファクタリングが目指すべき姿は究極的には存在しない(部分的な模範=パターンはある)。

かようによくよく考えれば、「リファクタリングは手術だ」というのは、「コードをいじる」という感覚以上には類似性が乏しいように思う。

 

じゃあリファクタリングとは何かというと、ふっと思いつくのは「鏡を磨く作業」に似ている。

ソフトウェアを最初に構築したとき、その中身は開発者の意図を明快に反映しているだろう。このとき、ソフトウェアは開発者の意図そのものと言える。ところが、些細な要求のためにトリッキーコードを入れたり、環境の変化にアドホックに対応したりすると、ちょっとずつソフトウェアの中身が開発者の理解からずれてくる。

これは、鏡が汚れて、ソフトウェアが開発者の意図を正確に映さなくなっている感覚に似ていると思う。また逆の解釈も可で、開発者がソフトウェアの姿をぼんやりとしか把握できなくなっている状態と見てもよい。

このような状況において、鏡を拭く、つまり開発者の意図が再び明確に反映された状態にすることがリファクタリングだと思う。リファクタリングが適切になされれば、ソフトウェアは開発者と意図と一体になり、変更容易性が大幅によくなる。

これは、DDDのいうところの「ドメインモデルを継続的に更新せよ」というパターンともいえる。要は、ソフトウェアは開発者と一心同体であることが、非常に重要であるということだ。

 

ところで、上記では、単に「良くない設計を直す」ことには言及していない。それは、そもそも最初から間違っているのであるから、それに対応する作業は単に不具合の修正だろう。ソフトウェアと一心同体になるのに必要な力量がない未熟な者は、リファクタリングの前に、まず完璧を期すことを心がけるべきだ。不具合対処を「リファクタリング」などといってさも高尚なことであるかのように見せかけるのは、あまり望ましくないと個人的には思っている。

演算、手続き、ルーチン、モジュール、関数、メソッド

先日、関数とメソッドの違いを学生に聞いたら、分からんとのこと。せっかくなので自分的に整理する。

コンピュータの一番最初の処理――演算から。

演算

コンピュータは当初、計算のための道具であった。実際、最近まで「計算機」と呼ばれていたのはその名残。

計算のための道具なのだから、それが行う処理は「演算」と呼ばれた。

歴史的な最初の演算は ENIAC が行った弾道計算だろう。

もちろん、いまの最近コンピュータのCPUにも ALU(演算装置)がある。

演算とは、コンピュータが行うごく簡単な計算のことである。

手続き(プロシージャ)

最初は計算(演算)のためのコンピュータだったが、処理の内容を柔軟にし、計算以外にも広げた方が有用であることが分かった。

つまり、処理を演算列から「命令列」に汎化し、「制御命令」を導入して、演算の実行順序を動的に変えた方がいろいろできるのである。もっと単純に言えば、条件付き分岐命令を設けたことで、次に実行する命令をコンピュータの状態によって変えられるようになった。

これには機械的な発展も関係しており、プログラム内蔵方式(命令列をメモリに載せる方式)の採用によって命令位置がアドレスで示せるようになったことが影響している。ちなみに、命令とデータを同一のアドレス空間に格納するものをノイマン型アーキテクチャ、異なる空間に格納するものをハーバードアーキテクチャという。

さて、こうして制御命令が導入されると、当初の演算自体の他に、「どういう演算を行っていくか」という判断もコンピュータが行うようになった。したがって、この判断を含む命令列は単なる演算ではないため、新しく「手続き」と呼ばれるようになった。

手続きとは、演算に制御を加えた処理である。

手続きは、いまでもコンピュータの基本的な動作である。

手続きの実行によって、コンピュータは単なる計算機から進化していった。

ルーチン

さて、演算と制御を手に入れたコンピュータは、問題によっては人間よりも圧倒的に速く解を出せるようになり、徐々に応用範囲が広がっていった。そうしてたくさんの人がプログラムを書くようになると、自然と「みんなが共通して記述する命令列」があることが分かってきた。また(たぶんこちらの方が先だが)ひとりでコーディングしていても、「さっきと同じ命令列」がひとつのプログラム中に散見されるようになってきた。

ルーチンとは、そうした共通して、もしくは繰り返して使用される命令列を取り出して、単独の手続きとして独立させたものである。

現代のプログラムにおいては、大抵の手続きはいつでもルーチンとして再利用できるようになっている。したがって、ルーチンの特性を強調する必要性が薄れ、ルーチンという用語は消えつつあると感じる。

サブルーチン

ルーチンは、繰り返し利用される命令列を取り出したものだが、ルーチンに名前がつけられるようになると、処理の抽象化の手段としても使われるようになった。コールスタックを用いる処理※には入れ子関係があるから、命令列をまとめたルーチン(小さいルーチン) → 小さいルーチンをまとめた大きなルーチン、とルーチン間に全体-部分関係が定義できる。

そこで、より処理範囲の広いルーチンをメインルーチンと呼び、メインルーチンの一部を処理するルーチンをサブルーチンと呼ぶようになった。

※ちなみにCPSスタイルの処理は入れ子関係がない……と思う。

高水準言語の効果

アセンブリ言語のプログラムでは、再入可能性再帰可能性再配置可能性などを確保することが難しかった。これらはコンパイラ・リンカなどの技術を集めて高水準言語C言語など)で解決され、現在のプログラミングモデルを支えている。

モジュール

メインルーチン−サブルーチンによってプログラムが階層構造を取れるようになったが、一方で対等なルーチン同士をグループ化する方法が必要とされた。

そこで新たに「モジュール」という概念を導入し、呼び出し関係はないが、意味的につながりの強いルーチン同士をまとめられるようにした。モジュールの役割は意味的グルーピングだが、プログラミング言語処理系によっては可視性の制御など付加的な機能を与えるものもある。

C言語においては、コンパイル単位 (.c ファイル) とモジュールが同一視されることもある。これは static グローバル変数の可視範囲がコンパイル単位になるためだと勝手に推測する。

関数

さて、コンピュータがある程度速くなってくると、極限的最適化を多少犠牲にしても、当事者の抱える問題をなるべく直感的にプログラムしたいという要求が生まれてきた。

どういうことかというと、それまではたとえば簡単な計算であっても、メモリからデータをロードして演算命令で計算してデータをストアして……と、およそ「計算」とは異なる知識が必要だった。しかし、そもそも命令列とアドレスという概念は非常にコンピュータ特有で、コンピュータの専門家以外は分からんのである。

そこで、コンピュータの専門知識が(あまり)なくても利用しやすくするために、プログラムを数学の知識を援用して記述できるようにした。この代表格がプログラミング言語での「関数」のサポートであり、これによって「なんとなく数学っぽく」書けるようになったのである。

したがって、「関数」という言葉は2種類の意味がある。ひとつめは数学の関数である。説明不要であろう。ふたつめは、数学の素養のある技術者のプログラム作成を助けるために、各プログラミング言語が独自で用意した「数学の関数に似た何か」である。

関数を取り入れたもっとも普及している言語はC言語だろう。C言語では手続きはすべて関数として表現される。また、sin(x) などとして呼び出し処理を行わせることができる。たしかに数学の関数に似ている。

しかし、C言語と数学の関数は本質的に異なり、それは副作用として現れている。C言語の関数は「副作用」の有無が重要だが、数学の関数にはそもそも副作用という概念は存在しない。ふたつの関数は、記法と振る舞いを似せてはいるものの、異なる存在である。

数学の関数をより忠実にコンピュータで実現したプログラミング言語は、一般に純粋関数型プログラミング言語と呼ばれる。それらの振る舞いは数学に近いが、しかしたとえばコンピュータは限定的にしか無限を扱えないなど、同じものではない。

メソッド

C言語の関数が数学に由来したように、他の思想から由来した手続きも存在する。その代表例がメソッドである。メソッドを備える言語には C#, Java, C++ がある。

メソッドの由来はクラス指向プログラミングである。クラス指向プログラミングはオブジェクト指向プログラミングの拡張であり、オブジェクト間のメッセージパッシングをクラス間のメソッドの呼び出しで実現する。

端的に言えば、メソッドも手続きの一種である。特徴的なところは、そのメソッドを処理するべきインスタンスのアドレスを必ず受け取ることである。

手続き、関数、メソッドの違い

よく混同されることから、手続きと関数、メソッドの違いを明確にする。

関数は手続きの一種である。メソッドも手続きの一種である。手続きとは命令列のことである。

手続きに対する関数の特徴は、副作用がなく、結果がパラメタのみで決まることである。この性質を実現するために、C言語ではスタックや呼び出し規約の策定などを行っている。もしスタックがなければ、C言語で再入可能・再帰可能な関数を記述することは困難になる。もし呼び出し規約がなければ、いかなる関数呼び出しも副作用が発生する(そもそもどんな呼び出しもCPUレジスタを破壊する。根本的に副作用のない関数などあり得ない)。なお、C言語では数学的関数の定義から逸脱した関数を記述することができるが、それはすでにC言語の「手続き」の別名でしかない。この、同じ単語を違う概念(悪いことに見た目は似せた)に当ててしまったことによる混同は、いまもずっと続く不幸である。

手続きに対するメソッドの特徴は、パラメタとして処理対象のオブジェクトを必ず一つは受け取ることである。より詳しくプロパティと対比した場合は、メソッドは副作用を持つことが特徴となる。

関数とメソッドの違いは、それらが由来する思想が違うのである。もちろん、最終的にはコンピュータで実行できるようどちらも手続きの一種に変換されるが、副作用の有無やパラメタなどに元々の思想による違いがみられる。

関数もメソッドも、もともとはコンピュータの機械的仕組みと直接関係しない概念を扱ったものである。このような、人の認識する問題構造をより直接的にコンピュータに扱わせる方向の進化(Prolog, エージェント指向アスペクト指向、Executable UML, SysML, Symlimk, MDD, ドメイン駆動開発, 形式手法、RDBMS, OODBMS などなど)は、今日も続くソフトウェアの技術革新である。

TxF (Transactional NTFS) はオワコン

TxF は将来サポートされなくなるらしい。

CopyFileTransacted の解説文より:

Microsoft strongly recommends developers utilize alternative means to achieve your application’s needs. Many scenarios that TxF was developed for can be achieved through simpler and more readily available techniques. Furthermore, TxF may not be available in future versions of Microsoft Windows. For more information, and alternatives to TxF, please see Alternatives to using Transactional NTFS.

TxFの理念は情報処理的に正統的だと思っていたので、終わりを迎えるとは思わなかった。やはり、使い方や振る舞いが難しく、それでいて全ての場合に正常に動くとは限らないからということなのかな。

リンク先の代替案には次のような方法が示されている。

結局、「ファイルシステムをDBだとみなす」のは、机上では可能であっても、現実には利用不可能なアイディアだったということか。確かに、APIドキュメントを読んだとき、使うのは覚悟が要りそうだと思った覚えがある。

IsSomething == true

同僚は次のようなコードスタイルらしい。

if( obj.IsExists == true ) { … }

私としては、”obj is exists” is true ? というふうに見えるので、冗長だなあと思う。もし、こんなふうに書いてあったら理解に苦しむ。

if( obj.IsExists != false) { … }

こう書けばよろしい。

if( obj.IsExists ) { … }

が、自分のコードスタイルには、否定時に欠点があることも自覚している。

if( !obj.IsExists ) { … }

これが、肯定の場合と比べて僅かな差異しか持たないことは明白だ。元気なときならいざ知らず、疲れているときのコーディングはこういうミスに気がつけなくなる。いうまでもなく、プログラマは大抵疲れている。

 

私のあらゆる言語への願いは、 単項 not 演算子を導入してもらうことだ。こう書けたらなんと読みやすいだろう。

if( not obj.IsExists ) { … }

気持ちを分かっていただくために、色づけもしてみた。

ifnot/unlessステートメントも望ましい。これは is 演算子との組み合わせで特に有用である。

ifnot( obj.IsExists ) { … }

ifnot( obj is IOException ) { … }

//written now; if( !( obj is IOException )) { … }

とにかく、! 演算子は見つけにくい。なんとかしてほしい。

最も根本的なスキルは「想起」だと思う

仕事でソフトウェア開発スキルを扱うことが多いが、最も根本的で得がたいスキルは「想起」、つまり、それが必要とされることを認知する能力だと思う。これがなければ、どんなに知識を身につけても意味がない。

たとえば、「会議ではクロージングを意識する」というスキルは、会議の冒頭に意識しないと効果がない。どんなに知識として知っていても、クロージングする時点で思い出したところでどうしようもない。ソフトウェア開発には、こういう「想起」がウエイトを占めるものが非常に多いと思う。

この想起スキルを養うには、脳の仕組みからいって、訓練しかない。

「10分で分かる」「1日で分かる」というキャッチコピーが虚しいのは、それが障害の本質ではないからだ。訓練でしか得られない「想起」というスキルを、知識と同じ方法で身につけようとしても上手くいかない。