-1

I user-define many functions and a few of them have six, ten, or even more arguments. Reading my code gets difficult when I forget what the arguments of a function are, or what order they go in. I've devised a way to deal with this, replacing all the arguments with a single array, and using array keys as labels for each argument. So, for example, instead of

function MyFunction(string $sSayThis, int $nRepeatTimes, bool $bLoud = $false) {...}

I now have

function MyFunction(array $args)
   {$sSayThis = $args['sSayThis']); CheckType($sSayThis, 'string');
    $nRepeatTimes = $args['nRepeatTimes']); CheckType($nRepeatTimes, 'int');
    $bLoud = (IsSet($args['bLoud']) ? $args['bLoud'] : false); CheckType($bLoud, 'bool');
    ...
    }

A call to this function, instead of

MyFunction('Hello', 3, true);

now looks like

MyFunction(array('sSayThis' => 'Hello', 'nRepeatTimes' => 3, 'bLoud' => true));

This is hardly necessary when there are only three arguments, as in this example, but it could be very helpful when reading code for a function with six or ten arguments! Also, if I only need to pass a value for the tenth argument and use the default values for all the optional arguments before that, I can just omit those other arguments from the call instead of passing a series of , '' for them.

This is a hack, and it seems kind-of ugly. But it does help make my code self-documenting and easier to read.

I know there are IDEs that would give me argument hints, but I'm using Notepad++, which doesn't do that.

This idea is discussed in a similar question asked last year, PHP Function Arguments - Use an array or not?, but that question doesn't show what the function calls look like, which is the most important part of the question. Some people in answer to that question said that a function should never need ten arguments and having that many indicates poor design. I understand that concern, but sometimes an algorithm just needs a lot of information.

Is there anything wrong with this approach, or is there a better way to self-document these function calls?

NewSites
  • 1,402
  • 2
  • 11
  • 26
  • This question screams opinion based. Don't have anything to add, just wanted to give you a heads-up – Andreas Aug 08 '18 at 16:51

2 Answers2

4

I would fire you. Just kidding. But seriously this is a svengali. You shouldn't try to invent something totally new for a problem that is already solved. You'll confuse anyone else who tries to read your code, and you set yourself up to be useless in the future. This isn't opinion based, because there are standard ways to deal with this.

Learn OOP, particularly interfaces. An interface defines the "contract" of what an object expects to receive. [Those are the inputs you keep forgetting]. If you have a function that takes 10 arguments, you should reduce your logic so that the function accepts an OBJECT with 10 properties. Then build the object from other objects. What you'll be left with is 5 or so [I assume the properties are somehow related, so it wouldn't be 10 objects] that can be dealt with in just a few lines of code, making it highly readable.

John Dee
  • 539
  • 4
  • 14
  • You should fire me, except that I work for free. Your answer is helpful to me in conjunction with Alvaro's, and I'm paying attention to interfaces as I read up on objects and classes and use them to replace my bungling array technique. Thank you. – NewSites Aug 08 '18 at 20:31
  • I've made my first class based on your suggestions and Alvaro's, although not yet using interfaces. If you'd like to take a look, I'd like to know what you think. I posted it at https://stackoverflow.com/questions/51773794/php-object-for-html-hyperlink . Thank you. – NewSites Aug 09 '18 at 18:46
2

IMHO, there's hardly any difference in terms of code readability. The second approach, however, adds some new drawbacks:

  • It no longer benefits from PHP type hints
  • Your IDE can no longer use information parsed from code and annotations to provide useful hints or auto-completion

Functions with large number of arguments are normally an indicator of legacy code that has grown beyond its design limits. I think that calls for some refactoring, e.g.:

class Speaker
{
    /**
     * @var string
     */
    private $sayThis;

    /**
     * @var int
     */
    private $repeatTimes;

    /**
     * @var bool
     */
    private $loud;

    /**
     * @param string $sayThis
     */
    public function __construct(string $sayThis)
    {
        $this->sayThis = $sayThis;
    }

    public function times(int $repeatTimes)
    {
        $this->repeatTimes = $repeatTimes;
        return $this;
    }

    public function loud(bool $loud = false)
    {
        $this->loud = $loud;
        return $this;
    }

    public function say()
    {
        $output = str_repeat($this->sayThis, $this->repeatTimes);
        echo $this->loud
            ? mb_strtoupper($output)
            : $output;
    }
}

(new Speaker('Foo'))
    ->times(4)
    ->loud(true)
    ->say();

As you can see, I also got rid of Hungarian notation.

Álvaro González
  • 142,137
  • 41
  • 261
  • 360
  • 1
    Okay, this is interesting. It seems like I was trying to invent an object with my array argument without understanding objects. Your code above helped me understand how to begin transitioning from my hackneyed array technique to passing an object instead. I'm reading up on objects and classes and will use that instead. Thank you. – NewSites Aug 08 '18 at 20:26
  • And now I've understood something else. You weren't telling me to pass an object as the argument of the function instead of an array, but the function become a method of the class. Very interesting. A whole new way of thinking. – NewSites Aug 08 '18 at 20:48
  • @NewSites You can pass an object as well (that'd be called dependency injection)—I do that often to e.g. pass search conditions. The idea (which I think you grasped perfectly) is to leverage language features and refactor when your original design doesn't scale. – Álvaro González Aug 09 '18 at 06:34
  • For a default, it seems it should be applied when the property is created, such as `private $loud = false;`, not in the method `loud()`, which requires that method to be called for the default to be applied. Is that correct? – NewSites Aug 09 '18 at 11:32
  • @NewSites Correct. It's just a quick prototype and missing default values is one of the most glaring omissions. – Álvaro González Aug 09 '18 at 11:36
  • And do the `return`s in methods `times()` and `loud()` do anything? It seems those methods finish their work when they apply the given values to the properties. Should those `return`s be omitted? – NewSites Aug 09 '18 at 11:43
  • They return current class instance. It's what makes method chaining possible. – Álvaro González Aug 09 '18 at 11:45
  • I've made my first class based on your suggestions. If you'd like to take a look, I'd like to know what you think. I posted it at https://stackoverflow.com/questions/51773794/php-object-for-html-hyperlink . Thank you. – NewSites Aug 09 '18 at 18:43