구성 소프트웨어 : 소개

스모크 아트 큐브 스모크 — MattysFlicks — (CC BY 2.0)
참고 :이 글은 JavaScript ES6 +의 기능 프로그래밍 및 컴포지션 소프트웨어 기술을 처음부터 배우는“소프트웨어 구성”시리즈 (현재 책!)의 일부입니다. 계속 지켜봐 주시기 바랍니다. 앞으로 더 많은 것들이 있습니다!
도서 구매 | 색인 | 다음>
구성 :“부분 또는 요소를 결합하여 전체를 형성하는 행위”~ Dictionary.com

저의 첫 고등학교 프로그래밍 수업에서 소프트웨어 개발은“복잡한 문제를 더 작은 문제로 세분화하고 간단한 해결책을 작성하여 복잡한 문제에 대한 완전한 해결책을 만드는 행위”라고 들었습니다.

인생에서 가장 큰 후회 중 하나는 일찍 그 교훈의 중요성을 이해하지 못했다는 것입니다. 소프트웨어 설계의 본질을 너무 늦게 배웠습니다.

수백 명의 개발자를 인터뷰했습니다. 그 세션에서 배운 것은 내가 혼자가 아니라는 것입니다. 소프트웨어 개발의 본질을 잘 이해하고있는 소프트웨어 개발자는 거의 없습니다. 그들은 우리가 가장 중요한 도구 나 도구를 잘 활용하는 방법을 알지 못합니다. 100 %는 소프트웨어 개발 분야에서 가장 중요한 질문 중 하나 또는 둘 다에 답하기 위해 노력했습니다.

  • 기능 구성이란 무엇입니까?
  • 객체 구성이란 무엇입니까?

문제는 자신이 모르는 것만으로 구성을 피할 수 없다는 것입니다. 당신은 여전히 ​​그렇습니다 – 그러나 당신은 그것을 잘못합니다. 더 많은 버그로 코드를 작성하고 다른 개발자가 이해하기 어렵게 만듭니다. 이것은 큰 문제입니다. 그 효과는 매우 비싸다. 소프트웨어를 처음부터 만드는 것보다 소프트웨어를 유지 관리하는 데 더 많은 시간을 소비하며 버그는 전 세계 수십억 명의 사람들에게 영향을 미칩니다.

전 세계는 오늘날 소프트웨어에서 실행됩니다. 모든 새 차는 바퀴가 달린 미니 슈퍼 컴퓨터이며 소프트웨어 디자인 문제로 인해 실제 사고가 발생하고 실제 인명에 대한 비용이 발생합니다. 2013 년, 배심원 단은 사고 조사 결과 10,000 개의 글로벌 변수를 가진 스파게티 코드가 밝혀진 후 Toyota의 소프트웨어 개발 팀이“무모한 무시”로 유죄를 선고 받았습니다.

해커와 정부는 사람들을 감시하고 신용 카드를 훔치고 DDoS (Distributed Denial of Service) 공격을 시작하기 위해 컴퓨팅 리소스를 활용하고 암호를 해독하며 ​​선거를 조작하기 위해 버그를 비축합니다.

우리는 더 잘해야합니다.

매일 소프트웨어를 작성

소프트웨어 개발자라면 알고 있든 모르 든 매일 함수와 데이터 구조를 작성합니다. 당신은 의식적으로 (그리고 더 나은) 그것을 할 수 있거나 실수로 덕트 테이프와 미친 접착제로 할 수 있습니다.

소프트웨어 개발 프로세스는 큰 문제를 작은 문제로 세분화하여 작은 문제를 해결하는 구성 요소를 구축 한 다음 이러한 구성 요소를 함께 구성하여 완전한 응용 프로그램을 구성합니다.

작성 기능

함수 구성은 함수를 다른 함수의 출력에 적용하는 프로세스입니다. 대수에서는 두 개의 함수, fand g, (f ∘ g) (x) = f (g (x))가 주어집니다. 원은 작성 연산자입니다. 일반적으로 "composed with"또는 "after"로 발음됩니다. "g로 구성된 f는 x의 f와 같다"또는 "g 후 f는 x의 f와 같다"라고 말할 수있다. g가 먼저 평가되므로 f 다음에 f라고합니다. 출력은 f에 인수로 전달됩니다.

이와 같은 코드를 작성할 때마다 함수를 작성합니다.

const g = n => n + 1;
const f = n => n * 2;
const doStuff = x => {
  const afterG = g (x);
  const afterF = f (afterG);
  after afterF;
};
doStuff (20); // 42

약속 체인을 작성할 때마다 함수를 작성합니다.

const g = n => n + 1;
const f = n => n * 2;
const wait = time => new Promise (
  (해결, 거부) => setTimeout (
    결의,
    시각
  )
);
기다림 (300)
  .then (() => 20)
  .then (g)
  .then (f)
  .then (값 => console.log (값)) // 42
;

마찬가지로, 배열 메소드 호출, lodash 메소드, 관찰 가능 항목 (RxJS 등)을 연결할 때마다 함수를 작성합니다. 연결하는 경우 작성 중입니다. 리턴 값을 다른 함수에 전달하면 작성중인 것입니다. 두 개의 메소드를 순서대로 호출하면이 메소드를 입력 데이터로 사용하여 작성합니다.

연결하는 경우 작성 중입니다.

의도적으로 기능을 구성하면 더 잘 수행 할 수 있습니다.

의도적으로 함수를 구성하여 doStuff () 함수를 간단한 단일 라이너로 향상시킬 수 있습니다.

const g = n => n + 1;
const f = n => n * 2;
const doStuffBetter = x => f (g (x));
doStuffBetter (20); // 42

이 형식에 대한 일반적인 반대 의견은 디버그하기가 더 어렵다는 것입니다. 예를 들어 함수 구성을 사용하여 어떻게 작성합니까?

const doStuff = x => {
  const afterG = g (x);
  console.log (`after g : $ {afterG}`);
  const afterF = f (afterG);
  console.log (`after f : $ {afterF}`);
  after afterF;
};
doStuff (20); // =>
/ *
"g 후 : 21"
"f 이후 : 42"
* /

먼저“f 이후”,“g 이후”trace ()라는 작은 유틸리티에 로그인하는 것을 추상화 해 보겠습니다.

const 추적 = 레이블 => 값 => {
  console.log (`$ {label} : $ {value}`);
  반환 값;
};

이제 다음과 같이 사용할 수 있습니다 :

const doStuff = x => {
  const afterG = g (x);
  자취 ( 'after g') (afterG);
  const afterF = f (afterG);
  자취 ( 'after f') (afterF);
  after afterF;
};
doStuff (20); // =>
/ *
"g 후 : 21"
"f 이후 : 42"
* /

Lodash 및 Ramda와 같이 널리 사용되는 기능 프로그래밍 라이브러리에는 기능 구성을보다 쉽게하는 유틸리티가 포함되어 있습니다. 위와 같은 함수를 다음과 같이 다시 작성할 수 있습니다.

'lodash / fp / flow'에서 파이프 가져 오기;
const doStuffBetter = 파이프 (
  지,
  자취 ( 'after g'),
  에프,
  자취 ( 'after f')
);
doStuffBetter (20); // =>
/ *
"g 후 : 21"
"f 이후 : 42"
* /

가져 오기없이이 코드를 사용하려면 다음과 같이 파이프를 정의 할 수 있습니다.

// 파이프 (... fns : [... 함수]) => x => y
const 파이프 = (... fns) => x => fns.reduce ((y, f) => f (y), x);

그래도 작동 방식을 따르지 않아도 걱정하지 마십시오. 나중에 함수 구성에 대해보다 자세히 살펴 보겠습니다. 실제로, 그것은 매우 중요합니다. 당신은이 본문에서 여러 번 정의되고 시연 된 것을 볼 것입니다. 요점은 정의와 사용법이 자동이되도록 익숙해 지도록 돕는 것입니다. 작곡과 하나가 되십시오.

pipe ()는 함수의 파이프 라인을 만들어 한 함수의 출력을 다른 함수의 입력으로 전달합니다. pipe () (그리고 그것의 twin, compose ())를 사용할 때 중간 변수는 필요하지 않습니다. 인수를 언급하지 않고 함수를 작성하는 것을 포인트 프리 스타일이라고합니다. 이를 위해 함수를 명시 적으로 선언하지 않고 새 함수를 반환하는 함수를 호출합니다. 즉, function 키워드 또는 화살표 구문 (=>)이 필요하지 않습니다.

포인트없는 스타일은 너무 멀리 가져갈 수 있지만, 중간 변수가 함수에 불필요한 복잡성을 추가하기 때문에 여기에 약간의 차이가 있습니다.

복잡성을 줄이면 몇 가지 이점이 있습니다.

작업 메모리

평균적인 인간 두뇌는 작업 메모리에서 이산 쿼터에 대한 공유 리소스가 거의 없으며 각 변수는 해당 쿼터 중 하나를 잠재적으로 소비합니다. 더 많은 변수를 추가하면 각 변수의 의미를 정확하게 기억하는 능력이 줄어 듭니다. 작업 메모리 모델에는 일반적으로 4-7 개의 이산 퀀텀이 포함됩니다. 이 숫자보다 오류율이 크게 증가합니다.

파이프 형식을 사용하여 3 개의 변수를 제거하여 사용 가능한 작업 메모리의 거의 절반을 다른 용도로 사용했습니다. 그것은 우리의인지 부하를 크게 줄입니다. 소프트웨어 개발자는 평범한 사람보다 작업 메모리에 데이터를 더 잘 넣는 경향이 있지만 보존의 중요성을 약화 시키지는 않습니다.

신호 대 잡음비

간결한 코드는 또한 코드의 신호대 잡음비를 향상시킵니다. 라디오를 듣는 것과 같습니다. 라디오가 방송국에 제대로 맞지 않으면 간섭 잡음이 많이 들리고 음악을 듣기가 더 어렵습니다. 올바른 방송국으로 선국하면 잡음이 사라지고 더 강한 음악 신호가 나타납니다.

코드도 마찬가지입니다. 간결한 코드 표현은 이해력을 향상시킵니다. 일부 코드는 유용한 정보를 제공하며 일부 코드는 공간을 차지합니다. 전송되는 의미를 줄이지 않고 사용하는 코드의 양을 줄일 수 있다면 코드를 읽고 다른 사람들이 이해하기 쉽게 코드를 분석 할 수 있습니다.

버그의 표면적

사전 및 사후 기능을 살펴보십시오. 기능이 다이어트를했고 체중이 줄어든 것 같습니다. 추가 코드는 버그를 숨길 수있는 추가 표면 영역을 의미하므로 더 많은 버그가 숨겨 질 수 있기 때문에 중요합니다.

적은 코드 = 버그의 적은 표면적 = 적은 버그.

객체 구성

"클래스 상속에 대한 선호 객체 구성"Gang of Four, "디자인 패턴 : 재사용 가능한 객체 지향 소프트웨어의 요소"
“컴퓨터 과학에서 복합 데이터 형식 또는 복합 데이터 형식은 프로그래밍 언어의 기본 데이터 형식 및 기타 복합 형식을 사용하여 프로그램에서 구성 할 수있는 모든 데이터 형식입니다. […] 복합 유형을 구성하는 행위는 구성으로 알려져 있습니다.”~ Wikipedia

이들은 기본 요소입니다.

const firstName = '클로드';
const lastName = 'Debussy';

그리고 이것은 합성물입니다.

const fullName = {
  이름,
  성
};

마찬가지로 모든 배열, 세트, ​​맵, 약도, 유형 배열 등은 복합 데이터 유형입니다. 기본이 아닌 데이터 구조를 만들 때마다 일종의 객체 구성을 수행합니다.

Gang of Four는 특정 유형의 재귀 객체 구성 인 복합 패턴이라고하는 패턴을 정의합니다. 개별 패턴과 집계 된 복합물을 동일하게 처리 할 수 ​​있습니다. 일부 개발자는 복합 패턴이 유일한 형태의 객체 구성이라고 생각하면서 혼란스러워합니다. 혼동하지 마십시오. 다양한 종류의 객체 구성이 있습니다.

Gang of Four는 계속해서“디자인 패턴에 객체 구성이 반복해서 적용되는 것을 볼 것입니다.”그들은 위임 (상태, 전략 및 방문자 패턴에 사용되는), 지인을 포함하여 3 가지 종류의 객체 구성 관계를 분류합니다. (객체가 참조로 다른 객체에 대해 알고있는 경우 일반적으로 매개 변수로 전달됨 : 예를 들어, 네트워크 요청 핸들러는 요청을 로그하기 위해 로거에 대한 참조를 전달할 수 있음-요청 핸들러는 로거를 사용함), 및 집계 (자식 개체가 부모 개체의 일부를 구성하는 경우 : DOM 자식은 DOM 노드의 구성 요소입니다. DOM 노드에는 자식이 있습니다).

클래스 상속은 복합 객체를 구성하는 데 사용될 수 있지만 제한적이고 취하기 쉬운 방법입니다. Gang of Four가“클래스 상속보다 선호하는 객체 구성”이라고 말하면 클래스 상속에 대한 견고하고 밀접하게 결합 된 접근 방식이 아닌 복합 객체 작성에 유연한 접근 방식을 사용하도록 조언합니다.

우리는“컴퓨터 과학의 범주 적 방법 : 토폴로지의 측면을 가진”(1989)의 객체 구성에 대한보다 일반적인 정의를 사용합니다.

"복합체는 각각의 물체가 전자의 '일부'가되도록 물체를 조합하여 형성됩니다."

1975 년 글렌 포드 J 마이어스 (Glenford J Myers)의“Composite Design을 통한 신뢰할 수있는 소프트웨어”가 좋은 참고 자료입니다. 기술적 깊이.

클래스 상속은 복합 객체 구성의 한 종류 일뿐입니다. 모든 클래스가 복합 객체를 생성하지만 모든 복합 객체가 클래스 또는 클래스 상속에 의해 생성되는 것은 아닙니다. “클래스 상속에 대한 선호 객체 구성”은 클래스 계층 구조의 상위 항목에서 모든 속성을 상속하지 않고 작은 구성 요소 부분에서 복합 객체를 형성해야 함을 의미합니다. 후자는 객체 지향 디자인에서 널리 알려진 다양한 문제를 일으 킵니다.

  • 밀접한 결합 문제 : 자식 클래스는 부모 클래스의 구현에 의존하기 때문에 클래스 상속은 객체 지향 디자인에서 사용할 수있는 가장 밀접한 결합입니다.
  • 취약한 기본 클래스 문제 : 긴밀한 연결로 인해 기본 클래스를 변경하면 잠재적으로 타사에서 관리하는 코드에서 많은 하위 클래스가 손상 될 수 있습니다. 저자는 모르는 코드를 깨뜨릴 수 있습니다.
  • 융통성없는 계층 구조 문제 : 단일 조상 분류법으로 충분한 시간과 진화가 주어지면 모든 분류 분류법은 결국 새로운 유스 케이스에 잘못됩니다.
  • 필요성 문제에 의한 복제 : 융통성이없는 계층으로 인해 새로운 유스 케이스는 종종 확장이 아닌 복제에 의해 구현되어 예기치 않은 분기가 비슷한 유사한 클래스로 이어집니다. 중복이 시작되면 어떤 클래스에서 어떤 클래스가 내려 와야하는지 명확하지 않습니다.
  • 고릴라 / 바나나 문제 :“… 객체 지향 언어의 문제점은 그들이 암시하는 모든 암시 적 환경을 가지고 있다는 것입니다. 바나나를 원했지만 바나나와 정글 전체를 들고있는 고릴라였습니다.”~ Joe Armstrong,“코더 스 직장”

JavaScript에서 가장 일반적인 형태의 객체 구성을 객체 연결 (일명 mixin 구성)이라고합니다. 아이스크림처럼 작동합니다. 바닐라 아이스크림과 같은 물체로 시작한 다음 원하는 기능을 혼합하십시오. 견과류, 카라멜, 초콜릿 소용돌이를 넣고 너티 카라멜 초콜릿 소용돌이 아이스크림으로 감아 라.

클래스 상속으로 컴포지트 작성 :

푸 클래스 {
  생성자 () {
    this.a = 'a'
  }
}
클래스 바 Foo {
  생성자 (옵션) {
    슈퍼 (옵션);
    this.b = 'b'
  }
}
const myBar = new Bar (); // {a : 'a', b : 'b'}

믹스 인 구성으로 복합 재료 제작 :

const a = {
  a : 'a'
};
const b = {
  b : 'b'
};
const c = {... a, ... b}; // {a : 'a', b : 'b'}

나중에 다른 스타일의 객체 구성에 대해 더 자세히 살펴 보겠습니다. 지금은 이해해야합니다.

  1. 여러 가지 방법이 있습니다.
  2. 어떤 방법은 다른 방법보다 낫습니다.
  3. 작업에 가장 단순하고 유연한 솔루션을 선택하려고합니다.

결론

이것은 기능 프로그래밍 (FP)과 객체 지향 프로그래밍 (OOP) 또는 언어와 언어에 관한 것이 아닙니다. 컴포넌트는 함수, 데이터 구조, 클래스 등의 형식을 취할 수 있습니다. 다른 프로그래밍 언어는 컴포넌트에 다른 원자 요소를 제공하는 경향이 있습니다. Java는 클래스를 제공하고 Haskell은 함수 등을 제공합니다. 그러나 어떤 언어와 선호하는 패러다임에 관계없이 함수와 데이터 구조를 구성 할 수 없습니다. 결국, 그것이 전부 요약됩니다.

함수는 JavaScript에서 작성하는 가장 간단한 것이므로 함수형 프로그래밍에 대해 많이 이야기하겠습니다. 함수형 프로그래밍 커뮤니티는 함수 구성 기술을 공식화하는 데 많은 시간과 노력을 투자했습니다.

우리가하지 않는 것은 함수형 프로그래밍이 객체 지향 프로그래밍보다 낫거나 다른 것을 선택해야한다는 것입니다. OOP 대 FP는 잘못된 이분법입니다. 최근 몇 년 동안 본 모든 실제 자바 스크립트 애플리케이션은 FP와 OOP를 광범위하게 혼합합니다.

객체 구성을 사용하여 기능 프로그래밍을위한 데이터 유형을 생성하고 기능 프로그래밍을 사용하여 OOP를위한 객체를 생성합니다.

소프트웨어 작성 방법에 관계없이 잘 작성해야합니다.

소프트웨어 개발의 본질은 구성입니다.

작곡을 이해하지 못하는 소프트웨어 개발자는 볼트 나 못을 모르는 주택 건설업자와 같습니다. 컴포지션에 대한 인식없이 소프트웨어를 구축하는 것은 덕트 테이프 및 미친 접착제로 벽을 결합하는 주택 건설업자와 같습니다.

이제는 단순화해야 할 시점이며 단순화하는 가장 좋은 방법은 본질에 도달하는 것입니다. 문제는 업계의 어느 누구도 필수 요소를 잘 다루지 못한다는 것입니다. 우리는 업계에서 소프트웨어 개발자 인 당신을 실패 시켰습니다. 개발자를 더 잘 훈련시키는 것은 업계의 책임입니다. 우리는 개선해야합니다. 우리는 책임을 져야합니다. 경제에서 의료 장비에 이르기까지 모든 것이 오늘날 소프트웨어에서 실행됩니다. 우리 행성의 소프트웨어 품질에 의해 영향을받지 않는 인간 생명의 구석은 문자 그대로 없습니다. 우리는 무엇을하고 있는지 알아야합니다.

이제 소프트웨어 작성 방법을 배울 차례입니다.

도서 구매 | 색인 | 다음>

EricElliottJS.com에서 자세히 알아보기

EricElliottJS.com 회원은 대화식 코드 문제가있는 비디오 강의를 이용할 수 있습니다. 회원이 아닌 경우 오늘 가입하십시오.

Eric Elliott는 분산 시스템 전문가이며“Composing Software”및“JavaScript Applications 프로그래밍”책의 저자입니다. DevAnywhere.io의 공동 창립자로서 개발자에게 원격으로 작업하고 업무 / 생활 균형을 수용하는 데 필요한 기술을 가르칩니다. 그는 암호화 프로젝트를위한 개발 팀을 구성하고 조언하며, Adobe Systems, Zumba Fitness, Wall Street Journal, ESPN, BBC 및 Usher, Frank Ocean, Metallica 등 최고의 레코딩 아티스트를위한 소프트웨어 경험에 기여했습니다.

그는 세상에서 가장 아름다운 여성과 함께 외진 생활을 즐깁니다.