プログラミングの「世界観」

(Last Updated On: 2018年8月8日)

rustcを通らないコードは間違っているに以下のような記述があります。

「そのコードがRust wayではない」という点で「間違っている」。microなスタイルからmacroな設計まで、ありとあらゆる点でRust的なコードであることを暗黙的ではあるが極めて強く要求する。それがRustというプログラミング言語だ。

Rustはコンパイル時点で可能な限りの正しく実行できないコードが生成されないような言語になっていることは比較的良く知られていると思います。このブログはこの違いを「世界観」が異なるという視点でまとめています。

Rustの場合、マルチスレッドプログラミングでデータ競合が起きないような書き方をしなければならない制約があり、従来のプログラミング言語とは異なる考え方(=世界観)が要求されている。自分のコードは正しくても「Rustの世界観では間違っている」としています。

「世界観」の違いで言語やプログラミング方式を捉える考え方は面白いと思います。

正しく動くコードの世界観

できる限り早く(”速く”ではない)コードが正しく実行されることを保証する、という考え方はエンジニアリング的に正しいアプローチです。プログラミングの基本原則から正しいと言いきれます。

Fail Fast原則 ー 失敗するモノは出来る限り早く失敗させる

正しく動かないモノはいくら頑張っても、正しく動かないケースには正しく動きません。コードがコンパイルされる時点で正しく動作することを保証できるモノが増えれば増える程、プログラムのバグが少くなることは明らかです。

  • 関数に渡されているデータ型が一致している
  • パラメーターとして渡した変数がローカル変数で上書きされていない
  • 関数から戻した変数が呼び出し元の変数のデータと一致している

コンパイル型の言語の場合はこういった単純ミスを簡単にチェックし、エラーとして実行形式ファイルを作らないか、少なくとも警告エラーを生成します。関数の引数/戻り値のデータ型が一致していない場合、100%に近い確率でそのコードに間違いがあります。

「コンパイル型言語(JavaやC#)の方が大規模プロジェクトに向いている」とよく言われます。大きなプロジェクトでは、開発者が多くなり、コードの量が増え、単純ミスによるバグで膨大なリソースを無駄にしてしまいます。コンパイル型言語ならデータ型の不一致やローカル変数による引数の上書きといった単純ミスによるバグを仕組的に防止可能です。

JavaScriptやPHPのように「ゆるふわ」な動的言語の場合、「正しく動作するコード」の必須条件といえる”データ型の一致”、不用意な変数の上書き、を禁止することが出来ません。これらだけではありませんが、コンパイラ型の言語に比べ動的言語これが動的言語が大規模プロジェクトに向いていない、と言われる大きな理由の1つです。

関数型言語の正しく動くコードの世界観

関数型言語は一般的な言語(CやC++)に比べ、正しく動作するコードが書き易い、と言われています。

基本的に変数の値が上書きできなかったり、コード記述方式として副作用がない書き方が必要であったり、プログラムにバグが発生しづらいような方式でコードを記述することが求められます。

同じデータ型の変数であっても、誤って別の値を保存してしまったり、関数呼び出し中のグローバル変数変更が他のコードに影響してしまったりすると、プログラムが正しく動作しなくなります。

副作用がある関数でも、変数を上書きするコードでも正しく動作するコードを書くことは可能です。しかし、自由に”変数”が書き換えられる世界は危険に満ちている。その危険を廃除するのが関数型プログラミングの「世界観」と言えるでしょう。

※ この他に”関数”を数学的に正しく動作することを検証可能である点も大きなメリットです。

関数型言語の作法に則ってコードを書くと、手続き型言語のコードと比べバグが少なくなります。しかし、副作用を認めない、という世界観は現実とマッチしていないことも少なくありません。広く一般に浸透している「世界観」にはなっていないと思われます。

構造化/オブジェクト指向プログラミングの正しく動くコードの世界観

現在は関数やメソッドを使い、gotoを使わない構造化プログラミングは当たり前です。オブジェクト指向プログラミングも当たり前と言えるでしょう。

構造化/オブジェクト指向プログラミングの正しく動くコードは一言でいうと、抽象化により正しく動くコードであることを保証する、と言えるでしょう。

  • 構造化プログラミング ー 複雑な処理は関数やモジュールに”抽象化”して正しく動作することを保証
  • オブジェクト指向プログラミング ー 複雑な処理や状態はオブジェクトに”抽象化”して正しく動作することを保証

※ 抽象化 – 実装の中身を知らなくても、関数/オブジェクトがこの処理をするよ、という処理は正しく実行されるようにすること。中身を知らなくても複雑な処理ができる部品にすること。

人間の能力には限界があります。人は構造化されていないプログラムで発生してしまう沢山の依存性や複雑性を処理しきれません。抽象化により処理可能な範囲に収めることが可能になります。

それぞれの処理を抽象化した関数やオブジェクトが正しく処理を行うことにより、プログラムが正しく動作することを保証するのが構造化/オブジェクト指向プログラミングの「世界観」です。

構造化/オブジェクト指向プログラミングは広く浸透した「世界観」だと言えるでしょう。

セキュアコーディングの正しく動くコードの世界観

セキュアコーディングが提唱されはじめてからかなりの年月が経ちます。現在ではISO 27000/ISMS、PCI DSSなどの標準規格でも要求されるプログラミング技術です。にも関わらず、セキュアコーディングの「世界観」は広く受け入れられていません。全ての入力を入力処理、MVCモデルならコントローラーレベル、で全てバリデーションしているアプリケーションは極少数です。

セキュアコーディングは言語やライブラリや環境とは関係なく、総合的に正しく動くコードであることを保証する考え方/技術です。様々な要素がありますが、最も重要な要素は次の原則です。

セキュアコーディングの第1原則 – 全ての信頼できない入力はバリデーション※する
※正しい入力のみ受け入れる。それ以外を拒否

この第1原則はFail Fast原則にも則った考え方です。データ型が間違っているコードを正しく実行できないことと同じく、不正な入力データは正しく実行することは出来ません。仕組的に数値でさえコンピュータは正しく処理ことができません。できる限り早く正しい入力データ以外を廃除します。

言語が仕組的に「正しく動くコード」となるよう、開発者をサポートすることは可能です。「正しく動くデータ」であること保証することもデータ型チェックなどのでサポート可能です。

しかし、言語は「正しく動くデータ」であることを完全に保証するできません。プログラミング言語はコード中の整数や文字列にどのような仕様/意味(年齢や電話番号など)があるのか解らないからです。

セキュアコーディングでは「正しく動くデータ」であることを保証する「世界観」を持つ事が第一に要求されます。アプリケーションソフトウェアレベルでは必要不可欠なセキュリティ要求事項です。

世界中のソフトウェアセキュリティの専門家がアプリケーションセキュリティの第1の対策が入力バリデーションであるとしています。しかし、開発現場に浸透していません。これは「世界観」の違いが原因なのかも知れません。

反対にセキュリティバグも局所的/抽象化して対策すればよい(ソフトウェア内部に抽象化しFail Late)、という世界観が広く浸透していると思います。これは80年代までのプログラムは動けばよかった頃の考え方です。Fail Fast原則、SoC原則に則らない世代遅れの世界観ですが、過去のコード資産と習慣により現在でも当たり前の世界観です。

とは言ってももう待った無しの状況です。

アプリケーションレベルのソフトウェアセキュリティには構造化/オブジェクト指向プログラミングの「世界観」”だけ”は通用しないし、無理に適用しようとしても無駄1ばかりが増えます。

セキュリティの基礎・基本は信頼境界での検証と無害化です。セキュアコーディングの第1原則の入力検証が不適切/不十分であるソフトウェアばかりです。このため2017年版OWASP TOP 10は新しい脆弱性を追加しています。

そろそろセキュアコーディングの「世界観」も導入しないと手遅れになります。セキュアコーディングをしていないから余計な賠償金を支払わされたり、追加の開発を無償で提供しなければならくなったり、などのリスクが考えられます。

参考:セキュアコーディングの10原則


  1. 問題を局所化して解決するアプローチだけだと「文字エンコーディングバリデーションを無用に繰り返す」といった無駄が発生します。例えば、PCREのUTF-8バリデーションを無効化すれば数百倍速い正規表現検索も可能です。PCREを呼び出す”前”に文字エンコーディングをバリデーションしていれば、このような無駄は発生しません。参考1参考2参考3 また、幾らセキュリティ対策を抽象化して関数やクラス内部に閉じ込めようとして無理です。無効/不正なデータは何をやっても無効で、いつどこでエラーが発生するの分らない不安定なソフトウェアになります。 

投稿者: yohgaki