순수한 기능성 대 말하기, 묻지 않습니까?


14

"함수에 대한 이상적인 인수 개수가 0"은 분명하지 않습니다. 이상적인 인수 수는 함수에 부작용이 없는데 필요한 숫자입니다. 그보다 적 으면, 불필요하게 함수가 불완전하게되어 성공의 구덩이에서 멀어지면서 고통의 구배를 상승하게됩니다. 때때로 그의 조언으로 "Uncle Bob"이 발견됩니다. 때때로 그는 장관이 잘못되었습니다. 그의 제로 논증 조언은 후자의 예입니다

( 출처 :이 사이트의 다른 질문에 @David Arno의 코멘트 )

이 의견은 133 개의 공감대를 얻었으므로 그 장점에 더주의를 기울이고 싶습니다.

내가 아는 한, 프로그래밍에는 두 가지 별도의 방법이 있습니다 : 순수한 기능 프로그래밍 (이 주석이 권장하는 것)과 묻지 말고 (이 웹 사이트에서도 때때로 권장되는) 말하지 마십시오. AFAIK이 두 원칙은 근본적으로 호환되지 않으며 서로 반대되는 점에 가깝습니다. 순수한 기능은 "반환 값만, 부작용 없음"으로 요약 할 수 있지만, 묻지 말고 "아무 것도 반환하지 않음"으로 요약 할 수 있습니다. 부작용 만 있습니다 " 또한 순수한 기능은 기능적 패러다임의 핵심으로 간주되는 반면, OO 패러다임의 핵심으로 간주되지 않는다고 생각했기 때문에 나는 혼란에 빠졌습니다.

개발자가 이러한 패러다임 중 하나를 선택하여 고수해야한다고 생각합니까? 글쎄, 나는 결코 나를 따라갈 수 없다는 것을 인정해야한다. 종종 값을 반환하는 것이 편리한 것처럼 보이며 부작용으로 만 달성하고자하는 것을 어떻게 달성 할 수 있는지 실제로 알 수 없습니다. 종종 부작용이있는 것이 편리한 것처럼 보이며 값을 반환해야만 달성하고자하는 것을 어떻게 달성 할 수 있는지 실제로 알 수 없습니다. 또한 종종 (나는 이것이 끔찍한 것 같아) 두 가지 방법을 모두 가지고 있습니다.

그러나이 133 개의 공감대에서 나는 현재 순수 기능 프로그래밍이 "승리"하고 있다고 말하는 것이 바람직하다. 이 올바른지?

따라서이 반 패턴 타기 게임의 예에서 나는 노력하고 있습니다 : 순수한 기능적 패러다임을 준수하고 싶다면-어떻게?!

전투 상태를 갖는 것이 합리적입니다. 이 게임은 턴 기반 게임이므로 전투 상태를 사전에 유지합니다 (멀티 플레이어-많은 플레이어가 동시에 여러 번 플레이 할 수 있음). 플레이어가 턴을 할 때마다 (a) 상태를 적절하게 수정하고 (b) 플레이어에게 업데이트를 반환하는 JSON을 적절한 방식으로 호출합니다.이 업데이트는 JSON으로 직렬화되고 기본적으로 판. 이것은 두 원칙을 동시에 위반하는 것으로 간주됩니다.

OK-실제로 원한다면 수정하지 않고 RETURN 방법으로 전투 상태를 만들 수 있습니다. 그러나! 그런 다음 전투 상태의 모든 것을 불필요하게 복사하여 완전히 수정하지 않고 완전히 새로운 상태를 반환해야합니까?

이제 이동이 공격이라면 HP가 업데이트 한 캐릭터 만 반환 할 수 있습니까? 문제는 간단하지 않다는 것입니다. 게임 규칙, 이동은 종종 플레이어의 HP의 일부를 제거하는 것보다 훨씬 더 많은 영향을 미칩니다. 예를 들어, 문자 사이의 거리를 늘리거나 특수 효과를 적용하는 등의 작업을 수행 할 수 있습니다.

상태를 수정하고 업데이트를 반환하는 것이 훨씬 간단 해 보입니다 ...

그러나 숙련 된 엔지니어가 어떻게이 문제를 해결합니까?


9
패러다임을 따르는 것은 실패의 확실한 길입니다. 정책은 결코 지능을 넘어서는 안됩니다. 문제에 대한 해결책은 문제 해결에 대한 종교적 신념이 아니라 문제에 의존해야합니다.
John Douma

1
나는 내가 전에 말한 것에 대해 여기에 질문을 한 적이 없다. 영광입니다. :)
David Arno

답변:


14

대부분의 프로그래밍 격언과 마찬가지로, "말하고 묻지 말 것"은 간결성을 얻기 위해 선명도를 희생합니다. 계산 결과 를 요구하지 말 것을 권유하는 것이 아니라 계산 입력 을 요구하지 않는 것이 좋습니다 . "가져 와서 계산하고 설정 한 다음 계산에서 값을 반환해도 괜찮습니다."

사람들이 getter를 호출하고 계산을 한 다음 결과와 함께 setter를 호출하는 것이 일반적이었습니다. 이것은 계산이 실제로 getter를 호출 한 클래스에 속한다는 분명한 신호입니다. "말하지 말아라"는 사람들이 그 반 패턴을 경계하고 있다는 사실을 상기시키기 위해 만들어졌으며, 이제는 일부 사람들은 그 부분이 분명하다고 생각하고 죽이다. 그러나 격언은 그러한 상황에만 유용하게 적용됩니다.

순수 기능 프로그램은 그 스타일에 세터가 없다는 단순한 이유 때문에 정확한 안티 패턴으로 고통받지 않았습니다. 그러나 동일한 기능에서 서로 다른 시맨틱 추상화 레벨을 혼합하지 않는보다 일반적인 (더보기 어려운) 문제점은 모든 패러다임에 적용됩니다.


"말하지 마세요"를 올바르게 설명해 주셔서 감사합니다.
user949300

13

Bob 아저씨와 David Arno (당신이 가진 인용문의 저자)는 모두 그들이 쓴 내용에서 얻을 수있는 중요한 교훈을 가지고 있습니다. 나는 교훈을 배우고 그것이 당신과 당신의 프로젝트에 실제로 의미하는 것을 외삽 할 가치가 있다고 생각합니다.

첫째 : 삼촌 밥의 교훈

Bob 아저씨는 함수 / 메서드에 더 많은 인수가있을수록 더 많은 개발자가 이해해야한다고 지적하고 있습니다. 그인지 부하는 무료가 아니며, 인수 등의 순서와 일치하지 않으면인지 부하는 증가합니다.

그것이 인간이라는 사실입니다. 나는 Uncle Bob의 Clean Code 책의 핵심 실수는 "함수에 대한 이상적인 인수 개수는 0" 이라는 문장이라고 생각합니다 . 미니멀리즘은 그렇지 않을 때까지 훌륭합니다. 미적분학의 한계에 결코 도달하지 않는 것처럼 "이상적인"코드에 도달하지도 않습니다.

알버트 아인슈타인 (Albert Einstein)은“모든 것이 가능한 한 단순해야하지만 단순해서는 안된다”고 말했다.

둘째 : David Arno의 교훈

David Arno가 개발 한 방법은 객체 지향적 인 것 보다 기능적인 스타일 개발 입니다. 그러나 기능적 코드는 기존의 객체 지향 프로그래밍보다 훨씬 우수합니다. 왜? 잠금으로 인해. 객체에서 시간 상태를 변경할 수있는 경우 경쟁 조건 또는 경합 잠금의 위험이 있습니다.

시뮬레이션 및 기타 서버 측 응용 프로그램에 사용되는 고도로 동시 시스템을 작성한이 기능 모델은 놀라운 일입니다. 접근 방식의 개선 사항을 증명할 수 있습니다. 그러나 요구 사항과 관용구가 다른 매우 다른 스타일의 개발입니다.

개발은 일련의 절충점입니다.

당신은 우리보다 당신의 응용 프로그램을 잘 알고 있습니다. 기능적 스타일 프로그래밍과 함께 제공되는 확장 성이 필요하지 않을 수 있습니다. 위에 나열된 두 가지 이상 사이에는 세상이 있습니다. 높은 처리량과 어리석은 병렬 처리를 처리해야하는 시스템을 다루는 사람들은 함수형 프로그래밍의 이상을 향할 것입니다.

즉, 데이터 객체를 사용하여 메소드에 전달해야하는 정보 세트를 보유 할 수 있습니다. 이는 Bob 아저씨가 해결 한인지 부하 문제를 해결하는 동시에 David Arno가 해결 한 기능적 이상을 계속 지원합니다.

나는 병렬 처리가 제한적이고 처리량이 많은 시뮬레이션 소프트웨어로 두 데스크탑 시스템에서 일했습니다. 그들은 매우 다른 요구를 가지고 있습니다. 익숙한 데이터 숨기기 개념을 중심으로 설계된 잘 작성된 객체 지향 코드를 이해할 수 있습니다. 여러 응용 프로그램에서 작동합니다. 그러나 모든 기능이 작동하지는 않습니다.

누가 옳아? 이 경우 데이비드는 밥 삼촌보다 더 옳습니다. 그러나 여기서 강조하고 싶은 근본적인 요점은 메소드가 이해하는만큼 많은 인수를 가져야한다는 것입니다.


병렬화가 있습니다. 여러 전투를 동시에 처리 할 수 ​​있습니다. 그러나 그렇습니다. 단일 전투는 처리되는 동안 잠겨 있어야합니다.
gaazkam

그렇습니다. 나는 독자들 (귀하의 비유 자들)이 그들의 (뿌린 씨) 저술에서 수집 할 것을 의미했습니다. 즉, 나는 과거에 쓴 것들을 다시 살펴보고 무언가를 다시 배우거나 내 이전의 자아에 동의하지 않았습니다. 우리는 모두 배우고 발전하고 있습니다. 이것이 배운 것을 어떻게 적용하는지 항상 추론해야하는 이유 중 하나입니다.
베린 로리 치

8

OK-실제로 원한다면 수정하지 않고 RETURN 방법으로 전투 상태를 만들 수 있습니다.

그렇습니다.

그런 다음 전투 상태의 모든 것을 복사하여 완전히 수정하지 않고 완전히 새로운 상태를 반환해야합니까?

아니요. "전투 상태"는 변경 불가능한 데이터 구조로 모델링 될 수 있으며 여기에는 빌딩 블록과 같은 다른 불변 데이터 구조가 포함되며 일부 불변 데이터 구조의 계층 구조에 중첩 될 수 있습니다.

따라서 한 차례에 변경하지 않아도되는 전투 상태와 변경해야 할 부분이있을 수 있습니다. 변경되지 않은 부분은 변경 될 수 없으므로 변경 될 수 없으므로 부작용이 발생할 위험없이 해당 부분에 대한 참조 만 복사하면됩니다. 가비지 수집 언어 환경에서 가장 잘 작동합니다.

"효율적인 불변 데이터 구조"를위한 Google이 있으며, 이것이 일반적으로 어떻게 작동하는지 참고할 것입니다.

상태를 수정하고 업데이트를 반환하는 것이 훨씬 간단 해 보입니다.

특정 문제의 경우 이는 실제로 더 간단 할 수 있습니다. 게임 상태의 대부분이 한 라운드에서 다른 라운드로 바뀌면 게임 및 라운드 기반 시뮬레이션이이 범주에 속할 수 있습니다. 그러나 실제로 "더 단순한"것에 대한 인식은 어느 정도 주관적이며 사람들이 익숙한 것에 따라 크게 좌우됩니다.


8

의견의 저자로서, 여기에 의견을 분명히해야한다고 생각합니다. 물론 내 의견이 제공하는 단순화 된 버전보다 더 많이 있습니다.

AFAIK이 두 원칙은 근본적으로 호환되지 않으며 서로 반대되는 점에 가깝습니다. 순수한 기능은 "반환 값만, 부작용 없음"으로 요약 할 수 있지만, 묻지 말고 "아무 것도 반환하지 않음"으로 요약 할 수 있습니다. 부작용 만 있습니다 "

솔직히 말해서, 이것은 "말하지 말아라"라는 용어를 정말 이상하게 사용합니다. 그래서 나는 몇 년 전 마틴 파울러가이 주제에 관해 말한 것을 읽었습니다 . 내가 이상하게 생각한 이유는 "tell do n't ask"가 내 머리 속의 의존성 주입과 동의어이고 가장 순수한 형태의 의존성 주입은 함수가 매개 변수를 통해 필요한 모든 것을 전달하기 때문입니다.

그러나 내가 "말하지 말아라"에 적용하는 의미는 Fowler의 OO 중심의 정의를 취하고 더 패러다임에 무관심하게 만드는 것에서 온 것으로 보인다. 그 과정에서 나는 그 개념이 논리적 결론에 도달한다고 믿는다.

간단한 시작으로 돌아가 봅시다. 우리는 "논리의 덩어리"(절차)를 가지고 있으며 전 세계 데이터를 가지고 있습니다. 프로시 저는 해당 데이터에 액세스하기 위해 해당 데이터를 직접 읽습니다. 간단한 "질문"시나리오가 있습니다.

조금 앞으로 감습니다. 이제 객체와 메소드가 있습니다. 이 데이터는 더 이상 전역적일 필요가 없으며 생성자를 통해 전달되어 객체 내에 포함될 수 있습니다. 그리고 그 데이터에 작용하는 방법이 있습니다. Fowler가 설명했듯이 이제 우리는 "말하지 말고 묻습니다". 객체에 데이터가 알려집니다. 이러한 방법은 더 이상 데이터의 전역 범위를 요구할 필요가 없습니다. 그러나 여기에 문지름이 있습니다. 이러한 방법이 여전히 객체 범위를 요구해야하기 때문에 이것은 여전히 ​​사실 "말하고 묻지 마십시오"는 아닙니다. 이것은 내가 느끼는 "이야기, 물어보기"시나리오에 가깝습니다.

따라서 현대에 바람을 불어 넣고 "OO로 끝까지 OO"접근 방식을 버리고 함수형 프로그래밍에서 몇 가지 원칙을 빌리십시오. 이제 메소드가 호출되면 매개 변수를 통해 모든 데이터가 제공됩니다. "무엇이 요점입니까, 코드를 복잡하게 만드는 것입니까?"라고 논쟁 할 수있었습니다. 예, 객체의 범위를 통해 액세스 할 수있는 데이터를 매개 변수를 통해 전달하면 코드가 복잡해집니다. 그러나 데이터를 전역 적으로 액세스 할 수 있도록 만드는 대신 객체에 저장하면 복잡성이 증가합니다. 그러나 글로벌 변수가 더 단순하기 때문에 항상 더 좋다고 주장하는 사람은 거의 없습니다. 요점은 "말하고 묻지 않는"이점이 범위 축소의 복잡성을 능가한다는 것입니다. 이것은 범위를 객체로 제한하는 것보다 매개 변수를 통해 전달하는 데 더 적용됩니다.private static매개 변수를 통해 필요한 모든 것을 전달하면이 방법을 사용하면 안되는 것들에 교묘하게 액세스하지 않을 수 있습니다. 또한 방법을 작게 유지하는 것이 좋습니다. 그렇지 않으면 매개 변수 목록이 제대로 작동하지 않습니다. 또한 "순수 기능"기준에 맞는 작성 방법을 권장합니다.

그래서 나는 "순수한 기능"과 "말하지 말아라"는 서로 반대되는 것으로 보지 않습니다. 전자는 내가 염려하는 한 후자의 완전한 구현입니다. 파울러의 접근 방식은 완전하지 않습니다. "말하지 마십시오".

그러나이 "완전하게 말해주지 말아라"라는 것이 이상적이라는 것을 기억하는 것이 중요합니다. 부작용이 거의 없다면 아무런 효과가 없다는 간단한 이유로 100 % 부작용이없는 앱은 거의 없습니다. 상태를 변경해야하고 앱이 유용하려면 IO 등이 필요합니다. 이러한 경우 방법은 부작용을 유발해야하므로 순수 할 수는 없습니다. 그러나 여기서의 경험상 규칙은 이러한 "불완전한"방법을 최소한으로 유지하는 것입니다. 표준보다는 오히려 필요하기 때문에 부작용이 있습니다.

전투 상태를 갖는 것이 합리적입니다. 이 게임은 턴 기반 게임이므로 전투 상태를 사전에 유지합니다 (멀티 플레이어-많은 플레이어가 동시에 여러 번 플레이 할 수 있음). 플레이어가 턴을 할 때마다 (a) 상태를 적절하게 수정하고 (b) 플레이어에게 업데이트를 반환하는 JSON을 적절한 방식으로 호출합니다.이 업데이트는 JSON으로 직렬화되고 기본적으로 판.

나에게 전투 상태를 갖는 것이 합리적이다. 필수 인 것 같습니다. 이러한 코드의 전체 목적은 상태 변경 요청을 처리하고 해당 상태 변경을 관리하며 다시보고하는 것입니다. 해당 상태를 전체적으로 처리하거나 개별 플레이어 객체 내부에 고정하거나 일련의 순수 함수 주위에 전달할 수 있습니다. 어떤 시나리오를 선택하면 특정 시나리오에 가장 적합한 지 결정됩니다. 전역 상태는 코드 디자인을 단순화하고 빠르며 이는 대부분의 게임의 핵심 요구 사항입니다. 그러나 코드를 유지 관리, 테스트 및 디버그하기가 더 어려워집니다. 순수한 함수 세트는 코드를 구현하기 더 복잡하게 만들고 과도한 데이터 복사로 인해 코드가 너무 느려질 위험이 있습니다. 그러나 테스트하고 유지 관리하는 것이 가장 간단합니다. "OO 접근 방식"은 중간에 있습니다.

핵심은 항상 작동하는 완벽한 솔루션이 없다는 것입니다. 순수한 기능의 목표는 "성공의 구덩이에 빠지도록"돕는 것입니다. 그러나 코드가 복잡하기 때문에 구덩이가 너무 얕 으면 넘어 질 때 코드에 너무 많이 빠지지 않으므로 올바른 접근 방식이 아닙니다. 이상을 목표로하지만 실용적이며 그 이상이 이번에 가기에 좋은 곳이 아닌 경우에는 중지하십시오.

그리고 마지막으로, 반복해서 말하면 순수한 기능과 "말하지 말아라"는 전혀 반대가 아닙니다.


5

무엇보다도, 당신이 그 말을 할 수있는 맥락이 존재하여 말도 안됩니다.

여기에 이미지 설명을 입력하십시오

제로 인수 조언을 요구 사항으로 취하면 Bob 아저씨는 완전히 잘못되었습니다. 모든 추가 인수로 인해 코드를 읽기가 어렵다는 것을 의미한다면 그는 옳습니다. 비용이 듭니다. 쉽게 읽을 수 있도록 함수에 인수를 추가하지 않습니다. 함수에 인수를 추가하면 해당 인수에 대한 종속성을 분명하게하는 올바른 이름을 생각할 수 없기 때문입니다.

예를 들어 pi()완벽하게 훌륭한 기능입니다. 왜? 계산 방법과 상관없이 상관하지 않기 때문입니다. 또는 e 또는 sin ()을 사용하여 반환 된 숫자에 도달 한 경우. 이름은 내가 알아야 할 모든 것을 알려주기 때문에 괜찮습니다.

그러나 모든 이름이 내가 알아야 할 모든 것을 말해주지는 않습니다. 일부 이름은 노출 된 인수뿐만 아니라 함수의 동작을 제어하는 ​​정보를 이해하는 데 중요하지 않습니다. 바로 여기에 기능적 프로그래밍 스타일을 쉽게 추론 할 수있는 요소가 있습니다.

나는 완전히 OOP 스타일로 불변과 부작용을 자유롭게 유지할 수 있습니다. 반환은 단순히 다음 절차를 위해 스택에 값을 남겨 두는 데 사용되는 메커니즘입니다. 사람들이 그것을 읽을 수있게하려면 마지막으로 무언가를 변경해야하는 마지막 출력 포트에 도달 할 때까지 출력 포트를 사용하여 불변의 상태를 유지하여 다른 불변의 것들과 값을 통신 할 수 있습니다. 기능적이든 아니든 모든 언어에 적용됩니다.

따라서 기능 프로그래밍과 객체 지향 프로그래밍이 "기본적으로 호환되지 않는다"고 주장하지 마십시오. 기능 프로그램에서 객체를 사용할 수 있고 OO 프로그램에서 순수한 기능을 사용할 수 있습니다.

그러나 혼합 비용은 다음과 같습니다. 기대. 두 패러다임의 역학을 충실하게 따르고 여전히 혼란을 일으킬 수 있습니다. 기능적 언어를 사용하면 얻을 수있는 이점 중 하나는 부작용이 있지만 결과를 얻기 위해서는 반드시 존재해야하지만 예측 가능한 위치에 있다는 것입니다. 물론 변경 불가능한 객체는 제어 할 수없는 방식으로 액세스됩니다. 그러면 당신이 그 언어로 주어진 것으로 취한 것이 분리됩니다.

마찬가지로 순수한 기능을 가진 객체를 지원할 수 있으며 변경 불가능한 객체를 디자인 할 수 있습니다. 문제는 함수가 순수하거나 객체가 불변하다는 신호를 보내지 않으면 사람들은 코드를 읽는 데 많은 시간을 할애 할 때까지 이러한 기능의 이점을 얻지 못한다는 것입니다.

이것은 새로운 문제가 아닙니다. 수년 동안 사람들은 "OO 언어"를 사용하기 때문에 OO를하고 있다고 생각하는 "OO 언어"로 절차 적으로 코딩되었습니다. 발로 자신을 쏘지 못하게하는 언어는 거의 없습니다. 이러한 아이디어가 작동하려면 그들은 당신 안에 살아야합니다.

둘 다 좋은 기능을 제공합니다. 둘 다 할 수 있습니다. 당신이 그들을 섞을 정도로 용감한 경우 명확하게 레이블을 지정하십시오.


0

나는 때때로 다양한 패러다임의 모든 규칙을 이해하려고 노력합니다. 그들은이 상황에서 때때로 서로 충돌합니다.

OOP는 위험한 일이 발생하는 세계에서 가위로 달리는 것에 대한 명령형 패러다임입니다.

FP는 순수한 계산에서 절대 안전을 찾는 기능적 패러다임입니다. 여기서는 아무 일도 일어나지 않습니다.

그러나 모든 프로그램이 유용하게 사용되어야합니다. 따라서 기능적 핵심 명령형 쉘 .

변경 불가능한 오브젝트 (명령이 실제로 변경되지 않고 수정 된 사본을 리턴하는 오브젝트)를 정의 할 때 상황이 혼란스러워집니다. "이것은 OOP입니다."및 "객체 동작을 정의하고 있습니다." 당신은 시도하고 테스트 한 Tell, Do n't Ask 원칙으로 되돌아갑니다. 문제는 잘못된 영역에 적용하는 것입니다.

영역은 완전히 다르며 다른 규칙을 따릅니다. 기능적 영역은 부작용을 세상에 방출하려는 시점까지 쌓입니다. 이러한 효과가 해제 되려면 명령형 개체에 캡슐화 된 모든 데이터 (이런 식으로 작성 되었음)가 명령형 쉘을 사용할 수 있어야합니다. 다른 세계에서 캡슐화를 통해 숨겨져 있던이 데이터에 액세스하지 않으면 작업을 수행 할 수 없습니다. 계산이 불가능합니다.

따라서 변경 불가능한 객체 (Clojure가 영구 데이터 구조라고 함)를 작성할 때 기능적 영역에 있다는 것을 기억하십시오. 던져라, 창문을 묻지 말고 명령형 영역에 다시 들어갈 때만 집으로 돌려 보내라.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.