TheSwamp

Code Red => AutoLISP (Vanilla / Visual) => Topic started by: Mark on March 30, 2005, 07:56:47 AM

Title: Function design (thoughts)
Post by: Mark on March 30, 2005, 07:56:47 AM
When defining a function, specifically a utility function;

How do you decide on what arguments to accept?

What about output?

Do you put a lot of emphasis on error checking?

Do you try a recursive approach first?

Is creating a separate function a last resort when designing an application?

You thoughts on any/all of the above would be very much appreciated.

Some of these questions are very broad, but I'm trying to get inside your thought process on function design.
Title: Function design (thoughts)
Post by: David Hall on March 30, 2005, 08:17:51 AM
Most of my Utility functions apply to the whole drawing. Like resetting ALL the text in a drawing back to the correct style, or going thru the entire drawing and resetting the ByLayer settings for color and linetype etc.
Title: Re: Function design (thoughts)
Post by: MP on March 30, 2005, 08:29:36 AM
I've been using the same basic technique for awhile in both lisp and vb and it's very simple in my mind. Other than the container that hosts all the subs and functions, let's call that the application, each function or sub does precisely one thing; no more, no less. This makes it easy to understand, easy to maintain, easy to debug and yields a lot of functions / subs that can be reused.

Having said that, distilling an app down to it's constituent functions yields functions whose arguments are usually very apparent, in fact, I don't recall struggling over argument determination. I would say that I normally avoid any globals (though constants do qualify as globals), so if a function needs some value not defined by constants it will usually become an argument.

At the function level I try to minimize error checking, that's the job of higher level programming. If every function is idiot proofed overall performance of the application really suffers. So ... if a function expects a string it will crash if passed an integer, not elegantly exist saying "Dood, wrong value passed as parameter, have a nice day."

Recursion is fun and the resulting code is usually very elegant, but there's usually a performance penalty to be paid. Also, recursion has (stack) limits, so one has to be careful to remove superfluous information from being pushed onto the stack for each call, lest the limits be reached unnecessarily quickly (read optimization is important). So ... normally I won't use recursion, but sometimes it's the right route, for example -- scanning directories for files.

<cough>

That's all I have for now. What are ya doing posting a question like this at 6am and why on earth am I trying to answer it before I've even had a coffee??? Medic: Can I get a jumbo coffee, triple caffiene special over here?

(http://www.theswamp.org/screens/mp/mpzzz.gif)
Title: Function design (thoughts)
Post by: JohnK on March 30, 2005, 09:17:20 AM
I agree with MP 150%.
Title: Function design (thoughts)
Post by: MP on March 30, 2005, 10:02:14 AM
I think what John's saying is that it would be real nice if someone bought him a real big coffee.

:)
Title: Function design (thoughts)
Post by: JohnK on March 30, 2005, 10:04:14 AM
Amen brother!
Title: Function design (thoughts)
Post by: CAB on March 30, 2005, 11:54:44 AM
????  How do you decide on what arguments to accept?
You may gain some versatility by accepting different types like ename, elist or object.
Those are easy to test and have the subroutine convert to what it needs. Else the overhead
of converting is on the calling routine. If you have a large application and the routine is
called many times from many places it may be more efficient to convert the variable in the subroutine.
That said I usually don't as all my routines are small. :)

????  What about output?
I assume you are referring to the returned value(s).
Seems to me that the function of the subroutine would dictate the return value(s)

????  Do you put a lot of emphasis on error checking?
Ah the 'What IF' game.
i think the user input need a lot of error checking but not so much on the subroutine level.
I think you have to ask what are the consequences of the error. If the error just dumps the user
and no harm to the drawing then a simple error trap with a message is fine. If a lot of user input is
going to have to be re-entered it would be a good thing to trap & correct the error else the user
will be miffed at the program.

????  Do you try a recursive approach first?
No, last. For me it's hard to program, hard to debug, hard to understand when i come back.
But it's a fun exercise at times.:)

????  Is creating a separate function a last resort when designing an application?
I usually look at how many times the subroutine is used and how large the subroutine is and how
if affects readability of the routine.
If the routine will be used 3 or more times
Or if the routine is larger than 10 lines of code, my code not MPs
(sorry I have this condense idous disease and can't seem to shake it.)
Or if the chunk of code when removed to a subroutine makes the Main routine read more clearly
I sometimes will move it to a subroutine.


That said, I always break my own rules.
I look back at some of my older code, last month is older, and see it need some cleanup but
who has the time? If it ain't broke.

I only wish I could write like MP, and Mark and Stig & John and others but I don't think it's in me.
I guess we have our own basic style & it's hard to change that.
So I guess the answerers are subjective to some degree.

My 2 cents.


Wow that got long winded :)
Title: Function design (thoughts)
Post by: Mark on March 30, 2005, 12:42:19 PM
I have found myself lately using a subroutine just to make the code more readable, at least to me it is. *g*

Example. I wrote a little clipboard app and I used this;
Code: [Select]

(defun is-text (ent)
    (cond
      ((= (cdr (assoc 0 (entget ent))) "MTEXT") T)
      ((= (cdr (assoc 0 (entget ent))) "TEXT") T)
      (T nil)
      )
    )


and then in the body of the app I simply used;

Code: [Select]
(if (is-text ent)

makes it easier to follow to me.
Title: Function design (thoughts)
Post by: MP on March 30, 2005, 01:03:41 PM
Good idea Mark. The only thing I'd probably do different is with regards to naming. Essentially you are writing a predicate function (return t or nil), e.g. listp, numberp, zerop so ...

Code: [Select]
(defun textp ( ename )
    (and
        (member
            (cdr (assoc 0 (entget ename)))
           '("TEXT" "MTEXT")
        )
    )    
)

Or of you do predominantly activex stufff ...

Code: [Select]
(defun textp ( object )
    (and
        (member
            (vla-get-objectname object)
           '("AcDbText" "AcDbMText")
        )
    )    
)
Title: Function design (thoughts)
Post by: Kerry on March 30, 2005, 01:10:41 PM
Quote from: Mark Thomas
I have found myself lately using a subroutine just to make the code more readable, at least to me it is. *g*

.. makes it easier to follow to me.


Mark, I do the same, for the same reason. In fact, I forgo thought of 'optimum speed freak efficiency' , preferring readability and 'scanability'.

Sometimes it seems  more difficult to find a name for the routine than writing the routine itself .... decisions, decisions ..

I find modularizing code this way fits my thought process and translates easily from a design outline [ pseudocode].

The ugly side to this can be the 'indiscriminate wrapping' I see sometimes.

It's my opinion that programming is a confidence game [ in the nicest sense], that is, we build on what we know is correct, confident in the foundation we have established. Anything I can do to reduce the potential for errors, or at least isolates 'thoughts' so they can be tested,  increases my confidence.
Title: Function design (thoughts)
Post by: Kerry on March 30, 2005, 01:23:51 PM
One thing I have gotten into the habit of doing is listing the user defined dependancy functions in the header for routines.

functionName < Parameters > :: fileName

.. though this may just be 'cause my memory isn't what it used to be .. :)
Title: Function design (thoughts)
Post by: Mark on March 30, 2005, 01:25:15 PM
Quote from: MP
Good idea Mark. The only thing I'd probably do different is with regards to naming. Essentially you are writing a predicate function (return t or nil), e.g. listp, numberp, zerop so ...

Oh yea............ great tip MP!!

why didn't I think of that in the first place...
Title: Function design (thoughts)
Post by: Mark on March 30, 2005, 01:28:58 PM
Quote from: Kerry Brown
The ugly side to this can be the 'indiscriminate wrapping' I see sometimes.

Sorry but I don't understand.  :oops:

Quote from: Kerry Brown
It's my opinion that programming is a confidence game [ in the nicest sense], that is, we build on what we know is correct, confident in the foundation we have established. Anything I can do to reduce the potential for errors, or at least isolates 'thoughts' so they can be tested,  increases my confidence.

Well said...........
Title: Function design (thoughts)
Post by: MP on March 30, 2005, 01:29:08 PM
Quote from: Mark Thomas
why didn't I think of that in the first place...

You kind of did, you're just following VB(A) naming conventions: IsArray, IsDate, IsEmpty ...

:)
Title: Function design (thoughts)
Post by: MP on March 30, 2005, 01:31:30 PM
Good stuff Kerry, thanks for sharing. I agree, esp. superfluous wrapping, even though I may be guilty as charged.

:)
Title: Function design (thoughts)
Post by: Mark on March 30, 2005, 01:38:26 PM
Quote from: MP
You kind of did, you're just following VB(A) naming conventions: IsArray, IsDate, IsEmpty ...

Maybe it's to much Python and C++ and PHP and PERL ...... *grin*

funny fact:
When I write something in C++ I compile it right from Vim using the builtin command "make". Well the other day I wrote a little autolisp routine and then ran "make" on it. "oop's that didn't work" :oops:
Title: Function design (thoughts)
Post by: MP on March 30, 2005, 01:45:01 PM
I hear ya -- can't tell you how many times I've been coding basic and typed "(defun ..."

(http://www.theswamp.org/screens/mp/icons/icon_rolleyes2.gif)
Title: Function design (thoughts)
Post by: JohnK on March 30, 2005, 01:48:13 PM
As long as we are on the subject of args/vars:

If im actualy building true "suport procedures" like Marks "istext" example i usualy rely on scoping to handle my args/vars.

Forinstance:
Code: [Select]
(defun c:MyApp ( / ename textp)
  (while (not (setq ename (entsel "\nselect object: "))))
 ;; suport procedure
  (defun textp ( )
    (and
      (member
        (cdr (assoc 0 (entget (car ename))))
        '("TEXT" "MTEXT")
        )
      )  
    )

  (if (textp)
    (alert "Congrats! You selected some Text.")
    (alert "Nope!")
    )
  )


This makes it that much easier for me to read. (Look at my "if" vs this:
 (if (textp (car ename))
   ...
   )
 )

Using lexical scoping in your procedure design, i believe, is much easier to follow.
Title: Function design (thoughts)
Post by: MP on March 30, 2005, 01:52:15 PM
To each their own, but I wouldn't code it like that myself, though I am a proponent of lexical scoping.
Title: Function design (thoughts)
Post by: JohnK on March 30, 2005, 01:55:51 PM
Why?
Title: Function design (thoughts)
Post by: MP on March 30, 2005, 01:57:23 PM
Primarilly code clarity and re-use.
Title: Function design (thoughts)
Post by: JohnK on March 30, 2005, 02:03:34 PM
Re-use is the reason i use it.

Pulling that procedure out and trying to hack together an app is hard.  (Forces me to "Build" a procdeure and not "cut 'n paste" one.) I realise that my "worker procedures" could get a bit bloated this way (-ie. building built-in support procedures for each) but its worth the overall robust-ness i achieve.
Title: Function design (thoughts)
Post by: Kerry on March 30, 2005, 02:11:33 PM
Quote from: Mark Thomas
Quote from: Kerry Brown
The ugly side to this can be the 'indiscriminate wrapping' I see sometimes.

Sorry but I don't understand.  :oops:
 <<<<


I have seen [ read as,  debugged others code ] that has taken this concept a little to far. Its a different issue if the wrapped routine is used more than once or if it has a significant purpose.  ... sort of negates the 'readability' aspect. .. but admittedly that was for code that I didn't originate, so a reasonable amount of mental gymnastics is expected. :)

 .... on the other hand, a 3 page function that has no white space for thought delineation, and no header comments [ I prefer those to in line comments, the code should speak to me ] and everything inline and questionable variable naming regimen is a completely different can of worms.
Title: Function design (thoughts)
Post by: Kerry on March 30, 2005, 02:19:21 PM
... while [ sort of ] on the topic of debugging ..

I frequently will write a named routine just for testing, then include its code in line to reduce the necessity of passing variables to a helper.