# Clean Code

> 🔗 [Junior Vs Senior Code - How To Write Better Code - Part 2](https://www.youtube.com/watch?v=5B587bQ-TNg)를 보고 정리한 내용입니다.
>
> 🔗 [Noob-Vs-Pro-Code - Github](https://github.com/WebDevSimplified/Noob-Vs-Pro-Code)

## 3. Async/Await

### Step 1️⃣

* 콜백 함수 - 콜백이 계속 중첩되는 🔥콜백 지옥🔥

```javascript
const readline = require("readline")
const readlineInterface = readline.createInterface({
  input: process.stdin,
  output: process.stdout
})

readlineInterface.question("What is your name? ", name => {
  readlineInterface.question("What is your job? ", job => {
    readlineInterface.question("How old are you? ", age => {
      console.log("Hello " + name + ". You are a " + age + " year old " + job + ".")
      readlineInterface.close()
    })
  })
})
```

### Step 2️⃣

* Async/Await - 콜백이 중첩되는 문제 해결

```javascript
const readline = require("readline")
const readlineInterface = readline.createInterface({
  input: process.stdin,
  output: process.stdout
})

async function main() {
  const name = await askQuestion(realineInterface, "What is your name? ")
  const job = await askQuestion(realineInterface, "What is your job? ")
  const age = await askQuestion(realineInterface, "How old are you? ")

  console.log("Hello " + name + ". You are a " + age + " year old " + job + ".")
  readlineInterface.close()
}

main()

function askQuestion(readlineInterface, question) {
  return new Promise(resolve => {
    readlineInterface.question(question, answer => {
      resolve(answer)
    })
  })
}
```

### Step 3️⃣

* `readlineInterface` 리팩토링, 함수 분리 - 모듈화

```javascript
// async.js
const askQuestion = require("./askQuestion")

async function main() {
  const name = await askQuestion("What is your name? ")
  const job = await askQuestion("What is your job? ")
  const age = await askQuestion("How old are you? ")

  console.log(`Hello ${name}. You are a ${age} year old ${job}.`)
}

main()

// askQuestion.js
const readline = require("readline")

function askQuestion(question) {
  const readlineInterface = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  })
  return new Promise(resolve => {
    readlineInterface.question(question, answer => {
      resolve(answer)
      readlineInterface.close()
    })
  })
}

module.exports = askQuestion
```

## 4. Single Responsibility Principle(SRP, 단일 책임 원칙)

> [SOLID: Single Responsibility Principle in JavaScript](https://dev.to/carstenbehrens/single-responsibility-principle-in-javascript-1mk1)
>
> "**class(a.k.a module)** 은 오직 **한 가지 이유** 때문에 변경되어야 한다."
>
> * Robert C. Martin
>
>   * SRP는 객체 지향 언어에 국한되어 있지 않고, 프로토타입 상속을 하는 자바스크립트에도 적용된다.
>   * 하나의 class는 오직 한 가지 책임만 가져야 한다(**단일 책임 원칙**)
>   * Employee class는 SRP 원칙에 따라 여러 class로 분리되어야 한다.
>
>   ```javascript
>   // 나쁜 예시
>   // 💣 함수 변경 시 class 내부의 코드 변경 해야 함
>
>   class Employee {
>   calculatePay() {
>   //...
>   }
>   reportHours() {
>   //...
>   }
>   saveToDB() {
>   //...
>   }
>   }
>   ```

### Step 1️⃣

* *의도하지 않은* ***🚫side effects🚫*** 존재

```javascript
// users.js
function createUser(user) {
  return {
    ...user,
    id: Date.now(),
    createdAt: new Date(),
    updatedAt: new Date()
  }
}

function updateUser(user) {
  return {
    ...user,
    updatedAt: new Date()
  }
}

module.exports = { createUser, updateUser }

// main.js
const { updateUser, createUser } = require('./api/users')

function saveUser(user) {
  // 🚫 validation
  // 🚫 error printing

  const errors = []
  if (user.username) {
    if (user.username.length < 3) {
      errors.push("Username must be 3 or more characters")
    }
  } else {
    errors.push("Username is required")
  }

  if (user.password) {
    if (user.password.length < 8) {
      errors.push("Password must be 8 or more characters")
    }
  } else {
    errors.push("Password is required")
  }

  if (errors.length > 0) {
    errors.forEach(error => console.error(error))
    return
  }

  // save users
  if (user.id == null) {
    console.log('Created User')
    createUser(user)
  } else {
    console.log('Updated User')
    updateUser(user)
  }
}

const user = {
  username: '',
  password: 'password'
}

saveUser(user)
```

### Step 2️⃣

* 기능별 함수 분리(`validateUser`, `saveUser`)
* 💣 validation 관련 코드 반복(비효율적)

```javascript
// main.js
const { updateUser, createUser } = require('./api/users')

// 1️⃣
function saveUser(user) {
  if(user.id == null) {
    console.log("Created User")
    createUser(user)
  } else {
    console.log("Updated User")
    updateUser(user)
  }
}

// 2️⃣
function validateUser(user) {
  return [
    ...validateUsername(user.username),
    ...validatePassword(user.password)
  ]
}

// --- helper function (returns an array of errors) ---
function validateUsername(username) {
  const errors = []
  if(!username) errors.push("Username is required")
  if(username != null && username.length < 3) {
    errors.push("Username must be 3 or more characters") 
  }
  return errors
}

function validatePassword(password) {
  const errors = []
  if(!password) errors.push("Password is required")
  if(password != null && password.length < 8) {
    errors.push("Password must be 8 or more characters") 
  }
  return errors
}
// -----------------------------------------------------

const user = {
  username: 'USER',
  password: 'password'
}

// 2️⃣ 
const errors = validateUser(user)
if(errors.length > 0) {
  errors.forEach(error => console.error(error))
  return
}

// 1️⃣
saveUser(user)
```

### Step 3️⃣

* 🔑 SRP(단일 책임 원칙) 충족하도록 validation 관련 **코드 분리**

```javascript
// validation.js
// 1️⃣
function validationMessages(validations, object) {
  return Object.entries(validations).reduce((errors, [property, requirements]) => {
    errors[property] = []
    if (requirements.required) {
      const errorMessage = validateRequiredMessage(object[property])
      if (errorMessage) errors[property].push(errorMessage)
    }

    if (requirements.length) {
      const errorMessage = validateLengthMessage(object[property], requirements.length)
      if (errorMessage) errors[property].push(errorMessage)
    }

    return errors
  }, {})
}

// --- helper function (returns error messages) ---
function validateLengthMessage(value, length) {
  if (value == null) return
  if (value.length >= length) return

  return `must be ${length} or more characters` 
}

function validateRequiredMessage(value) {
  if (value) return

  return 'is required' 
}
// ---------------------------------------------------

// 2️⃣ 
function printErrors(errors) {
  Object.entries(errors).forEach(([property, messages]) => {
    messages.forEach(message => {
      console.error(`${property} ${message}`)
    })
  })
}

module.exports = {
  validationMessages,
  printErrors
}

// main.js
const { updateUser, createUser } = require('./api/users')
const { validationMessages, printErrors } = require('./pro/validation')

function saveUser(user) {
  if (user.id == null) {
    console.log('Created User')
    createUser(user)
  } else {
    console.log('Updated User')
    updateUser(user)
  }
}

function validateUser(user) {
  // hash of validations
  const validations = {
    username: {
      required: true,
      length: 3
    },
    password: {
      required: true,
      length: 8
    }
  }

  const errors = validationMessages(validations, user)

  return {
    valid: Object.values(errors).every(messages => messages.length === 0),
    errors: errors
  }
}

const user = {
  id: 1,
  username: 'USER',
  password: 'password'
}

const { errors, valid } = validateUser(user)
if (valid) {
  saveUser(user)
} else {
  printErrors(errors)
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yong2401.gitbook.io/til-by-ellie/development-2/clean_code2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
