Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specific functions vs many Arguments vs context dependent

An Example

Suppose we have a text to write and could be converted to "uppercase or lowercase", and can be printed "at left, center or right".

Specific case implementation (too many functions)

writeInUpperCaseAndCentered(char *str){//..}
writeInLowerCaseAndCentered(char *str){//..}
writeInUpperCaseAndLeft(char *str){//..}
and so on...

vs

Many Argument function (bad readability and even hard to code without a nice autocompletion IDE)

write( char *str , int toUpper, int centered ){//..}

vs

Context dependent (hard to reuse, hard to code, use of ugly globals, and sometimes even impossible to "detect" a context)

writeComplex (char *str)
{    
    // analize str and perhaps some global variables and 
    // (under who knows what rules) put it center/left/right and upper/lowercase
}

And perhaps there are others options..(and are welcome)

The question is:

Is there is any good practice or experience/academic advice for this (recurrent) trilemma ?

EDIT:

What I usually do is to combine "specific case" implementation, with an internal (I mean not in header) general common many-argument function, implementing only used cases, and hiding the ugly code, but I don't know if there is a better way that I don't know. This kind of things make me realize of why OOP was invented.

like image 861
Hernán Eche Avatar asked Jul 01 '10 13:07

Hernán Eche


People also ask

What to do if a function has too many arguments?

There are two techniques that can be used to reduce a functions' arguments. One of them is to refactor the function, making it smaller, consequently, reducing the arguments' number. The Extract Method technique can be use to achieve this goal.

How many arguments should a function have?

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification - and then shouldn't be used anyway.

How many arguments can a function have?

Except for functions with variable-length argument lists, the number of arguments in a function call must be the same as the number of parameters in the function definition. This number can be zero. The maximum number of arguments (and corresponding parameters) is 253 for a single function.


4 Answers

I'd avoid your first option because as you say the number of function you end up having to implement (though possibly only as macros) can grow out of control. The count doubles when you decide to add italic support, and doubles again for underline.

I'd probably avoid the second option as well. Againg consider what happens when you find it necessary to add support for italics or underlines. Now you need to add another parameter to the function, find all of the cases where you called the function and updated those calls. In short, anoying, though once again you could probably simplify the process with appropriate use of macros.

That leaves the third option. You can actually get some of the benefits of the other alternatives with this using bitflags. For example

#define WRITE_FORMAT_LEFT   1
#define WRITE_FORMAT_RIGHT  2
#define WRITE_FORMAT_CENTER 4
#define WRITE_FORMAT_BOLD   8
#define WRITE_FORMAT_ITALIC 16
....
write(char *string, unsigned int format)
{
  if (format & WRITE_FORMAT_LEFT)
  {
     // write left
  }

  ...
}

EDIT: To answer Greg S.

I think that the biggest improvement is that it means that if I decide, at this point, to add support for underlined text I it takes two steps

  1. Add #define WRITE_FORMAT_UNDERLINE 32 to the header
  2. Add the support for underlines in write().

At this point it can call write(..., ... | WRITE_FORMAT_UNLDERINE) where ever I like. More to the point I don't need to modify pre-existing calls to write, which I would have to do if I added a parameter to its signature.

Another potential benefit is that it allows you do something like the following:

#define WRITE_ALERT_FORMAT  (WRITE_FORMAT_CENTER | \
                             WRITE_FORMAT_BOLD |   \
                             WRITE_FORMAT_ITALIC)
like image 173
torak Avatar answered Nov 09 '22 04:11

torak


I prefer the argument way.

Because there's going to be some code that all the different scenarios need to use. Making a function out of each scenario will produce code duplication, which is bad.

Instead of using an argument for each different case (toUpper, centered etc..), use a struct. If you need to add more cases then you only need to alter the struct:

typedef struct {
    int toUpper;
    int centered;
    // etc...
} cases;
write( char *str , cases c ){//..}
like image 32
Luca Matteis Avatar answered Nov 09 '22 05:11

Luca Matteis


I'd go for a combination of methods 1 and 2.

Code a method (A) that has all the arguments you need/can think of right now and a "bare" version (B) with no extra arguments. This version can call the first method with the default values. If your language supports it add default arguments. I'd also recommend that you use meaningful names for your arguments and, where possible, enumerations rather than magic numbers or a series of true/false flags. This will make it far easier to read your code and what values are actually being passed without having to look up the method definition.

This gives you a limited set of methods to maintain and 90% of your usages will be the basic method.

If you need to extend the functionality later add a new method with the new arguments and modify (A) to call this. You might want to modify (B) to call this as well, but it's not necessary.

like image 44
ChrisF Avatar answered Nov 09 '22 05:11

ChrisF


I've run into exactly this situation a number of times -- my preference is none of the above, but instead to use a single formatter object. I can supply it with the number of arguments necessary to specify a particular format.

One major advantage of this is that I can create objects that specify logical formats instead of physical formats. This allows, for example, something like:

Format title = {upper_case, centered, bold};
Format body = {lower_case, left, normal};

write(title, "This is the title");
write(body, "This is some plain text");

Decoupling the logical format from the physical format gives you roughly the same kind of capabilities as a style sheet. If you want to change all your titles from italic to bold-face, change your body style from left justified to fully justified, etc., it becomes relatively easy to do that. With your current code, you're likely to end up searching through all your code and examining "by hand" to figure out whether a particular lower-case, left-justified item is body-text that you want to re-format, or a foot-note that you want to leave alone...

like image 42
Jerry Coffin Avatar answered Nov 09 '22 04:11

Jerry Coffin