2015年5月27日水曜日

ライブラリ等の命名ルールを再考

このブログで紹介しているように、いろいろなアプリで共通に使える機能を、少しずつ独自ライブラリ化しています。ライブラリの数が増えてきて、少し気になっていることがあります。今まではあまり考慮していなかった、命名ルールのことです。

今後もライブラリは増えるでしょうから、あとから区別しやすいように、クラス名などの命名ルールを考え直すことにしました。対象となるのは、ファイル名、クラス名、メソッド名、データ型名、変数名、定義値名などです。何と何を区別すべきなのか、どうしたら簡単に区別できるのか、いろいろと悩んで得た結論を紹介します。

 

まず最初は、何と何を区別するかです。普通に考えて出てくるのは、共通ライブラリとアプリ独自の区別でしょう。

でも良く考えると、ソースコードには他のものも含まれています。Swiftの予約語、iOSのAPIに含まれるクラス名やメソッド名、それ以外のライブラリなどです。これらを、自分が作ったもの(共通ライブラリとアプリ独自の両方)と区別したくなります。つまり、共通ライブラリ、アプリ独自の処理、その他の3つで区別するという結論になります。

この3つのうち、その他だけは、すでに名前が与えられています。自分で名前を変えることはできません。そうなると、共通ライブラリとアプリ独自処理の両方に命名ルールを決めて、その他と区別するのが現実的な方法でしょう。

 

命名ルールで区別する方法としては、参考になる前例がいくつかあります。もっとも有力なのは、名前の先頭に、共通の1文字または2文字を付ける方法です。iOS関連で挙げると、NSで始まる一連のクラス名でしょう。NSはNextStepの略で、iOSのもととなったNextStepのクラスから続いています。というわけで、私も同じように、先頭に共通の文字をつけて区別することにしました。

すでに使っているのは、zという文字です。共通ライブラリとして作ったクラスのインスタンスを入れる変数の一部では、先頭にzを付けています。日付関連ならzDate、ファイル関連ならzFilesと。zを選んだのは、zで始まる英語の言葉が少ないのが理由です。

区別するのは2つですから、文字がもう1つ必要です。いろいろ悩んだ結果、やはり言葉の数が少なそうなyを選びました。つまり、共通ライブラリにはzを、アプリ共通の処理にはyを使うということです。

 

具体的な文字が決まったので、命名ルールを順番に決めていきましょう。

 

まずはクラス名から。クラス名は大文字で始まるため、共通ライブラリでは、大文字のZを先頭に付けます。例えばPhotoSelectorなら、ZPhotoSelectorとするわけです。先頭のZの次も大文字にすることで、先頭のZが、名前とは違う特別な文字だと伝わりやすいでしょう。アプリ独自で使うクラスも同様に、先頭に大文字Yを付けます。

これらのクラスが混在すると、変数宣言でクラス名が現れたとき、先頭がZなら独自ライブラリのクラス、先頭がYならアプリ独自のクラス、その他はそれ以外のクラスと区別できます。それ以外にも先頭がZやYのものがあるので、絶対とは言えませんが、ほとんど問題にならないでしょう。

 

続いて、クラスのインスタンスを入れる変数名です。今まで、共通ライブラリでの変数名の一部には、先頭に小文字のzを付けていました。前述のzDateやzFilesです。この命名ルールのままで構わないので、これからも継続します。また、今まで付けていなかった変数名でも、先頭にzを付けるように変更します。

問題は、アプリ独自のクラスです。そのインスタンスを入れる変数名だけ、先頭に小文字のyを付けるのはどうでしょうか。たとえばyProductとか。確かに区別はできるものの、使いやすいかどうかは試してみないと分かりません。とりあえず試してみて、良かったら続け、さほど良くなかったら命名ルールなしに戻したいと思います。

 

続いて、メソッド名です。これは、クラスのインスタンスを入れた変数名と一緒に使います。その変数名が区別を含んでいるため、わざわざ区別を入れても、二重の情報になってしまいます。というわけで、特別な命名ルールは使いません。

 

今度は、enumなどに付けるデータ型の名前です。enumでデータ型を用意すると、決められた値しか入れられませんから、値のエラーチェックも不要になり、さらに読みやすさも向上したコードが作れます。大変便利で良く使っています。

今までは、あまり気にせずに名前を付けてきました。もしかしたら、似たような名前を作っているかも。また、これから作りそうな気もします。重複しないとともに、どこに所属しているのか明らかなほうが良いと考えました。所属する場所のほとんどは、それを使うクラスです。クラスとの関連性を示せたら良いでしょう。

そこで命名ルールは、クラス名の略号を数文字で表し、先頭に付けることにしました。その数文字ですが、最初の1文字だけが大文字で、それ以外は小文字にします。たとえば、ZPhotoSelectorクラスならZpsと略せ、それをデータ型の名前の先頭に付けます。もしデータ型の名前がZpsBtnTypeなら、ZPhotoSelectorクラスで使われている、ボタンのタイプを意味すると推測できます。Zで始まるため、ライブラリのクラスであることも分かります。

アプリ独自のクラスでも、同じ命名ルールを採用しましょう。Yで始まる数文字の略語を得て、データ型の先頭に付けます。

 

続いて、定義値の名前です。定義値というのは、最大値や固定値などに名前を付けて定義し、処理の外に出して、見つかりやすくするものです。後から変更しそうな値に適用します。C言語の時代から使われていて、すべて大文字で表現する名前がよく用いられます。

私の場合だけかもしれませんが、処理の外に出す目的以外でも使っています。共通で使うような値を、定義値として最初に用意し、アプリ全体で使ったりします。定義値を用意することで、名前による間接参照にもなりますし。

このような使い方なので、使う機会はかなり多いです。作った場所というか、持ち主が区別できると良いと思っています。

その命名ルールですが、ライブラリの場合は、先頭にZ_を付けることにしました。名前の全部が大文字なので、Zだけだと区切りが不明確になるからです。今までCOLOR_ERRORとしていたのを、今後はZ_COLOR_ERRORとします。

もう1つとなるアプリ独自の定義値の名前は、何も付けないことにしました。その他の分類で似たような定義値が使われていないようなので、ライブラリだけ区別すれば十分と考えたからです。先頭にZ_が付いていればライブラリの定義値、それ以外はアプリ独自の定義値と判断します。

 

最後は、ソースコードのファイル名です。これは以前に「管理しやすい分類情報付きファイル名を使おう」にて紹介しました。あれから分類が1つ増えたので、今では次のようになっています。

// ファイル名の先頭に付ける主な分類情報
・AC:特定の条件(装置など)に依存する共通ライブラリのクラス(複数アプリで利用)
・BP:共通ライブラリの関数や定義値など(複数アプリで利用)
・BV:共通ライブラリのクラスでUIView型(複数アプリで利用)
・BC:共通ライブラリのクラスでUIView型以外(複数アプリで利用)
・MC:アプリで使うモデルのクラス
・VC:アプリで使うビューのクラス

このような形でファイル名を付けると、ファイルの数が増えても大まかに分類できます。最初の2文字が大分類、その次の言葉が中分類、さらに次の言葉が小分類という形で、それぞれの役割が簡単に推測できてしまいます。非常に便利に使っているので、今のままの命名ルールで続けます。

 

今回の考察によって、ライブラリとアプリ独自のコードが、かなり区別できるようになるはずです。ライブラリ内の名前の変更が必要ですが、後になるほど大変なので、今やっておくしかないでしょう。少しずつ更新しようと思います。

もう1つ、作らなければならないものがあります。上記のルールを書いた書類を用意して、ライブラリを使っているアプリにも入れることです。そうしておくと、私以外の人がアプリをメンテするとき、ソースコードを解読しやすくなるでしょう。私が永遠にメンテできるはずはないので、次の人への準備は非常に重要だと思います。

アプリをビルドしたとき、余計な書類が含まれていてもアレなので、ルールを書いた書類はSwiftのソースコードが良いかもしれませんね。すべてをコメントとして書き、ファイル名にZZ_to_Developerとか付けて。メンテする人が、必ず見てくれるように。

 

さっそく、今作成中のライブラリから適用しています。これまでのライブラリやアプリも、バージョンアップの際に更新する予定です。

2015年5月21日木曜日

iOS実験専用アプリを活用しよう(5)

かなり前になりますが、「iOS実験専用アプリ」の作り方と使いかたを紹介しました。今でも使い続けていますが、以前よりも使い方が上手になっています。というわけで、まだ公開していない新しい使い方を、続編として紹介します。

 

このアプリにはボタンが40個もあり、それぞれに別な機能を割り当てられます。割り当てられた機能の数が増えるほど、画面上に加えるUI部品の数も増えます。そのままでは使いづらいので、使い終わったボタンの機能は、オフする方法も紹介しました。ボタン名の最初に「x」を付けて、ボタンに割り当てた機能をコメント化する方法です。

この方法で問題なく使えるのですが、やはりオンオフが面倒だと感じるようになりました。オフした機能を使う際に、今使っている機能をオフすることも多く、切り替え作業は意外に面倒なのです。

というわけで、いちいちオフしなくても構わない作り方はないのかと、深く考えてみました。やはり真剣に考えると、思い付くものなのですね。今までよりも格段に良い作り方が、できてしまいました。

 

新しい作り方を紹介する前に、以前の作り方を簡単に説明します。

各ボタンには、2つの関数が用意されています。初期化の際に呼ばれるstartBtnXXF関数(XXは2桁の数字)と、ボタンをタップした時に呼ばれるtestBtnXXF関数(XXは2桁の数字)です。

通常の作り方は、初期化のstartBtnXXF関数でUI部品を表示し、ボタン用のtestBtnXXF関数で何かを実行するという形式でした。初期化の際にUI部品を表示してしまうため、複数のボタンをオンすると重ねて表示され、一緒にオンすることができなかったのです。

 

この問題を解決するために、少し発想を変えました。ボタン用のstartBtnXXF関数に初期化の処理も含めたら、オフする必要がないのではないかと。つまり、使い始めるまで何も表示させないという作り方です。

それとともに、使い終わったときの状態も考えなければなりません。使い終わってUI部品が表示されたままだと、別なボタンの機能を使う時に邪魔になります。そうならないようにと、画面上をクリアーする機能も、ボタンの機能に加えることにしました。

ただし、これらの機能を複数のボタンに割り当てたら、ボタンをどんどん消費してしまい、40個もあるボタンがすぐに足りなくなります。そこで、1つのボタンだけ使って、初期化からクリアーまでを実行できる方法を考えました。

解決策は簡単でした。タップされるたびに、順番に実行していけば良いのです。最初のタップで初期化だけします。次のタップでは、1つ目の使い方を実行します。さらに次のタップでは、2つ目の使い方を実行します。このように続けて、一番最後のタップにクリアー処理を割り当て、画面上のUI部品を消します。

クリアー処理が終わると、最初の状態に戻します。そのまま同じボタンをタップすれば、2回目の初期化処理が始まります。このように、初期化からクリアー処理まで何度も繰り返せるように作れば良いのです。

 

以上のような説明だと、難しそうに感じたかもしれませんが、実は意外なほど簡単です。具体的なSwiftコードを紹介しましょう。単純な例のほうが理解しやすいので、2種類の使い方しか含まない例を。

// ======================================== ボタン17:棒グラフ
var graphView17: GraphBarView!
var num17: Int = 0

func startBtn17F() {
    aryButton[17].setTitle("棒グラフ", forState: .Normal)
}
func testBtn17F(rSender:UIButton) {
    setMsgF(1, "タップ17")
    run17F()
}
func run17F() {
    switch num17 {
    case 0:
        setMsgF(2, "生成")
        graphView17 = GraphBarView()
        graphView17.setupF(10, 10, 600, 400)
        graphView17.setLabelF(["SEP 1","SEP 2","SEP 3","SEP 4","SEP 5","SEP 6","SEP 7"])
        graphView17.setDataF([1,24,12,18,30,10,21])
        graphView17.setDataRangeF(0, 40)
        viewBase.addSubview(graphView17)
        graphView17.drawGraphF()
        num17 = 1
    case 1:
        setMsgF(2, "正常1")
        graphView17.setLabelF(["NOV 1","NOV 2","NOV 3","NOV 4","NOV 5","NOV 6","NOV 7"])
        graphView17.setDataF([11,4,22,28,3,20,1])
        graphView17.setApparanceF(1)
        graphView17.drawGraphF()
        num17 = 2
    case 2:
        setMsgF(2, "正常2")
        graphView17.setLabelF(["SEP 1","SEP 2","SEP 3","SEP 4","SEP 5","SEP 6","SEP 7"])
        graphView17.setDataF([1,24,12,18,30,10,21])
        graphView17.setApparanceF(2)
        graphView17.drawGraphF()
        num17 = 999
    case 999:
        setMsgF(2, "終了")
        graphView17.removeFromSuperview()
        num17 = 0
    default:
        break
    }
}

見てのとおり、値ゼロで用意した変数num17を使って、順番に実行させているだけです。要点だけ簡単に説明します。

同じボタンを何度もタップするので、何の処理をしているのか画面上に示す必要があります。この例では、メッセージ欄に短い文字列を表示しています。最初が「生成」で、UI部品を画面上に表示させます。続く「正常1」は、正常系の1番目の実行です。この例には含まれてませんが、エラー系(エラーを含んだ処理)も後に付けるため、通常の使い方を「正常」と表示しています。次の「正常2」も正常系で、別な内容を実行します。最後の「終了」がクリアー処理で、画面上からUI部品を消し、変数num17を初期状態のゼロに戻します。

「終了」してしまうと、メッセージ欄の文字列を除いて、画面上には何も残りません。このボタンの機能を繰り返し実行しても構いませんし、別なボタンの機能を実行することもできます。他のボタンの機能を続いて試す場合は、「終了」で終わらせるのが基本的な使い方です。

 

実行させたい機能は、正常系だけではありません。ほとんどの機能にはエラー処理も含まれているので、正常系に続いて、エラー系の実行もボタンに含めます。前述の例にエラー系の実行を1つだけ加えたSwiftコードは、次のようになります。

// 前述のコードに、エラー系の実行を1つ加えたもの
func run17F() {
    switch num17 {
    case 0:
        setMsgF(2, "生成")
        graphView17 = GraphBarView()
        graphView17.setupF(10, 10, 600, 400)
        graphView17.setLabelF(["SEP 1","SEP 2","SEP 3","SEP 4","SEP 5","SEP 6","SEP 7"])
        graphView17.setDataF([1,24,12,18,30,10,21])
        graphView17.setDataRangeF(0, 40)
        viewBase.addSubview(graphView17)
        graphView17.drawGraphF()
        num17 = 1
    case 1:
        setMsgF(2, "正常1")
        graphView17.setLabelF(["NOV 1","NOV 2","NOV 3","NOV 4","NOV 5","NOV 6","NOV 7"])
        graphView17.setDataF([11,4,22,28,3,20,1])
        graphView17.setApparanceF(1)
        graphView17.drawGraphF()
        num17 = 2
    case 2:
        setMsgF(2, "正常2")
        graphView17.setLabelF(["SEP 1","SEP 2","SEP 3","SEP 4","SEP 5","SEP 6","SEP 7"])
        graphView17.setDataF([1,24,12,18,30,10,21])
        graphView17.setApparanceF(2)
        graphView17.drawGraphF()
        num17 = 101
    case 101:
        setMsgF(2, "エラー1")
        println("----------TEST-ERROR-1:Apparanceが範囲外")  // 区切り行
        graphView17.setLabelF(["NOV 1","NOV 2","NOV 3","NOV 4","NOV 5","NOV 6","NOV 7"])
        graphView17.setDataF([11,4,22,28,3,20,1])
        graphView17.setApparanceF(9)    // エラー
        graphView17.drawGraphF()
        num17 = 999
    case 999:
        setMsgF(2, "終了")
        graphView17.removeFromSuperview()
        num17 = 0
    default:
        break
    }
}

エラー系が正常系と違うのは、メッセージ欄に「エラー1」と出すだけではありません。続くprintln文で、エラーメッセージの区切り行を出力しています。区切り行には、発生が予想されるエラー内容を、短い言葉で書くのが基本です。

エラー系の実行では、処理内容に何らかのエラーを含めるため、実行対象の機能からエラーメッセージが表示されます。そのメッセージが、エラー系のどの実行で出されたメッセージなのか、判別できる必要があります。その判別を容易にするために、区切りのメッセージを出力するわけです。

区切り行だという点を強調するために、先頭にハイフン10個を付けています。エラーメッセージと一緒に並んだとき、区切り行として見えるようにとの配慮です。具体的には、次のように出力されます。

----------TEST-ERROR-1:Apparanceが範囲外
2015/05/20 20:24:50 ERROR:GBV_SA:Apparance番号が範囲外

これだけだと効果を実感できませんが、複数のエラー実行が並んでいて、1つの実行で複数のエラーメッセージが出力されたとき、区切り行のおかげで見やすく整います。

 

ちなみに、変数num17に設定する値ですが、単純な連番ではありません。この例では、正常系は1から始まり、エラー系は101から、終了は999にしてあります。このように作ってあるのは、正常系やエラー系の実行内容を追加したとき、変更する箇所を減らすためです。

もし最初から最後まで連番の場合、修正が大変になります。たとえば、正常系が15個、エラー系が20個あったとき、単純な連番だとエラー系は16番から始まります。そこへ。正常系の処理を1つ追加する場合、エラー系の開始番号が16番から17番へと変更になり、後に続く番号をすべて変えなければなりません。そんな無駄を避けるために、飛び飛びの番号をつけているのです。

この例では正常系の番号を1つずつ増やしていますが、2ずつ増やすとか、10ずつ増やすとかでも構いません。後から追加するとき、間に挿入しやすくなりますから。別な考え方として、正常系の実行を複数グループに分け、最初のグループは1番から始まり、次のグループは21番から、さらに次のグループは41番から、という形で番号を付ける方法もアリです。

 

今回は、「iOS実験専用アプリ」の上手な使い方として、機能をオフしなくて済むような作り方を紹介しました。

この方法が適さない機能もありますから、必要に応じて使い分けたり、別な新しい方法を考え出したりする必要があります。工夫して使うほど、iOS実験専用アプリはどんどん便利になりますね。私の場合、もはや必須ツールとなっています。

 

(使用開発ツール:Xcode 6.3.2, SDK iOS 8.3)

2015年5月18日月曜日

ライブラリ化とInterface Builder不使用の重大な関係

先日の連休中に、友人のIT技術者と、じっくり話をしながら飲みました。そのときに、ライブラリ化の話で盛り上がったのですが、私が気付いてなかった点を指摘されました。その話を短かく取り上げます。

 

友人の指摘は「ライブラリ化が容易になっているのは、Interface Builderを使わずに開発していることが、非常に大きい」というものでした。ぜんぜん気にしていない点だっただけに、かなり驚きました。

友人の意見は大事な点が何個も含まれていましたが、要旨を整理すると以下のようになります。

友人の意見の要旨
・Interface Builderを使わないことで、
 ・ライブラリに含まれるのはSwiftのソースコードだけ
 ・サイズの変更が容易となり、Viewの大きさに合わせたサイズ調整が容易
 ・必要な数だけのUI部品が、短いソースコードだけで連続して生成できる

この中で一番大きな点は、Swiftのソースコードだけでライブラリが作れる点だというのです。

 

このブログの最初に宣言したとおり、私の開発ではInterface BuilderとStoryboardをまったく使っていません。UI部品の生成関数を用意して、すべてのUI部品をその関数で生成しています。そのため、ライブラリに含まれるのは、Swiftのソースコードだけとなります。まさに、指摘されたとおりです。

どのライブラリも、Swiftのソースコードだけで作られていて、しかも1つのファイルに収まっているため、アプリへは簡単にコピーして持っていけます。

当然ですが、アプリ内でのライブラリのバージョンアップも簡単です。古いソースコードを削除して、新しいソースコードに入れ替えるだけです。ライブラリのインターフェースはめったに変えませんから、アプリ側を直さずにバージョンアップできています。Swift側やAPI側の仕様変更を除いて。

 

ライブラリを作るときだけでなく、テスト環境まで保存しているiOS実験専用アプリ自体も含めて、Interface BuilderやStoryboardを使っていません。Xcodeの新規プロジェクトから、Single View Applicationを選び、空の画面のアプリケーションを用意しました。そのままSwiftコードだけを追加して、iOS実験専用アプリを仕上げています。

当然ですが、ライブラリのテストで使う画像や音のファイルは、iOS実験専用アプリのプロジェクトに追加しています。それ以外のテキストデータなどは、Swiftのテストコードに中に入れ、ソースコードの形で用意できるようにしています。テスト用のテキストファイルも基本的に、Swiftのソースコードから生成し、わざわざファイルを用意しないのが基本です。

このように、改めて見直すと、できる限りSwiftのソースコードから作っていますね。今書いていて気付いたのですが、別に用意している画像ファイルも、描画機能を使って描いてからファイルに保存すれば、Swiftのソースコードから作れますね。まあ、やらないでしょうけど。

 

Swiftでアプリ開発を始めるとき、ツールによる余計なトラブルを避ける理由と、自由度を優先するという理由から、Interface BuilderやStoryboardを使わないと決めました。その選択が結果として、ライブラリ構築の自由度も上げていたわけです。

自分にとっては、あまりに当たり前の選択だっただけに気にしておらず、言われるまで気付きませんでした。結果としては、Interface BuilderとStoryboardを使わない選択が、ライブラリ構築にも良かったわけです。

ちなみに友人も、Interface BuilderやStoryboardを使わない方式に切り替えたそうです。そして、自分専用の独自ライブラリを少しずつ構築中だとか。

 

とは言うものの、Interface BuilderやStoryboardを使わない開発を、他の人に勧めるかといえばノーですね。開発者にもいろいろな人がいて、便利ツールがないと苦労する人もいますから。ですから最初は、とりあえずInterface Builderを試すように勧めます。

Interface Builderを使わずに開発する方法には、軽い壁があると思います。それほど大きな壁ではありませんが、それを乗り越えられる人でないと難しいでしょう。具体的には、UI部品を生成するライブラリを自分で整えるとか、Interface Builderの代わりとなる道具を自分で用意する手間と技術が必要となります。その条件を軽く乗り越えられる人なら、Interface Builderを使わなくても大丈夫です。

もし大丈夫な人なら、使わないほうを積極的に勧めます。ライブラリさえ用意してしまえば、開発の自由度が格段に増し、すべてソースコードでコントロールできる形になりますから。経験が長いというか、それなりに実力のある人には、使わないほうを積極的に勧めたいですね。

 

Interface Builderを使わない開発では、画面レイアウトの問題が常に生じます(使う開発でもある程度は生じていますが、自動レイアウトで何とかなる場合もありますから、問題が軽減されているでしょう)。

今のところiPad開発ばかりなので、画面サイズの違いを考慮しなくて済んでいます。UI部品の表示位置や大きさを数値で指定する方法で、問題はまったくありません。しかし今後はiPadも、複数の画面サイズを意識しなければならない状況になると予想します。現状のiPhoneのように。

一応、対策は考えています。自動レイアウトは使いたくないので、画面サイズごとにレイアウトを切り替える予定です。切り替えといっても、ソースコードは1つにして、画面サイズごとに別な座標数値を使う方法です。座標数値を配列に入れ、その配列を参照しながら表示位置を決めれば、複数のレイアウトへ簡単に対応できます。UIView型ライブラリがサイズ可変なのも、このような状況を予想してのことです。配列から座標を読み込む方法で作った結果、見栄えや使い勝手が悪い場合にだけ、完全に別なレイアウトで作る予定です。

複数の画面レイアウトの問題は、Interface Builderで解決できるものではありません。自動レイアウトも限界はありますし、細かな見栄えは低下しがちです。今後も、大きな課題として残り続けるでしょう。

 

画面レイアウトの問題があっても、Interface BuilderとStoryboardを使わない開発を、止める気はありません。今までどおり、Swiftのソースコードだけで作った独自ライブラリを、どんどんと増やしていくつもりです。

Swiftでの開発に慣れてきて、Interface Builderを使っている方は、使わない方法を一度検討してみてはいかがでしょう。もしかしたらですが、素晴らしい未来が待っているかもしれません。最初の取り掛かりとして、UI部品を生成するライブラリ作りからお勧めしたいですね。