2019年7月23日 星期二

Reuse, but don't make a huge function

  • The concept of DRY is super simple - do not duplicate codes if there are already in the program. The basic practice is using a function to do a particular action and let other places call the function if they need.
      // bad
      const name1 = 'Bob'
      const msg1 = 'Hi'
      console.log(`${name1} says: ${msg1}`)
    
      const name2 = 'Tom'
      const msg2 = `How's it going?`
      console.log(`${name2} says: ${msg2}`)
    
      // good
      function say(name, message) {
        console.log(`${name} says: ${message}`)
      }
    
      say('Bob', 'Hi')
      say('Tom', `How's it going?`)
    
  • However, once the requirement changed and we modify the function, of course, all function calls will be affected. That's good we save time.
      function say(from, to, message) {
        console.log(`${from} says ${to}: ${message}`)
      }
    
      say('Bob', 'John', 'Hi')
      say('Tom', 'Peter', `How's it going?`)
    
  • It is very common that we use parameters as inputs to let a function deal with for different situations.
      function say(from, to, message) {
        if (to !== null && to !== undefined) {
          console.log(`${from} says ${to}: ${message}`)
        } else {
          console.log(`${from} says: ${message}`)
        }
      }
    
      say('Bob', 'John', 'Hi')
      say('Tom', null, `How's it going?`)
    
  • You know, the requirement might change many times because customers are indecisive. Even if they don't, we still have to do refactoring in order to have better code quality. That means we might change codes.
      function say(from, to, message, isQuestion) {
        const ending = isQuestion ? '?' : '.'
        if (to !== null && to !== undefined) {
          console.log(`${from} says ${to}: ${message}${ending}`)
        } else {
          console.log(`${from} says: ${message}${ending}`)
        }
      }
    
      say('Bob', null, 'Hi', false)
      say('Tom', 'Peter', `How's it going`, true)
    
  • You will realize that functions have more complex parameters and conditions are very hard to modify. It calls "high coupling" if there are too many places rely on a heavy code.
      // the combinations you need to test in order to make sure your function works:
      say('name', null, 'message', false)
      say('name', 'name', 'message', false)
      say('name', null, 'message', true)
      say('name', 'name', 'message', true)
    
      // the combinations you may not expect that will ruin your function:
      say(null, 'name', 'message', false)
      say('name', 'name', null, true)
    
  • Therefore, when you are trying to DRY your code, be careful that don't put too many codes into one function. The function will be super complex and hard to maintain. It may be a good idea to have small functions instead.
      // there are actually 4 scenarios, so it may be a good idea to have 4 functions
      // new function names are more semantic
      // there are no magical boolean flag and optional parameters (reduce parameter mistakes)
      // we get rid of if conditions (reduce the complexity)
      // once one of the functions changed, we won't affect other functions
    
      function talk(name, message) {
        console.log(`${name} says: ${message}.`)
      }
    
      function askQuestion(name, message) {
        console.log(`${name} says: ${message}?`)
      }
    
      function talkToSomeone(name, someone, message) {
        console.log(`${name} says to ${someone}: ${message}.`)
      }
    
      function askQuestionToSomeone(name, someone, message) {
        console.log(`${name} says to ${someone}: ${message}?`)
      }
    
      talk('Bob', 'Hi')
      askQuestion('Tom', `How's it going`)
      talkToSomeone('Bob', 'John', 'Hi')
      askQuestionToSomeone('Tom', 'Peter', `How's it going`)
    
  • This example is overly simple, and the naming actually sucks, but I hope I give you a general idea of how to organize your functions.