はじめに
ここ数日、null安全についてバズってますね!
null安全でない言語は、もはやレガシー言語だ
http://qiita.com/koher/items/e4835bd429b88809ab33
そういや先週はt-wadaさんのスライドもバズりましたね。
PHP7で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 / PHP Conference 2016
https://speakerdeck.com/twada/php-conference-2016
これらの記事、実は同じ話してるなぁ🤔と思ったので、まとめてみました。
※なお、この記事はコード改善 meetup #2 http://kaizen.connpass.com/event/42118/ での同名の発表を、文章形式でリライトしたものです。
スライド版(Speaker Deck)はこちら。
null安全について、さっくりおさらい
-
null安全でない言語
-
String
に文字列が入るかもしれないし、nullが入るかもしれない - null への操作は実行時エラー
-
-
null安全な言語
-
String
(non-null) とString?
(nullable) を明確に区別 -
String?
は、String
に変換しないと使えない - コンパイル時(動的型付言語では静的解析時)にエラー
-
わずらわしい? 生産性が下がる? NO!
うまく扱うための仕組みがたくさんあるから大丈夫!
というのが、「null安全でない言語は、もはやレガシー言語だ」での説明でした。
Swiftを書いていても、わずらわしさは実際ほぼありません1。
実際、nullの可能性が絶対ないなら、ただの String
を引き回せば事足ります。
String
にnullが入る可能性は、意識すべき場合にだけ明確に意識すればいい(というか、型が違うので意識せざるを得ない)…というのが、null安全な言語のデザインです。
t-wadaさんの発表との共通点
さて。
ひたすら「PHP7で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計」からの引用していきますが、
「防御的プログラミング」とは、問題発生を事前に防ごうというコーディングスタイル
"「出来てならぬことを禁じる」のではなく、はじめから「出来ていいことだけを出来るようにする」と考えるのです"
問題領域の知識を活用して固有の型を作ることで、取り得る組み合わせを大幅に減らせる
契約による設計
実行時の表明違反(…)は、そのソフトウェアにバグがある証拠である
事後条件違反は供給者側にバグがある証拠である
どうよ?
あれっ、t-wadaさん、PHP confなのにnull安全の話をしてたのかな? って気分になってきますよね。なってきませんか。
TDDと型システムとコンパイルエラーの話
話変わるんですけど。
今年の2月、TDD Boot Campというワークショップに行ってきたんですよ。
TDD。Test Driven Developmentです。
TDDBC | Doorkeeper
https://tddbc.doorkeeper.jp/
それぞれ言語畑の違う参加者に、共通の課題が与えられて、1ステップずつ、それぞれの好きな言語でTDDしていくというワークショップでした。
自分はもちろんSwiftチームです。
TDDというのはつまり、
- 失敗するテストを書く(Red🚫)
- テストが通るように修正する(Green💚)
- リファクタリングする♻️
の繰り返し。
で、その結果…困ったことが起きました😧
最初の「失敗するテストを書く」でいきなり躓いたのです。
何故なら、テスト対象の「正しくないコード」がコンパイルを通らないから…!
コンパイルが通らないんじゃ、テストコードの書きようがない。
会場のチューターさんにヘルプを求めると…
🤖「コンパイルエラーはRedだと思ってもらって構いません」
はっとしました。
テストコードの書きようがないんじゃない。
テストコードを書く必要がないんだ。
これがtakasekが型システムに恋した瞬間でした。どうでもいいですね。
大で小を兼ねるな
さらにところで、先日、potatotipsというiOS界隈の勉強会で、こんな発表をしました。
大で小を兼ねるな // Speaker Deck
https://speakerdeck.com/takasek/da-dexiao-wojian-neruna
あまり言語関係ない内容なんですが、読まなくてもいいように要約すると、こんな内容です。
- 日付📆を表現したい場合
- Swift標準のDate型はオーバースペック
- Year, Month, DayをIntで持つ構造体があれば充分
- 機能が多い=罠が多い
- 大が小を兼ねると悲しみが生まれる
これ、null安全でない型 が抱えているのも同じ問題ではないでしょうか。
- 本来的な型の機能(文字列, 数, etc…の保持と操作)
- nullが入れられるという機能
これらは別の機能なのに、大で小を兼ねてしまった結果、悲しみ😢と10億ドルの損害💸が生まれたのです。
できていなかったのは——本当に必要だったのは、型安全を保証できる、正しい型システムの設計だったのです。
型安全性とは何か
型安全性とは何か | プログラミング | POSTD
http://postd.cc/what-is-type-safety/
という記事で、Robin Milner氏の1978年の論文「プログラミングにおける型ポリモーフィズムについての考察」が翻訳されています。
正しく型付けされている→不正な動作をしない
言語の型システムは、型安全な言語において”正しい”(間違っていない)プログラムだけが通過できるように保証する特別な方法です。具体的には、型システムが条件を満たしたプログラムだと判断すれば、そのプログラム(またはプログラムフェーズ)は正しく型付けされていると言えます。型安全は正しく型付けされたプログラムが決して不正な動作をしないように保証します。そういうプログラムは(正しく定義された)意味を持つことになります。下図はその状況を視覚化しています。
型安全な言語においては、正しく型付けされたプログラムは、正しく定義されたプログラムのサブセットであり、それらは更に全ての(シンタックス的に正しい)プログラムのサブセットとなります。
null安全でない型システムが不正な動作をしている(型安全でない)ということは、正しく型付けされていないということです。null安全は、その型付けを正しく設計しなおし、機械に任せるべきものを機械に任せる(実行時ではなく、コンパイル時にエラーを検知する)仕組みと言えます。
なので、null安全はテストコードと対立するものではない…ってことは、「null安全を誤解している人達へのメッセージ」という素晴らしい記事でも触れられているとおりです。
null安全は特にテストコードと対立するものではありません。ただ、テストコードを書く前から、nullに関するバグを発見してくれるものです。自動で完璧に網羅したテストコードを書いてくれるようなものです。(…)コンパイラは機械なのでミスはしません、完全に網羅してくれます。
一方で、nullにまつわる型システム以外のところで生じるバグは発見できません。そこでテストが必要になります。(…)テストコードを書いている出発点も、機械が得意なことは機械にやってもらおう、という発想なはずです。同じ気持ちです。
それな。
結論。型はいいぞ。
- 型は機械が分かる形で契約を表現する技術
- だから防御的プログラミングを自動化できる
- null安全は well typedな型システム
- だからこそ、null安全はいいぞ
-
完全にわずらわしさを感じないわけでは…まあ、特定の状況を除いては…。といってもnull安全に罪があるわけじゃなくて、Swiftの言語仕様がそこまで到達してないだけなんですけど。 ↩