简体   繁体   中英

Refactoring comparision/operators blocks to DRY up and reduce C.R.A.P level

I set out to make a small project around a bounch of classes that return generators (php 5.5).

The main motivation for the small project was to expand on my TDD journey, fiddle with generators and have a package I could throw on packagist for later use.

The current state of the whole "project" can be found at Github

All tests are green, the methods does what I want. Now I want to refactor as I there is lots of dublication.

    /**
    *   Returns a Generator with a even range.
    *
    *   getEven(10); // 10,12,14,16,18,20,22 ...
    *   getEven(null, 10); // 10,8,6,4,2,0,-2,-4 ...
    *   getEven(10, null, 2); // 10,6,2, -2 ...
    *   getEven(10,20); // 10,12,14,16,18,20
    *   getEven(20,10); // 20,18,16,14,12,10
    *   getEven(10,20,2); // 10,14,18
    *
    *   @param int|null $start
    *   @param int|null $end
    *   @param int $step
    *   @throws InvalidArgumentException|LogicException
    *   @return Generator
    */
    public function getEven( $start = null, $end = null, $step = 1 )
    {
        // Throws LogicException
        $this->throwExceptionIfAllNulls( [$start, $end] );
        $this->throwExceptionIfInvalidStep($step);

        // Throws InvalidArgumentException
        $this->throwExceptionIfNotNullOrInt( [$start, $end] );

        // infinite increase range
        if(is_int($start) && is_null($end))
        {
            // throw LogicException
            $this->throwExceptionIfOdd($start);

            $Generator = function() use ($start, $step)
            {
                for($i = $start; true; $i += $step * 2)
                {
                    yield $i;
                }
            };
        }
        // infinite decrease range
        elseif(is_int($end) && is_null($start))
        {
            // throws LogicException
            $this->throwExceptionIfUneven($end);

            $Generator =  function() use ($end, $step)
            {
                for($i = $end; true; $i -= $step * 2)
                {
                    yield $i;
                }
            };
        }
        // predetermined range
        else 
        {
            // throws LogicException
            $this->throwExceptionIfUneven($start);
            $this->throwExceptionIfUneven($end);

            // decrease
            if($start >= $end)
            {
                $Generator = function() use ($start, $end, $step)
                {
                    for($i = $start; $i >= $end; $i -= $step * 2)
                    {
                        yield $i;
                    }
                };
            }
            // increase
            else
            {
                $Generator = function() use ($start, $end, $step)
                {
                    for($i = $start; $i <= $end; $i += $step * 2)
                    {
                        yield $i;
                    }
                };
            }
        }

        return $Generator();
    }

The class also has a method named getOdd (and yes it looks alot like it ;) )

The main dublication is the closures $Generator = function() ... and the difference is mostly operators such as + - * / and arguments in the for loop. This is mainly the same in the rest of th class.

I read Dynamic Comparison Operators in PHP and come to the conclusion that there is no native method like compare(...)

Should I make a private/protected method for comparison. If so should I make a new class/function for this? I do not think it belongs in the current class.

Is it something else I am missing, I am unsure on how to DRY this up, in a proper way?

Btw. iknow a getEven, getOdd is kinda silly when i got a getRange With step function, but it is a more general refactoring / pattern question.

Update @github the getEven and getOdd are now removed...

The code below has not been tested or verified to work, but I have faith in it and at least it shows one possible way of removing the multiple generator functions.

As you state yourself, the duplication you are trying to remove is mainly in the generator function. If you look into this you can see that every generator function you have can be written as this:

function createGenerator($index, $limit, $step) {
    return function() use($index, $limit, $step) {
        $incrementing = $step > 0;
        for ($i = $index; true; $i += 2 * $step) {
            if (($incrementing && $i <= $limit) || (!$incrementing && $i >= $limit)) {
                yield $i;
            }else {
                break;
            }
        }
    };
}

In order to utilize this you need to do some magic with the input arguments and it helps (at least makes it pretty) to define some constants. PHP allready got a PHP_INT_MAX constant holding the greatest value possible for an integer, however it does not got a PHP_INT_MIN . So I would define that as a constant of its own.

define('PHP_INT_MIN', ~PHP_INT_MAX);

Now lets take a look at the four cases in your function.

1) Infinite increase range

Infinte is a rather bold claim here, if we change it to "greatest value possible given the constraints of an int" we get a finite range from $index to PHP_INT_MAX , hence by setting $limit = PHP_INT_MAX; the above mentioned generator function will still be the same.

//$limit = PHP_INT_MAX;
createGenerator($index, PHP_INT_MAX, $step);

2) Infinite decrease range

The same argument as above can again be used here, but with a negativ $step and swapping $index and $limit ;

//$index = $limit;
//$limit = PHP_INT_MIN;
//$step *= -1;
createGenerator($limit, PHP_INT_MIN, -1 * $step);

3) Predetermined decreasing range

Swap and negate once again.

//$temp = $index;
//$index = $limit;
//$limit = $temp;
//$step *= -1;
createGenerator($limit, $index, -1 * $step);

4) Predetermined increasing range

Well this is just the default case, where all arguments are given. And nothing needs to change.

createGenerator($index, $limit, $step);

The revised code

public function getEven($index = null, $limit = null, $step = 1) {
    // Throws LogicException
    $this->throwExceptionIfAllNulls([$index, $limit]);
    $this->throwExceptionIfInvalidStep($step);

    // Throws InvalidArgumentException
    $this->throwExceptionIfNotNullOrInt([$index, $limit]);

    //Generator function
    function createGenerator($index, $limit, $step) {
        return function() use($index, $limit, $step) {
            $incrementing = $step > 0;
            for ($i = $index; true; $i += 2 * $step) {
                if (($incrementing && $i <= $limit) || (!$incrementing && $i >= $limit)) {
                    yield $i;
                }else {
                    break;
                }
            }
        };
    }
    // infinite increase range
    if (is_int($index) && is_null($limit)) {
        // throw LogicException
        $this->throwExceptionIfodd($index);
        return createGenerator($index, PHP_INT_MAX, $step);
    }
    // infinite decrease range
    elseif (is_int($limit) && is_null($index)) {
        // throws LogicException
        $this->throwExceptionIfodd($limit);
        return createGenerator($limit, PHP_INT_MIN, -1*$step);
    }
    // predetermined range
    else {
        // throws LogicException
        $this->throwExceptionIfodd($index);
        $this->throwExceptionIfodd($limit);

        // decrease
        if ($index >= $limit) {
            return createGenerator($limit, $index, -1 * $step);
        }
        return createGenerator($index, $limit, $step);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM