카피-온-라이트

hojoon·2023년 11월 20일
0
post-thumbnail

카피-온-라이트 방식과 안전지대 읽기와 쓰기 동작을 구분하는 법을 배웠다.

이야기 해보고 싶거나 느꼈던 점

모던 자바스크립트 딥 다이브라는 책에서

  • 자바스크립트에 배열이라는 타입은 존재하지 않는다.
  • 배열은 객체지만 일반 객체와 구별되는 독특한 특징이 있다.
    • 값의 순서와 length 프로퍼티를 가진다. (어떻게 보면 문자열도 배열이다(유사배열 객체))
  • 일반적으로 자료구조에서 말하는 배열은 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 자료구조를 말하는데, 자바스크립트의 배열은 일반적인 의미의 배열과 다르다.
    • 각각의 메모리 공간은 동일한 크기를 갖지 않아도 되며, 연속적으로 이어져 있지 않을 수도 있다.(희소배열 )

-> 자바스크립트 배열은 일반적인 배열의 동작을 흉내 낸 특수한 객체이다.

희소 배열

const 희소배열 = [ ,2, , 4];
f
const a = "string";

console.log(a.length); // 6

console.log(a[1]); // t

공부하다 보니 머리가 뒤죽박죽이 된 거 같음

  • 분명 저번시간에는 액션, 계산, 데이터로 구분해서 함수를 쪼갠다고 들었는데 이번 시간에는 카피온라이트에서 읽기와 쓰기로 구분하게 되면서 arraySet 함수를 만들때 계산이 일어나는 함수를 따로 뺄 수 있을거 같아서 이렇게 만들어봤는데 다른 분들의 생각은?

// 질문 쓰면서 나도 잘 정리가 안되는거 같지만

// 이 코드는 함수형 코딩 취지에 맞는 함수일까?

const set = (array, idx, value) => {
  array[idx] = value;
};

const arraySet = (array, idx, value) => {
  const newArray = [...array];
  set(newArray, idx, value);
  return newArray;
};

function bookAnswer(array, idx, value) {
  var copy = array.slice();
  copy[idx] = value;
  return copy;
}
```

Delete object

아직도 기초가 많이 부족하구나를 느꼈다. delete는 오브젝트에서 성공적으로 속성을 제거하면 true를 반환한다. 이걸 몰라서 조금 당황했다.ㅋ

const objectDelete = (object, key) => {
  //  const newObject = delete { ...object }[key];
  // 이러면 안되는 이유가 있는데 delete는 속성을 성공적으로 제거하면 true를 반환한다. 그래서 newObject가 true값이 담긴다.
  //
  const newObject = { ...object };
  // 이 delete 부분을 계산으로 뺄수는 없나?
  //delete newObject[key];
  deleteFunc(newObject, key);
  return newObject;
};
const deleteFunc = (object, key) => {
  delete object[key];
};

console.log(objectDelete({ a: 5 }, "a"));

카피 온 라이트

장바구니와 제품에 대한 동작 리스트

  • 장바구니

    • 제품 개수 가져오기 (읽기)
    • 제품 이름으로 제품 가져오기 (읽기)
    • 제품 추가하기 (쓰기)
    • 제품 이름으로 제품 빼기 (쓰기)
    • 제품 이름으로 제품 구매 수량 바꾸기 (쓰기)
  • 제품

    • 가격 설정하기
    • 가격 가져오기
    • 이름 가져오기

모든 동작을 불변형으로 만든다? (p 100)

동작을 읽기와 쓰기로 구분하기

  • 읽기는 데이터를 바꾸지 않고 정보를 꺼낸다
  • 쓰기는 어떻게든 데이터를 바꾼다.

읽기 동작을 하면서 동시에 쓰는 동작도 가능하다.

  • shift 메서드
var a = [1,2,3,4];
var b = a.shift();
console.log(a)
console.log(b)

a는 2,3,4 b는 1

이걸 읽기와 쓰기로 분리할 수 있을까?

shift를 함수로 만들기

  • shift 는 단순히 배열의 첫번째 값을 리턴한다.
const shiftFunc = (array) => array[0]
const writeShiftFunc = (array) => {
  array.shift();
}

카피 온 라이트

const drop_first = (array) => {
 array.shift();
}
const copy_drop_first = (array) =>{
  const newArray = [...array][0];
  return newArray;
}

카피-온-라이트의 동작 세 단계

  • 복사본 만들기
  • 복사본 변경하기
  • 복사본 리턴하기
function add_element_last (array, elem){
  var new_array = array.slice();
  new_array.push(elem);
  return new_array;
}

읽기 이다. 데이터를 바꾸지 않았고 정보를 리턴했기 때문이다.

const remove_item_by_name = (cart,name) =>{
  //생략
}

remove_item_by_name 함수

이 함수는 인자로 받는 cart를 변경한다.

  1. 복사하기
var new_cart = cart.slice();
  1. 원래 cart -> 복사본 new_cart로 바꿔서 사용하기

    for문에 있는 cart -> new_cart

  2. 복사본 리턴하기

별거 없다 복사한 거 리턴만 해주면 됨.

return new_cart;

함수를 사용하고 있는 곳에서 전역변수에 할당만 해주면 됨, 상황에 맞게 고쳐주기만 하면 된다.

읽기와 쓰기를 분리하는 접근 방법은 분리된 함수를 따로 쓸 수 있기 때문이다.

shift의 경우 원래는 무조건 함께 쓸 수밖에 없었지만 이제 선택해서 쓸 수 있다.

값을 두개 리턴하는 함수 만들어보기

shift를 감싸서 함수로 만들기

const shiftFunc = (array) => {
  const newArray = [...array];
  return {
    first : newArray[0],
    array : newArray,
  }
}
const shiftFunc = (array) => {
  return {
    first: first_element(array),
    array: drop_first(array)
  }
}

불변 데이터 구조를 읽는 것은 계산이다.

  • 변경 가능한 데이터를 읽는 것은 액션
  • 쓰기는 데이터를 변경 가능한 구조로 만든다.
  • 어떤 데이터에 쓰기가 없다면 데이터는 변경 불가능한 데이터이다.
  • 불변 데이터 구조를 읽는 것은 계산이다.
  • 쓰기를 읽기로 바꾸면 코드에 계산이 많아진다.

CHAPTER 7

  • 데이터를 변경하는 코드를 함께 사용하면서 불변성을 지키는 방법에 대해 !
  • 잘 동작되고 있는 서비스를 유지하기 위해서는 ?

안전지대

  • 카피온라이트로 만든 코드는 안전지대에 있다.
  1. 깊은 복사로 신뢰할 수 없는 코드안에 있는, 변경 가능한 데이터를 복사한다.
  2. 들어오고 나가는 데이터의 복사본을 만든다.
  3. 전후에 복사한다.
copyItem = deepCopy(item)
추가되는 기능 함수(copyItem)
참조하는 아이템 = deepCopy(copyItem)

규칙

  1. 데이터가 안전한 코드에서 나갈 때 복사하기
  • 복사하고, 신뢰할 수 없는 코드에 복사본을 전달하기(깊은복사)
  1. 안전한 코드로 데이터가 들어올 때 복사하기
  • 깊은 복사본을 만들어 안전한 코드에서 사용하기

카피 온 라이트 VS 방어적 복사

카피 온 라이트

  • 통제할 수 있는 데이터를 바꿀 때 사용
  • 안전지대 어디서나 쓸 수 있다.
  • 얕은 복사

방어적 복사

  • 신뢰할 수 없는 코드와 데이터를 주고받아야 할 때 (레거시 코드)
  • 안전지대의 경계에서 데이터가 오고 갈 때 방어적 복사를 쓴다.
  • 깊은 복사

예제 풀이

역시 JS가 젤 재밌고 문제푸는거도 재밌었다.

var mailing_list = [];

function add_contact(email) {
  mailing_list.push(email);
}

function submit_form_handler(event) {
  var form = event.target;
  var email = form.elements["email"].value;
  add_contact(email);
}

// 카피온라이트
const copy_add_contact = (mailing_list, email) => {
  const newList = [...mailing_list];

  return newList.push(email);
};

const copy_submit_form_handler = (event) => {
  const form = event.target;
  const email = form.elements["email"].value;
  mailing_list = copy_add_contact(email);
};

// var a = [1,2,3,4];
// var b = a.shift();
// console.log(a)
// console.log(b)

// pop 풀기 p.125

// 쓰기

const write_popFunc = (array) => {
  return array[array.length - 1];
};

// 읽기

const read_popFunc = (array) => {
  array.pop();
};

// 카피 온 라이트

const copyPop = (array) => {
  const newArray = [...array][array.length - 1];
  return newArray;
};

// 값 두개를 리턴하는 함수로 만들기

const 값두개함수 = (array) => {
  const newArray = [...array];
  newArray.pop();
  return {
    last: newArray[newArray.length - 1],
    popArray: newArray,
    originArray: array,
  };
};

const test = (array) => {
  var copy = array.slice();
  var first = copy.pop();
  return {
    first: first,
    array: copy,
  };
};

console.log("1!!!!", test([1, 2, 3, 4]));
console.log("예제 풀이", 값두개함수([1, 2, 3, 4]));

// p.128 ~ 129

//push

const push = (array, elem) => {
  const newArray = [...array];
  return newArray.push(elem);
};

// add_contact

const add_contact2 = (mailing_list, email) => push(mailing_list, email);

// arraySet()

// a[15] = 2;

const set = (array, idx, value) => {
  array[idx] = value;
};

const arraySet = (array, idx, value) => {
  const newArray = [...array];
  set(newArray, idx, value);
  return newArray;
};

function bookAnswer(array, idx, value) {
  var copy = array.slice();
  copy[idx] = value;
  return copy;
}

console.log(arraySet([1, 2], 1, 5));

// 136
// object set

const objectSet = (object, key, value) => {
  const newObject = { ...object };
  newObject[key] = value;
  return newObject;
};

// console.log(objectSet({ name: "hojoon" }, "name", "change"));

const setPrice = (item, newPrice) => {
  var itemCopy = Object.assign({}, item);
  itemCopy.price = newPrice;
  return itemCopy;
};

const A_setPrice = (item, newPrice) => {
  return objectSet(item, "price", newPrice);
};

// 138
const setQuantity = (item, new_quantity) => {
  return objectSet(item, "quantity", new_quantity);
};

// 139

const objectDelete = (object, key) => {
  //  const newObject = delete { ...object }[key];
  // 이러면 안되는 이유가 있는데 delete는 속성을 성공적으로 제거하면 true를 반환한다. 그래서 newObject가 true값이 담긴다.
  //
  const newObject = { ...object };
  // 이 delete 부분을 계산으로 뺄수는 없나?
  //delete newObject[key];
  deleteFunc(newObject, key);
  return newObject;
};
const deleteFunc = (object, key) => {
  delete object[key];
};

console.log(objectDelete({ a: 5 }, "a"));

// p. 143 동그라미 치기
const answer = "두 개 ";

// p. 144

const setQuantityByName = (cart, name, quantity) => {
  for (var i = 0; i < cart.length; i++) {
    if (cart[i].name === name) {
      return cart[i].quantity === quantity;
    }
  }
};

const testArr = [
  { name: "shoes", quantity: 20 },
  { name: "hojoon", quantity: 30 },
  { name: "qwer", quantity: 40 },
];

const A_setQuantityByName = (cart, name, quantity) => {
  // const newCart = [...cart].map((item) => {
  //   if (item.name === name) item.quantity = quantity;
  //   return item;
  // });
  // return newCart;
  const newCart = [...cart].map((item) => {
    if (item.name === name) {
      return { ...item, quantity };
    }
    return { ...item };
  });
  console.log(newCart);
  return newCart;
};
console.log(testArr);
A_setQuantityByName(testArr, "hojoon", 50);
// console.log(A_setQuantityByName(testArr, "hojoon", 50));

// p.154 급여 계산

const payrollCalc = (employess) => {
  return payrollchekcs;
};

const payrollCalcSafe = (employess) => {
  const newEmployess = deepCopy(employess); // 복사
  const payrollchekcs = payrollCalc(newEmployess); // 추가되는 기능에 복사본 전달
  return deepCopy(payrollchekcs); // 나갈때 다시 복사
};

// p.155

userChanges.subscribe(function (user) {
  const newUser = deepCopy(user);

  processUser(newUser);
});

// p. 161

const 일번답 = "DC";
const 이번답 = "SC";
const 삼번답 = "SC";
const 사번답 = "DC";
const 오번답 = "DC";

// p. 163
const DC = "DC";
const CW = "CW";
const p_163_answer = {
  1: DC,
  2: CW,
  3: DC,
  CW,
  4: CW,
  5: CW,
  6: DC,
  7: DC,
  8: CW,
  9: DC,
  10: DC,
};

const P_164_1 = "방어적 복사를 쓰는게 맞는듯";
const p_164_2 = "안전지대가 아닐 수도 있다.그래서 방어적 복사 써야할듯";
const p_164_3 = "없으면 그냥 좀 쓰자 ";
const P_164_4 = "맘대로";
const p_164_5 = "팀을 어떻게 믿나요";

마무리

함수형 코딩 2주차 끝났다. 배워가는 것도 있고 함수를 어떻게 만들고 어떻게 설계해서 유연하고 안전한 코딩을 할 수 있을까에 대한 고민이 있었는데 해소가 되는거 같다. 프로젝트를 진행할 때 열심히 코드짜고 그러다보면 항상 되던게 안되고 고치면 또 뭐가 안되고 맨날 그랬는데 그런 문제를 예방하기 위해서는 액션, 계산에 대한 분리 또는 읽기와 쓰기 동작에 대한 구별, 안전지대에 대한 추상화, 방어적 복사, 카피-온-라이트와 같은 개념들을 적용하고 익숙해진다면 더 좋지 않을까?? 곧 시작하는 사이드 프로젝트에서는 꼭 적용해봐야겠다.

profile
프론트? 백? 초보 개발자의 기록 공간

0개의 댓글