Swiftでぬるぽをなくす技術
夏休みの宿題は提出日まで何もしない子供でした。はい。
この文章はWanoグループAdvent Calendarの17日目として書かれたものですが、諸事情で公開が遅れました。
多分この記事はは20日分になると思います。
アドベントカレンダーは土日にしないほうが良いという気づきを得た
本題
Swiftは良い言語なんですよ。Swiftは。
去年辺りから弊社でiOSアプリの開発を担当するようになって、それまでサーバーサイドでPerlとかを触ってた人間がSwiftに触れて、思ったんですよ。あー、これ良い言語だなって。
今日はその辺をつらつら書いていきたいと思います。
みんなぬるぽで死ぬ
みんな大好きNull Pointer Exception!
これはサーバーサイド全般に言えると思うんですけど、何かのタイミングでぬるぽで死んでもまぁ、daemontoolsとかsupervisordとかで再起動すれば良いので、基本動作に問題なければ、そこまで致命的じゃないと思うんですよね。
エラー画面も出るし。いや、まぁ、ダメなんですけど。
ただアプリの場合は、ぬるぽで死んだときは突然アプリの画面からホーム画面に切り替わるんですよ。
突然ですよ?500エラー画面とかもなしですよ?ビビりません?
ユーザーからしたら、は?なにこれ何いきなりホーム画面に飛んでんの?意味わかんないんだけど?
ってなるじゃないですか。
しかもサーバーサイドはエラーを監視しておいて、エラー通知をslackかなんかに飛ばせば、開発者はそれを見てすぐに対応できるじゃないですか。
だからぶっちゃけ5分後にはエラーを修正することができる。理論的には。
いや、めっちゃうらやましいんですけど。なによそれ。ずるくない?
こちとら審査っていうね、Apple神にお伺いをたてる儀式をしてですよ、万が一お伺いで逆鱗に触れたら英語で罵倒しあうわけですよ。
しかも罵倒した結果、お前のアプリクソだからださねぇ、ってApple神に言われたらおしまいですからね。
そこは穏便に行かないといけないわけですよ。
いや、何が言いたいかって言うと、iOSアプリは修正版を適用するのに時間がかかるんですよね。
最近は審査が早くなったからまだマシになったけれども、昔は最低1週間とかかかってましたからね。
しかもそのあとアプリを更新するかどうかはユーザー次第じゃないですか。
あれ意外とみんな更新してくれないんですよ。
となるとアプリを制作するにあたって、最初からぬるぽをなるべく潰していきたいわけですよ、なるべく。
ところがPerlとかJavascriptとかどうよ。
実際に動かしてみて、warnとかconsole.logとかで出力して初めて、あ、この変数nullだわ。そりゃこの処理死ぬよなー、ってわかるじゃないですか。
なにそれ、いちいち全部の動作を試さないといけないの?苦行なの?
って思うわけですよ。
ところがですよ、Swiftはぬるぽかどうか明示しなければいけない言語(Null安全)なので、予めプログラムを静的解析する時点で、こいつぬるぽになりそうだな、となるとここで処理が死にそうだな、ってわかるんですよ。
っていうかそういう不定な部分がある時点でコンパイルが通らないようになってるんですよ。
しかも、じゃあそこでNullチェックを入れようと処理を書くんですけど、そもそもの言語仕様としてNullチェックが書きやすいようになってるんですよ。
なにこれ、神なの?Apple神がまた新たな神を作っちゃったの?って思ったわけです。
Optionalという考え方
とりあえず以下のSwiftで書かれたクラスを見てください。
class HogeClass {
var hoge: String
var fuga: String?
var piyo: String!
init(){
self.hoge = "hogehoge"
}
}
このように、Swiftには型名のあとに、?とか!とかがつけられます。
?が付いている型はOptional型というものです。Nullを許す型です。
!が付いている方はUnoptional型というものです。Nullを許さない型です。
何も付いていない場合は不定です。どこかで明示的に代入をしない限り(Unoptional型と明示しない限り)コンパイルが通りません。
このHogeClassの場合、hogeプロパティが不定なので、明示的にinitで代入処理をしているんですね。
hogeは確かに見ればNullじゃないことは明らかなので問題はないと。
あれ、じゃあこのfugaとかpiyoプロパティってどうなのよ、って思いますよね。思います。
ここでPerlとかにあるように、オブジェクトのメソッドを実行して、nullだったら処理がどうなるかみてみましょう。
class HogeClass {
var hoge: String
var fuga: String?
var piyo: String!
init(){
self.hoge = "hogehoge"
let splittedHoge = self.hoge.components(separatedBy: ".")
let splittedFuga = self.fuga?.components(separatedBy: ".")
let splittedPiyo = self.piyo.components(separatedBy: ".") //ここで死ぬ
}
}
はい、死んだ。
(.components(separatedBy:)はPerlで言うsplitみたいなやつです。)
これを見ると、fugaプロパティの処理では死んでいませんが、piyoプロパティの処理で死んでますね。
これはどういうことかというと、
- 明示的に!をつけた型の場合、コンパイラはNullではないことを信じて処理を実行します。
- 明示的に?をつけた方の場合、コンパイラはNullである可能性があるので、勝手にチェックしてnullの場合は処理を実行しません。(なにもしないのと同じ。)
これの何が嬉しいの、って話ですけど、ようは明示的にNullであるOptional型の場合、実行しても死なないんですよ。
PerlでTengを使って処理をする時に、
[perl]
my $row = $teng->single("hogeTable", {id => 1});
$row->get_columns() # 死んだ
[/perl]
ってことがないんですよ。
また、Nullじゃない型の場合は、死ぬ、逆を言えばそこで処理を殺すことができるんですね。
逆にここがNullだとアプリがまともに動かない、プロダクトとして成り立たない、みたいな話の場合、あえて殺すことでデバッグ時に気付けるようにすることもできるんです。
(普通はエラー処理とかするのであまりやらないけど。)
となると次は、Nullの時になにもしないのは困るし、かと言って死ぬのも困る。普通にNullの時に別の処理をしたいんだけど、ってなりますよね?なります。
その場合はfugaに対してこうやって書けば良いんです。
class HogeClass {
var hoge: String
var fuga: String?
var piyo: String! = "piyopiyo"
init(){
guard let unwrappedFuga = self.fuga else {1
print("Fuga is Null")
return
}
self.hoge = "hogehoge"
let splittedHoge = self.hoge.components(separatedBy: ".")
let splittedFuga = unwrappedFuga.components(separatedBy: ".")
let splittedPiyo = self.piyo.components(separatedBy: ".")
}
}
guardはif notみたいな意味の処理だと思ってください。
これは、
- self.fuga、という変数をUnoptionalの変数(unwrappedFuga)に代入できたら処理を続ける
- それ以外ではprint文でエラーを吐いて処理を終わらせる
ということをやっているんです。
Swiftではif文(guard文)でこのようにUnoptional型の変数に突っ込んでキャストすることが出来ます。
この処理をunwrapする、と言います。(また、!をつけることをForce Unwrapする、と言います。)
こうすることで、Nullの場合のエラー処理を書けるし、以降の処理はNullではない前提で処理を書けるようになるので楽になる、という感じです。
また、if文と違い、guard文はSwiftの言語仕様として、処理の最後にかならずreturnかbreakを書かなければいけません。
どういうことかというと、guard文を書いたときは、自然と条件に合わない場合は処理を弾く、という書き方になるのです。
この場合、文字通りぬるぽをguardする処理になります。
これ結構便利なんですよ。
他の言語にもこの仕様がほしいと何度思ったことか。
まとめ
このように、SwiftではNull安全の考え方、guardのようなNull安全に書ける言語仕様がそろっているので、とても安全に書きやすいのです。
ほかにもprotocol、extensionをはじめ、色々と安全に書く仕様が揃っているので、アプリを作るのにはもってこいの言語だと思います。
もちろん適当にForce Unwrapしまくって処理を書いていればいくらでも死にますが、そうならないように書ける、もしくは書きやすい言語というのはエンジニアとしては嬉しいのではないでしょうか。
Swiftをちゃんと書くようになると、Perlのような型もNull安全もない言語は恐怖すごいと思います。
そのうち、サーバーサイドがそのような言語で書かれた場合にどうやって厳密にNull安全を担保するか、というつらみ方法を書けたらと思います。
みんなSwiftにすれば良いのに。