[FP] 절차형 -> 선언형

yongkini ·2024년 8월 29일
0

Functional Programming

목록 보기
9/10
post-thumbnail

절차형 코드를 선언형 코드로 변환하는 과정을 연습해본다.

연습용

const names = [
  "alonzo church",
  "Haskell curry",
  "stephen_kleene",
  "John von Neumann",
  "stephen_kleene",
];

const result = []

for (let i = 0; i < names.length; i++) {
  const n = names[i];
  if (n !== undefined && n !== null) {
    const ns = n.replace(/_/, " ").split(" ");
    for (let j = 0; j < ns.length; j++) {
      let p = ns[j];
      p = p.charAt(0).toUpperCase() + p.slice(1);
      ns[j] = p;
    }
    if (result.indexOf(ns.join(" ")) < 0) {
      result.push(ns.join(" "));
    }
  }
}

console.log(result); // ['Alonzo Church', 'Haskell Curry', 'John Von Neumann', 'Stephen Kleene']

const isValid = (el) => !_.isUndefined(el) && !_.isNull(el);
const replace_toBlank = (value) => value.replace(/_/, " ");
const splitWithBlank = (value) => value.split(" ");
const reduceCallback = (fullName, partOfName, idx, arr) => {
  fullName +=
    partOfName[0].toUpperCase() +
    partOfName.substring(1) +
    (idx === arr.length - 1 ? "" : " ");

  return fullName;
};
const mapCallbackWithReduce = (data) => data.reduce(reduceCallback, "");

const resultNames = _.chain(names)
  .filter(isValid)
  .map(replace_toBlank)
  .map(splitWithBlank)
  .map(mapCallbackWithReduce)
  .uniq()
  .value();

console.log(resultNames); // ['Alonzo Church', 'Haskell Curry', 'John Von Neumann', 'Stephen Kleene']

위의 코드는 names라는 배열 내의 이름 데이터들의 규격을 맨 앞문자를(각 이름의) 대문자로 바꾸면서 surname과 firstname 사이를 ' '으로 맞추는 로직을 나타낸다.

위에 2중 for문을 사용한 부분이 절차형 프로그래밍으로 만든거고, 아래가 선언형으로 만든거다.

근데 가만보면,, 선언형이 더 길다..?!
=> 맞다 선연형 프로그래밍 코드의 양이 더많다. 하지만, 여기서 포인트는 replace_toBlank , splitWithBlank 이 함수를 나중에 재사용한다는 가정하에 만들었다고 생각해보면, 더 효율적일 수 있다는 점이다. 물론 재사용 안할 수 있고, 그에 따라 명령형이 더 낫다고도 할 수 있다. 하지만, 개인적으로 가독성 측면이나 후에 내가 이 코드를 다시 봤을 때 무슨 로직인가를 파악하기에 함수형 프로그래밍이 추상화가 잘돼 있어서 굳이 명령형처럼 안에 로직을 하나하나 다시 보지 않고서도 뭘하는 로직인지를 알 수 있을 것 같다.

프로젝트 내의 코드

        for (const lang of Object.keys(addForm.answerData)) {
          const typeLang = lang as LangType;
          const mappedArr = [];
          if (addForm.answerFormats[typeLang] === 'json') {
            try {
              if (addForm.answerJson[typeLang].value.length === 0) {
                result[typeLang] = [];
              } else {
                result[typeLang] = JSON.parse(addForm.answerJson[typeLang].value);
              }
            } catch {
              throw Error('422');
            }
          } else {
            for (const el of addForm.answerData[typeLang]) {
              if ((el.type === 'text' || el.type === 'html') && isValid(el.text)) {
                mappedArr.push({
                  type: el.type,
                  text: el.text.trim(),
                });
              } else if (el.type === 'reply' && isValid(el?.replies)) {
                mappedArr.push({
                  type: 'reply',
                  replies: el.replies.map((reply) => reply.trim()),
                });
              } else if (el.type === 'button' && isValid(el?.buttons)) {
                mappedArr.push({
                  type: el.type,
                  buttons: [
                    {
                      title: returnStringOrEmptyString(el.buttons.title.value).trim(),
                      url: returnStringOrEmptyString(el.buttons.main.value).trim(),
                      action:
                        el.buttons.type[0] === 'custom'
                          ? el.buttons.action.value.trim()
                          : faqJSON.addForm.actionButtonTypeMap[el.buttons.type[0]],
                      id: returnStringOrEmptyString(el?.buttons?.id?.value).trim(),
                      file_name: returnStringOrEmptyString(el.buttons.fileName).trim(),
                    },
                  ],
                });
              } else if (el.type === 'image' && isValid(el?.images)) {
                mappedArr.push({
                  type: el.type,
                  images: el.images.map((image) => image.src),
                });
              }
            }
            result[typeLang] = mappedArr;
          }
        }

위와 같은 코드를 아래와 같이 바꿨다. 완벽하게 선언형으로 컨버팅하진 못했지만, 그래도 훨씬 깔끔하고, 가독성 있는 코드로 바꿀 수 있었다.

        Object.keys(addForm.answerData).forEach((langType) => {
          const typeLang = langType as LangType;
          if (addForm.answerFormats[typeLang] === 'json') {
            try {
              if (addForm.answerJson[typeLang].value.length === 0) {
                result[typeLang] = [];
              } else {
                result[typeLang] = JSON.parse(addForm.answerJson[typeLang].value);
              }
            } catch {
              throw Error('422');
            }
          } else {
            result[typeLang] = _.chain(addForm.answerData[typeLang]).filter(filterAnswerDataIsValid).map(mapAnswerDataWithType).value();
          }
        });

앞으로 최대한 이렇게 선언형 프로그램이 방식으로 사고하는 버릇을 들이자.

profile
완벽함 보다는 최선의 결과를 위해 끊임없이 노력하는 개발자

0개의 댓글