객체지향프로그래밍 / 선언형 프로그래밍

김진주·2023년 8월 7일
0

문제해결

목록 보기
3/3

의문점

명령형 프로그래밍에서 객체지향 프로그래밍 방법론은 유지보수와 코드 재사용성이 용이하다고 배운적이 있습니다! 그래서 수업 중 명령형 프로그래밍의 단점에서 코드의 재사용성과 유지보수성이 떨어진다는 부분이 잘 이해가 되지 않습니다.

선언형 프로그래밍이 객체지향 프로그래밍보다 유지보수성과 재사용성이 더 좋다고 보면 될까요?


답변

오늘 수업에서 다룬 것은 명령형 프로그래밍 코드 구문과 선언형 프로그래밍 코드 구문 비교입니다. 질문에서 처럼 객체 지향 프로그래밍 코드와 비교하지는 않았습니다. 객체 지향 프로그래밍 코드는 함수를 사용해 작성하는 프로그래밍과 비교하는 중이었죠. 내일 수업에서 이어집니다.

그럼 아래 작성된 명령형 vs. 선언형 코드 구문을 먼저 살펴본 후, 아래 작성된 글을 참고하세요. 😃

  • 명령형 프로그래밍 코드 함수 정의 및 호출 과정 없이 작성된 명령형 코드 (원본 변형함)
    const courses = [
      { 
        id: 1,
    		name: ' imperative programming'
    	},
      { 
        id: 2,
    		name: 'declarative programming '
    	},
    ];
    
    // 과정 배열을 순환하여 각 과정 이름의 좌우 공백 제거
    for (let i=0, l=courses.length; i < l; ++i) {
      courses[i].name = courses[i].name.trim();
    }
    
    // 과정 배열을 순환하여 각 과정 이름 대문자화
    for (let i=0, l=courses.length; i < l; ++i) {
      courses[i].name = courses[i].name.toUpperCase();
    }
    
    // 과정 배열을 순환하여 각 과정 이름의 공백 밑줄 변경
    for (let i=0, l=courses.length; i < l; ++i) {
      courses[i].name = courses[i].name.replace(/\s+/g, '_');
    }
  • 선언형 프로그래밍 코드 각 기능 별 함수 선언 후, 배열의 map 메서드를 사용해 함수 조립(구성)한 선언형 코드 (원본 변형 안함)
    const subjects = [
      { 
        id: 1,
    		name: ' imperative programming'
    	},
      { 
        id: 2,
    		name: 'declarative programming '
    	},
    ];
    
    // 객체 이름(name) 속성 좌우 공백 제거 기능
    function toTrim(object) {
      const o = { ...object };
      o.name = o.name.trim();
      return o;
    }
    
    // 객체 이름(name) 속성 대문자화 기능
    function toUpperCase(object) {
      const o = { ...object };
      o.name = o.name.toUpperCase();
      return o;
    }
    
    // 객체 이름(name) 속성 밑줄 변경 기능
    function toUnderscore(object) {
      const o = { ...object };
      o.name = o.name.replace(/\s+/g, '_');
      return o;
    }
    
    // 함수 조합
    **subjects
    	.map(toTrim)
    	.map(toUpperCase)
    	.map(toUnderscore);**

자! 이제 유지보수 관점에서 생각해봅시다. 다음의 수정사항이 주어졌습니다.

  1. 먼저 이름을 대문자로 변경합니다.
  2. 그리고 이름에 포함된 공백을 모두 밑줄로 변경합니다.
  3. 좌우 공백이 제거되도록 코드를 수정합니다.
  • 명령형 프로그래밍 코드 명령형 구문을 통째로 잘라내서 붙여넣든 하는 방식으로 코드를 옮겨야 유지보수가 되요.
    // 원래 순서: 2
    for (let i=0, l=courses.length; i < l; ++i) {
      courses[i].name = courses[i].name.toUpperCase();
    }
    
    // 원래 순서: 3
    for (let i=0, l=courses.length; i < l; ++i) {
      courses[i].name = courses[i].name.replace(/\s+/g, '_');
    }
    
    // 원래 순서: 1
    for (let i=0, l=courses.length; i < l; ++i) {
      courses[i].name = courses[i].name.trim();
    }

같은 내용의 유지보수를 선언형 프로그래밍에서 진행해봅니다.

  1. 먼저 이름을 대문자로 변경합니다.
  2. 그리고 이름에 포함된 공백을 모두 밑줄로 변경합니다.
  3. 좌우 공백이 제거되도록 코드를 수정합니다.
  • 선언형 프로그래밍 코드 선언된 함수 조립 방식으로 설계된 코드이므로 수정 사항을 코드 라인의 순서만 바꾸면 됩니다.
    // 함수 조립(구성) 순서만 변경하면 됨
    subjects
    	.map(toUpperCase) // 원래 순서: 2
    	.map(toUnderscore) // 원래 순서: 3
    	.map(toTrim); // 원래 순서: 1
    위 코드는 추후에 함수형 프로그래밍 방식에 따라 아래처럼 사용됩니다.
    // 함수 조립(구성)
    pipe(
    	toUpperCase,
    	toUnderscore,
    	toTrim
    )(subjects);
    subjects 말고도 다양한 객체에 코드를 재사용하기 용이합니다.
    pipe(
    	toUpperCase,
    	toUnderscore,
    	toTrim
    )(**courses**);
    선언된 데이터에 기능을 추가하거나, 삭제하는 것도 쉽습니다.
    pipe(
    	**filterStocked(10), // 재고가 10개 이상인 제품만 걸러냄
    	filterMoreExpensive(50_000), // 5만원 이상의 제품만 걸러냄
    	filterMadeInKorea() // 국산 제품만 걸러냄**
    )(**products**);

명령형도 함수로 분리할 수 있지 않나 의문이 들 수도 있어요. 그럼 아래와 같이 작성되겠죠. 역시 문으로 구성되어 있다 보니 유지보수가 용이하지는 않아 보여요.

for (let i=0, l=courses.length; i < l; ++i) {
  **toUpperCase(courses[i]);**
}

for (let i=0, l=courses.length; i < l; ++i) {
  **toUnderscore(courses[i]);**
}

for (let i=0, l=courses.length; i < l; ++i) {
  **toTrim(courses[i]);**
}

그럼? 반복문을 3번 돌리지 않고 하나로 묶는 생각도 할 수 있을 거에요. 아래 처럼 말이죠. 뭐 이러면 유지 보수가 나아보일 수 있지만, 재사용은 어렵죠.

**for (let i=0, l=courses.length; i < l; ++i) {**
  const course = courses[i];
  toUpperCase(course);
	toUnderscore(course);
	toTrim(course);
**}**

그럼 더 나아가 재사용을 목적으로 함수로 만들 생각을 할 수도 있을 거에요.

**function toUpperCaseAndUpserscoreAndTrim(courses) {**
  for (let i=0, l=courses.length; i < l; ++i) {
	  ****toUpperCase(courses[i]);
		toUnderscore(courses[i]);
		toTrim(courses[i]);
	}
**}**

그런데 함수로 선언해서 재사용은 가능하지만, 순서가 항상 정해져 있잖아요. 수정이 또 발생하면 함수는 재사용이 어려워져요. 1회성으로 끝나고 버려지게 되죠. 결국은 다시 아래 코드로 돌아가야 합니다.

for (let i=0, l=courses.length; i < l; ++i) {
  const course = courses[i];
  toUpperCase(courses[i]);
	toUnderscore(courses[i]);
	toTrim(courses[i]);
}

정리

어쩌면 작성된 글만으로는 여전히 의문이 남을지 몰라요. 😅

수업에서도 말했던 바를 다시 한 번 말씀드리지만, 절대 “함수형이 명령형보다 낫다” 이런 말이 아니에요. 프로그래밍 하는 방법이 다를 뿐. 패러다임은 다양합니다.

지하철을 타거나, 버스를 타거나, 자전거를 타는 것 중 “어떤게 낫다 아니다” 그런 문제가 아니라 상황에 따라 최선의 방법은 달라질 수 있어요. 어떤 경우는 지하철이 다른 경우는 버스가 나을 수 있는 거죠.

다만, 우리 수업에서 다루는 주제인 React가 채택한 프로그래밍 방식이 “함수형”이라는 겁니다.

그럼 함수형 프로그래밍에서 코드를 어떻게 관리하는가? 그 부분을 중점적으로 봐주시고, 절대적으로 함수형이 명령형 또는 객체 지향 프로그래밍 보다 낫다고 말한 것이 아니므로 오해가 없었으면 합니다. 😄


결론

객체지향 프로그래밍과 선언형 프로그래밍 중에서 유지보수성과 코드 재사용성에서 뭐가 더 나은지 비교할 순 없다. => 설계에 따라서 달라짐

profile
진주링딩동🎵

0개의 댓글