2015年3月31日火曜日

エラーメッセージ補助機能のライブラリ化

エラーメッセージ出力機能を独自ライブラリに加えたので、エラーメッセージの内容を改めて考えてみました。今まで作ったときに考えていたのは、エラーを知らせることだけです。今後は、時間が経過してから見た場合も考慮して、もっと良い内容でメッセージが出せないのか、考え直してみたというわけです。

考えた結果、エラーメッセージの補助機能を独自ライブラリに加えることにしました。エラーメッセージを後から見るときだけでなく、開発時のデバッグ時にも使える補助機能が作れました。単純な機能ですが、ここで紹介します。

 

先日作成した、エラーメッセージ出力機能のライブラリ化では、メッセージの頭に日付と時刻を加えます。これで、発生した時期が特定できるようになりました。

また、どのアプリが出しているのかの情報は、必要ありません。そのアプリのDocumentsフォルダへ保存するのですから、どのアプリかは明らかです。

 

エラーメッセージに、上記以外の情報は何か必要でしょうか。

そう考えて思い浮かんだのが、過去に作ったオンラインシステムでのトラブル対処です。大量のデータを扱うシステムだったので、エラーが発生したとき、どのデータで発生しているのか、なかなか特定できませんでした。特定するために、エラーメッセージを改良して、どのデータで発生したのか出力するようにしました。その結果、エラーの発生したデータが特定でき、システムの改良が容易になりました。

時間が経過してから見る、iPadアプリのエラーメッセージにも、同じような要望があると思います。どのような状況で発生したのか、どのデータで発生したのか、エラーの種類によっては詳しく知りたいはずです。その要望を満たすような機能があれば、非常に有益だと言えるでしょう。

 

そこで、状況やデータを示せるようなエラーメッセージ補助機能を、独自ライブラリに追加することにしました。状況やデータの値に関係なく、メッセージへ情報を簡単に加えられる機能です。どの情報を加えるかは、その補助機能を使う側が決めます。

基本として、メッセージに追加したい情報を設定する機能と、設定した情報をエラーメッセージに加える機能が必要です。これも、エラーメッセージ出力機能と同じように、アプリのどこからでも呼び出しやすいことが大事です。そのため、グローバル関数として作ることにしました。作る場所は、前回のエラーメッセージ出力機能と同じ「BP_Base.swift」です。

メッセージへ入れる情報に合わせて、データ構造を考える必要があります。今回は、何層かの階層として扱えたほうが良いと考えました。たとえば、1番目の階層には、画面(View)の名前を入れます。次の階層には、画面に含まれる処理名を入れます。さらに次の階層には、キーとなるデータの値を入れます。同様に、データの値を4階層ほど用意します。

4階層も付けたのは、4つの階層が必要になるという意味よりも、同じ階層で2つ以上の値を使うかもしれないと考えたからです。階層関係があるので使い方は制限されますが、階層の余裕を別な形で使えるようにとの配慮です。

階層で大事なのは、自動クリアー機能です。もし画面が変わったら、それ以下の階層に設定された値は意味がなくなります。そのため、第1階層の値が変更されたら、第2階層以下の値は自動クリアーします。同様に、第2階層の値が変更されたら、第3階層以下の値を自動クリアーします。このように、全ての階層で、下の階層を自動クリアーする機能を組み込みます。

 

完全な階層構造の値だけだと、より自由な使い方はできません。そこで、階層関係とは別の値を入れる場所も2つ用意しました。

ただし、こちらも自動クリアー機能がないと、使いやすくはなりません。そこで、1つの値は、第1階層の設定でクリアーさせます。もう1つの値は、第1階層および第2階層の設定でクリアーさせます。第2階層だけのクリアーでない理由は、すべての設定をクリアーする機能が必要で、それが第1階層の役目だと考えたからです。

 

こうして作ったのが、以下のグローバル関数です。実際のSwiftコードは次のとおりです。

// エラーメッセージの補助機能:画面やデータを特定するために、処理中の画面やデータを記録に残す
private var procKeyL1: String = ""  // 通常はView名を設定(V=XXX)
private var procKeyL2: String = ""  // 通常は処理の種類(P=XXX)
private var procKeyL3: String = ""  // 通常は処理データのレベル1
private var procKeyL4: String = ""  // 通常は処理データのレベル2
private var procKeyL5: String = ""  // 通常は処理データのレベル3
private var procKeyL6: String = ""  // 通常は処理データのレベル4
private var procKeyA1: String = ""  // 通常は処理データの属性(L1設定でクリアー)
private var procKeyA2: String = ""  // 通常は処理データの属性(L1設定とL2設定でクリアー)

// 処理中のキーを設定する
func zSetProcKeyL1F(rStr:String) {
    procKeyL1 = rStr
    zSetProcKeyL2F("")
    zSetProcKeyA1F("")
    zSetProcKeyA2F("")
}
func zSetProcKeyL2F(rStr:String) {
    procKeyL2 = rStr
    zSetProcKeyL3F("")
    zSetProcKeyA2F("")
}
func zSetProcKeyL3F(rStr:String) {
    procKeyL3 = rStr
    zSetProcKeyL4F("")
}
func zSetProcKeyL4F(rStr:String) {
    procKeyL4 = rStr
    zSetProcKeyL5F("")
}
func zSetProcKeyL5F(rStr:String) {
    procKeyL5 = rStr
    zSetProcKeyL6F("")
}
func zSetProcKeyL6F(rStr:String) {
    procKeyL6 = rStr
}
func zSetProcKeyA1F(rStr:String) {
    procKeyA1 = rStr
}
func zSetProcKeyA2F(rStr:String) {
    procKeyA2 = rStr
}

// 処理中のキーを得る
func zGetProcKeyF() -> String {
    let STR_DELIMIT: String = ","
    var iStr: String = procKeyL1
    if procKeyL2 != "" { iStr += STR_DELIMIT + procKeyL2 }
    if procKeyL3 != "" { iStr += STR_DELIMIT + procKeyL3 }
    if procKeyL4 != "" { iStr += STR_DELIMIT + procKeyL4 }
    if procKeyL5 != "" { iStr += STR_DELIMIT + procKeyL5 }
    if procKeyL6 != "" { iStr += STR_DELIMIT + procKeyL6 }
    if procKeyA1 != "" { iStr += STR_DELIMIT + procKeyA1 }
    if procKeyA2 != "" { iStr += STR_DELIMIT + procKeyA2 }
    if iStr == "" {
        return ""
    } else {
        return "(" + iStr + ")"
    }
}

難しい処理はしてませんので、見てのとおりの機能です。いつのもように要点だけ解説します。

エラーメッセージに表示する値(キー)は、8つの変数に入れます。6階層に作られたL1〜L6と、階層ではないA1とA2の8変数です。それぞれに設定関数が用意され、値を設定すると同時に、関連する自動クリアー機能が含まれています。値のクリアー機能は用意せず、空の文字列を入れることでクリアーと同じの役目をします。

階層構造の値の設定では、下の階層をクリアー(空文字の設定)を順番に呼び出す形で作っています。このように作っておくと、階層を増やすときの変更箇所が少なくて済みます。

階層とは関係ない2つの値(A1とA2)の設定では、クリアーする処理が含まれません。下の階層を持ってないからです。ただし、これらの値を自動クリアーする必要があるため、第1と第2の階層の設定処理で、それぞれの階層に応じたクリアー処理を含めています。

最後は、これらキーの値を文字列として取り出す処理(zGetProcKeyF関数)です。順番に追加するだけですが、追加する値が空でないときだけ、区切り文字を間に入れて追加しています。処理を良く見ると分かるのですが、第1階層が空で、第2階層以下のどれかが空でない場合は、最初に区切り文字が付いてしまいます。これに対処するとソースが汚くなるのと、第1階層を使わない状況は想定してないので、この形にしました。

この補助機能を使わない場合は、8つの変数が全て空です。その場合に、中身の入ってない括弧が表示されると醜いので、最後の処理では、中身が入っているときだけ括弧で囲んでいます。

 

これでエラーメッセージの補助機能は作り終わりました。ただし、このままだと、エラーメッセージに毎回追加する処理が必要です。そこで、前回紹介したエラーメッセージの処理を少し変更して、キーの値が自動的に追加するようにしました。以下のように、たった1行だけの変更です。

// 変更前
let iErrMsg: String = getStrDateNowF() + " " + rMsg
// 変更後
let iErrMsg: String = getStrDateNowF() + " " + rMsg + zGetProcKeyF()

前述のように、補助機能を使ってないときは空の文字列となるので、キー値の追加されない(つまり以前と同じ)エラーメッセージが出力されます。

 

すべて作り終わったので、使い方を簡単に紹介しましょう。

まずは、基本的なルールから。複数の値を一緒に表示するため、それぞれが何の値を表示しているのか理解しやすいように、形式を決めます。画面であればViewを意味するVをキー名として、「V=Menu」という形式で値を入れます。このように、キー名とキー値をペアにして等号でつなげた形を、私の使用ルールとしました。

第1階層のキーには、どの画面を動かしているかの情報を入れます。編集画面の場合は、次のような使い方になります。

// 編集画面に入った直後に
zSetProcKeyL1F("V=Edit")  // キーの設定
// この後に画面生成処理を続ける
...

// 画面を消す処理の最後にも
...
zSetProcKeyL1F("")  // キーのクリアー

この例のように、画面を意味するキー設定だけでなく、キー設定をクリアーする処理も最後に加えています。この形にする理由が2つあります。1つは、画面に関わる処理の範囲を明確に示し、画面外の処理で間違った表示をさせないためです。もう1つは、キー設定を全部の画面に付けるとは限らないので、クリアー処理を付けないと、別な画面で間違ったキーが設定されたままになってしまいます。全ての画面に付ければ、クリアー処理がなくても構わないのですが、他に依存するような使い方は好ましいとは言えません。トラブルの元になります。そのため、設定とクリアーの両方を付けるがベストな使い方と言えます。

このようなキー設定により、エラーメッセージが出されたとき、どの画面で動いているのか明らかになります。複数の画面で使われているような処理では、どの画面で使われたときに発生したエラーなのか分かると、原因の究明に大いに役立つでしょう。

 

続いて、第2階層の処理です。ボタンをタップしたとき呼ばれる関数の例を挙げてみました。

// 追加ボタンをタップしたときの処理
func AddDataF(rSender:UIButton) {
zSetProcKeyL2F("P=AddBtn")  // キーの設定
...
... // ここにタップ時の処理を入れる
...
zSetProcKeyL2F("")  // キーのクリアー
}

こちらでも画面の場合と同じように、キーのクリアーを入れています。また、キーの形式もルールに則り、「P=AddBtn」としています。

処理の途中でエラーメッセージが出されると、画面のキーとともに、処理の種類のキーも一緒に表示されます。どの画面の、どの処理でエラーが発生したのか判明すれば、原因の絞り込みがさらに容易になるでしょう。

 

これ以下の階層でも、基本的な使い方は同じです。処理する値ごとにキーを設定すれば、どの値の処理中にエラーが発生したのか、明らかにできます。たとえば、商品idごとにキーを設定するなら、値の形式を「ProdID=XXXXX」として、「XXXXX」の部分に商品idの値を入れることになります。キーの名前を極端に短くしないことが、後から見て理解しやすいコツでしょう。

原因究明に役立つのは、データのキーとなる値だけではありません。何件目のデータを処理しているのか、全部で何件のデータを処理しているのかといった、処理件数の関わる情報も役立つことがあります。その場合は「cnt=XXXXX」と入れることになります。過去のデバッグ経験と照らし合わせて、ヒントになりそうな情報を入れるのも上手な使い方と言えそうです。

階層とは関係のないA1とA2は、少し違う使い方が可能です。入力画面であれば、入力モードを記録するとか、入力エラーの有無を記録するとか、階層に関係のない独立した値なら何にでも使えます。エラー発生に関係しそうな情報を入れるのがコツでしょう。

 

以上のように使った場合、エラーメッセージを出すグローバル関数zSendErrMsgFの使い方は以前と同じですが、エラーメッセージの最後に、補助機能で設定した情報が括弧付きで加わります。具体的には、次のようになります。

// エラーメッセージを出力するソースコードは、以前と同じまま
zSendErrMsgF("ERROR:ZF_WF:ファイル書き出しが失敗しました。")
// エラーメッセージの出力例
2015/03/31 15:23:47 ERROR:ZF_WF:ファイル書き出しが失敗しました。(V=Edit,P=AddBtn,ProdID=S015037)

zSendErrMsgF関数により、前側に日付と時刻が、後ろ側に補助機能による付加情報が加わってメッセージが出力されます。もし補助機能を使っていなければ、後ろ側の括弧の部分が、まったく表示されません。もし一部の画面だけで使っている場合は、使ってない画面(補助機能が設定されてない画面)でも、後ろ側の括弧の部分が表示されません。あくまで、補助機能が設定されているときだけ、設定された情報を追加します。

 

当然のことですが、エラーメッセージの出る確率は、アプリの信頼性が高いほど少なくなります。作り手が優秀な場合、エラーメッセージはほとんど出ないはずです。

というわけなので、キーを細かく設定しても、無駄な処理を動かしているだけになります。実際には、多量のデータを扱う箇所に入れない限り、処理自体は軽いので、入れておいてもバッテリー消耗に影響がないでしょう。保険のために入れておくという感じでしょうか。

加えて、多少ですが、入れる手間も増えます。現実的には、全部の画面に入れるのではなく、大事な画面を選び、その中の重要な処理にだけ入れるというのが現実的だと思います。その場合、画面名と処理名は必須として、データのキーとなる値を入れるかどうかは悩みます。こちらも、難しい処理をしているときにだけ、念のために入れておくというのが現実的でしょうか。

最初入れてなくて、エラーが発見されたときに、入れたバージョンのアプリと入れ替えて様子を見るという使い方も可能です。臨機応変に活用するのも現実的な使い方でしょう。

 

ここまで、エラーメッセージの補助機能として紹介してきました。しかし、それ以外にも有効な使い方があります。開発中のデバッグです。

まず、この補助機能を、開発当初から入れておきます。主な画面と主な処理で。加えて、集中的にデバッグしたい処理では、データのキーとなる値を設定する処理も追加します。

面白いのは、ここからです。エラーメッセージが出るのを待つのではなく、条件文付きのprintln文を使って、zGetProcKeyF関数を呼び出すのです。すると、条件を満たしたときにメッセージが出て、実行中のキーの値が表示できます。println文を入れた箇所からは参照が困難な値も、この補助機能を使えば表示できるというわけです。

 

今回は、より良いエラーメッセージを考えている中で、思い付いた機能を作ってみました。この補助機能を使えば、発生状況が特定しづらいエラーの状況判明に、かなり役立つと思います。次に作るアプリや、既存アプリの改良時に、組み込んでみようと考えています。どのレベルまで入れるのか悩みながら、上手な使い方を模索してみます。

 

(使用開発ツール:Xcode 6.2, SDK iOS 8.1)

2015年3月24日火曜日

エラーメッセージ出力機能のライブラリ化

当ブログでは、いろいろな機能のライブラリ化を紹介しています。もちろん、ソースコードも公開無しながら。それらのコードの中では、エラー検出機能も含まれていて、検出時にはエラーメッセージを出力しています。通常は、println文を使って。

それでも構わないのですが、println文で出力したメッセージは、コンソールのログを見なければ確認できません。とくに、iPadの実機上で実行中のアプリなら、簡単には見れないのが欠点です。

そこで、エラーメッセージの出力方法を変更しようと考えました。コンソールにも一応出力しますが、その他として、Documentsフォルダ内のファイルにも書き出します。こうすると、以前に「ファイル閲覧機能のライブラリ化」で作った、Documentsフォルダ内のファイル閲覧機能で見ることが可能となります。閲覧機能を新たに作らなくても、エラーメッセージを見れるというわけです。

また、エラーメッセージの出力先を1つにまとめることで、間接参照を実現したことにもなります。println文以外の出力先を追加したり、別な出力先に変更したりが、1カ所の変更だけで可能です。また、エラーメッセージの内容に応じて、出力先を切り替える機能も、一カ所の変更で実現できます。つまり、今回の機能のライブラリ化は、エラーメッセージの出力方法を、間接参照で作ろうという意味でもあります。

 

いつものように最初は、作るべき機能の仕様を検討しましょう。実現方法としては、アプリ内のグローバル関数とします。いろいろなところから呼び出すので、簡単に呼び出せたほうが使いやすいでしょうから。この関数を入れるファイルは、UI部品を生成するグローバル関数が入っている「BP_Base.swift」としました。

大まかな機能は、次のようにします。呼び出し側からメッセージを受け取り、メッセージの先頭に現在の日付と時刻を追加して、コンソールと指定ファイルに出力します。既にファイルがあるか調べて、あればファイルの最後に追加し、なければファイルを新規で作ります。

時刻の文字列生成や、ファイルの読み書きは、すでに独自ライブラリとして作ったものがあります。でも、今回はそれを使いません。エラーメッセージの生成は、アプリの基本となる機能で、他のライブラリに依存するのは良くありません。依存すると、ライブラリの読み込み順序などにも影響され、余計な考慮が必要となります。まったく依存しないようにと、普通にコーディングしました。

 

将来の拡張も、最初から考慮すべき点です。今回の関数内でのエラーメッセージの扱い方が、1種類だけで済むとは限りません。将来的に、ファイルに出力しないメッセージとか、メールで送信するメッセージとか、新たな扱い方のメッセージが追加となる可能性もあります。どう処理するのか、呼び出し側で指定できるような機能の追加も必要になるでしょう。その場合、それまで作った呼び出し側のコードを変更しないように作っておくのがベストです。

最初に考えたのは、エラーメッセージの扱い方を示す番号を、Int型の引数として加える方法でした。最初はゼロを指定しておき、別な扱い方のメッセージが出てきたら、ゼロ以外の番号を指定するわけです。最初に作った呼び出し側は、全部がゼロを指定していますから、後から変更する必要はありません。変更が予想される機能の実現では、よく使われる方法でしょう。

でも、まったく変更しないときは、余計な引数が付いたままとなり、美しくありません。今回は、エラーメッセージが引数なので、文字列のデータ型です。ということは、いろいろな情報が含められるということです。もし新しい扱い方のメッセージを追加するときには、エラーメッセージの先頭に特殊な文字を入れることで対応可能です。たとえば、メールで送るエラーメッセージなら、メッセージの先頭を「@」にするとか。つまり、先頭の1文字か2文字を、コマンドとして利用するわけです。文字列型ならではの拡張方法です。

文字列の先頭にコマンドを入れる方法は、いろいろな応用が可能です。たとえば、先頭が「@」のとき、メールアドレスを続けるような仕様にして、メールアドレスの終了特殊文字も規定します。先頭「@」の次から、その終了特殊文字の前までが、メールアドレスの文字列として解釈され、終了特殊文字の後ろがエラーメッセ時として扱われるという仕様にも作れます。このように、引数付きのコマンドも実現可能なのです。まあ、実際に作るかは別な話ですけど。ともかく、何とかなる余地が残っていることが大切です。

 

いろいろと書きましたが、機能としては単純です。短時間で作れました。具体的には、次のようなSwiftコードになっています。

// エラーメッセージを出力するためのグローバル関数

let STR_FNAME_ERR = "AppErrMsg.txt"    // エラーメッセージのファイル名

func zSendErrMsgF(rMsg:String) {
    let iErrMsg: String = getStrDateNowF() + " " + rMsg
    println(iErrMsg)
    // エラーメッセージファイルへ出力(存在してなければ新規作成)
    var iStrWrite: String = ""    // 書き出す文字列
    let iPathDocs = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
    let iPathFile: String = iPathDocs.stringByAppendingPathComponent(STR_FNAME_ERR)
    let iFileManager: NSFileManager = NSFileManager.defaultManager()
    if iFileManager.fileExistsAtPath(iPathFile) {    // ファイルが存在したら、その内容を読み込む
        let iStrOld = NSString(contentsOfFile:iPathFile, encoding:NSUTF8StringEncoding, error:nil) as String
        iStrWrite = iStrOld
    }
    iStrWrite += iErrMsg + "\n"
    iStrWrite.writeToFile(iPathFile, atomically:true, encoding:NSUTF8StringEncoding, error:nil)
}
// 現在の日付時刻の文字列を返す
private func getStrDateNowF() -> String {
    let iDateFmtr = NSDateFormatter()
    iDateFmtr.locale = NSLocale(localeIdentifier:"ja_JP")
    iDateFmtr.dateFormat = "yyyy/MM/dd HH:mm:ss"
    return iDateFmtr.stringFromDate(NSDate())
}

見てすぐに分かるコードですが、簡単に説明します。

ファイルが存在するかは、きちんと調べています。読み込みが失敗したら存在しないという判断する方法は使っていません。存在したら読み込んで、新しいメッセージを追加します。メッセージごとに改行したいので、最後に改行文字を追加しています。

ファイルの読み書きでは、エラーチェックをしていません。エラーを発見したとしても、おそらく何もできないからです。エラーメッセージが消えるだけですし、普通の状態ではエラーが発生しないでしょうから、何もしなくて構わないと判断しました。

日付時刻の文字列を生成するgetStrDateNowF関数では、日付と時刻の桁が縦に揃うように、前ゼロ付きの書式を選びました。また、時刻の後ろに空白文字を入れるのですが、この関数内では入れずに、日付時刻を追加する行でわざわざ入れています。これには理由があります。エラーメッセージ出力の関数が拡張されるとき、日付時刻の文字列がどのように使われるのか不明です。文字列の最後に余計な空白付きで返ってくるのは、その空白が不要になったときに、この関数の変更が生じるでしょう。最後に空白を入れるのは、この関数の戻り値を使う側の都合であるため、使う側で付けるのが当然の作り方です。とても小さなことですが、このように考えて作ると、将来の余計な手間を増やさずに済みます。

 

関数名に関しても、少しだけ解説します。私の場合は、UI部品の生成などをグローバル関数として用意しています。ただし今までは、関数名を普通に付けていました。でも、グローバル関数と分かるような関数名を付けたほうが、後でメンテナンスするときに良いと思うようになりました。

そこで今回から、グローバル関数の名前は、先頭に「z」を付けることにしました。なぜ「g」ではなく「z」かという理由ですが、まず「g」はメソッド名の先頭文字に多く使われますから避けたいと考えました。逆に「z」は使われません。また、日付やファイル処理の機能をクラスとして用意し、そのグローバル変数の名前として、先頭に「z」を付けています。それと共通なら分かりやすいと考え、先頭に「z」を付けることとしました。この辺の考え方は、個人差が大きいと思います。

既存のグローバル関数は、今回のルールに従っていません。何かの機会に、同じルールで変更する予定です。

あと、動詞を何にするかで迷いました。とりあえず「Send」にしたのは、メッセージを送るという感じが、自分なりには動きに一番近いと思えたからです。これがベストという感じはしてませんが、とりあえず何か付けないといけないので。

 

使い方は、非常に簡単です。今までprintln文で書いていたものを、zSendErrMsgF関数を呼び出す形に変更するだけで済みます。言ってみれば、「println」の文字列を「zSendErrMsgF」の文字列で置き換えるだけです。次の例のように。

// エラーメッセージ出力の使用例
zSendErrMsgF("ERROR:GD_PT:商品テーブルの読み込みに失敗しました。")

極端な話、ソースコードの文字列検索&置き換え機能を使い、すべての「println」文字列を「zSendErrMsgF」で一括置き換えしても構わないぐらいです。まあ、実際には行ないませんけど。もし置き換え機能を使うとしたら、1つ1つ確認しながらですね。

 

今回の機能を作ったことで、今後のエラーメッセージ処理は、これを常に使うことになりそうです。次回以降の新作アプリとアプリ更新では、すべてのエラーメッセージをファイルへも出力するでしょう。

Documentsフォルダ内のファイルを削除しない限り、エラーメッセージは消えずに追加され続けますから、時間が経過した過去のエラーも後から見れます。これが一番大事な点で、エラーを見逃さない仕組みが用意できたとも言えるでしょう。これが、作った一番の理由かもしれません。

また何か思い付いたら、自分の独自ライブラリに機能を追加したいと思います。

 

(使用開発ツール:Xcode 6.2, SDK iOS 8.1)

2015年3月19日木曜日

起こりうる失敗(理不尽な更新)でXcode 6.2に入れ替えられた

他のOSと同じようにOSXも、アプリのバージョンアップやセキュリティ更新によるアップデートを、手助けする仕組みが備わっています。OSXのバージョンが進むごとに、アップデートの手間が減るように機能アップしてきました。今では、自動更新を選択することもできます。

Xcodeの最新版がMac App Storeに登録されるようになってからは、OSXのアップデートの対象にXcodeも含まれるようになりました。これって、本当に困りものなのです。私のように、少し枯れたバージョンを使う人にとっては。

 

で、また失敗してXcodeを更新してしまいました。気付いたときには更新が始まっていて、もう停止できませんでした。Xcode 6.1.1が6.2にバージョンアップされたというわけです。これが確か2回目の失敗です。

OSXのソフトウェア・アップデートは、画面の右上に知らせが表示されます。それを一旦停止させて、App Storeアプリのアップデート画面を呼び出し、更新内容を確認してからアップデートするのが、いつもの手順です。この操作方法なら、Xcodeなどの更新したくないソフトをアップデートせずに済ませられます。

でも、この方法は非常に危険です。何回も繰り返すうちに、いつか失敗して、更新したくないアプリを更新してしまうのです。まさに「起こりうる失敗」なのです。

今回は、忙しく作業している中でメッセージが表示され、間違って、更新するほうをクリックしてしまいました。操作ミスに気付いたのは、更新処理が行なわれている途中です。終わるまで待とうと思って眺めていると、いつもよりも更新時間が長めで終了しません。その時点で、Xcodeも更新されていることに気付きました。あーあ、やってしまったと。

 

そもそもXcodeが、OSXのアップデートに含まれていること自体が間違いです。Xcodeを更新するタイミングは、ユーザー自身が自分で決めるのが当たり前です。もし開発途中でXcodeが更新されてしまったら困るでしょう。今まで正常に動いていたコードが、突然と動かなくなる可能性もありますから。通常は、開発ツールのバージョンを決め、特別な理由がない限り、そのまま開発終了まで固定するのが普通です。もしかしてアップル社内では、Xcodeは最新版を必ず使えという、誰かの強い意見でもあるのでしょうか。まさに「理不尽な更新」と言えるでしょう。

人間はときどき失敗しますから、「起こりうる失敗」を含んだ更新方法なんて、数多く繰り返さなくても失敗が発生します。私も、すでに2回目も失敗しました。今後も、同じように失敗するでしょう。毎回注意する方法では、いつか失敗してしまいます。

何か防止する方法が必要でしょう。App Storeアプリのアップデート画面で、購入済みアプリの一覧が見られます。その画面に、特定のアプリだけ更新しない設定があれば良いのに。本当に困りものです。

 

最近のXcodeは、アプリの形をしたXcodeの中に、各種ツールを含んでいます。この構造に変更されたおかげで、Xcodeの複数バージョンを一緒に入れておけるようになりました。アプリケーションフォルダ内で、Xcodeアプリの名前を片方だけ変えて、共存させるだけで実現可能です。

でも、OSXのソフトウェア・アップデートでXcodeが更新されると、複数のXcodeは全部消えてしまい、最新のXcodeだけ残る状態になります。これも非常に困りものですね。

もしかして「アプリケーション」フォルダ以外へXcodeをインストールしておいたら、ソフトウェア・アップデートで消えないのでしょうか。でも、それで正常に動くかどうかのほうが心配です。一部の機能が動かないとき「もしかして、アプリケーションのフォルダに入れてないことが原因かも」と余計な心配が生じます。こんな不安を抱え込む方法なんて、やりたくないでしょう。

 

今はXcodeを使った急ぎの開発がないので、大きな問題は発生しません。せっかくなので、Xcode 6.2を使ってみましょうかね。最新バージョンを試すための試験マシンには、6.3ベータが入っているので、6.2を一緒に入れるのは面倒ですから。

 

とにかく、OSXのソフトウェア・アップデートでXcodeが更新されないようにしてほしいです。何か良い方法はないかと、いちおう検索しましたが、見付けられませんでした。もし回避方法を知っている人が居たら、ぜひとも教えてください。お願いします。