Checks user level and limit the data before saving it to mongoDB I have a function that checks user level and limits the data before saving it to mongoDB database (pre 'save' middleware for mongoose).
It have been getting complexity warnings and tried to rewrite it, and came up with the second version. I think now it's really not human readable! Anyone has any suggestions?

Before:

providerSchema.pre('save', function(next) {

    if(this.level === 4){
        if(this.description.length &gt;= 80 || this.certifications.length &gt; 5){
            next(new Error('your current plan does not have this feature'));
        } else {
            next()
        }
    } else if(this.level === 3){
        if(this.description.length &gt;= 50 || this.certifications.length &gt; 3){
            next(new Error('your current plan does not have this feature'));
        } else {
            next()
        }
    } else if(this.level === 2){
        if(this.description.length &gt;= 30 || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0){
            next(new Error('your current plan does not have this feature'));
        } else {
            next()
        }
    } else if(this.level === 1){
        if(this.description || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0 || this.locationLat || this.locationLong || this.workingHourEnd || this.workingHourStart){
            next(new Error('your current plan does not have this feature'));
        } else {
            next()
        }
    } else {
        if(this.description || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0 || this.locationLat || this.locationLong || this.workingHourEnd || this.workingHourStart){
            next(new Error('your current plan does not have this feature'));
        } else {
            next()
        }
});


After:

providerSchema.pre('save', function(next) {

    if(((this.level === 4) &amp;&amp; (this.description.length &gt;= 80 || this.certifications.length &gt; 5)) || 
       ((this.level === 3) &amp;&amp; (this.description.length &gt;= 50 || this.certifications.length &gt; 3)) ||
       ((this.level === 2) &amp;&amp; (this.description.length &gt;= 30 || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0)) || 
       ((this.level === 1 || this.level === 0) &amp;&amp; (this.description || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0 || this.locationLat || this.locationLong || this.workingHourEnd || this.workingHourStart))){
         next(new Error('your current plan does not have this feature'));
    } else {
         next()
    }
});
```
According to www.lizard.ws the original's function cyclomatic complexity is 29 and for the second version is 22. Both numbers are usually considered high, and teams aim for much lower values (debatable what the good range is though and will see within this answer why).

In order to reduce it, one way is to encapsulate the if statements, among with removing the code duplication and separate the responsibilities. 

The next calls seem duplicate, so let's reduce them first.

providerSchema.pre('save', function (next) {

     var valid = true;
    if (this.level === 4) {
        if (this.description.length &gt;= 80 || this.certifications.length &gt; 5) {
            valid = false;
        }
    } else if (this.level === 3) {
        if (this.description.length &gt;= 50 || this.certifications.length &gt; 3) {
            valid = false;
        }
    } else if (this.level === 2) {
        if (this.description.length &gt;= 30 || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0) {
            valid = false;
        }
    } else if (this.level === 1) {
        if (this.description || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0 || this.locationLat || this.locationLong || this.workingHourEnd || this.workingHourStart) {
            valid = false;
        }
    } else {
        if (this.description || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0 || this.locationLat || this.locationLong || this.workingHourEnd || this.workingHourStart) {
            valid = false;
        }
    }

    if (valid) {
        next();
    } else {
        next(new Error('your current plan does not have this feature'));
    }
});


With this first refactoring we didn't gain much in terms of lowering CC, in fact it increased to 30, because of the added if statement. However this can let us to split the validation responsibility from enabling the actual feature (as @Margon mentioned).

providerSchema.pre('save', function (next) {
    if (isValidFeatureRequest()) {
        next();
    } else {
        next(new Error('your current plan does not have this feature'));
    }

    function isValidFeatureRequest() {
        if (this.level === 4) {
            if (this.description.length &gt;= 80 || this.certifications.length &gt; 5) {
                return false;
            }
        } else if (this.level === 3) {
            if (this.description.length &gt;= 50 || this.certifications.length &gt; 3) {
                return false;
            }
        } else if (this.level === 2) {
            if (this.description.length &gt;= 30 || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0) {
                return false;
            }
        } else if (this.level === 1) {
            if (this.description || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0 || this.locationLat || this.locationLong || this.workingHourEnd || this.workingHourStart) {
                return false;
            }
        } else {
            if (this.description || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0 || this.locationLat || this.locationLong || this.workingHourEnd || this.workingHourStart) {
                return false;
            }
        }
        return true;
    }
});


The isValidFeatureRequest function is at 29 and providerSchema is at 2. We still need to work on.

Checking again the code duplication, I noticed the last two blocks have the the same checks for other levels than 2, 3 or 4, so let's merge them.

providerSchema.pre('save', function (next) {
    if (isValidFeatureRequest()) {
        next();
    } else {
        next(new Error('your current plan does not have this feature'));
    }

    function isValidFeatureRequest() {
        if (this.level === 4) {
            if (this.description.length &gt;= 80 || this.certifications.length &gt; 5) {
                return false;
            }
        } else if (this.level === 3) {
            if (this.description.length &gt;= 50 || this.certifications.length &gt; 3) {
                return false;
            }
        } else if (this.level === 2) {
            if (this.description.length &gt;= 30 || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0) {
                return false;
            }
        } else {
            if (this.description || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0 || this.locationLat || this.locationLong || this.workingHourEnd || this.workingHourStart) {
                return false;
            }
        }
        return true;
    }
});


We gained the following figures

function                               | CC
-------------------------------------------
providerSchema                         | 2
isValidFeatureRequest                  | 20


The CC for isValidFeatureRequest is now at 20, which is an improvement.
The check for description and certifications also seems to vary, so I can encapsulate it into a function.

providerSchema.pre('save', function (next) {
    if (isValidFeatureRequest()) {
        next();
    } else {
        next(new Error('your current plan does not have this feature'));
    }

    function isValidFeatureRequest() {
        if (this.level === 4) {
            if (descriptionOrCertificationsOffLimits(80, 5)) {
                return false;
            }
        } else if (this.level === 3) {
            if (descriptionOrCertificationsOffLimits(40, 3)) {
                return false;
            }
        } else if (this.level === 2) {
            if (descriptionOrCertificationsOffLimits(30, 0) || this.teaser || this.social.length &gt; 0) {
                return false;
            }
        } else {
            if (this.description || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0 || this.locationLat || this.locationLong || this.workingHourEnd || this.workingHourStart) {
                return false;
            }
        }
        return true;
    }

    function descriptionOrCertificationsOffLimits(descriptionLimit, certificationsLimit) {
        return this.description.length &gt;= descriptionLimit || this.certifications.length &gt; certificationsLimit;
    }
});


function                               | CC
-------------------------------------------
providerSchema                         | 2
isValidFeatureRequest                  | 17
descriptionOrCertificationsOffLimits   | 2


CC is now at 17, slightly better.

There is lot to check in the last branch, so let's extract it into his own function.

providerSchema.pre('save', function (next) {
    if (isValidFeatureRequest()) {
        next();
    } else {
        next(new Error('your current plan does not have this feature'));
    }

    function isValidFeatureRequest() {
        if (this.level === 4) {
            if (descriptionOrCertificationsOffLimits(80, 5)) {
                return false;
            }
        } else if (this.level === 3) {
            if (descriptionOrCertificationsOffLimits(40, 3)) {
                return false;
            }
        } else if (this.level === 2) {
            if (descriptionOrCertificationsOffLimits(30, 0) || this.teaser || this.social.length &gt; 0) {
                return false;
            }
        } else if (hasAny()) {
            return false;
        }
        return true;
    }

    function descriptionOrCertificationsOffLimits(descriptionLimit, certificationsLimit) {
        return this.description.length &gt;= descriptionLimit || this.certifications.length &gt; certificationsLimit;
    }

    function hasAny() {
        return this.description || this.certifications.length &gt; 0 || this.teaser || this.social.length &gt; 0 || this.locationLat || this.locationLong || this.workingHourEnd || this.workingHourStart;
    }
});


Which results into

function                               | CC
-------------------------------------------
providerSchema                         | 2
isValidFeatureRequest                  | 10
descriptionOrCertificationsOffLimits   | 2
hasAny                                 | 8


We have now 4 functions with manageable complexities.
The hasAny function seems to have a large CC, compared to what it does. What we can do here is to improve its readability, by displaying one condition per line. This is also the moment when I think we can't do anything about this function, and is the time not to look at an arbitrary CC limit in order to squize the code just to pass the analyzer.

 function hasAny() {
        return this.description ||
            this.certifications.length &gt; 0 ||
            this.teaser ||
            this.social.length &gt; 0 ||
            this.locationLat ||
            this.locationLong ||
            this.workingHourEnd ||
            this.workingHourStart;
    }


Let's extract more, to check if it has a teaser or social data

providerSchema.pre('save', function (next) {
    if (isValidFeatureRequest()) {
        next();
    } else {
        next(new Error('your current plan does not have this feature'));
    }

    function isValidFeatureRequest() {
        if (this.level === 4) {
            if (descriptionOrCertificationsOffLimits(80, 5)) {
                return false;
            }
        } else if (this.level === 3) {
            if (descriptionOrCertificationsOffLimits(40, 3)) {
                return false;
            }
        } else if (this.level === 2) {
            if (descriptionOrCertificationsOffLimits(30, 0) || hasTeaserOrSocial()) {
                return false;
            }
        } else if (hasAny()) {
            return false;
        }
        return true;
    }

    function descriptionOrCertificationsOffLimits(descriptionLimit, certificationsLimit) {
        return this.description.length &gt;= descriptionLimit || this.certifications.length &gt; certificationsLimit;
    }

    function hasTeaserOrSocial() {
        return this.teaser || this.social.length &gt; 0;
    }

    function hasAny() {
        return this.description ||
            this.certifications.length &gt; 0 ||
            this.teaser ||
            this.social.length &gt; 0 ||
            this.locationLat ||
            this.locationLong ||
            this.workingHourEnd ||
            this.workingHourStart;
    }
});


Which results into

function                               | CC
-------------------------------------------
providerSchema                         | 2
isValidFeatureRequest                  | 9
descriptionOrCertificationsOffLimits   | 2
hasTeaserOrSocial                      | 2
hasAny                                 | 8


The if followed by an inner if can be combined into and and operation so we can have this

 function isValidFeatureRequest() {
        if (this.level === 4 &amp;&amp; descriptionOrCertificationsOffLimits(80, 5)) {
            return false;
        } else if (this.level === 3 &amp;&amp; descriptionOrCertificationsOffLimits(40, 3)) {
            return false;
        } else if (this.level === 2 &amp;&amp; descriptionOrCertificationsOffLimits(30, 0) || hasTeaserOrSocial()) {
            return false;
        } else if ((this.level === 1 || this.level === 0) &amp;&amp; hasAny()) {
            return false;
        }
        return true;
    }


The CC doesn't change, but it enables me to extract validation for every level, so I gain smaller functions, with smaller complexity.
Edit to fix a bug here - This step introduced a bug, as @Roland Illig mentioned in the comments (the story of my life when I refactor even a simple if).
After fixing it the CC actually increased with 2, to 11, as I introduced two new checks and I also had to add a new function. end of edit

function isValidFeatureRequest() {
    if (isLevel4AndNotValid()) {
        return false;
    } else if (isLevel3AndNotValid()) {
        return false;
    } else if (isLevel2AndNotValid()) {
        return false;
    } else if (isBellowLevel2AndNotValid()) {
        return false;
    }
    return true;
}

function isLevel4AndNotValid() {
    return this.level === 4 &amp;&amp; descriptionOrCertificationsOffLimits(80, 5);
}

function isLevel3AndNotValid() {
    return this.level === 3 &amp;&amp; descriptionOrCertificationsOffLimits(40, 3);
}

function isLevel2AndNotValid() {
    return this.level === 2 &amp;&amp; (descriptionOrCertificationsOffLimits(30, 0) || hasTeaserOrSocial());
}

function isBellowLevel2AndNotValid() {
    return (this.level === 1 || this.level === 0) &amp;&amp; hasAny();
}


Which are

function                               | CC
-------------------------------------------
providerSchema                         | 2
isValidFeatureRequest                  | 5
isLevel4AndNotValid                    | 2
isLevel3AndNotValid                    | 2
isLevel2AndNotValid                    | 3
isBellowLevel2AndNotValid              | 3
descriptionOrCertificationsOffLimits   | 2
hasTeaserOrSocial                      | 2
hasAny                                 | 8


The isValidFeatureRequest still looks odd, I can remove the else statements and I can convert the last call into a return statement, which decrease the complexity with one point.

function isValidFeatureRequest() {
    if (isLevel4AndNotValid()) {
        return false;
    }

    if (isLevel3AndNotValid()) {
        return false;
    }

    if (isLevel2AndNotValid()) {
        return false;
    }

    if (isBellowLevel2AndNotValid()) {
        return false;
    }

    return true;
}


My final attempt is this:

providerSchema.pre('save', function (next) {
    if (isValidFeatureRequest()) {
        next();
    } else {
        next(new Error('your current plan does not have this feature'));
    }

    function isValidFeatureRequest() {
        if (isLevel4AndNotValid()) {
            return false;
        }

        if (isLevel3AndNotValid()) {
            return false;
        }

        if (isLevel2AndNotValid()) {
            return false;
        }

        if (isBellowLevel2AndNotValid()) {
            return false;
        }

        return true;
    }

    function isLevel4AndNotValid() {
        return this.level === 4 &amp;&amp; descriptionOrCertificationsOffLimits(80, 5);
    }

    function isLevel3AndNotValid() {
        return this.level === 3 &amp;&amp; descriptionOrCertificationsOffLimits(40, 3);
    }

    function isLevel2AndNotValid() {
        this.level === 2 &amp;&amp; (descriptionOrCertificationsOffLimits(30, 0) || hasTeaserOrSocial());
    }

    function isBellowLevel2AndNotValid() {
        return (this.level === 1 || this.level === 0) &amp;&amp; hasAny();
    }

    function descriptionOrCertificationsOffLimits(descriptionLimit, certificationsLimit) {
        return this.description.length &gt;= descriptionLimit || this.certifications.length &gt; certificationsLimit;
    }

    function hasTeaserOrSocial() {
        return this.teaser || this.social.length &gt; 0;
    }

    function hasAny() {
        return this.description ||
            this.certifications.length &gt; 0 ||
            this.teaser ||
            this.social.length &gt; 0 ||
            this.locationLat ||
            this.locationLong ||
            this.workingHourEnd ||
            this.workingHourStart;
    }
});


With the following resuts:

function                               | CC
-------------------------------------------
providerSchema                         | 2
isValidFeatureRequest                  | 5
isLevel4AndNotValid                    | 2
isLevel3AndNotValid                    | 2
isLevel2AndNotValid                    | 3
isBellowLvel2AndNotValid               | 3
descriptionOrCertificationsOffLimits   | 2
hasTeaserOrSocial                      | 2
hasAny                                 | 8