Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

item50 적시에 방어적 복사본을 만들라 #105

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions 8장_메서드/item50.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# item 50. 적시에 방어적 복사본을 만들라

해당 아이템에서는 되도록 가변 타입을 사용하지 말고, 혹시나 가변 타입을 사용하는 경우 어떻게 해야 불변식을 보장할 수 있는지를 설명하고 있습니다.

외부에서 악의적인 의도나 혹은 단순한 프로그래머의 실수로 우리가 만든 클래스를 오작동하게 만들 수 있기 때문에 방어적으로 프로그래밍해야 한다고 설명합니다.

아래의 클래스는 기간을 표현하는 클래스입니다.

```swift
final class Period {
private var start: Date
private var end: Date
Comment on lines +11 to +12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private var start: Date
private var end: Date
private let start: Date
private let end: Date

=> 사소한건데 자바(final)처럼 let 으로 변경 부탁드립니다.


init(start: Date, end: Date) throws {
if start > end {
throw NSError(domain: "\(start)가 \(end)보다 늦다.", code: 999)
}

self.start = start
self.end = end
}

public func getStart() -> Date {
return self.start
}

public func getEnd() -> Date {
return self.end
}

//...
}

var start = Date()
var end = Date()

let period = try? Period(start: start, end: end)

// period class의 end에 영향이 갈까요?
end.addTimeInterval(1000)
```

Java에서는 Date가 가변 타입이므로 외부의 `start` 혹은 `end` 변수를 수정하게 될 경우 `period` 내부의 `start`, `end` 값 또한 변경됩니다.

하지만 Swift에서는 이러한 부작용이 적용되지 않습니다. Swift의 `값 타입(Value Type)`은 기본적으로 깊은 복사를 지원하고 Date가 `값 타입`인 struct(구조체)로 구성되어 있기 때문입니다.

Comment on lines +43 to +46
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와우 역시 Swift가 대단하네요. Struct라는 다른 수단이 있으니 말이죠 😎

<br>

하지만 class같은 `참조 타입(Reference Type)`인 경우 얕은 복사에 대한 부작용을 걱정해야 합니다.

```swift
class Person {
var isHungry: Bool = true
}

let lin = Person()
let anotherPerson = lin

print(anotherPerson.isHungry) //true
Comment on lines +52 to +59
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class Person {
var isHungry: Bool = true
}
let lin = Person()
let anotherPerson = lin
print(anotherPerson.isHungry) //true
class Person {
var isHungry: Bool = true
}
let lin = Person()
let anotherPerson = lin
lin.isHungry = false
print(lin.isHungry) //false
print(anotherPerson.isHungry) //false

=> 이렇게 내용 추가하면 참조타입의 얕은 복사에 대해 더 설명될 것 같습니다. 👍 아래 코드 보고 추가해봤어요.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 이렇게 변경이 되어야 할 것 같아요 ㅎㅎ

```

<br>

이 책에서는 방어적 복사본을 만들어 반환하거나, 유효성을 검사하기전에 방어적 복사본을 만들어 해당 복사본으로 유효성 검사를 진행하라고 합니다.

그럼 class와 같은 참조 타입은 어떤식으로 방어적 복사본을 만들 수 있을까요?

```swift
class Person: NSCopying {
func copy(with zone: NSZone? = nil) -> Any {
let copy = Person()
copy.isHungry = self.isHungry

return copy
}
}

let lin = Person()
let anotherPerson = lin.copy()
lin.isHungry = false

print(lin.isHungry) // false
print(anotherPerson.isHungry) // true
Comment on lines +69 to +83
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class Person: NSCopying {
func copy(with zone: NSZone? = nil) -> Any {
let copy = Person()
copy.isHungry = self.isHungry
return copy
}
}
let lin = Person()
let anotherPerson = lin.copy()
lin.isHungry = false
print(lin.isHungry) // false
print(anotherPerson.isHungry) // true
class Person: NSCopying {
var isHungry: Bool = true
func copy(with zone: NSZone? = nil) -> Any {
let copy = Person()
copy.isHungry = self.isHungry
return copy
}
}
let lin = Person()
let anotherPerson: Person = lin.copy() as! Person
lin.isHungry = false
print(lin.isHungry) // false
print(anotherPerson.isHungry) // true

해당 코드 컴파일 에러나서 (because of isHungry, Any) 에러안나도록 코드 수정하였습니다.

```
class의 경우 `NSCopying` protocol을 채택하고 `copy` 메소드를 구현해 내부에서 새 객체를 만들어 반환하는 방법이 있습니다.

이렇게 불변식을 구성하는 것도 좋은 방법이지만, 성능 저하가 일어날 수 있고 메서드나 생성자의 매개변수로 넘기는 행위가 그 객체의 통제권을 명백히 이전함을 뜻하기도 하기 때문에 항상 방어적으로 복사해 저장해야 하는 것은 아닙니다.

하지만 이럴 경우 방어적 복사를 수행하는 대신에 해당 객체의 내부 요소를 수정했을 때의 책임이 클라이언트에 있음을 문서에 명확히 명시하도록 해야합니다.