前回, コマンドライン引数から逆ポーランド記法で記述した式を入力できるようにしました. 入力のチェックを何もしていないので, 対策していきます. まずは状況の確認から始めます.
1)エラーが生じる条件
プログラムには想定している入力の範囲があります. 作成しているプログラムでは, 次のような入力を想定しています.
- 数値
- 四則演算子(+, -, ×, /)
想定していない誤った式を入力してみます.
PS C:\TSWork\RPN> node .\dist\index.js 9 A + 4 *
計算結果 NaN
9にAを加えていますが, AをNaNと解釈したため, 計算結果もNaNとなっています. もう一つ試してみます.
PS C:\TSWork\RPN> node .\dist\index.js 9 3 * 4 %C:\TSWork\RPN\dist\index.js:39throw new Error('スタックの要素数が1ではありません. 式を間違えているかも?');^Error: スタックの要素数が1ではありません. 式を間違えているかも?at calc (C:\TSWork\RPN\dist\index.js:39:15)at Object.<anonymous> (C:\TSWork\RPN\dist\index.js:44:21)[90m at Module._compile (internal/modules/cjs/loader.js:1072:14)[39m[90m at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)[39m[90m at Module.load (internal/modules/cjs/loader.js:937:32)[39m[90m at Function.Module._load (internal/modules/cjs/loader.js:778:12)[39m[90m at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)[39m[90m at internal/main/run_main_module.js:17:47[39mPS C:\TSWork\RPN>
余りを求める演算子のつもりで「%」を使っています. 「%」もNaNと解釈しているため, このようなエラーを表示しています. 最低限の対策として, 文字列をNaNと解釈した場合にエラーを投げるようにすれば良さそうです.
2)エラー処理の方針
このまま対策を入れてしまうと, 逆ポーランド記法で記述した式を計算するcalc関数が複雑になってきます. そこで, エラー処理を2つに分けます.
- 入力した文字列が想定している範囲内にあることを確認する。
- 式の構造が正しいことを確認する。
式の構造の正しさは, calc関数で計算を進めながら確認していきます. この確認は, 既にcalc関数に作ってあります. 足りないことは, 入力文字列が想定内であることを確認する処理です. これを作っていきます.
3)データ型の定義
入力文字列をチェックする関数など簡単に作成できますが, もう少し考えを進めます. この程度のプログラムではありえないのですが, 2人のプログラマが関わっており, 入力をチェックする部分と, 計算する部分に担当が分かれているとします. 2人の間でうまく連携を取らないと, 入力のチェック後に計算担当へ文字列配列を渡した後で, 計算の担当者が受け取った文字列配列をチェックし始めます.
二重にチェックをしてしまうと, 仕様変更時の修正箇所が増えてしまい, 修正漏れがあると不具合の原因となります. そこで, 2人のプログラマの間での取り決めとして, データ型の配列を渡すようにします. データ型を定義して, プログラムを修正した結果は次の通りです.
修正した内容を説明します. まず, データ型の定義から.
式を構成する演算子や数値を表すデータ型を定義します. 演算子に対応した文字列リテラルとnumberの合併としています. これだけ見ると, わざわざ型を導入する必要があるのか, と自分でも疑問を感じますが, 後でありがたみが分かってきます.
文字列をToken型に変換するための関数を定義しました. 文字列の配列のままならこの関数も不要なので, Token型を作らなければ良かったのでは, と後悔し始めています.
文字列の配列をTokenの配列に変換するための関数も定義しました. Token型を導入したばかりに, コード量が2倍に膨れ上がりました. 失敗か?
逆ポーランド記法で記述した式を計算するための関数本体です. 引数inputの型を文字列の配列からTokenの配列に変更しています. Token型に変更したので, ifからswitchに変更しました. スッキリとしました.
最後に, コマンドライン引数をcalc関数に渡すため, parse関数を間に挟んでいます. コンパイルして実行した結果を確認します.
PS C:\TSWork\RPN> .\node_modules\.bin\tsc
PS C:\TSWork\RPN> node .\dist\index.js 9 4 * 6 /
解析結果 [ 9, 4, 'MUL', 6, 'DIV' ]
計算結果 6
PS C:\TSWork\RPN> node .\dist\index.js 9 4 * 6 %
C:\TSWork\RPN\dist\index.js:21
throw new Error("\u5BFE\u5FDC\u3057\u3066\u3044\u306A\u3044\u5165\u529B\u3067\u3059:" + str);
^
Error: 対応していない入力です:%
at makeToken (C:\TSWork\RPN\dist\index.js:21:23)
at parse (C:\TSWork\RPN\dist\index.js:34:21)
at Object.<anonymous> (C:\TSWork\RPN\dist\index.js:79:21)
[90m at Module._compile (internal/modules/cjs/loader.js:1072:14)[39m
[90m at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)[39m
[90m at Module.load (internal/modules/cjs/loader.js:937:32)[39m
[90m at Function.Module._load (internal/modules/cjs/loader.js:778:12)[39m
[90m at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)[39m
[90m at internal/main/run_main_module.js:17:47[39m
PS C:\TSWork\RPN>
4)型システムの恩恵
Token型を導入したありがたみを確認しておきます. calc関数では, switch文を使って演算子の種別に応じた計算をしています. 対応していない演算子がある場合, コンパイラが怒ってきます.
Token型を導入したことにより, 演算子を追加した場合の対応漏れを防ぐことができます. このようにすれば, つまらない不具合をコンパイル時に潰せるようになります. ただし, 型システムの恩恵に与るには, 型システムにチェックしてもらえるようにデータ型を定義する必要があり, それなりに経験が必要になります.
5)まとめ
この記事では, コマンドラインから入力した式をチェックするためにデータ型を導入しました. データ型を導入することで, プログラミング時にありがちな誤りをコンパイラにチェックさせる方法を確認しました.
最後に, この記事で使用したNode.jsとTypeScriptのバージョンは次の通りです.
PS C:\TSWork\RPN> node --version
v14.17.4
PS C:\TSWork\RPN> .\node_modules\.bin\tsc --version
Version 4.3.5
PS C:\TSWork\RPN>
0 件のコメント:
コメントを投稿