这个函数被框架的初始化代码所调用

主流的编制程序语言中,表示出现谬误的手腕不外乎三种:

  • 函数调用再次来到错误码
  • 函数调用抛出格外

C语言就属于前者,它的fopen函数在中标开垦文件时回来二个FILE指针,失利时重回NULL,并将错误代码写入全局变量errno;Ruby语言属于前面一个,它的File.open方法在找不到文件时抛出ENOENT的老大,在File.open之外包裹一层begin…rescue…end就可以捕捉其抛出的不行。

Common Lisp的失实机制叫做境况系统(Condition
System),与特别机制相似,能够兑现抛出特别(使用函数error)和捕捉十分(使用宏handler-case)。但与别的语言的万分机制分裂的地点在于,境况系统有一种名称为RESTART的表征,能够由使用者、恐怕由代码决定是不是要、以及怎么样从错误中恢复过来。听起来很别致也很蹊跷,无妨继续往下看。

假定作者有贰个Web框架,它提供了将日志记录到文件中的作用。这一个职能须要先调用名称叫init-logger的函数进行初步化,该函数完成如下

(defun init-logger () (with-open-file (s "/tmp/log/web.log" :direction :output :if-exists :supersede) (format s "Logger module starting...")))

本条函数被框架的开头化代码所调用,如下

(defun init-framework () (format t "Framework starting...~%") (init-plugin))(defun init-plugin () (format t "Plugins starting...~%") (init-logger))

而框架的最初化代码则由使用的初阶化代码所调用,如下

(defun init-app () (format t "Application starting...~%") (init-framework))

比如在目录/tmp/log空头支票时调用init-app函数,笔者将会接收二个荒唐,表明其不恐怕找到/tmp/log/web.log本条文件。为了防止不当打断了运用的平常化运维流程,能够让init-app函数负担成立这几个用于贮存日志文件的目录。将init-app函数改写为如下方式

(defun init-app () (format t "Application starting...~%") (ensure-directories-exist "/tmp/log/") (init-framework))

即使这种做法确实管用,但它导致应用层的代码(init-app函数)必得询问位于最底层的日记模块的落到实处细节。直觉告诉我那样子是颠三倒四的,框架的作业应该由框架本人提供方案来消除。借助Common
Lisp的restart效用,框架确实能够对外提供一种减轻方案。

率先须要主动检查实验用于存放日志文件的目录是或不是留存。借助UIOP这一个包能够写出如下代码

(defun init-logger () (let ((dir "/tmp/log/")) (unless (uiop:directory-exists-p dir) (error 'file-error :pathname dir)) (with-open-file (s (format nil "~Aweb.log" dir) :direction :output :if-exists :supersede) (format s "Logger module starting..."))))

进而,init-logger须求积极为调用者提供目录不真实的标题标缓慢解决方案,一种办法就是当那几个目录荒诞不经时,能够由调用者选拔是或不是成立。使用Common
Lisp的restart-case宏,笔者将init-logger改写为如下情势

(defun init-logger () (let ((dir "/tmp/log/")) (restart-case (unless (uiop:directory-exists-p dir) (error 'file-error :pathname dir)) (create-log-directory () :report (lambda  (format stream "Create the directory ~A" dir)) (ensure-directories-exist dir))) (with-open-file (s (format nil "~Aweb.log" dir) :direction :output :if-exists :supersede) (format s "Logger module starting..."))))

此时尽管调用第一版的init-app函数,那么init-logger仍将抛出十一分(类型为file-error与事先同一)并将本人带走到SBCL的调节和测量检验器中,但总的来看的从头到尾的经过会稍有区别

error on file "/tmp/log" [Condition of type FILE-ERROR]Restarts: 0: [CREATE-LOG-DIRECTORY] Create the directory /tmp/log ;; <- 新增了这一行 1: [RETRY] Retry SLIME REPL evaluation request. 2: [*ABORT] Return to SLIME's top level. 3: [ABORT] abort thread (#<THREAD "new-repl-thread" RUNNING {1001F958A3}>)

在Restarts中新扩展了名叫create-log-directory的一项,那多亏在init-logger中通过restart-case概念的新的”复苏措施“。小编输入0触发这一个restart,Common
Lisp会回到它被定义的restart-case宏相应的子句中执行当中的表明式,也正是调用CL:ENSURE-DIRECTORY-EXIST函数创设/tmp/log。

假若总是期望试行CREATE-LOG-DIRECTORY其一选项来创设贮存日志文件的目录,能够直接在代码中钦点,只必要相称使用Common
Lisp的handler-bindinvoke-restart函数就能够,最后init-app函数的落到实处如下

(defun init-app () (format t "Application starting...~%") (handler-bind ((file-error #'(lambda  (declare (ignorable c)) (invoke-restart 'create-log-directory)))) (init-framework)))

相关文章