アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Lisp on Rails 第6回 〜 ここらでリファクタリング

こんにちは!! tahara です。 Lisp on Rails 第6回です!

has-one を has-many のコピペで書いてしまったので、 ここらでリファクタリングしたいと思います。

has-one のスロット定義

(defclass ar-has-one-slot-mixin ()
  ((has-one :initarg :has-one
            :initform nil
            :accessor has-one)
   (class-symbol :initarg :class-symbol
                 :initform nil
                 :accessor class-symbol)))

(defmethod initialize-instance :after ((self ar-has-one-slot-mixin) &rest args)
  (declare (ignore args))
  (unless (class-symbol self)
    (setf (class-symbol self) (has-one self))))

(defclass ar-has-one-direct-slot-definition (ar-direct-slot-definition
                                             ar-has-one-slot-mixin)
  ())

(defclass ar-has-one-effective-slot-definition (ar-effective-slot-definition
                                                ar-has-one-slot-mixin)
  ())

has-many のスロット定義

(defclass ar-has-many-slot-mixin ()
  ((has-many :initarg :has-many
             :initform nil
             :accessor has-many)
   (class-symbol :initarg :class-symbol
                 :initform nil
                 :accessor class-symbol)))

(defmethod initialize-instance :after ((self ar-has-many-slot-mixin) &rest args)
  (declare (ignore args))
  (unless (class-symbol self)
    (setf (class-symbol self)
          (sym (singularize (has-many self))))))

(defclass ar-has-many-direct-slot-definition (ar-direct-slot-definition
                                              ar-has-many-slot-mixin)
  ())

(defclass ar-has-many-effective-slot-definition (ar-effective-slot-definition
                                                 ar-has-many-slot-mixin)
  ())

いやー、ひどいですね。 ほとんど one と many の違いだけです。

さて、これをリファクタリングするのに Common Lisp にはマクロという手抜きプログラマには必須の機能があります。

普通リファクタリングするとなると、関数、メソッド、スーパークラス等々の 切り出しが必要になりますよね? でも、マクロなら何ら設計を変更することなくリファクタリングが可能になります。

では、実際にマクロを使ってリファクタリングしてみましょう。

(defmacro def-has-xxx-slot-definition (xxx
                                       default-class-symbol-form)
  `(progn
     (defclass ,(sym "ar-has-" xxx "-slot-mixin") ()
       ((,(sym "has-" xxx) :initarg ,(key-sym "has-" xxx)
                 :initform nil
                 :accessor ,(sym "has-" xxx))
        (class-symbol :initarg :class-symbol
                      :initform nil
                      :accessor class-symbol)))

     (defmethod initialize-instance :after ((self ,(sym "ar-has-" xxx "-slot-mixin")) &rest args)
        (declare (ignore args))
        (unless (class-symbol self)
          (setf (class-symbol self) ,default-class-symbol-form)))

     (defclass ,(sym "ar-has-" xxx "-direct-slot-definition") (ar-direct-slot-definition
                                                  ,(sym "ar-has-" xxx "-slot-mixin"))
       ())

     (defclass ,(sym "ar-has-" xxx "-effective-slot-definition") (ar-effective-slot-definition
                                                     ,(sym "ar-has-" xxx "-slot-mixin"))
       ())
     ))

(def-has-xxx-slot-definition one (has-one self))
(def-has-xxx-slot-definition many (sym (singularize (has-many self))))

すばらい。 最初のひどい設計を何ら変えることなくリファクタリングできました。

手抜き設計のベタ書きコードそのままで、リファクタリングを可能とするマクロは、 未熟なプログラマにとって、なくてはならない存在です。

ソースはこちらから http://github.com/quek/lisp-on-rails

第7回につづきます。