Swifty Methods
Swift の登場により、Objective-C のとても長い API がより短く、読みやすいメソッドに書きかえられるようになりました。しかし、文字数を減らしたからといって、必ずしも分かりやすくなるというものでもありません。Radek Pietruszewski は、今回、明快さと簡潔さのちょうど良いバランスを保つために、どのようにしてコードからノイズ(不要な部分)を取り除いたかについての発表です。
イントロダクション (0:00)
“プログラムは、人々がそれを読むために書かれるべきである。たまたま、それが計算機で実行できるにすぎない。”
- 計算機プログラムの構造と解釈
これを見たとき、少し大げさなことを言っている引用文だと思う人がいるかもしれません。つまり、アプリは、ついでにコンピュータによって実行されるに過ぎないのです。この言葉の裏側には、今回私が伝えたい大事な考えがあり、まだあまり多くのプログラマが、それを完全には認識していないと思っています。その考え方とは、私たちが書くコードというのは、ただコンパイラが理解しやすいだけではなく、人間の読み手にも理解しやすいものでないといけないということです。
明快さ: 賢いコードは愚か (1:32)
私は、コードは常に明確さを念頭に置いて書くべきだと思っています。そういう風にコードを書くべきで、少なくともできるだけ明確に書こうとすべきです。もちろん明確なコードを書くのは、簡単なことではありません。時間がかかることですし、スキルも必要です。
プログラマの人は、賢いコードを書こうとする傾向にある人が多い気がします。確かに、賢いコードが書ければ気持ちが良いです。しかし、自分を賢いと思えるだけで、6ヶ月後にはそのコードがどういう意味なのか覚えていません。賢いコードというのは、理解するのが難しいものです。明確ではなく、愚かなのです。
明確さは価値のあること (2:03)
長い目で見て、明確に書くことは非常に価値のあることです。分かりやすいコードを書くことは、良いプログラマーである証であり、プログラムがより良くなります。普通に書くよりも時間がかかりますが、後々扱いやすいコードベースになります。そういったコードは、読みやすく、理解するのに時間がかからないのです。しかも、バグが出るのも避けやすくなります。なぜなら、分かりやすいコードでそれ程、間違いを犯すと思いますか? 複雑なものや理解しにくいものを扱ってるときの方が、明らかに間違いやすくなります。とにかく、明確さは重要で、価値のあることであるという点を分かっていただきたいです。
明確 != 冗長 (3:08)
私は、Objective-C の明確さは、間違った考え方だと思います。Objective-C は、冗長さと明確さを混同してしまっています。その点について明らかなのは名前です。ご存知の通り、Objective-C での名前の付け方は以下の例のようです。
stringByReplacingOccurrencesOfString:withString;
[string componentsSeparatedByString:@"\n"];美しいですね。文のように読めます。スライドの中にでさえ長すぎて収まりきれていないものもあります。さて、これのどこがいけないのでしょうか? 一つ例を挙げます。皆さんは、これらのメソッドが何をするのか既にご存知かと思います。たとえ、それほど知らなくても推測できると思います。なぜなら、この分かりやすい名前があるからです。ここからメソッドの意味が理解できると思います。しかし、もし知らなかったとしたらこれはどうでしょうか? ここでいう components というのは何を意味しますか? 何か特定のものを指していますか? または NSComponent というものがあったでしょうか?
確かに分かりやすいですが、これだけの言葉では正確にメソッドの意味を表すことは不可能です。よって、これは明確さ 95%、冗長さ 100% といったところでしょうか。そして、何か他に考えられる手段はありますか? Ruby の場合は以下のようになります。
string.split("\n")これは何を行っていますか? 改行で string を分割しています。皆さんはどうか分かりませんが、私はこれは分かりやすいと思います。Objective-C よりは明確ではありませんが、この場合、名前を長くすることで分かりやすくなっているとは思いません。でも、もし説明が十分でない場合はどうですしょう? もし components の意味を知らなかったらどうですか? もし初めて見るメソッドで何を行うのか全く知らなかったらどうでしょうか?
さて、どのようにして理解しましょう。多分、alt-click を押して説明を読みますよね? そうです Xcode というツールがあります。もし意味がわからないものがあったとき、alt-click で説明を読めば良いのです。そして、一度理解すれば、split でも componentsSeparatedByString と分かりやすさは変わらなくなります。
Objective-C を長く書いてきた方なら、このあたりで止めてもらいたいと思ってるかもしれません。componentsSeparatedByString という名前のどこが悪いのかと思っていることでしょう。では、これの何が問題なのでしょうか? ここからどうすれば良いでしょう? なぜ文字数を減らす必要があるのでしょうか? ここでも、明確さが簡潔さに勝るのは変わりません。
冗長さに縛られてる (5:40)
簡潔であることは明確であることは関係ないと言う人もいると思います。冗長さに縛られてるということもあります。最近のツールにはオートコンプリートがあるのが普通だし、長いメソッドを書くのが苦ではないのは事実です。しかし、同時にそれを読まなければいけません。実際、それを読むののは大変です。紛らわしいコードよりも分かりやすいコードの方が良いように、コードが短ければ、長いコードよりも簡単に読めると思います。
簡潔さと冗長さの丁度いい点を見つけることが大事です。そこに明確さがあるのです。そして、それを達成するには、意味のある情報として機能しない余分なノイズを取り除く必要があります。split は componentsSeparatedByString と同じぐらい分かりやすいですし、replace も stringByReplacingOccurrencesOfString:withString: と同じぐらい分かりやすいです。
そして、私は、replace の方が余分なものを含んでいないため後者よりも分かりやすいと思っています。stringByReplacingOccurrencesOfString:withString では string が三度も登場します。これに何の意味があるのでしょうか? 変換する型の情報がメソッド名の中にあります。これはナンセンスだと思います。私の思う限りでは、この情報は誰にも役立つとは思えません。99%、明らかに分かりきっていることです。もし知らなかっても、alt-click で説明を見れば良いのです。また、“by”, “of”, “with” も無駄です。何の情報も含んでいません。ここは、replace で十分です。この方が、シンプルに一つの動詞だけで、明確で分かりやすいと思います。
ノイズの除去 (7:31)
それでは、Swift について見ていきましょう。この2つのネーミングの考え方は Swift にも当てはまっていると思います。型推論がある言語でノイズを削除できる考え方についてご説明します。メソッドに型についての情報を含む必要はありません。明らかなことなのに、重複させる必要はないです。これらはノイズですので、取り除くことにします。
セミコロンやポインタのアスタリスクや角括弧はどうでしょうか? それほど多くあるというわけではありませんが、これもノイズだと思います。なので、除去できます。Swift にはクロージャのショートカットもあり、また、他の言語にもあるようなオペレータや Optinal Chaining もあります。
コードを短くすることは良いこと (8:38)
理解のためにコードを短くすることは良いことです。何もノイズから意味のある情報を引き出す必要はありません。以下が別のノイズの例です。これは Mac アプリで NSWindow のインスタンスを作るときのコードです。
[[NSWindow alloc]
initWithContentRect: frame
styleMask: NSTitledWindowMask
backing: NSBackingStoreBuffered
defer: NO
screen: nil]これは前からうんざりしていた部分です。initWithContentRect:frame の部分だけが本当に設定したい部分でした。他のところはデフォルトの値で良いのです。なので、その部分がノイズでした。Swift では、デフォルト引数を定義できるようになったため、この問題が解決されました。イニシャライザは以下の通りです。
init(
contentRect: NSRect,
styleMask: NSWindowMask = .Titled,
backing: NSBackingStoreType = .Buffered,
defer: Bool = false,
screen: NSScreen? = nil)NSWindow(contentRect: frame) と初期化できるようになり、かなり良くなりました。
Swift らしい API (9:27)
この冗長さと簡潔さのバランスが取れたこの考え方は素晴らしいと思います。また、ノイズを取り除くことによってより明確になっています。そして、どのように名前を付けるかという文脈だけでなく、どのように API をデザインするかという点から考えてみましょう。以下が、一つの例です。
[NSTimer scheduledTimerWithTimeInterval: 1.0
target:self selector: @selector(foo:)
userInfo: nil repeats: YES]
- (void) foo: (NSTimer *timer) {
NSLog(@"Hello world!")
}これは、Objective-C でシンプルなスケジューラをセットする方法です。1秒毎に、コンソールに “Hello world!” が出力されます。ここからノイズに当たる部分を取り除くとどうなるでしょうか? それでは、メソッドの名前を一語一語見ていきましょう。“Scheduled” は、タイマーをセットするので必要です。NSTimer と言っているので、“Timer” という文字は重複していることになります。“With” はノイズです。何も意味のない情報です。”Time” も時間以外のインターバルをセットするわけではないので余分です。これで少し良くなりました。しかし、もう少し改善の余地があるところがあります。
NSTimer.schedule(interval: 1.0,
target: self,
selector: "foo:",
userInfo: nil,
repeats: true)
func foo(timer: NSTimer) {
println("Hello world")
}ここからどのように改善できるでしょうか? ターゲットセレクタ の部分はどうでしょうか? 簡単のタイマーをセットするだけなのに、新しいメソッドを定義する必要はありません。それが必要ある時もありますが、しかしここでは必要ありません。私の経験から言うと、だいたいの場合はブロックだけで十分です。
NSTimer.schedule(interval: 1.0,
userInfo: nil,
repeats: true) {
println("Hello world")
}他にできることは何かあるでしょうか? userInfo があります。ターゲットセレクタがない今、これはもはや不要です。よって消しましょう。これでさらに改善できました。
NSTimer.schedule(interval: 1.0, repeats: true) {
println("Hello world")
}また、repeats: true もあります。これは削除しても問題ないです。しかし、ただ削除するだけでなく他にも変更を加えてみましょう。API をより分かりやすくするためにメソッドを以下のような名前に変えます。
NSTimer.schedule(every: 1.0) {
println("Hello world")
}毎秒という意味で “every” を使うことができます。きちんと意味が通っています。ノイズを取り除くことは、一つの手段に過ぎません。覚えておいてください、ここでの目的をより明確にすることです。明確さという視点から言うと、“1.0” というのは分かりにくい部分です。これは本当はどういう意味ですか? Ruby on Rails から借りてきたアイデアとして、second, seconds, minute, hours と付け加えるのはどうでしょうか? NSTimeInterval を返すため、以下のように書けるようになります。
NSTimer.schedule(every: 1.second) {
println("Hello world")
}繰り返さないタイマーにしたい場合はどうしますか? every の代わりに after とすれば、1秒後に実行されるように書き変えれます。かなり良いですね。これで分かりやすく、読みやすくなりました。どうですか、明確さを犠牲にして簡潔にしているわけではありません。その代わりに、ノイズを取り除くことによって、より明確にできました。
まとめ (12:40)
簡単にまとめると、今日お伝えしたかった4つの考え方です。
1: 明確さにフォーカスする
分かりやすく書くことを優先度を一番にしてください。分かりやすいコードは、扱いやすく、読むのが簡単です。また、理解するのも簡単になります。バグが出るのも避けれます。明確さは、コードの品質の大事な要素です。よって、プログラムとプログラマー、どちらにとっても大事です。
2: 賢いコードは書かない
賢いコードを書くのは誰のためにもなりません。理解するのが困難で、明確ではありません。その代わり、明確で簡潔に書くように心がけます。決して、簡単ではありませんが、価値のあることです。
3: 冗長性によって明確さは達成されない
明確さは、ノイズを減らすことによって達成されます。簡潔にすることは決して目的ではありません。しかし、明確にする一つの要因となります。簡潔さは、冗長さは決してイコールではありません。
4: Swift を書くことに対して前向きであれ
Swift は違う言語なので Objective-C を単にコピーするようにはしないで下さい。Swift を書くときまた違ったアプローチが必要です。考え方を変えるようにしてください。
Q&A (14:24)
Q: Swift で簡潔に書きすぎてるまたは、賢く書きすぎてて、それゆえに、読みにくくなっているコードの例などはありますか?
Radek: 簡潔に書きすぎてるため明確さが失われている例ってことですか? あまりはっきりとは覚えていないですが、そのようなものをいくつか見ました。Visual Format Language で作られている AutoLayout を操作するオペレータがありました。まあ簡潔に書きすぎてるという以外の理由もあるのですが、あまり良くない気がしました。一度も見たことない人が見て、すぐに理解できないぐらい簡潔に書きすぎていたらだめだと思います。一番難しいポイントで、ここで言うバランスは、人それぞれの別の違った意見を持っているところです。
Q: NSTimer の理想である API についてお話しされていましたが、Apple が今後、変更を加えると思いますか? また、もし変わらないのであれば、開発者がより Swift っぽく API をラップすることに価値はあると思いますか?
Radek: 最終的にはそうなると思います。NSTimer は、Objective-C から改善の余地があった少し極端な例です。以前から blocks は使えましたし、でもそこでは使われていませんでした。とても古い API です。最新の Xcode ベータでは、たくさんの Foundation API の変更がありました。NSTimer のような基本的なところは置き換わっていくと思います。Cocoa もそうです。しかし、それは長い時間がかかることでしょう。
みんなが API の上でラッパーを書くことが良いことかはわかりません。効率が悪い気がしますが、やりたいのであれば可能です。NSTimer は標準ライブラリの例です。これを選んだ理由は、普段使うものなので、多くの人がコードに活かせると思ったからです。例は標準ライブラリに適したことです。アプリケーションを書いているとき、冗長にせざる終えないところがまだまだあるからです。この例は、一般的なことではなくて、特定の分野に依存していることです。
Radek のブログでこの発表に関する記事があります!