mutable 배열을 되돌리기 전에
16812 단어 TypeScript설계DDD대상을 향하다tech
며칠 전프라하 도전의 일환으로 "DDD 기반으로 코드를 써보자!"코드 리뷰라는 과제를 할 때 이런 코드를 봤어요.
class User {
constructor(private readonly _articleIds: string[], private readonly _name: string) {
// 何らかの不変条件
}
public get name() {
return this._name
}
public get articleIds() {
return this._articleIds
}
}
User 실례의property에 setter를 정의하지 않으면 필드 실체의 변하지 않는 조건이 보호됩니다!이런 목적이지만 배열 처리로 인한 주의점이 있어서 기사를 써보려고요TL;DR
mutable의 배열을 가볍게 되돌리면 어떤 일이 일어날까요?
이번 인코딩은'name과articleads는 외부에서 바꿀 수 없다'는 전제 조건이다.확실히, name은 변경할 수 없습니다.
const user1 = new User([], 'hoge')
user1.name = 'fuga' // これはread-only propertyなので不可
배열은 정의된 setter가 없어도 내용을 수정할 수 있습니다.const user1 = new User([], 'hoge')
user1.articleIds.push('fuga', 'piyo') // これは可
임시 변수의 정렬을 다른 코드로 잘못 변경할 수도 있습니다.const user1 = new User([], 'hoge')
const articleIds = user1.articleIds
// ...たくさんのコードが書かれている
articleIds.push('fuga') // 意図せずuser1.articleIdsも変わってしまった!
가변 배열을 되돌려주면user의property에 대해 직접적인 배열 작업(pop이나 splice 등)을 허용하기 때문에'특정 조건을 충족시킬 때만 배열 작업을 허용한다'는 역 논리를 강제할 수 없습니다.User를 만족시키는 불변의 조건에 빠지기 쉽다(예를 들어articleIds는 항상 1건 이상 5건 미만 등) 보장할 수 없는 상태// 呼び出し側からやりたい放題にされてしまい、全くロジックの整合性が取れなくなったuser1
user1.articleIds.push('1', '2' ,'3', '4', '5') // 本当はarticleIdsに5件以上登録させたくなくても、登録されてしまう
user1.articleIds.splice(1) // 勝手に中身を消されてしまう
user1.articleIds.pop() // 本当はarticleIdsに1件以上は登録しておかなければいけないのに、最後の1件も消されてしまった...
대책: 되돌아오는 그룹 복사
사용자 밖에서 직접 배열 작업을 하는 것을 어떻게 방지하는지 봅시다.
우선 수조 자체가 아니라 복제값의 새로운 배열입니다.user내의 배열을 직접 조작하지 않기 때문에 안심하십시오
class User {
constructor(private readonly _articleIds: string[], private readonly _name: string) {
// 何らかの不変条件
}
public get name() {
return this._name
}
public get articleIds() {
return [...this._articleIds] // 新しい配列を作成して返す。this._articleIds.slice(0)でも可
}
}
const user1 = new User([], 'hoge')
user1.articleIds.push('hoge') // 新しい配列にpushしているだけなのでuser1の_articleIdsは外から変えられない
호출자에게articleIds의 내용만 요구하면(업데이트가 아니라면) 복사본에 답장하면 충분하죠.업데이트된 논리는user클래스에만 정의되어 있으면, 진열이 변경되기 전에 검증될 수 있습니다class User {
.
.
.
public removeLastArticleId() {
if (this._articleIds.length > 1) { // 更新する前に必ず残数を確認する
this._articleIds.pop()
}
}
}
데이터와 변경 데이터의 처리를 의미적으로 가까운 곳으로 정의할 수 있다면 코드의 가독성이 높아지거나 배열 자체를 공개하고 호출자에게 너무 많은 자유를 공개하여 제한적인 인터페이스를 공개하는 사람에게공연과 결합된 것이지만 이 같은 작법을 의식해 볼 수도 있겠지.대책:readonly 순서대로 배열
일단 실례화된 후에 배열 내용을 바꿀 필요가 없다면articleIds의 유형
string[]
이 아니라readonly string[]
파괴적인 조작을 방지할 수 있다.class User {
// _articleIdsの型がstring[]からreadonly string[]に変わっている
constructor(private readonly _articleIds: readonly string[], private readonly _name: string) {
// 何らかの不変条件
}
public get name() {
return this._name
}
public get articleIds() {
return this._articleIds
}
}
const u = new User([], 'hoge')
u.articleIds.push('fuga', 'piyo') // read-onlyなので不可
예전에는 ReadonlyArray<string>
처럼 쓰는 것이 필요했지만 최근에는 readonly
만 추가되어 개작도 수월하다.참고로, 그룹을 수정할 수 있는 함수에readonly 진열을 전달할 수 없습니다.
const hoge: readonly string[] = ['a']
const dosomething = (_: string[]) => {} // string[]型なので、引数の配列に変更を加えうる事が型から推察される
dosomething(hoge) // なので、readonlyな配列は渡せない
매개 변수를readonly의 배열로 정의하면 팝 등 파괴적인 방법은 원래 호출할 수 없기 때문에 더욱 안심하고 배열을 사용할 수 있다.const dosomething = (a: readonly string[]) => { // readonly string[]にしてみたら
a.pop() // popが定義されていないので呼び出せない
}
mutable의 배열과immutable의 배열은 원래 다른 유형으로 정의된 언어라면 쉽게 알 수 있지만 TS가 주지 않으면readonly
기본적으로mutable이 되기 때문에 언어적으로 무방비한 배열을 사용한 것을 볼 수 있는 기회가 더 많을 것이다."여기readonly 넣으면 안 돼...?"이런 의식을 자주 가지면 나중에 편해지니까 추천해드릴게요.총결산
인스턴스 속성을 그대로 되돌리기 전에 먼저 "복제하면 해결되는 거 아닌가요?""immutable(변하지 않음) 못해요?"그렇게 생각하면 좋은 일이 생길 수도 있어요.
Reference
이 문제에 관하여(mutable 배열을 되돌리기 전에), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/dowanna6/articles/674c923b26ecd9텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)