> For the complete documentation index, see [llms.txt](https://sois-organization.gitbook.io/today-i-learned/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sois-organization.gitbook.io/today-i-learned/clean-code/03..md).

# 함수

### SPR : 함수의 역할은 하나만 할당한다.

* 하나의 함수에 역할이 많아지면 오류 발생 가능성이 커지고, 가독성이 떨어지며 테스트를 진행하기 어렵다.
* 따라서 하나의 함수에 많은 로직을 넣지 말자.

```swift
// as-is
func createUser(email, password) -> Bool {
    if email.contains("@") || password.contains("@") {
        //error
        return false
    }

    var user = User()
    user.email = email
    user.password = password

    database.add(user)

    emailClient = EmailClient()
    emailClient.setConfig()
    emailClient.send(email, "회원가입이 완료되었습니다")
    
    return true
}
    
```

```swift
// to-be
func createUser(email: String, password: String) -> Bool { // 주요 로직 실행 함수
    validateCreateUser(email, password)

    user = BuildUser(email, password)

    saveUser(user)
    sendEmail(email)
    return true
}

// 역할 별로 함수를 작성한다.
func validateCreateUser(_ email: String, _ password: String) { }

func Builduser() { }

func saveUser(_ user: User) { }

```

### DRY (Don’t repeat yourself) : 반복하지 말자

* 반복되는 코드가 많아지면, 전체 코드가 많아진다.
* 특정 부분에 변경사항이 생기면, 공통적인 모든 부분을 변경해줘야한다.
* 이에 관심사를 잘 분리하고 의존성을 줄이기 위해 반복되는 코드를 하나의 함수로 만들어서 사용하자.

```swift
// as-is

func createUser(email: String, password: String) {
    if email.contains("@") || password.contains("@") {
        //error
        return false
    }

    // code...
}

func updateUser(email: String, password: String) {
        if email.contains("@") || password.contains("@") {
        //error
        return false
    }

    // code...
}
```

```swift
// to-be
func validateCreateUser(_ email: String, _ password: String) -> Bool {
    if email.contains("@") || password.contains("@") {
        //error
        return false
    }
}

func createUser(email, password) {
    guard validateCreateUser(email, password) else { ... }
    // code..
}

func updateUser(email, password) {
    guard validateCreateUser(email, password) else { ... }
    // code..
}
```

### **파라미터 수는 적게 유지하자**

```swift
// as-is
func saveUser(userName: String, email: String, password: String, createAt: Int) {
    ...
}

// to-be
func saveUser(user: User) {
    ...
}
```

### **사이드 이펙트를 잘 핸들링하자**

* 사이드 이펙트란? (Side Effect)

  * 함수가 실행되었을 때, 함수 이외의 어떤 것들에 변화를 주는 것을 의미한다.
  * 이를 잘 다루지 못하면 예측하지 못하는 문제가 발생할 수 있다.
  * A, B, C, D라는 함수가 있을 때, A 실행시 B또는 C함수에도 영향을 주는 경우가 Side Effect라고 한다.

  ```swift
  // 사이드 이펙트가 없는 경우
  func getUser(email, password) -> User {
      let user = User(email, password)
      return user
  }

  // 사이드 이펙트가 있는 경우
  func updateUser(user) {
      var user = getUser(user.email, user.password)
      user.eail = "newEmail" // 인자로 받은 user 객체를 업데이트한다
      return user
  }

  // 사이드 이펙트가 있는 경우
  func createUser(email, password) {
      let user = User(email, password)
      dbSession() // 외부 DB 세션에 변화를 준다.
  }
  ```
* 사이드 이펙트 핸들링하는 방법
  * 코드를 통해 사이드이펙트를 예측할 수 있도록 네이밍을 잘 해야한다.
    * update, set 등 직관적인 prefix를 붙여 암시한다.
  * 함수의 사이드 이펙트가 있는 부분과 없는 부분으로 나누어 관리한다.
    * 명령과 조회를 분리하는 CQRS 방식이 존재한다.
      * 명령(Command) : 사이드이펙트가 존재
      * 조회(Read) : 사이드 이펙트가 없다
    * 하나의 함수에 명령과 조회 모두가 실행된다면, 사이드이펙트 덩어리가 된다. 이를 함수나 클래스로 영역을 나누어 분리시킨다.
  * 순수함수 형태로 사용하여 직관적으로 표현하고, 에러를 방지한다.

    * update 함수를 남발하지 않는다.

    ```swift
    // as-is
    var carts = [Cart]()

    // 사이드이펙트 발생
    func addCart(product: Cart) {
        casts.append(product)        // 외부에 있는 carts의 변경
    }

    product = Product()
    addCart(product: product)
    ```

    ```swift
    // to-be
    var carts = [Cart]()

    // 사이드이펙트가 없는 순수함수
    func getAddCart(product: Cart) -> [Cart] {
        return carts + product
    }

    carts = getAddCart(product)                // carts를 재선언한다.
    ```
