文字列を処理する方法
文字列を処理するといえば、検索、抽出、比較、置換、整形ということに集約できそうですが、どういう手段を使うかについてまとめておきます。
検索
- 正規表現
- indexOf メソッド
- lastIndexOf メソッド
- カスタム Scanner クラスを利用(自作なりDLなり)
抽出
- substring メソッド
- split メソッド
- trim メソッド
- java.util.StringTokenizer を利用
- カスタム Scanner クラスを利用(自作なりDLなり)
比較
- equals メソッド
- equalsIgnoreCase メソッド
- startsWith メソッド
- endsWith メソッド
- matches メソッド
- java.text.RuleBasedCollator を利用(大小関係のルールを任意に定義できる)
置換
- toUpperCase メソッド
- toLowerCase メソッド
- replaceFirst メソッド
- replace メソッド
- replaceAll メソッド
- カスタム Scanner クラスを補助的に利用(自作なりDLなり)
整形
- 単純に + する
- concat メソッド
- StringBuilder クラスを利用
- StringBuffer クラスを利用
- java.text.MessageFormat を利用
- カスタムフォーマッタを利用(自作なりDLなり)
とりあえずそれぞれの目的ごとに最もよく使うであろうものから順に並べてみています。手に負えなくなると下の方が使われていくような感じです。
JavaDoc はいつでも読めるようにしておこう。何がどこにあるかの大体の目処を頭に入れておこう
これを何のために挙げておくかというと、以下のような切り口の主張を私が好まないためです。
- 「全部正規表現で処理します。応用範囲広いし統一できるから」
- 「全部 StringBuilder 使います。わかりやすいから」
- 「全部 + でつくります。最適化されて速いから」
「それで何が悪いの?」と思う方には、今何をしようとしているかを表現するにはもっとうまく役割を理解して構成した方がいいんじゃないの?と問いかけてみたいところではありますが、多分、JavaDoc を参照するような環境が揃っていないとか、本を開く余裕がないとか、そういう事情があるんでしょうが、気がかりです。
せめて java.lang.*、java.io.*、java.text.*、java.util.* はひと通り眺めて、いつでも JavaDoc 開いてサンプルコード書いて使えるようにしておくだけで、複雑なコードを書いてしまったり大きな jar をダウンロードしてきて依存しまくったりせずに済むケースが多いのです。
Reader クラスを活用しよう
文字列を1文字ずつ取り出して何かをするような状況で、
String s = "abcdefg"; for (int i = 0; i<s.length(); i++) { switch (s.charAt(i)) { case 'a': … … default: … break; } }
というようなコードを見かけることがありますが、文字構成の前後関係を気にして何か処理をするときにはこのやり方は条件分岐を多数記述することになりがちで、困ったことになることが多いです。どうリファクタリングしてもなかなかうまくいかない。
そんなとき、以下のような記述を行うとうまくいくことが結構あります。慣れると応用範囲がとても広いのですが、上のやり方に慣れすぎると無自覚にも「やってみようとすら思わない」というような壁ができていることがあります。
StringReader r = new StringReader("abcdefg"); int ch; while ((ch = r.read())!=-1) { switch (ch) { … } }
「IOException を catch する記述が必要になるからインデント深くなって嫌だ」とかあれこれ理由をつけて遠慮されるものですが、結局のところ「恩恵感じるまでやりこまないと聞いたところでわからない」ってこともあるのかもしれません。
というのはさておき、Readerクラスには以下のような特徴があります。
- 「文字」およびその配列の読み込みに適したクラスである
- 文字列、ファイル、ストリームなどいろいろなデータソースから文字を読み込んで処理できる
- FilterReader を派生させることで、あらかじめデータソースの内容を加工できる
- 文字列を読みすぎた場合に「読まなかったことにし」「やりなおす」ことができる
これのメリットは以下の通り。
- 文字列中の位置を示す具体的なインデックスがないため、読み出す順番を間違えない。
- 途中まで読み出した結果、他のメソッドに処理を移して後続の文字列の処理を任せたいというようなときに、文字列だとsubstringなりインデックス渡しなりをする必要があるが、Reader クラスを使っていれば単に Reader を引き渡すだけとなる。
- 余分な空白はあらかじめ除去する、とか途中の # から LF まではコメントとみなしてなかったことにする、などといったことを Filter で前処理として抜き取ることができる
- 複数の文字で特別な意味を持つケース(-- でコメント、など)の場合に「読み戻す」のにインデックスを操作することなく、unread メソッドで文字を「押し戻し」、処理を切り替えたり、予めマークしておいた位置に reset メソッドで戻ったあとで処理をやり直すことができる。
こうやって書いたコードは、まさに文字を処理する記述だけで構成され、現在読んでいる位置の情報が雑味として混じることがありません。
Writer クラスを活用しよう
StringBuffer/StringBuilder のように途中を加工する必要がない場合には、StringWriter を使うことで確定した出力文字列はもう操作できないから意図がはっきりする、というような言い回しで個人的には利用を推奨していましたが、実際には中身が StringBuilder を利用していることもあり、同期がかかって若干処理速度が落ちるというのがあって良いことばかりではありません。
しかし、StringWriter はともかくとして、Writer クラスの活用にはやはり以下のようにメリットがあります。
- 出力先としてファイル、ストリーム、文字列などがあるが利用する側は与えられた Writer オブジェクトを使うだけである。
- FilterWriter の派生としてクラスを作る(他の Writer クラスでもかまいはしませんが)ことで、ある出力のついでに他の出力(ヘッダやフッタなど)を追加したり、不適切な出力を抑制したり、バッファリングを行うことができる。
要は本来の処理に関係ない出力される側の事情を Writer クラスが担うことでコードの役割の分離を行い、見通しをよくできるということです。
JavaScriptでのコードの書き方を4種類挙げる
JavaScriptの使い方として、4つのコードの書き方を挙げておきます。
日常的に使うのはスクリプトとして記述、関数として記述の二つで十分でしょう。ただし、グローバル変数を使わずに、状態を保持したいという目的でオブジェクトを使うこともあると思います。
なお、コードには分かりやすいようにステートメントの末尾にセミコロンを入れています。多くの処理系ではこのセミコロンはいらないと思います。
スクリプトとして記述
これはどこでも紹介されているのでいまさらな感じがしますが、ただステートメントを並べて結果を得るもので、シェル的な使い方、とでも言えばいいでしょうか。
var sum = 0; var data = [1,2,3,4,5,6,7,8,9,10]; for (var i in data) { sum += data[i]; } alert(sum);
関数として記述
スクリプトとして記述したものをいつでも呼び出せるようにして使いたい。そういうときの使い方です。
function sum(data) { var result = 0; for (var i in data) { result += data[i]; } return result; } alert(sum([1,2,3,4,5,6,7,8,9,10]));
オブジェクトとして記述(1)
prototype.js や jQuery を利用している人は、ライブラリの中でそのようなものが使われていると考えると良いでしょう。オブジェクト指向言語に出てくるクラスと同様の使い方です。
function Calc() { // Calc のインスタンスのsumプロパティに該当するコードを割り当てる this.sum = function(data) { var result = 0; for (var i in data) { result += data[i]; } return result; }; } var calculator = new Calc(); alert(calculator.sum([1,2,3,4,5,6,7,8,9,10]));
ここで注目すべきは、以下の2点です。
ただし、これは以下のように使うのが普通、というか推奨されると思います。先ほどのものはオブジェクトが new されるたびに function が sum プロパティに割り当てられるのですが、こちらはあらかじめ割り当てられているものが使われます。
function Calc() {}; // Calc クラスの prototype の sum プロパティに該当するコードを割り当てる Calc.prototype.sum = function(data) { var result = 0; for (var i in data) { result += data[i]; } return result; }; var calculator = new Calc(); alert(calculator.sum([1,2,3,4,5,6,7,8,9,10]));
new Calc()が実行されるとインスタンスが作られ、prototype プロパティの下位プロパティがインスタンスのプロパティとして複写されます。インスタンスの sum を書き換えても prototype.sum には影響がありません。
function Calc() {}; // Calc クラスの prototype の sum プロパティに該当するコードを割り当てる Calc.prototype.sum = function(data) { var result = 0; for (var i in data) { result += data[i]; } return result; }; var data = [1,2,3,4,5,6,7,8,9,10]; var calculator = new Calc(); alert(calculator.sum(data)); calculator.sum = function(data) {return data;}; // 書き換える alert(calculator.sum(data)); // 書き換えた sum が動く alert((new Calc()).sum(data)); // 元の sum が動く
ところが、生成した後で prototype.sum に変更を加えると、前に生成したインスタンスであってもその影響を受けます。
function Calc() {}; // Calc クラスの prototype の sum プロパティに該当するコードを割り当てる Calc.prototype.sum = function(data) { var result = 0; for (var i in data) { result += data[i]; } return result; }; var data = [1,2,3,4,5,6,7,8,9,10]; var calculator = new Calc(); alert(calculator.sum(data)); Calc.prototype.sum = function(data) {return data;}; // 書き換える alert(calculator.sum(data)); // 書き換えた sum が動く alert((new Calc()).sum(data)); // 書き換えた sum が動く
ついでに、関数を退避させておくと、また違う動作が見られます。
function Calc() {}; // Calc クラスの prototype の sum プロパティに該当するコードを割り当てる Calc.prototype.sum = function(data) { var result = 0; for (var i in data) { result += data[i]; } this.pre = result; return result; }; var data = [1,2,3,4,5,6,7,8,9,10]; var calculator = new Calc(); alert('A:'+calculator.sum(data)); alert('B:'+calculator.pre); var backup = calculator.sum; // 書き換える calculator.sum = function(data) { this.pre = data; return data; }; alert('C:'+calculator.sum(data)); // 書き換えた sum が動く alert('D:'+calculator.pre); // 結果が反映されている alert('E:'+backup(data)); // 退避したものを使う alert('F:'+calculator.pre); // 結果が反映されていない!
backup に割り当てられたものは calculator.sum であり、その内部で this.pre = result; としているわけでこれで calculator.pre に 55 が入りそうなものですが、[1,2,3,4,5,6,7,8,9,10] が返されました。
calculator.sum に function を新たに割り当てる時点で、backup の中の this は calculator を参照しなくなります。これって一体どうなってるんでしょうね?
function Calc() {}; // Calc クラスの prototype の sum プロパティに該当するコードを割り当てる Calc.prototype.sum = function(data) { var result = 0; for (var i in data) { result += data[i]; } this.pre = result; return result; }; var data = [1,2,3,4,5,6,7,8,9,10]; var calculator = new Calc(); alert('A:'+calculator.sum(data)); alert('B:'+calculator.pre); var backup = calculator.sum; alert('C:'+calculator.sum(data)); // 書き換えた sum が動く alert('D:'+calculator.pre); // 結果が反映されている alert('E:'+backup([1,2,3])); // 退避したものを使う alert('F:'+calculator.pre); // 結果が反映されていない! alert('G:'+calculator.sum([1,2,3])); alert('H:'+calculator.pre); // 結果が反映されている
prototype.sum を書き換えなくても同様の結果になりました。つまり、backup = calculator.sum とした時点で backup と calculator.sum は厳密には同じものではないということです。考えてみれば、backup における this というのは calculator ではなく、実行コンテキストそのものともいえるわけで、これには気をつける必要がありますね。「どーせ同じとこ指しているんだから、短い変数名で代用して」…とか考えているとよくわからない動作に悩まされることがあるわけです。Cの関数ポインタとは意味が違うところです。
クロージャを利用
ここではクロージャの意味合いを把握できる例を挙げることにします。
function sum(producer) { var data = producer(); var result = 0; for (var i in data) { result += data[i]; } return result; } // ひとまず、その場で関数を定義して sum に渡すと結果が返される。 alert(sum(function(){ return [1,2,3,4,5,6,7,8,9,10]; })); // クロージャを定義する。この段階では x は宣言されていない。 // function は定義されただけで中身は実行されていない。 // x は、lambda が実行されるときに初めて参照される。 var lambda = function(){ return x; }; // 現在のスコープに変数 x を定義する。 var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]; // 実行する。sum の中で lambda が実行され、ここでの(sum の外での) // x が評価されて取り込まれる。 // あとは sum の中の加算処理が動いて合計を返す。 alert(sum(lambda)); // さらに関数をかぶせてみる。sum 内部で producer() が実行される // までは以下の関数は評価されない。 // 以下のコードでは [1..20]の配列に21を足してできた結果を使って // 加算を行う alert(sum(function() { var r = lambda(); r.push(21); return r; })); // これも気をつけること。lambda() は x のコピーではなくて // x そのものを返していたため、x 自体が操作されて内容が変わっている。 alert(x);
まあ JavaScript はブロックスコープの変数を持たない実装があるとか(letがあればいいんですが)、純粋な関数型言語ではないということがあり、破壊的な操作をしない、無理にクロージャとしての動作をさせるようなことはしない、というのに気をつけておいた方がいいかもしれませんね。趣味でやる分には十分ですが。
略語に対する違和感から考えるマネジメント・ハザード
ひとつは「ワイヤー」、そして「ジャバスク」。もうひとつ「マシン」。
これらの言葉が(私がかかわった状況で)何を指していたかというと、
- ワイヤー=基本設計
- ジャバスク=JavaScript
- マシン=コンピュータのハードウェア
ということになるわけですが、いつもいつも何か違和感を感じていました。違和感についてちょっと詳しく書いてみます。
「ワイヤー」は「ワイヤフレーム(透視図)」を略したものだと思います。ソフトウェア自体の設計に透視図もなにもあったものではないというのはさておき、枠組みだけでも表現したもの、というような意味合いでこの言葉を使ったのであろうとは推測できます。ただ、「ワイヤー」では何のことやらさっぱり分からない。ジャーゴンでもスラングでもなく、ただの符牒といったところでしょうかね。
「ジャバスク」は個人的には何かとても嫌な感じがします。ナントカスクリプトをナントカスクと略すやり方を他に知らないというのもありますし、対象を代表する名前をさらに省略されては意味が欠けてしまうような印象があるためです。たぶんこの略記は「スクリプト」を発音しにくいと感じた人が言い出したことなんではないかと思ってみたりしますが。ジャバスクリプトという言葉には破裂音が二つもありますし。これはやっぱりジャバスクリプト、と発声してほしいところです。
「マシン」については、まさにリアルな「動く機械(からくり)」がそれであると私が感じていたため、違和感を抱いたものです。また「麻疹」という比較的身近な同音異義語もあります。コンピュータの場合はマシンというにふさわしい稼動部分はファンであったりモータであるわけですが、そこはそもそもコンピュータの要ではないということが、違和感の正体だと思います。コンピュータを「マシン」と呼ぶには概念の拡張が必要で、情報処理を行う部分も機械とみなし、マシンと呼ぶ、としないと違和感が消えませんでした。
そういえば「DOS」はどう呼ぶのかというのを思い出しました。見たときからドスと呼んでいたわけですが、「ドスは短刀を想像するから変だ。ディーオーエスだろう?」と友人から突込みが入ったのは20年以上前。でも普通にドスで通じますよね?いまだとディスクオペレーティングシステムとサービス拒否攻撃の二つの意味がかぶりますがそこは文脈依存で聞き取るとして。あえてドスを避けるなら「ディスクオーエス」かな。
このようなことを書いているのは、どれが正しい呼び方か、ということを示したいわけではありません。しかしどれが正しい呼び方かなんてのはどうでもいいというわけでもありません(そういう場合は言外に「話をしたくない」と言っていることが多い。すべてがそうではないけれど)。
何を示したいのかというと、「今現在の自分が保持している意味解釈を行う仕組みが作り出す感覚」が形成する場の中でのみの判断として「わかった」とか「わからない」とか「心地よい」とか「心地よくない」などと言える、ということです。つまり意見を言うということは自分の意味の場を全てではないにせよ、さらけ出しているのと同じということ。もうひとつ、「意味解釈システム」に変化を加えれば出てくる感覚そのものが変化する可能性があるということ。
それが何を意味するかというとまさしく「人の言葉はあてにならない」。かといって話半分で聞いていていい、というわけでもないのですが、「言質を取った」といってその先の行動を縛るという日常的によく使われる行動が大変奇妙なものだということです。他人の意味解釈システムが固定であることを要求するというのは養老孟司さんの言葉を借りれば「情報として扱おうとしている」ということです。そのようにしたら「人が何を考えているか分からない」といって悩むようなことになるのは当然のことで、一方「人が何を考えているか分からない」のはとりあえず当たり前だと認識してそれを前提とし、それを元にさてこちらはどう行動するかね、と決めるのは似たようであってもまったく違うものになってきます。
さて前置きが長くなりましたが、ここでホームグラウンドのソフトウェア開発の話に戻ります。
オブジェクト指向で物を作るとき、オブジェクトの中身はどうあれインタフェースが一定の応答を保証するならそれでよしとする考え方があります。これは言い換えると、「そのオブジェクトを実際に使わずともその他の状況から判断してそのオブジェクトの振る舞いを確実に予言できる」ことを意味します。もっと言えば、そのようにコードを書けば、書けるように努力すれば、それだけでソフトウェアの品質は上がります。(ここで言う「品質」は「予想もしない動作をしない」という意味です。)しかし、多くのプロジェクトではこれを人の振る舞いを「統制」することで制御しようとしています。決まったパターン、慣れたパターンのなかで思考することで補助的に振る舞いを予言できるようにしているように見えますが、これでは個人が持つ意味の場がそのパターンの中だけで成長することになり、融通がきかない育ち方をすることになりかねません。
まあ人を情報として扱おうとして取替え可能としようとしているところからすると勝手に成長されては困るということもあるかもしれませんが、人はどうあれ勝手に意味解釈のシステムを更新していくものです。それを無視してこのようなやり方を行うというのは、いずれどこかでそのやり方そのものが役に立たなくなりかねない。これは「マネジメント・ハザード」とでも呼んでも良いんじゃないでしょうか。
プロジェクトで作るのは製品ではなくて人、製品はその副産物、そういう考え方をしたほうが良いと思います。この考え方をしてしまうと、あるプロセスを適用すれば物ができる、というわけじゃないことになってしまうため困る人は困るのですが。
メインPCがダウン気味
PCが不調です。
- 音飛びがする
- 突然電源が切れる
- CPUが過熱する
- 3.3V電源が4Vほど出ている
導電性の埃でも詰まっていたのか、埃を除去したら音飛びはなくなり、AudioESPのダイアログが頻繁に表示されるというのはなくなりましたが、次いでほかのトラブルが発生。ハードウェアモニタがCPUの過熱と3.3V電源の異常を訴えてきたため、電源をはずして開けてみたら、二つほど目視でわかる不調がありました。
- 電解コンデンサが3個ほど劣化している(膨れている)
- ファンが回らない
基盤や部品の焼けは見られないので、コンデンサとファンを取り替えて正常に戻るとしたら御の字としても、部品だけ買ってきて治らずという無駄足は避けたいので交換することにしました。(続くかもしれない)
「見える化」が目指すもの。
昨今「見える化」という言葉がはやっていましたが、これは情報の共有をしましょう、ガラス張りだから何が行われているか一目で分かります、何が得意かが把握できれば有効に活用できるでしょう、といったような意味合いで推進されていたと思います。何を「見える化」するかによってその目的はさまざまですが、メリットはいっぱい出されているので、ひとつ暗い話でも書いてみようかと。
そもそも見えるようになるというのはどういうことかというと「分かりやすくなる」ということです。これは良いことかというとそうでもありません。ある種の切り口から見ればわかりやすいことがわかりにくいからと言って捻じ曲げられ、分かりやすくさせられるような状況は多くあるでしょう。見る側に権力がなまじあると、怠慢というか傲慢というかこのようなことが起きてきます。分かりにくいのは見る側の目が節穴だから。もっとも、これを「見える化」だとは私は認めませんが。
もうひとつ、見えるようになるというとその先に、外部からの加工操作がしやすくなる、ということがあります。ソフトウェア開発においてはこれはとても重要なことで、だからこそより推進すべきことになりますが、これが組織や個人に対するものとなるとちょっと注意が必要になってきます。組織自体の風通しが良いというのはある意味歓迎すべきことかもしれませんが、そもそも組織の向かう先が反社会的ではないにせよ、常識的ではないような場合はどうか、という面がまずあります。個人に対してもそうで、このようなところには矛盾はあって当然、言葉はあまり当てにはならない、事実は解釈次第、そのようになっていることがありうるのはごく当たり前のこです。
これを分かりにくいから分かりやすくせよというような言葉には安易に与することができません。というのは、そのことによって明らかに無視できない被害が出ている、より良い可能性が奪われている、他の方法では対処ができないというような事態でもない限り、分かりやすくする事によって生じる害の方が大きいからです。
見えたことによって、見えなければ芽生えた可能性が潰される。見えすぎることによって、それ以外の可能性が見えづらくなり、消されてしまう。この可能性が育てば多様性が確保されたかもしれないのに、それが減ったことで全体の可能性が減じてしまう。その結果、減った可能性を奪い合う事態が起こる。そして見当違いのところを指してあれが原因いやこれが原因とまるで勘違いの議論を続ける。このようなことは避けたいものです。
根は暗いところにしっかりと根付くものです。
もっとも、見えなさ過ぎて可能性が奪われていることが多すぎるのならまずは見えても良いところをはっきりとさせるべきとは思いますがね。
とりあえずは「歪曲」と「外部操作の横暴」に気をつけましょうということです。
ソフトウェアファクトリの夢は諦めるべきかも
部品をあらかじめ作っておいて、それを組み合わせることでモノを組み立て、品質をあらかじめ確保した高度なソフトウェアを作る。そういう目的でソフトウェアファクトリという概念が、いわゆるリアルなマテリアルを相手にした製造業的な考え方に倣って作られましたが、ここには「コードを書く必要がない」「手順に従えば高度なソフトウェアの知識がなくてもできる」というようなお題目がありました。
なんとかそれを実現しようとコードの自動生成ツール、フレームワーク、ライブラリの開発などが行われてはきたものの、傍から見るとうまくいっている様には見えません。なぜうまくいかないのかということを説明するのは大変難しいところですが、少なくとも、その手法に慣れていないからといったことではありません。そのことについてメモを残します。
「品質」に期待される意味が同じではない。
リアルなマテリアルを相手にした製造業においては、大量生産されるものの「品質」は原料および加工プロセスの状態によって「品質」がばらつきます。それでも一定誤差に収まる「品質」のものを作らないといけないわけで、これは「均質であること」を大いに含む言葉です。また、多品種少量生産においては、多くの品種を扱いながら、それぞれの品種の「品質」は前に作ったものと同じでなければならない。そして顧客の要望に応じてある程度のアレンジを施しつつ、かつ「品質」がばらつくのは好ましくない。この場合においては、「かつて行ったことは常に再現可能である」ことが含まれているといえるでしょう。
翻って、ソフトウェアではどうでしょうか?ソフトウェアの場合「大量生産」という言葉そのものが馴染みません。それはコピーです。コピーは極端な話、単純にバイト列を複写するだけです。ここに限っていえば、「品質がばらつく」という問題が生じません。また、多品種少量生産に対しては、「プロジェクト」または「ビルドファイル」というリソースやビルド手順が対応し、いつでも前にやったものと同じ結果を出力することができます。ここにおいても、品質がばらつくことはありません。
ではソフトウェアの品質とは何なのでしょうか?バグがない?拡張がたやすい?機能変更に低いコストで対応できる?性能が安定している?多くのプラットフォームで同じ動きをする?製造業とは別の意味で「品質」という言葉が使われていことは明らかです。
まあ、品質の面で言うなら例えば、この前作ったSJIS→UTF変換モジュールと今回作った同じ意味合いのモジュールが違う結果を出すのは好ましくない。よってこれらは共通の資産として流用すべきである、くらいのことなら似通っていると言えるでしょうが、明らかにそれ以上のことが求められているのではないでしょうか。
業務が分かっているだけでは物は作れないし運用もオートにはできない。
至極当たり前のことですが、ソフトウェアは手続きの集まりです。「業務をおおむね理解していること」をもって、即ソフトウェアが作れるかというとそういうわけにはいきません。可能性のあるあらゆる入力にどう対応するか、ということをすべて記述してやっと「まともに動く可能性がある」くらいのものです。なぜかというとこれだけでは「もっともうまくいく場合」を頭で追う、あるいは紙に書き出した程度のものをソフトウェアに変換しただけのことで、実際には「ありえない入力をどう判断し、排除するか」「途中までうまくいった入力をやめる場合どのように手続きをするか」「入力画面を複製して同時並行入力が行われた場合どのように処理するか」といった、そもそも手続きが成り立たないこと、途中でのキャンセルがあること、複数の時系列の重畳といったものをどう排除するかというようなことを考えねばならないからです。
こういったことに対して各論対処をある程度実現している製品はあっても、総合的に対応できているものというと私は知りません。そこをどうにかうまく適応させるために調整するのがエンジニアの領分だと思いますが、このためには特別なスキルが必要だと思います。ウィキペディアの説明では「高度な帰納的推論の才能」とのこと。才能だそうです。(http://ja.wikipedia.org/wiki/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%83%95%E3%82%A1%E3%82%AF%E3%83%88%E3%83%AA%E3%83%BC)
さらには、ソフトウェアのバグ、ハードウェアのトラブル、OSの不明な動作、バッチ処理といったものまで考慮するとなると、かなり大変なことになります。運用の計画と実行が必要なのは当たり前の話となってきます。