PHP: Using array keys to identify function argumen

2019-09-20 18:49发布

问题:

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?

回答1:

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.



回答2:

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.