概要
こんな感じでプログラミングしてます的なやつ。
個人的なメモ用で、別にベストプラクティスというわけではないです。
メソッド名は返り値を想定できるようにする
いろんな種類のリクエストを送るライブラリを書いていると想定する。
以下だと、(request client req)
のような呼び出しで何が返ってくるかはreqの宣言を遡らないとわからない。なので可読性の低いコードを作ってしまいそう。
// A案 (△)
(defgeneric request (client request))
(defmethod request ((client client) (request http-request) ...)
(defmethod request ((client client) (request ftp-request) ...)
以下だと、(http-request client req)
はHTTPのレスポンスが返ってくるということが分かる。なぜなら、このインターフェイスではrequestの実装の違いを吸収してhttp-requestを実行し、HTTPのレスポンスを返却しているから。
// B案 (〇)
(defgeneric http-request (client request))
(defmethod http-request ((client client) (request request) ...)
(defgeneric ftp-request (client request))
(defmethod ftp-request ((client client) (request request) ...)
たいていの場合、メソッド名で返り値の型を想定できるようにすればよさそう。
A案の場合でも、http-requestとftp-requestというリクエストの違いを吸収し何か共通の処理をして、処理をした結果得られる値が同じ型になるなら、たぶん問題はないと思う。でも、今回の場合は単純にそれぞれリクエスト処理をするコードを実装するということだったので、A案のようなインターフェイス設計はたぶんNG。
また、そもそも返り値がないなら別に何してもいいかなとは思う。
Common Lisp以外の処理をする場所はメソッドに切り出す
IO等、Common Lisp以外のものとやり取りしてデータを取得するようなところはメソッドに切り出したほうがよさそう。なぜなら、取得したデータを処理するロジックがテスト可能になるから。
// A案 (△)
(defun http-request (client request) ...)
// B案 (〇)
(defgeneric http-request (client request))
(defmethod http-request ((client client) request) ...)
以上のようにしておけば、http-requestを使うロジックがテスト可能になる。
(defclass fake-client ()
((responses
:initarg :responses
:accessor fake-client-responses)))
(defmethod http-request ((client fake-client) request)
(pop (fake-client-responses client)))
(test test-abstract-logic-using-http-request
(let ((client (make-instance 'fake-client :responses <responses>)))
(is (equal (some-abstract-logic client) ...)))