エラー処理の新しい方法

最近、新しい throw()コマンドを使った新しいエラー処理メカニズムが導入されました。
これは、エラーを生成するコードに可能な限り近い場所に配置された、新しいエラー処理システムへの第一歩となりました。
4D 20 R4 では、式実行中のエラーをインターセプトできるように機能を拡張します。
その詳細を見ていきましょう。

try()、新しい便利なキーワード

4D 20 R4 の導入により、Try(Expression) という便利なキーワードが利用できるようになりました。Try() キーワードを使用すると、括弧で囲まれた式を実行して、その実行中にスローされたエラーをシームレスにインターセプトすることができます。これらのエラーは、式の実行直後に Last errorsコマンドで処理できます。これにより、エラーの発生源となるコードにできるだけ近いところでエラー処理を定義し、最終的にはローカル変数を使用することができます。

詳細な説明の前のサンプルシナリオ

ゼロによる除算

ゼロによる除算という簡単なシナリオを考えてみましょう。次のユークリッド除算メソッドのサンプルでは、除数がゼロの場合はエラーをスローし、戻り値としてゼロを返します。Try() を使用すると、エレガントにエラーを管理できます:

#DECLARE($dividend : Real; $divisor : Real)->$result : Real
If ($divisor=0)
  $result:=0
   throw(-12345; "ゼロによる除算!")
Else
  $result:=($dividend/$divisor)
End if

$result:=Try(division($dividend; $divisor))
If (Last errors#Null)
  // エラーへの対応
  logErrors(Last errors)
End if

文書へのアクセス

ドキュメントにアクセスする際に、そのドキュメントが存在しない場合のエラー処理もより簡単になります。
4D のエラーダイアログが表示されないようにするには、エラー処理メソッドをインストールし、このエラーハンドラー内にエラー処理のコードを記述する必要があります:

$previousErrorHandler:=Method called on error
ON ERR CALL("errorOpenDoc")
var $fileHandle : 4D.FileHandle:=File($path).open()
If ($fileHandle#Null)
  ON ERR CALL("errorReadDoc")
  $text:=$fileHandle.readText()
  If (Last errors#Null)
    $text:="ファイル読み込みエラー"
  End if
End if
ON ERR CALL($previousErrorHandler)

Try() キーワードを使用すると、同じセクションにすべてを記述することができ、ローカル変数にもアクセス可能で、コードを簡素化できます:

var $fileHandle : 4D.FileHandle:=Try(File($path).open())
If ($fileHandle#Null)
  $text:=Try($fileHandle.readText()) || "ファイル読み込みエラー"
End if

ORDAのsave関数

もう 1 つの例として、予測可能なエラーと予測不可能なエラーを生成する可能性のある ORDA の save() 関数を考えてみましょう。
エンティティロックのような予測可能なエラーは、$result.success 属性を使って直接処理できます。しかし、主キーの重複のような予測不可能なエラーを処理するには、ON ERR CALLコマンドでエラーハンドラーをインストールする必要がありました (でないと、4D のエラーダイアログが表示されます):

var $customer : cs.CustomerEntity
// $customer エンティティでなにかしらの処理をおこないます
$previousErrorHandler:=Method called on error
ON ERR CALL("errorSaveManagement")
$result:=$customer.save()
ON ERR CALL($previousErrorHandler)
If (Not($result.success))
  Case of
    : ($result.status=3)
      // ロックされたエンティティへの対応
    : ($result.status=4)
      // その他のエラーへの対応
  End case
End if

しかし、Try() キーワードを使うことで、エラーダイアログを表示することなく、予測可能なエラーと予測不可能なエラーの両方を処理することが容易になります:

var $customer : cs.CustomerEntity
// $customer エンティティでなにかしらの処理をおこないます
$result:=Try($customer.save())
If (Not($result.success))
  Case of
    : ($result.status=3)
      // ロックされたエンティティへの対応
    : ($result.status=4)
      // その他のエラーへの対応
  End case
End if

式の実行

Try() キーワードは、変数・代入・メソッド・関数など、括弧で囲まれた任意の 4D 式を受け付けます。エラーが発生した場合、実行フローは停止し、直前の Try()キーワード (コールスタックで最初に見つかったもの) に戻ります。その結果、式の実行後にエラースタックをチェックするだけで、エラーが発生したかどうかを知ることができます。
なお、throw() コマンドを遅延モードで使用してエラーが生成された場合には、実行フローは現在のメソッド/関数の実行が終了するまで継続され、エラーの伝播を簡単に管理することができます。

戻り値

実行に成功すると、Try() は式の結果を返します。
エラーの場合、式の最新の結果が返され、結果が使用できない場合は ‘undefined’ が返されます。
式が戻り値を定義していない場合 (戻り値のないメソッドやコマンドなど)、try キーワードは ‘undefined’ を返します。

エラースタックについて

式が実行される前にエラースタックはクリアされ、Try() コンテキスト内でのエラー処理のために白紙に戻されます。式の実行中に発生したエラーはエラースタックに追加され、デバッグのための包括的な情報をデベロッパーに提供します。

エラー処理

ON ERR CALLコマンドによってカレントエラーハンドラーが定義されていない場合でも、Try() を使用すれば、エラーダイアログによってユーザー体験が妨げられることはありません。
Try() ブロックに入る前に設定されたグローバルおよびローカルなエラーハンドラーも呼び出されないため、Try のコンテキストとコードの他の部分がきれいに分離されます。
式内でカレントデータベースのグローバルまたはローカルなエラーハンドラーを定義している場合には、実行中にエラーが発生したときにそのエラーハンドラーが呼び出されます。

エラー処理の新たな可能性

この新機能は、エラー処理の可能性を広げるものです。しかしそれは、複数行の try-catch 文への道を開く、4D のエラー処理に向けた第2ステップに過ぎません!
フォーラムであなたの考えや経験をシェアし、この新機能についてどう思うか教えてください。

ハッピー・コーディング!

Avatar
- プロダクトオーナー - Damien Fuzeauは、2019年2月に4D Productチームに参加しました。プロダクトオーナーとして、ユーザーストーリー(ユーザーが期待する新機能とその使用法)を書き、それを具体的な機能仕様に変換することを担当しています。また、実装された機能が顧客のニーズを満たしているかどうかを確認することも彼の役割です。ナント大学のソフトウェア工学科を卒業。前職の会社では最初は開発者として(1997年に4Dを発見)、後にエンジニアリングマネージャーとソフトウェアアーキテクトとして、23年以上勤務しました。この会社は、4DのOEMパートナーであり、現在は数千のユーザーと数百のサーバーに向けて4Dベースのビジネスソフトを展開しています。ですから、Damienは、多言語環境での4D開発・導入に慣れています。