1
\$\begingroup\$

I have code to ensure parameters existence all over the place. Is there a better way to do it? So that I do not have so many repeated codes inside different functions

export const requestUpdateAccountDetail = (
  firebase,
  uid,
  accountId,
  accountDetail = {}
) => {
  if (firebase === undefined) {
    throw Error('`firebase` instance is required')
  } else if (uid === undefined) {
    throw Error('`uid is required to update account detail`')
  } else if (accountId === undefined) {
    throw Error('`accountId` is required to update account detail')
  }

  return firebase.update(`/accounts/${uid}/${accountId}`, accountDetail)
}

export const requestAddNewAccount = (firebase, uid) => {
  if (firebase === undefined) {
    throw Error('`firebase` instance is required')
  } else if (uid === undefined) {
    throw Error('`uid is required to update account detail`')
  }

  const EMPTY_ACCOUNT_OBJ = {
    name: '',
    note: '',
    owner: uid
  }

  return firebase.push(`/accounts/${uid}`, EMPTY_ACCOUNT_OBJ)
}

export const requestDeleteAccount = (firebase, uid, accountId) => {
  if (firebase === undefined) {
    throw Error('`firebase` instance is required')
  } else if (uid === undefined) {
    throw Error('`uid is required to update account detail`')
  } else if (accountId === undefined) {
    throw Error('`accountId` is required to update account detail')
  }

  return firebase.remove(`/accounts/${uid}/${accountId}`)
}
\$\endgroup\$
  • \$\begingroup\$ Where do you ever catch these Errors, and what do you do with them? \$\endgroup\$ – 200_success Aug 21 '17 at 13:37
  • \$\begingroup\$ What's more of an issue for you, repetitive code or pollution of the functions? \$\endgroup\$ – James Aug 21 '17 at 13:40
  • \$\begingroup\$ I just want to make sure user pass in the params as require params \$\endgroup\$ – Xinyang Li Aug 21 '17 at 13:41
  • \$\begingroup\$ I just feel lazy to keep writing repetitive code. @james \$\endgroup\$ – Xinyang Li Aug 21 '17 at 13:42
  • \$\begingroup\$ @XinyangLi ok, that narrows it down slightly. How flexible are the parameters? Is it a case of there are too many of the existing functions to change them? Or is modifying the existing parameters an option? \$\endgroup\$ – James Aug 21 '17 at 13:43
4
\$\begingroup\$

So given this is a blanket rule across all the functions i.e. val === undefined then throw you could introduce a common validator function you could share across all your functions e.g.

function assertParams(params) {
   for (const prop in params) {
       if (params[prop] === undefined) {
           throw new Error(`Argument ${prop} is undefined`);
       }
   }
}
...
export const function requestAddNew(firebase, uid, accountId) {
    assertParams({ firebase, uid, accountId });
    ...
}
export const function requestDeleteAccount(firebase, uid, accountId) {
    assertParams({ firebase, uid, accountId });
    ...
}

you'd need to call it in every function, however, this would give you the same effect of what you already have in one line as opposed to X lines based on the number of parameters you have.

If your using Babel, something to look at would be perhaps moving it to a decorator so it's completely outside the scope of your logic e.g.

@assertParams()
export const function requestAddNew(...)

The above code wouldn't just "work" in the decorator, would need a little bit of tweaking but I'm pretty sure it's doable.

\$\endgroup\$
  • \$\begingroup\$ Is there a lib doing this already? Just asking. Thanks for the code refercoring. \$\endgroup\$ – Xinyang Li Aug 21 '17 at 14:08
  • 1
    \$\begingroup\$ @XinyangLi doing the assertion? Probably, but given how small the code is I'd question why you'd want an external dependency for such small code. Were you aware of the the infamous left-pad disaster last year? Something to keep in mind when pulling in 3rd party libraries. \$\endgroup\$ – James Aug 21 '17 at 14:11
2
\$\begingroup\$

I guess if you just want to validate, you could write something like the following, though i'm not sure if that really answers your question.

/**
 * @method validateArguments
 * @param {[]} an object with the arguments that need to be checked
 * @param {{0: string, ..., n: string}} the named parameters that should be used as an output
 * @throws Error when given argument is empty
 */
const validateArguments = ( args, props ) => {
  Object.keys(props).forEach( prop => {
    if (!args[prop]) {
      throw new Error(`${props[prop]} must be defined`);
    }
  });
};

const requestUpdateAccountDetail = ( firebase, uid, accountId ) => {
  // send the arguments to the validate method and an object with the expected parameters, containing the name you want to have as part of the error message
  validateArguments( [firebase, uid, accountId], { 0: 'firebase', 1: 'uid', 2: 'accountId' } );
};

// or alternatively
const requestAddNewAccount = (firebase, uid) => {
  validateArguments( [firebase, uid], ['firebase', 'uid']);
};

// doesn't throw error
requestAddNewAccount({ name: 'firebase' }, 'some-uid' );

// will throw error on accountId
requestUpdateAccountDetail ( 't', 1 );

You could still extract the required parameters object from the method itself, so you can more easily validate repeating methods.

It has an advantage that you can rearrange the required arguments a bit flexible by using the object key to validate if the argument is part of the args array

However, you could also go for a different approach and define firebase much earlier in your design, seeing that firebase & uid are recurring arguments, you could define all interactions like

const firebaseInteraction = (firebase, uid) => {
  if (!firebase) {
    throw new Error("firebase is undefined");
  }
  if (!uid) {
    throw new Error("uid is undefined");
  }
  return {
    requestUpdateAccountDetail(accountId, accountDetail) {
      if (!accountId) {
        throw new Error("accountId not defined");
      }
      return firebase.update(`/accounts/${uid}/${accountId}`, accountDetail)
    },
    requestDeleteAccount(accountId) {
      if (!accountId) {
        throw new Error("accountId not defined");
      }
      return firebase.remove(`/accounts/${uid}/${accountId}`)
    },
    requestAddNewAccount() {
      const EMPTY_ACCOUNT_OBJ = {
        name: '',
        note: '',
        owner: uid
      }
      return firebase.push(`/accounts/${uid}`, EMPTY_ACCOUNT_OBJ)
    }
  };
};

let firebaseHandler = firebaseInteraction( { name: 't' }, 'someuid' );
// work with this handler

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.