2019년 3월 5일 화요일

[javascript] 그래서 커리를 어떻게 쓰려고?

내가 처음 커리를 써야겠다고 생각했던 것은 로직이 계속 겹치고 있었기 때문이다.

예를들어보자. 인풋별로 유효성을 체크하는데 유효성을 체크할 때마다 하는일이 조금씩 다르다.

// 핸드폰 인풋 묶음 3개 유효성검사
var phone_1 = trim($("#phone_1").val());
var phone_2 = trim($("#phone_2").val());
var phone_3 = trim($("#phone_3").val());

if (phone_1 < 3 && phone_1 ...) { // 여러 and문과 유효성검사
  $("#phone_1").focus();
  alert("휴대폰 유효성검사실패");
  return;
}
... phone_2, phone_3

$("#hidden_phone_").val(phone_1 + "-" phone_2 + "-" + phone_3);
이런 코드가 있다. 익숙하다. 일단 커리를 쓰기 전에 여기서 if문에 있는 모든 유효성검사는 하나의 함수로 추상화 해놓자.
if (validate(phone_1) { // 여러 and문과 유효성검사
  $("#phone_1").focus();
  alert("휴대폰 유효성검사실패");
  return;
}
if (validate(phone_2) {
  $("#phone_2").focus();
  alert("휴대폰 유효성검사실패");
  return;
}
if (validate(phone_3) {
  $("#phone_3").focus();
  alert("휴대폰 유효성검사실패");
  return;
}
이런 코드를 본 적 없는가? 여기서 분명 우리는 뭔가 더 할 수 있을 것 같다. 어떻게 해야 할까? 맞다 객체를 넘기는 것이다.
function validateAndFocus(phone, $phone) {
  if (!validatePhone(phone) {
    alert("휴대폰 유효성검사 실패");  
    $phone.focus();
    return false;
  }
  return true;
}
이제 이걸 사용해보자
if(!validateAndFocus(phone_1, $("#phone_1")) {
  return;
}
if(!validateAndFocus(phone_2, $("#phone_2")) {
  return;
}
if(!validateAndFocus(phone_3, $("#phone_3")) {
  return;
}
...
뭔가 여기도 겹치는 것 같다.
if (!(validateAndFocus(phone_1, $("#phone_1")) &&
      validateAndFocus(phone_2, $("#phone_2")) &&
      validateAndFocus(phone_3, $("#phone_3"))) {
  return;
}
undersocre.js의 and가 있다면 더 좋을 것 같다. 끝이 없을 것 같으니 여기서 넘어가자. underscore를 쓸 수 없어서 curry만 임시 사용하는 것이다. 이정도만 깔끔해보인다. 그런데 여기 주민번호와 카드번드까지 추가되었다.
var card1 = $("#card1");
var card2 = $("#card2");
var card3 = $("#card3");
var card4 = $("#card4");

var national_id_1 = $("#national_id");
var national_id_2 = $("#national_id");

우리는 validateAndFocus를 사용할 수 있는가? 사용할 수 없다. 1. 휴대폰번호의 유효성과 아이디의 유효성은 다르다. (predicate의 분리 필요) 2. 유효성 검사 이후 하는 일이 각각 다를 것이다. 하지만 형태는 동일하다. 1. 주어진 값의 유효성을 검사한다. 2. 통과하면 true 실패하면 false를 리턴한다. 3. 유효성검사에 실패하여 false를 리턴하기 전에 각각 해야하는 일이 있다.
// 동일한 형태의 일을 하는 함수 템플릿 정의
function validateTemplate(pred, func, obj1, obj2, obj3) {
  if (pred(obj1, obj2, obj3)) {
    func(obj1, obj2, obj3);
    return false;
  }
  return true;
}
자 이 형태를 만들었다. 이제 어떻게 사용할지 보자. 유효성 검사를 분리해서 넣을 것이다. 그렇다면 이렇게 될 것이다.
var curried = curry(validateTemplate);
var validatePhoneCurried = curried(function(n) {
  return validatePhone(n);
});
var validateCardCurried = curreid(function(n) {
  return validatePhone(n);
});
var validateNationalidCurreid = curried(function(n) {
  return validateNationalid(n);
});
뭐 대충 이렇게 코딩했다. 현재까지는 pred를 커리하고 이제는 각 pred마다 할일을 다르게 넣을 수 있겠다. 첫번째 인자를 _로 한 이유는 focus나 val("") 등 DOM조작을 위한 객체를 따로 넘긴다고 하자.
var validatePhoneFinal = validatePhoneCurried(function(_, $dom) {
  alert("헨드폰문제!");
  $dom.focus();
});

var validateCardFinal = validateCardCurried(function(_, $dom) {
  alert("카드문제");
  $dom.focus();
});

var validateNationalidFinal = validateNationalidCurreid (function(_, $dom) {
  alert("주민번호문제");
  $dom.focus();
});
이렇게 만들었다 치자. 이제 위에 코드를 저것들로 바꾸면 된다.
if (!(validatePhoneFinal(phone_1, $("#phone_1")) &&
      validatePhoneFinal(phone_2, $("#phone_2")) &&
      validatePhoneFinal(phone_3, $("#phone_3"))) {
  return;
}

....
if (!(validateCardFinal(card_1, $("#card_1")) &&
      validateCardFinal(card_2, $("#card_2")) &&
      validateCardFinal(card_3, $("#card_3")) &&
      validateCardFinal(card_4, $("#card_4"))) {
  return;
}
...
if (!(validateNationalidFinal(national_id_1 , $("#national_id_1")) &&
      validateNationalidFinal(national_id_2 , $("#national_id_2"))) {
  return;
}
여기서 map이나 every 같은 것을 이용하는 것이 큰 도움이 될 것 같지만 그러진 않겠다. 오늘의 주제는 커리니까.
curry라는 함수 한번 가지고 놀아봤다.

댓글 1개: