Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reduce code duplication using method combination but keeping possible early return

I got a set of classes which represent a message that has to be handled. But there is only a limited amount of open spots for handlers. Therefore any "dispatch" of a handler handling an message object has to check first whether there is a free spot.

If there is -> dispatch.

If there is not -> do not dispatch and return corresponding message

As this part of the code will be the same in any dispatch method I figured it would be best to use the method combination facility to enforce that, but I cannot figure out how.

In my current code base I tried to use a :before method, but apparently you cannot use return in such context:

(defclass message () ((msg :initarg :msg :reader msg)))

(defclass message-ext (message) 
    ((univ-time :initarg :univ-time :reader univ-time)))

(defparameter *open-handler* nil)

(defgeneric handle (message)
  (:documentation "handle the given message appropriately"))

(defmethod handle :before ((message message))
  (when (> (length *open-handler*) 1)
    (return :full)))

(defmethod handle ((message message))
  (push (FORMAT nil "dispatched handler") *open-handler*))

(defmethod handle ((message-ext message-ext))
  (push (FORMAT nil "dispatched ext handler") *open-handler*))

(handle (make-instance 'message :msg "allemeineentchen"))

(handle (make-instance 'message-ext 
                       :msg "rowrowrowyourboat" 
                       :univ-time (get-universal-time)))

(handle (make-instance 'message-ext 
                       :msg "gentlydownthestreet" 
                       :univ-time (get-universal-time)))

Execution of a form compiled with errors.
Form:
  (RETURN-FROM NIL FULL)
Compile-time error:
  return for unknown block: NIL
   [Condition of type SB-INT:COMPILED-PROGRAM-ERROR]

Restarts:
 0: [RETRY] Retry SLIME interactive evaluation request.
 1: [*ABORT] Return to SLIME's top level.
 2: [TERMINATE-THREAD] Terminate this thread (#<THREAD "worker" RUNNING {100594F743}>)

Backtrace:
  0: ((SB-PCL::FAST-METHOD HANDLE :BEFORE (MESSAGE)) #<unavailable argument> #<unavailable argument> #<unavailable argument>)
  1: ((SB-PCL::EMF HANDLE) #<unavailable argument> #<unavailable argument> #<MESSAGE-EXT {1005961733}>)
  2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (HANDLE (MAKE-INSTANCE 'MESSAGE-EXT :MSG "gentlydownthestreet" :UNIV-TIME (GET-UNIVERSAL-TIME))) #<NULL-LEXENV>)
  3: (EVAL (HANDLE (MAKE-INSTANCE 'MESSAGE-EXT :MSG "gentlydownthestreet" :UNIV-TIME (GET-UNIVERSAL-TIME))))
  4: ((LAMBDA () :IN SWANK:INTERACTIVE-EVAL))

Is this approach even sane, and if yes how can I do it in a working fashion? (I did already try return-from with the same result)

like image 650
Sim Avatar asked Aug 22 '13 15:08

Sim


People also ask

How do you reduce code duplication?

Don't Repeat Yourself (DRY): Using DRY or Do not Repeat Yourself principle, you make sure that you stay away from duplicate code as often as you can. Rather you replace the duplicate code with abstractions or use data normalization. To reduce duplicity in a function, one can use loops and trees.

How do you avoid code duplication in Python?

The natural thing would be to have the if/break code only be present if K is not None , but this involves writing syntax on the fly a la Lisp macros, which Python can't really do.

Why is code duplication not recommended?

It's safe to say that duplicate code makes your code awfully hard to maintain. It makes your codebase unnecessary large and adds extra technical debt. On top of that, writing duplicate code is a waste of time that could have been better spent.

How to avoid code duplication in a test suite?

When a test suite grows in size, you often start to see code duplication. This is mainly reflected in the arrange / setup step. One way to avoid this is to take advantage of factory functions. The use of factory functions is a universal pattern that ensures the code remains maintainable and easy to read.

How can I reduce the amount of duplicate code I write?

Google and StackOverflow are great resources for finding answers to solving many common programming problems. They can be your first port of call for discovering and learning new APIs that may help reduce some of the duplicate code you may have written previously. Sometimes code duplication is easy to spot and to remove.

Is it possible to fix a duplicated code?

Over time, the duplication might become varied slightly such that it’s not even clear which version is the correct version. Ideally, the duplicated code can be simply extracted to one function to be called from multiple places. However, sometimes fixing the duplication is not so simple or obvious.

How important is coding style in reducing code duplication?

But it's opinion-based and coding style is subjective. Readability, project coding conventions, and team buy-in are equally important. The emphasis for reducing duplication is not at all for reducing lines of code, but for reducing the opportunity for bugs, and to make maintaining easier.


1 Answers

I think you should be using the :around method qualifier instead:

(defmethod handle :around ((message message))
  (if (cddr *open-handler*)
      :full
      (call-next-method)))

However, a more "lispy" approach is to use the CL Condition System, e.g., something like this:

(define-condition too-many-messages (...) (...) ...)
(defun add-message (message)
  (when (cddr *open-handler*)
    (signal 'too-many-messages))
  (push message *open-handler*))
(defmethod handle ((message message))
  (add-message (FORMAT nil "dispatched handler")))

You will have to handle the condition (using, e.g., handler-bind) in addition to checking the return values of your handle function.

PS. Calling length on a list to check that it is long enough is not a very good idea - although in your case, when the list is guaranteed to be short, this might be more of a style issue.

PPS. It is not a very good idea to use the word handle as a name of your function because CL has functions which contain it (e.g., handler-case). This will complicate the search in your code in addition to confusing people reading your code.

like image 140
sds Avatar answered Sep 18 '22 01:09

sds