“모든 것이지도입니다”, 제가 올바르게하고 있습니까?


69

나는 스튜어트 시에라 (Stuart Sierra)의 " 씽킹 인 데이터 (Thinking In Data) " 연설을 보고이 게임의 아이디어 중 하나를 내가 만드는이 게임의 디자인 원칙으로 채택했다. 차이점은 그가 Clojure에서 일하고 있고 JavaScript에서 일하고 있다는 것입니다. 나는 우리 언어들 사이에 몇 가지 큰 차이점이 있음을 알았습니다

  • Clojure는 관용적으로 기능적인 프로그래밍입니다
  • 대부분의 상태는 불변입니다

나는 "모든 것이지도"라는 슬라이드에서 아이디어를 얻었습니다 (11 분, 6 초에서> 29 분). 그가 말한 것들은 :

  1. 2-3 개의 인수를 사용하는 함수가 표시 될 때마다이를 맵으로 바꾸고 맵을 전달하는 경우를 만들 수 있습니다. 여기에는 많은 장점이 있습니다.
    1. 인수 순서에 대해 걱정할 필요가 없습니다.
    2. 추가 정보에 대해 걱정할 필요가 없습니다. 여분의 키가 있다면 그것은 실제로 우리의 관심사가 아닙니다. 그들은 그냥 흐르고 방해하지 않습니다.
    3. 스키마를 정의 할 필요가 없습니다
  2. 객체를 전달하는 것과는 달리 데이터 숨기기가 없습니다. 그러나 그는 데이터 숨기기가 문제를 일으킬 수 있으며 과대 평가되었다고 주장합니다.
    1. 공연
    2. 간편한 구현
    3. 네트워크 또는 프로세스를 통해 통신하자마자 양측이 데이터 표현에 동의해야합니다. 데이터 작업 만하면 건너 뛸 수있는 추가 작업입니다.
  3. 내 질문과 가장 관련이 있습니다. "함수를 구성 할 수있게하라" 는 29 분입니다 . 다음은 개념을 설명하기 위해 사용하는 코드 샘플입니다.

    ;; Bad
    (defn complex-process []
      (let [a (get-component @global-state)
            b (subprocess-one a) 
            c (subprocess-two a b)
            d (subprocess-three a b c)]
        (reset! global-state d)))
    
    ;; Good
    (defn complex-process [state]
      (-> state
        subprocess-one
        subprocess-two
        subprocess-three))
    

    필자는 대다수의 프로그래머가 Clojure에 익숙하지 않다는 것을 알고 있으므로이를 필수 스타일로 다시 작성하겠습니다.

    ;; Good
    def complex-process(State state)
      state = subprocess-one(state)
      state = subprocess-two(state)
      state = subprocess-three(state)
      return state
    

    장점은 다음과 같습니다.

    1. 손쉬운 테스트
    2. 이러한 기능을 분리하여 쉽게 볼 수 있습니다.
    3. 한 줄을 주석 처리하고 단일 단계를 제거하여 결과가 무엇인지 쉽게 확인할 수 있습니다.
    4. 각 하위 프로세스는 상태에 대한 추가 정보를 추가 할 수 있습니다. 하위 프로세스 1이 하위 프로세스 3과 통신해야하는 경우 키 / 값을 추가하는 것만 큼 간단합니다.
    5. 데이터를 다시 저장할 수 있도록 상태에서 필요한 데이터를 추출 할 상용구가 없습니다. 전체 상태를 전달하고 하위 프로세스가 필요한 것을 할당하도록하십시오.

이제 제 상황으로 돌아가서 : 저는이 교훈을 게임에 적용했습니다. 즉, 거의 모든 고급 기능이 gameState객체를 가져오고 반환 합니다. 이 개체는 게임의 모든 데이터를 포함합니다. EG : badGuys 목록, 메뉴 목록, 지상 전리품 등입니다. 다음은 업데이트 기능의 예입니다.

update(gameState)
  ...
  gameState = handleUnitCollision(gameState)
  ...
  gameState = handleLoot(gameState)
  ...

내가 여기서 물어볼 것은 기능적 프로그래밍 언어에서만 실용적 아이디어를 왜곡 한 가증을 일으켰 는가? JavaScript는 관용적으로 기능 하지 않으며 (그렇지만 작성할 수는 있지만) 불변의 데이터 구조를 작성하는 것은 정말 어려운 일입니다. 저와 관련된 한 가지 사항은 각 하위 프로세스가 순수 하다고 가정 한다는 것입니다. 왜 그런 가정을해야합니까? 내 함수 중 일부가 순수한 것은 드문 일입니다 (따라서, 나는 종종을 수정한다는 것을 의미 gameState합니다. 불변의 데이터가 없다면 이러한 아이디어가 분리됩니까?

언젠가 깨어나서이 전체 디자인이 가짜임을 깨달았으며 실제로 Big Ball Of Mud 안티 패턴을 구현하고 있습니다.


솔직히, 나는이 코드를 몇 달 동안 노력해 왔으며 훌륭했습니다. 나는 그가 주장한 모든 이점을 얻고있는 것 같습니다. 내 코드는 매우 쉽게 나를 위해 추론 할 수 있습니다. 그러나 나는 한 사람 팀이므로 지식의 저주를 가지고 있습니다.

최신 정보

이 패턴으로 6 개월 이상 코딩했습니다. 보통이 시점까지 나는 내가 한 일을 잊어 버렸습니다. 그리고 그곳에서 "내가 이것을 깨끗하게 작성 했습니까?" 놀이에 온다. 내가하지 않으면 정말 힘들 것입니다. 지금까지 나는 전혀 고투하지 않습니다.

유지 보수성을 검증하기 위해 또 다른 눈 세트가 필요한 방법을 이해합니다. 내가 말할 수있는 것은 무엇보다도 유지 보수성에 관심이 있다는 것입니다. 나는 어디에서 일하든 항상 깨끗한 코드를위한 가장 큰 전도자입니다.

이 코딩 방법으로 이미 개인적인 경험이 나쁜 사람들에게 직접 답장하고 싶습니다. 그때는 몰랐지만 코드를 작성하는 두 가지 방법에 대해 이야기하고 있다고 생각합니다. 내가 한 방식은 다른 사람들이 경험 한 것보다 더 구조화 된 것 같습니다. 누군가가 "모든 것이지도"라는 개인적인 경험이 없으면 다음과 같은 이유로 유지 관리가 얼마나 어려운지 이야기합니다.

  1. 함수가 요구하는 맵의 구조를 절대 알 수 없습니다
  2. 모든 함수는 예상치 못한 방식으로 입력을 변경할 수 있습니다. 특정 키가 맵에 들어간 방법 또는 사라진 이유를 찾으려면 코드베이스 전체를 살펴 봐야합니다.

그러한 경험을 가진 사람들에게 아마도 코드베이스는 "모든 것이 N 유형의 맵 중 1 개를 필요로합니다." 내 것은 "모두 1 개 유형의지도 중 1 개가 필요합니다"입니다. 그 1 가지 유형의 구조를 안다면 모든 것의 구조를 알고 있습니다. 물론, 그 구조는 보통 시간이 지남에 따라 커집니다. 그래서 ...

참조 구현 (예 : 스키마)을 찾을 수있는 곳이 하나 있습니다. 이 참조 구현은 게임이 사용하는 코드이므로 오래되지 않습니다.

두 번째 요점은 참조 구현 외부의 맵에 키를 추가 / 제거하지 않고 이미 존재하는 것을 변경하는 것입니다. 또한 대규모 자동 테스트 제품군이 있습니다.

이 아키텍처가 결국 자체 무게로 무너지면 두 번째 업데이트를 추가하겠습니다. 그렇지 않으면 모든 것이 잘되고 있다고 가정하십시오. :)


2
멋진 질문 (+1)! 기능적 관용구를 비 기능적 (또는 매우 기능적이지 않은) 언어로 시도하고 구현하는 것은 매우 유용한 연습입니다.
Giorgio

15
OO 스타일 정보 숨기기 (속성 및 접근 자 기능 포함)는 (일반적으로 무시할만한) 성능 저하로 인해 나쁜 것입니다. 그리고 모든 매개 변수를 맵으로 바꾸도록 지시합니다. 값을 검색하려고 할 때마다 해시 조회의 (훨씬 큰) 오버 헤드는 무시해도됩니다.
메이슨 휠러

4
@MasonWheeler는 이것에 대해 옳다고 말할 수 있습니다. 이 한 가지 문제 때문에 그가하는 다른 모든 점을 무효화 할 것입니까?
Daniel Kaplan

9
파이썬 (그리고 자바 스크립트를 포함한 대부분의 동적 언어를 믿는다)에서 객체는 실제로 dict / map의 구문 설탕 일뿐입니다.
Lie Ryan

6
@EvanPlaice : Big-O 표기법은 기만적 일 수 있습니다. 단순한 사실이며, 아무것도 두 개 또는 세 개의 개별 컴퓨터 코드 지침에 직접 액세스에 비해 속도가 느린이며, 함수 호출만큼 자주 일어나는 일에, 그 오버 헤드가 매우 빠르게 추가 할 것입니다.
메이슨 휠러

답변:


42

전에는 '모든 것이지도'인 응용 프로그램을 지원했습니다. 끔찍한 생각입니다. 하지 마십시오!

함수에 전달되는 인수를 지정하면 함수에 필요한 값을 쉽게 알 수 있습니다. 그것은 프로그래머를 혼란스럽게하는 함수에 외부 데이터를 전달하는 것을 피합니다-전달 된 모든 값은 그것이 필요하다는 것을 암시하며 코드를 지원하는 프로그래머가 데이터가 왜 필요한지 알아 내야합니다.

반면에 모든 것을 맵으로 전달하면 앱을 지원하는 프로그래머는 맵에 포함해야하는 값을 알기 위해 모든 방법으로 호출 된 함수를 완전히 이해해야합니다. 더 나쁜 것은, 데이터를 다음 함수로 전달하기 위해 현재 함수로 전달 된 맵을 재사용하는 것이 매우 유혹적입니다. 즉, 앱을 지원하는 프로그래머는 현재 기능이 수행하는 작업을 이해하기 위해 현재 기능에서 호출하는 모든 기능을 알아야합니다. 그것은 함수를 작성하는 목적과 정확히 반대입니다. 문제를 추상화하여 생각할 필요가 없습니다! 이제 5 번의 통화 깊이와 5 번의 통화 폭을 상상해보십시오. 그것은 당신의 마음에 많은 지옥과 많은 실수가 있습니다.

"모든 것이지도"는지도를 반환 값으로 사용하는 것으로 보입니다. 나 그거 봤었 어. 그리고 다시, 그것은 고통입니다. 호출 된 함수는 서로의 반환 값을 덮어 쓸 필요가 없습니다. 모든 기능을 알고 다음 함수 호출을 위해 입력 맵 값 X를 바꿔야한다는 것을 알지 않는 한. 그리고 현재 함수는 맵을 수정하여 값을 반환해야합니다.이 값은 때때로 이전 값을 덮어 써야하고 때로는 그렇지 않아야합니다.

편집-예

이것이 문제가 된 곳의 예입니다. 이것은 웹 애플리케이션이었습니다. UI 레이어에서 사용자 입력을 받아 맵에 배치했습니다. 그런 다음 요청을 처리하기 위해 함수가 호출되었습니다. 첫 번째 기능 세트는 잘못된 입력을 확인합니다. 오류가 있으면 오류 메시지가 맵에 표시됩니다. 호출 함수는이 항목에 대한 맵을 확인하고 존재하는 경우 ui에 값을 씁니다.

다음 기능 세트는 비즈니스 로직을 시작합니다. 각 함수는 맵을 가져오고, 일부 데이터를 제거하고, 일부 데이터를 수정하고, 맵의 데이터를 조작하고 결과를 맵에 넣는 등의 작업을 수행합니다. 후속 함수는 맵의 이전 함수에서 결과를 기대합니다. 후속 함수에서 버그를 수정하려면 호출자뿐만 아니라 모든 이전 함수를 조사하여 예상 값이 설정 될 수있는 곳을 결정해야했습니다.

다음 함수는 데이터베이스에서 데이터를 가져옵니다. 또는지도를 데이터 액세스 계층으로 전달합니다. DAL은 쿼리 실행 방법을 제어하기 위해 맵에 특정 값이 포함되어 있는지 확인합니다. 'justcount'가 키인 경우 쿼리는 'count select foo from bar'입니다. 이전에 호출 된 함수는 모두 'justcount'를 맵에 추가 한 함수일 수 있습니다. 쿼리 결과가 동일한 맵에 추가됩니다.

결과는 발신자 (비즈니스 로직)에게 버블 링되어 맵에서 수행 할 작업을 확인합니다. 이 중 일부는 초기 비즈니스 로직에 의해 맵에 추가 된 것에서 나옵니다. 일부는 데이터베이스의 데이터에서 나옵니다. 출처를 알 수있는 유일한 방법은 코드를 추가 한 코드를 찾는 것입니다. 그리고 다른 위치도 추가 할 수 있습니다.

이 코드는 사실상 모 놀리 식 엉망이었으며 맵의 단일 항목이 어디에서 왔는지 완전히 이해해야했습니다.


2
두 번째 단락은 나에게 의미가 있으며 정말 짜증나는 것처럼 들립니다. 세 번째 단락에서 동일한 디자인에 대해 실제로 이야기하고 있지 않다는 생각이 들었습니다. "재사용"이 요점입니다. 피하는 것은 잘못된 일입니다. 그리고 나는 당신의 마지막 단락과 관련이 없습니다. 나는 모든 기능이 그 gameState전후에 일어난 일에 대해 전혀 모른 채 가지고 있습니다. 주어진 데이터에 반응합니다. 기능이 서로 발가락을 밟을 상황에 어떻게 들어갔습니까? 예를 들어 줄 수 있습니까?
Daniel Kaplan

2
좀 더 명확하게하기 위해 예제를 추가했습니다. 도움이 되길 바랍니다. 또한, UI 로직, 비즈니스 로직 및 데이터베이스 액세스 논리 혼합, 여러 가지 이유로 많은 장소 변경 설정하는 덩어리 주위에 전달에 비해 잘 정의 된 상태 개체 주위에 지나가는 사이에 차이가있어
공격력

28

개인적으로, 나는 어느 패러다임에서도 그 패턴을 추천하지 않을 것입니다. 나중에 추론하기가 더 어려워지면서 초기에 글쓰기가 더 쉬워졌습니다.

예를 들어, 각 하위 프로세스 기능에 대한 다음 질문에 대답하십시오.

  • 어떤 분야 state가 필요합니까?
  • 어떤 필드를 수정합니까?
  • 변경하지 않은 필드는 무엇입니까?
  • 기능의 순서를 안전하게 재 배열 할 수 있습니까?

이 패턴을 사용하면 전체 기능을 읽지 않으면 이러한 질문에 대답 할 수 없습니다.

객체 지향 언어에서는 추적 상태가 객체의 기능이기 때문에 패턴의 의미가 떨어집니다.


2
"불변의 이점은 불변의 객체가 커질수록 줄어 듭니다"왜? 성능 또는 유지 관리성에 대한 의견입니까? 그 문장을 자세히 설명하십시오.
Daniel Kaplan

8
@tieTYT 불변은 작은 것이있을 때 잘 작동합니다 (예 : 숫자 타입). 당신은 그것들을 복사하고, 만들고, 버리고, 저비용으로 플라이급 할 수 있습니다. 수백 개의 변수가 아닌 경우 깊고 큰지도, 나무, 목록 및 수십 가지로 구성된 전체 게임 상태를 처리하기 시작하면 복사 또는 삭제 비용이 증가합니다 (비행 중량이 비현실적입니다).

3
내가 참조. "불완전한 언어의 불변 데이터"문제 또는 "불변 데이터"문제입니까? IE : 아마도 Clojure 코드에서는 문제가되지 않을 것입니다. 그러나 JS에서 그것이 어떻게되는지 볼 수 있습니다. 모든 상용구 코드를 작성하는 것도 고통입니다.
Daniel Kaplan

3
@MichaelT와 Karl : 공정하기 위해서는 불변성 / 효율성 이야기의 다른면을 언급해야합니다. 예, 순진한 사용법은 끔찍하게 비효율적 일 수 있으므로 사람들이 더 나은 접근 방식을 생각해 냈습니다. 자세한 내용은 Chris Okasaki의 작품을 참조하십시오.

3
@MattFenwick 저는 개인적으로 불변의 것을 좋아합니다. 스레딩을 다룰 때 나는 불변에 대해 알고 있으며 안전하게 작업하고 복사 할 수 있습니다. 나는 매개 변수 호출에 넣고 누군가 나에게 돌아올 때 그것을 수정하지 않을까 걱정하지 않고 다른 사람에게 전달합니다. 복잡한 게임 상태에 대해 이야기하고 있다면 (질문을 예로 사용했습니다. 불변의 것으로 넷 핵 게임 상태와 같은 '간단한'것으로 생각하는 것이 무섭습니다) 불변성은 아마도 잘못된 접근법 일 것입니다.

12

당신이하고있는 것은 사실상 수동 상태 모나드입니다. 내가 할 일은 (단순화 된) 결합 결합기를 만들고 그것을 사용하여 논리 단계 사이의 연결을 다시 표현하는 것입니다.

function stateBind() {
    var computation = function (state) { return state; };
    for ( var i = 0 ; i < arguments.length ; i++ ) {
        var oldComp = computation;
        var newComp = arguments[i];
        computation = function (state) { return newComp(oldComp(state)); };
    }
    return computation;
}

...

stateBind(
  subprocessOne,
  subprocessTwo,
  subprocessThree,
);

당신은 사용할 수 있습니다 stateBindsubsubprocesses에서 다양한 하위 프로세스를 구축하고, 적절하게 계산을 구조화하는 콤비 바인딩의 나무를 계속합니다.

완전하고 단순화되지 않은 State 모나드에 대한 설명과 JavaScript에서 모나드에 대한 훌륭한 소개는 이 블로그 게시물을 참조하십시오 .


1
좋아, 나는 그것을 조사 할 것이다 (나중에 그것에 대해 언급 할 것이다). 그러나 패턴을 사용하는 아이디어에 대해 어떻게 생각하십니까?
Daniel Kaplan

1
@tieTYT 패턴 자체는 아주 좋은 생각이라고 생각합니다. State 모나드는 일반적으로 의사 변경 가능 알고리즘 (변경 불가능하지만 변경 가능한 에뮬레이션)에 유용한 코드 구성 도구입니다.
Ptharien 's Flame

2
이 패턴이 본질적으로 Monad라는 점에 주목하여 +1. 그러나 실제로 변경이 가능한 언어에서는 좋은 생각이 아닙니다. 모나드는 변이를 허용하지 않는 언어로 전역 / 변이 가능 상태의 기능을 제공하는 방법입니다. IMO는 불변성을 강요하지 않는 언어에서 Monad 패턴은 단지 정신 자위입니다.
Lie Ryan

6
@LieRyan Monads는 일반적으로 가변성 또는 전역과 전혀 관련이 없습니다. State 모나드 만 구체적으로 수행합니다 (특히 그렇게하도록 설계 되었기 때문에). 또한 상태 모나드가 변경 가능한 언어에서는 유용하지 않다는 것에 동의하지 않지만, 아래의 변경 가능성에 의존하는 구현이 내가 준 불변의 것보다 더 효율적일 수도 있습니다 (아직 확실하지는 않지만). monadic 인터페이스는 쉽게 액세스 할 수없는 높은 수준의 기능을 제공 할 수 있습니다. stateBind제가 준 결합기는 매우 간단한 예입니다.
Ptharien의 불꽃

1
@LieRyan 두 번째 Ptharien의 의견-대부분의 모나드는 상태 또는 변경 가능성에 관한 것이 아니며, 심지어 글로벌 상태 에 관한 것이 아닙니다 . 모나드는 실제로 OO / 제 국어 / 변경 가능한 언어에서 상당히 잘 작동합니다.

11

따라서 Clojure에서이 방법의 효과 사이에 많은 논의가있는 것 같습니다. Rich Hickey의 철학을 보면 왜 이런 방식으로 데이터 추상화를 지원하기 위해 Clojure를 만들 었는지 알 수있을 것입니다 .

포 거스 : 일단 우발적 인 복잡성이 줄어들면 Clojure가 어떻게 문제를 해결하는 데 도움이 될 수 있습니까? 예를 들어, 이상적인 객체 지향 패러다임은 재사용을 촉진하기위한 것이지만 Clojure는 고전적으로 객체 지향적이지 않습니다. 어떻게 재사용 코드를 구성 할 수 있습니까?

Hickey : OO에 대해 논쟁하고 재사용 할 것입니다. 그러나 물건을 재사용 할 수 있으면 자동차를 만드는 대신 바퀴를 재발 명하지 않기 때문에 문제를 더 간단하게 만들 수 있습니다. 그리고 Clojure가 JVM에 있으면 많은 휠 (라이브러리)을 사용할 수 있습니다. 도서관을 재사용 할 수있게 만드는 요인은 무엇입니까? 하나 또는 몇 가지 일을 잘 수행하고 상대적으로 자급 자족해야하며 클라이언트 코드에 대한 요구가 거의 없습니다. 그 중 어느 것도 OO에서 벗어나지 않으며 모든 Java 라이브러리 가이 기준을 충족 시키지는 않지만 많은 경우가 있습니다.

알고리즘 수준으로 내려 가면 OO가 재사용을 심각하게 방해 할 수 있다고 생각합니다. 특히, 간단한 정보 데이터를 표현하기 위해 객체를 사용하는 것은 정보 별 마이크로 언어, 즉 클래스 방법, 관계형 대수와 같은 훨씬 강력하고 선언적이며 일반적인 방법의 생성에서 거의 범죄입니다. 정보를 담을 수있는 자체 인터페이스가있는 클래스를 발명하는 것은 모든 단편 소설을 작성할 수있는 새로운 언어를 발명하는 것과 같습니다. 이것은 재사용 방지이며 일반적인 OO 응용 프로그램에서 코드가 폭발적으로 증가한다고 생각합니다. Clojure는 이것을 피하고 대신 정보에 대한 간단한 연관 모델을 옹호합니다. 이를 통해 정보 유형 전체에서 재사용 할 수있는 알고리즘을 작성할 수 있습니다.

이 연관 모델은 Clojure와 함께 제공되는 몇 가지 추상화 중 하나 일 뿐이며, 재사용에 대한 접근 방식의 진정한 기초는 추상화에 대한 함수입니다. 알고리즘을 재사용하고 라이브러리 상호 운용성을 유지하려면 개방적이고 규모가 큰 기능 집합이 개방적이고 작고 확장 가능한 추상화 집합에서 작동해야합니다. Clojure 함수의 대부분은 이러한 추상화의 관점에서 정의되며, 라이브러리 작성자는 독립적으로 개발 된 라이브러리 간의 엄청난 상호 운용성을 실현하기 위해 입력 및 출력 형식을 디자인합니다. 이것은 DOM 및 OO에서 볼 수있는 다른 것들과 완전히 대조적입니다. 물론, 인터페이스 (예 : java.util 콜렉션)를 사용하여 OO에서 유사한 추상화를 수행 할 수 있지만 java.io 에서처럼 쉽게 수행 할 수 없습니다.

Fogus는 그의 저서 Functional Javascript 에서 이러한 요점을 반복합니다 .

이 책 전체에서, 세트에서 트리, 테이블에 이르기까지 추상화를 표현하기 위해 최소 데이터 유형을 사용하는 방법을 사용합니다. 그러나 JavaScript에서는 객체 유형이 매우 강력하지만 해당 객체 유형과 함께 작동하도록 제공된 도구가 완전히 작동하지는 않습니다. 대신 JavaScript 객체와 관련된 더 큰 사용 패턴은 다형성 디스패치 목적으로 메소드를 첨부하는 것입니다. 고맙게도 명명되지 않은 (생성자 함수를 통해 빌드되지 않은) JavaScript 객체를 단순한 연관 데이터 저장소로 볼 수도 있습니다.

Book 객체 또는 Employee 유형의 인스턴스에서 수행 할 수있는 유일한 작업이 setTitle 또는 getSSN이면 데이터를 정보 별 마이크로 언어로 잠근 것입니다 (Hickey 2011). 데이터 모델링에 대한보다 유연한 접근법은 연관 데이터 기술입니다. 프로토 타입 기계를 제외한 JavaScript 객체는 연관 데이터 모델링을위한 이상적인 수단으로, 명명 된 값을 구성하여 균일 한 방식으로 액세스하여 더 높은 수준의 데이터 모델을 구성 할 수 있습니다.

데이터 맵으로 JavaScript 객체를 조작하고 액세스하기위한 도구는 JavaScript 자체에서 드물지만 Underscore는 유용한 작업을 제공합니다. 파악하는 가장 간단한 함수 중에는 _.keys, _.values ​​및 _.pluck이 있습니다. _.keys 및 _.values는 기능에 따라 이름이 지정되는데, 이는 객체를 가져와 해당 키 또는 값의 배열을 반환하는 것입니다.


2
나는이 Fogus / Hickey 인터뷰를 전에 읽었지만 지금까지 그가 말한 것을 이해할 수 없었습니다. 답변 주셔서 감사합니다. 아직도 Hickey / Fogus가 내 디자인에 축복을 줄 것인지 확실하지 않습니다. 나는 그들의 조언의 정신을 극단으로 끌어 들인 것에 대해 걱정하고 있습니다.
Daniel Kaplan

9

악마의 옹호자

나는이 질문에 악마의 옹호자가 필요하다고 생각한다 (물론 나는 편견이다). @KarlBielefeldt가 매우 좋은 지적을하고 있다고 생각합니다. 먼저 그의 요점이 훌륭하다고 말하고 싶습니다.

그가 함수형 프로그래밍 에서조차 이것이 좋은 패턴이 아니라고 언급했기 때문에, 나는 답장에서 JavaScript 및 / 또는 Clojure를 고려할 것입니다. 이 두 언어 사이의 매우 중요한 유사점은 동적으로 형식화된다는 것입니다. Java 또는 Haskell과 같이 정적으로 유형이 지정된 언어로 이것을 구현한다면 그의 요점에 더 동의 할 것입니다. 그러나 "Everything is a Map"패턴에 대한 대안을 정적 인 유형의 언어가 아닌 JavaScript 의 전통적인 OOP 디자인 으로 고려할 것입니다. 알려주세요).

예를 들어, 각 하위 프로세스 기능에 대한 다음 질문에 대답하십시오.

  • 어떤 주 분야가 필요합니까?

  • 어떤 필드를 수정합니까?

  • 변경하지 않은 필드는 무엇입니까?

동적으로 입력되는 언어에서 일반적으로 이러한 질문에 어떻게 대답 하시겠습니까? 함수의 첫 번째 매개 변수는이라고 할 수 foo있지만 그게 무엇입니까? 배열? 객체? 객체 배열의 객체? 어떻게 알아? 내가 아는 유일한 방법은

  1. 설명서를 읽으십시오
  2. 함수 본문을보십시오
  3. 테스트를 봐
  4. 프로그램을 추측하고 실행하여 작동하는지 확인하십시오.

"모든 것이지도 다"패턴이 여기에 어떤 차이를 만들지 않는다고 생각합니다. 이것들은 여전히 ​​내가이 질문에 대답하는 유일한 방법입니다.

또한 JavaScript 및 대부분의 명령형 프로그래밍 언어에서는 function액세스 할 수있는 모든 상태를 요구, 수정 및 무시할 수 있으며 서명은 아무런 차이가 없습니다. 서명은 종종 거짓말입니다.

"Everything is a Map"과 잘못 설계된 OO 코드 사이에 잘못된 이분법을 설정하려고하지 않습니다 . 더 작거나 더 미세하고 거친 입자 매개 변수를 사용하는 서명이 있다고해서 함수를 분리, 설정 및 호출하는 방법을 알 수있는 것은 아닙니다.

그러나 내가 틀린 이분법을 사용할 수있게한다면 : 전통적인 OOP 방식으로 JavaScript를 작성하는 것과 비교할 때 "Everything is a Map"이 더 좋습니다. 전통적인 OOP 방식에서 함수는 전달한 상태 또는 전달하지 않은 상태를 요구, 수정 또는 무시할 수 있습니다 .이 "모든 것이 맵"패턴을 사용하면 전달한 상태 만 요구, 수정 또는 무시합니다. 에서.

  • 기능의 순서를 안전하게 재 배열 할 수 있습니까?

내 코드에서는 그렇습니다. @Evicatos의 답변에 대한 두 번째 의견을 참조하십시오. 아마도 이것은 내가 게임을하고 있기 때문에 만 가능하지만 말할 수는 없습니다. 60 배 두 번째 업데이트있어 게임에, 정말 만약 문제가되지 않습니다 dead guys drop lootgood guys pick up loot또는 그 반대의 경우도 마찬가지입니다. 각 함수는 실행 순서에 관계없이 수행해야 할 작업을 계속 수행합니다. update주문을 바꾸면 동일한 데이터가 다른 전화로 전달됩니다. 당신이있는 경우 good guys pick up loot다음 dead guys drop loot, 좋은 사람은 다음의 전리품을 데리러 update그것은 별거 아니. 인간은 그 차이를 알아 차리지 못할 것입니다.

적어도 이것은 나의 일반적인 경험이었습니다. 나는 이것을 공개적으로 인정하는 것이 정말로 취약하다고 느낀다. 어쩌면이 괜찮아 질을 고려하는 것은입니다 매우 , 매우 할 나쁜 일. 내가 여기서 끔찍한 실수를했는지 알려주십시오. 내가 가진다면, 그것은 순서가되도록 기능을 다시 정렬하는 것이 매우 쉽다 dead guys drop loot다음 good guys pick up loot다시. 이 단락을 쓰는 데 걸리는 시간보다 시간이 덜 걸립니다. : P

아마도 당신은 "죽은 놈들 먼저 전리품 떨어 뜨려야 한다고 생각할 것 입니다. 당신의 코드가 그 명령을 시행한다면 더 좋을 것입니다". 그러나 전리품을 집어 들기 전에 왜 적들이 전리품을 떨어 뜨려야합니까? 이해가되지 않는 나에게. 전리품이 100 년 updates전에 떨어졌을 수도 있습니다. 임의의 나쁜 사람이 이미 땅에있는 전리품을 가져와야하는지 확인할 필요가 없습니다. 그렇기 때문에 이러한 작업의 순서는 완전히 임의적이라고 생각합니다.

이 패턴으로 분리 된 단계를 작성하는 것은 당연하지만 전통적인 OOP에서는 결합 된 단계를 알아 채기가 어렵습니다. 내가 전통적인 OOP를 쓰고 있었다면, 자연스럽고 순진한 사고 방식은 그 dead guys drop loot결과 Loot를 내가 전달해야 할 대상으로 만드는 것 good guys pick up loot입니다. 첫 번째는 두 번째 입력을 반환하므로 해당 작업을 재정렬 할 수 없습니다.

객체 지향 언어에서는 추적 상태가 객체의 기능이기 때문에 패턴의 의미가 떨어집니다.

객체에는 상태가 있으며 추적하기 위해 코드를 수동으로 작성하지 않는 한 상태를 변경하여 관례를 사라지는 것은 관용적입니다. 상태 추적은 "어떻게"합니까?

또한 불변의 이점은 불변의 객체가 커질수록 줄어 듭니다.

내가 말했듯이 "내 기능이 순수한 것은 드물다". 그들은 항상 자신의 매개 변수 에서만 작동하지만 매개 변수를 변경합니다. 이것은 JavaScript에이 패턴을 적용 할 때해야한다고 생각한 절충안입니다.


4
"함수의 첫 번째 매개 변수 이름은 foo 일 수 있지만 그게 무엇입니까?" 그렇기 때문에 매개 변수 이름을 "foo"로 지정하지 않고 "반복", "부모"및 함수 이름과 결합 할 때 예상되는 것을 명확하게하는 다른 이름의 이름을 지정합니다.
Sebastian Redl

1
나는 모든 점에서 당신과 동의해야합니다. Javascript가 실제로이 패턴으로 제기하는 유일한 문제는 변경 가능한 데이터를 작업하고 있으므로 상태를 변경할 가능성이 높다는 것입니다. 그러나 일반 자바 스크립트로 클로저 데이터 구조에 액세스 할 수있는 라이브러리가 있습니다. 인수를 객체로 전달하는 것도 듣지 못하지만 jquery는이 여러 위치를 수행하지만 객체의 어떤 부분을 사용하는지 문서화합니다. 개인적으로하지만, 나는 UI 필드와 GameLogic 필드를 분리해서 것이지만, 어떤이 : 당신을 위해 작동
로빈 Heggelund 한센

@SebastianRedl 무엇을 전달해야 parent합니까? 가 repetitions숫자 또는 문자열 배열 또는 그것은 중요하지 않습니다? 아니면입니다 반복 단지 내가 원하는 repretitions의 수를 나타내는 숫자가? 옵션 객체를 취하는 많은 API가 있습니다 . 물건의 이름을 올바르게 지정하면 세상이 더 좋은 곳이지만 질문을하지 않고도 API 사용법을 알 수 있다고 보장하지는 않습니다.
Daniel Kaplan

8

내 코드가 다음과 같이 구조화되는 경향이 있음을 발견했습니다.

  • 맵을 취하는 함수는 더 큰 경향이 있고 부작용이 있습니다.
  • 인수를 취하는 함수는 더 작고 순수한 경향이 있습니다.

나는이 구별을 만들기 시작하지 않았지만 그것이 종종 내 코드에서 끝나는 방식입니다. 한 스타일을 사용하는 것이 다른 스타일을 반드시 부정한다고 생각하지 않습니다.

순수한 기능은 단위 테스트가 쉽습니다. 지도가있는 큰 것은 더 많은 움직이는 부분을 포함하는 경향이 있기 때문에 "통합"테스트 영역에 더 많이 들어갑니다.

자바 스크립트에서 많은 도움이되는 것은 매개 변수 유효성 검사를 수행하기 위해 Meteor 's Match 라이브러리와 같은 것을 사용하는 것입니다. 함수가 기대하는 것을 매우 명확하게하고 맵을 매우 깨끗하게 처리 할 수 ​​있습니다.

예를 들어

function foo (post) {
  check(post, {
    text: String,
    timestamp: Date,
    // Optional, but if present must be an array of strings
    tags: Match.Optional([String])
    });

  // do stuff
}

자세한 내용은 http://docs.meteor.com/#match 를 참조 하십시오 .

:: 업데이트 ::

스튜어트 시에라의 Clojure / West의 "Clojure in the Large" 비디오 녹화 도이 주제를 다루고 있습니다. OP와 마찬가지로 그는 맵의 일부로 부작용을 제어하므로 테스트가 훨씬 쉬워졌습니다. 또한 현재 Clojure 워크 플로우를 설명 하는 블로그 게시물이 있습니다.


1
@Evicatos에 대한 나의 의견은 여기에서의 나의 입장에 대해 자세히 설명 할 것이라고 생각합니다. 예, 변경 중이며 기능이 순수하지 않습니다. 그러나, 내 기능은 정말 , 특히 내가 테스트를 계획하지 않은 회귀 결함으로 돌이켜에서 테스트하기 쉬운. 학점의 절반은 JS로갑니다. 테스트에 필요한 데이터만으로 "맵"/ 객체를 구성하는 것은 매우 쉽습니다. 그런 다음 전달하고 돌연변이를 확인하는 것만 큼 간단합니다. 부작용은 항상 표현 지도, 그래서 그들은 테스트하기 쉽다.
Daniel Kaplan

1
두 방법을 실용적으로 사용하는 것이 "올바른"방법이라고 생각합니다. 테스트가 쉬우면서 필요한 필드를 다른 개발자에게 전달하는 메타 문제를 완화 할 수 있다면 그것은 승리처럼 들립니다. 질문 해 주셔서 감사합니다. 나는 당신이 시작한 흥미로운 토론을 읽는 것을 즐겼습니다.
alanning

5

이 관행에 대해 내가 생각할 수있는 주요 주장은 함수가 실제로 어떤 데이터를 필요로하는지 말하기가 매우 어렵다는 것입니다.

즉, 코드베이스의 미래 프로그래머는 호출하는 함수가 호출하기 위해 호출되는 함수가 내부적으로 작동하고 중첩 된 함수 호출이 어떻게 작동하는지 알아야합니다.

내가 그것에 대해 더 많이 생각할수록 gameState 객체는 전역 냄새가 더납니다. 그것이 그것이 사용되는 방법이라면, 왜 그것을 전달합니까?


1
예, 일반적으로 변경하기 때문에 전역입니다. 왜 지나가는가? 잘 모르겠습니다. 유효한 질문입니다. 그러나 내 직감은 그것을 통과하지 않으면 내 프로그램이 즉시 추론하기가 더 어려워 질 것이라고 말합니다 . 모든 함수 는 전역 상태에 대해 모든 것을 수행 하거나 전혀 수행하지 않을 수 있습니다. 지금과 같은 방식으로 함수 시그니처의 가능성을 알 수 있습니다. 당신이 말할 수 없다면, 나는 이것에 대해 확신하지 않습니다 :)
Daniel Kaplan

1
BTW : re : 그것에 대한 주요 주장 : 클로저 또는 자바 스크립트에 있었는지 여부는 사실처럼 보입니다. 그러나 그것은 중요한 가치입니다. 아마도 나열된 이점은 그 단점보다 훨씬 큽니다.
Daniel Kaplan

2
나는 그것이 전역 변수인데도 왜 그것을 전달하는 것을 귀찮게하는지 알고 있습니다. 순수한 함수를 작성할 있습니다. 내 변경하는 경우 gameState = f(gameState)f(), 그것은 훨씬 더 어려운 시험에입니다 f. f()전화 할 때마다 다른 것을 반환 할 수 있습니다. 그러나 f(gameState)동일한 입력이 주어질 때마다 같은 것을 반환하는 것은 쉽습니다 .
Daniel Kaplan

3

Big ball of mud 보다 당신이하는 일에 더 적합한 이름이 있습니다 . 당신이하고있는 것을 신의 대상 패턴 이라고합니다 . 언뜻보기에는 그런 식으로 보이지 않지만 Javascript에서는 약간의 차이가 있습니다.

update(gameState)
  ...
  gameState = handleUnitCollision(gameState)
  ...
  gameState = handleLoot(gameState)
  ...

{
  ...
  handleUnitCollision: function() {
    ...
  },
  ...
  handleLoot: function() {
    ...
  },
  ...
  update: function() {
    ...
    this.handleUnitCollision()
    ...
    this.handleLoot()
    ...
  },
  ...
};

그것이 좋은 아이디어인지 아닌지는 아마도 상황에 달려 있습니다. 그러나 그것은 Clojure 방식과 일치합니다. Clojure의 목표 중 하나는 Rich Hickey 가 "사고 복잡성"이라고 부르는 것을 제거하는 것 입니다. 여러 통신 개체는 단일 개체보다 확실히 더 복잡합니다. 기능을 여러 개체로 나누면 갑자기 의사 소통과 조정 및 책임 분담에 대해 걱정해야합니다. 이는 프로그램 작성의 원래 목표에 부수적 인 합병증입니다. Rich Hickey의 연설이 간단 해졌 습니다 . 나는 이것이 매우 좋은 생각이라고 생각합니다.


관련되고 더 일반적인 질문 [ programmers.stackexchange.com/questions/260309/… 데이터 모델링 대 기존 클래스)
user7610

"객체 지향 프로그래밍에서 신 객체는 너무 많이 알고 있거나 너무 많은 객체이다. 신 객체는 반 패턴의 예이다." 신의 대상은 좋지 않지만, 당신의 메시지는 반대라고 말하는 것 같습니다. 저에게는 약간 혼란 스럽습니다.
Daniel Kaplan

@tieTYT 당신은 객체 지향 프로그래밍을하고 있지 않으므로 괜찮습니다
user7610

그 결론에 어떻게 도달 했습니까 ( "괜찮습니다")?
Daniel Kaplan

OO에서 신 객체의 문제점은 "객체가 모든 것을 인식하게되거나 모든 객체가 신 객체에 의존하게되어 수정이나 버그가있을 때 구현하기에는 악몽이된다"는 것이다. 소스 코드에는 God 객체 옆에 다른 객체가 있으므로 두 번째 부분은 문제가되지 않습니다. 첫 번째 부분과 관련하여, 당신의 신 목표 프로그램이며 프로그램은 모든 것을 알고 있어야합니다. 그래도 괜찮습니다.
user7610

2

나는 새로운 프로젝트를 가지고 놀면서 오늘 일찍이 주제에 직면했습니다. Clojure에서 포커 게임을하고 있습니다. 나는 액면가와 양복을 키워드로 나타내고 카드를 다음과 같이지도로 나타내기로 결정했습니다.

{ :face :queen :suit :hearts }

두 키워드 요소의 목록이나 벡터를 만들 수도 있습니다. 메모리 / 성능에 차이가 있는지 모르겠으므로 지금은지도를 사용하고 있습니다.

나중에 마음이 바뀌면 프로그램의 대부분이 "인터페이스"를 통해 카드 조각에 액세스하여 구현 세부 사항을 제어하고 숨기도록 결정했습니다. 나는 기능을 가지고있다

(defn face [card] (card :face))
(defn suit [card] (card :suit))

나머지 프로그램에서 사용합니다. 카드는 맵으로 함수에 전달되지만 함수는 합의 된 인터페이스를 사용하여 맵에 액세스하므로 엉망이 될 수 없습니다.

내 프로그램에서 카드는 아마도 2 값 맵 일 것입니다. 문제에서 전체 게임 상태는지도로 전달됩니다. 게임 상태는 단일 카드보다 훨씬 복잡하지만 맵 사용에 대해 제기해야 할 결점이 없다고 생각합니다. 객체-제국 언어에서 하나의 큰 GameState 객체를 가지고 메소드를 호출 할 수 있으며 같은 문제가 있습니다.

class State
  def complex-process()
    state = clone(this) ; or just use 'this' below if mutation is fine
    state.subprocess-one()
    state.subprocess-two()
    state.subprocess-three()
    return state

이제 객체 지향적입니다. 특히 문제가 있습니까? 나는 그렇게 생각하지 않으며, State 객체를 처리하는 방법을 알고있는 함수에 작업을 위임하고 있습니다. 그리고 맵이나 객체로 작업하든, 작은 조각으로 분할 할 때주의해야합니다. 따라서 객체를 사용할 때와 동일한주의를 기울이는 한지도를 사용하는 것이 좋습니다.


2

내가 본 (작은) 것으로부터, 이와 같은 단일 전역 불변 상태 객체를 만들기 위해 맵이나 다른 중첩 구조를 사용하는 것은 기능 언어, 적어도 순수한 언어에서 상당히 일반적입니다. 특히 스테이트 모나드를 @ Ptharien'sFlame 으로 사용할 때 mentioend .

내가 보거나 읽은 (및 여기에 언급 된 다른 답변)을 효과적으로 사용하기위한 두 가지 장애물은 다음과 같습니다.

  • (불변) 상태에서 (깊게) 중첩 된 값을 뮤팅
  • 필요하지 않은 함수에서 대부분의 상태를 숨기고 작업 / 변경에 필요한 약간의 비트 만 제공

이러한 문제를 완화하는 데 도움이되는 몇 가지 기술 / 공통 패턴이 있습니다.

첫 번째는 Zippers입니다 . 이들은 불변의 중첩 계층 구조 내에서 상태를 깊이 이동하고 변경할 수 있습니다.

또 다른 하나는 렌즈입니다 .이 렌즈 를 사용하면 구조에 특정 위치에 초점맞추고 값을 읽거나 변경할 수 있습니다 . OOP의 조정 가능한 속성 체인과 같이 다른 렌즈를 함께 사용하여 다른 것에 초점을 맞출 수 있습니다 (실제 속성 이름으로 변수를 대체 할 수 있습니다!)

Prismatic은 최근 JavaScript / ClojureScript에서 이러한 종류의 기술을 사용 하는 방법에 대한 블로그 게시물 을 보았습니다. 그들은 기능을 위해 커서를 (지퍼와 비교하여) 창 상태로 사용합니다.

Om은 커서를 사용하여 캡슐화 및 모듈성을 복원합니다. 커서는 지퍼와 같이 응용 프로그램 상태의 특정 부분에 업데이트 가능한 창을 제공하여 구성 요소가 전역 상태의 관련 부분 만 참조하고 컨텍스트없이 업데이트 할 수 있도록합니다.

IIRC는 또한 해당 게시물에서 JavaScript의 불변성을 다루고 있습니다.


언급 된 토크 OP 는 함수가 상태 맵의 하위 트리로 업데이트 할 수있는 범위를 제한하기 위해 업데이트 기능을 사용하는 방법에 대해서도 설명합니다 . 아직 아무도 이것을 제기하지 않았다고 생각합니다.
user7610

@ user7610 캐치, 나는 그 기능을 언급하는 것을 잊었다 고 믿을 수 없다. 나는 그 기능을 좋아한다 assoc-in. 방금 하스켈이 뇌에 있다고 생각합니다. 누군가가 JavaScript 포트를 사용했는지 궁금합니다. 사람들은 아마 나처럼 대화를 보지 못했기 때문에 그것을 가져 오지 않았을 것입니다 :)
paul

@paul은 ClojureScript에서 사용할 수 있기 때문에 의미가 있지만 마음에 "계산"되는지 확실하지 않습니다. PureScript에있을 수 있으며 JavaScript로 변경할 수없는 데이터 구조를 제공하는 라이브러리가 하나 이상 있다고 생각합니다. 나는 그들 중 적어도 하나가 이것들을 가지고 있기를 바랍니다. 그렇지 않으면 사용하기 불편합니다.
Daniel Kaplan

@tieTYT 나는 그 의견을 말할 때 네이티브 JS 구현을 생각했지만 ClojureScript / PureScript에 대해 좋은 지적을합니다. 나는 불변의 JS를 살펴보고 거기에 무엇이 있는지보아야한다. 나는 전에는 일하지 않았다.
paul

1

이것이 좋은 아이디어인지 아닌지 여부는 실제로 하위 프로세스 내부의 상태로 수행중인 작업에 달려 있습니다. Clojure 예제를 올바르게 이해하면 반환되는 상태 사전은 전달되는 상태 사전과 동일하지 않습니다. 추가 및 수정 사항이있을 수있는 사본입니다. 언어의 기능적 특성은 언어에 달려 있습니다. 각 함수에 대한 원래 상태 사전은 어떤 식 으로든 수정되지 않습니다.

내가 제대로 이해하고있어 경우에, 당신은 하는 자바 스크립트 함수로 당신이 통과 상태의 오브젝트를 수정하기보다는 그 Clojure의 코드가 무엇을하고 있는지 매우, 매우 다른 일을 의미 사본을 반환. Mike Partridge가 지적했듯이 이것은 기본적으로 실제 이유없이 명시 적으로 함수에 전달하고 함수에서 반환하는 전역입니다. 이 시점에서 나는 그것이 실제로 당신이 실제로하지 않은 일을하고 있다고 생각 하게 만드는 것이라고 생각 합니다.

실제로 명시 적으로 상태의 사본을 작성하고 수정 한 다음 수정 된 사본을 리턴하는 경우 계속하십시오. 필자가 이것이 자바 스크립트에서 수행하려는 작업을 수행하는 가장 좋은 방법인지 확실하지 않지만 Clojure 예제가 수행하는 작업과 "가까운"것 같습니다.


1
결국 그것은이다 정말 "매우, 매우 다른"? Clojure 예제에서 그는 이전 상태를 새 상태로 덮어 씁니다. 그렇습니다. 실제 변이는 없으며 정체성은 변하고 있습니다. 그러나 그의 "좋은"예제에서, 그는 서브 프로세스 2로 전달 된 사본을 얻을 방법이 없습니다. 해당 값의 식별을 덮어 썼습니다. 따라서 "매우, 매우 다른"것은 언어 구현 세부 사항 일 뿐이라고 생각합니다. 적어도 당신이 자란 것의 맥락에서.
Daniel Kaplan

2
Clojure 예제에는 두 가지가 있습니다. 1) 첫 번째 예제는 특정 순서로 호출되는 함수에 따라 다르며 2) 함수는 순수하므로 부작용이 없습니다. 두 번째 예의 함수는 순수하고 동일한 서명을 공유하므로 호출 순서에 따라 숨겨진 종속성에 대해 걱정할 필요없이 순서를 바꿀 수 있습니다. 함수에서 상태를 변경하면 동일한 보장이 없습니다. 상태 돌연변이는 버전이 컴포지션 할 수 없음을 의미하며, 사전의 원래 이유였습니다.
Evicatos

1
이것에 대한 나의 경험에서, 나는 마음대로 물건을 움직일 수 있고 효과가 거의 없기 때문에 당신에게 나에게 예를 보여 주어야 할 것입니다. 스스로 증명하기 위해 update()함수 중간에 두 개의 임의의 하위 프로세스 호출을 이동했습니다 . 하나는 맨 위로, 하나는 맨 아래로 이동했습니다. 모든 테스트가 여전히 통과되었고 게임을 할 때 아무런 효과가 없었습니다. 내 함수는 느낌이 바로 Clojure의 예제로 작성 가능. 우리는 각 단계마다 이전 데이터를 버리고 있습니다.
다니엘 카플란

1
테스트가 통과하고 악영향을주지 않으면 현재 다른 곳에서 예기치 않은 부작용이있는 상태를 변경하지 않는 것입니다. 함수가 순수하지 않기 때문에 항상 그렇다는 보장은 없습니다. 함수가 순수하지 않지만 각 단계 후에 이전 데이터를 버리고 있다고하면 구현에 대해 근본적으로 오해해야한다고 생각합니다.
Evicatos

1
@Evicatos-순수하고 동일한 서명이 있다고해서 기능의 순서가 중요하지 않다는 것을 의미하지는 않습니다. 고정 및 비율 할인이 적용된 가격을 계산한다고 상상해보십시오. (-> 10 (- 5) (/ 2))2.5를 반환합니다. (-> 10 (/ 2) (- 5))0 반환

1

각 프로세스에 전달되는 "신 개체"라고도하는 전역 상태 개체가있는 경우 여러 요소를 혼란스럽게하는데,이 요소는 모두 결합을 증가시키면서 응집력을 감소시킵니다. 이러한 요소는 모두 장기적인 유지 보수성에 부정적인 영향을 미칩니다.

트램프 커플 링 (Tramp Coupling) 이것은 거의 모든 데이터를 필요로하지 않는 다양한 방법을 통해 데이터를 실제로 처리 할 수있는 장소로 가져 오기 위해 발생합니다. 이러한 종류의 커플 링은 전역 데이터를 사용하는 것과 유사하지만 더 많이 포함될 수 있습니다. 트랩 커플 링은 "알아야 할 필요"와 반대입니다.이 기능은 효과를 지역화하고 하나의 잘못된 코드 조각이 전체 시스템에 미칠 수있는 손상을 포함하는 데 사용됩니다.

데이터 탐색 예제의 모든 하위 프로세스는 필요한 데이터를 정확하게 얻는 방법을 알아야하며이를 처리하고 새로운 전역 상태 객체를 구성 할 수 있어야합니다. 이것이 트램프 커플 링의 논리적 결과입니다. 데이텀에서 작동하려면 데이텀의 전체 컨텍스트가 필요합니다. 다시, 비 국소 지식은 나쁜 것입니다.

@paul의 게시물에 설명 된 것처럼 "지퍼", "렌즈"또는 "커서"를 전달하는 경우 한 가지입니다. 액세스를 포함하고 지퍼 등이 데이터 읽기 및 쓰기를 제어 할 수있게합니다.

단일 책임 위반 "서브 프로세스 1", "서브 프로세스 2"및 "서브 프로세스 3"각각은 단일 책임, 즉 올바른 값을 가진 새로운 전역 상태 객체를 생성해야한다는 주장은 엄청나게 줄어 듭니다. 결국 모든 것이 아닌가?

여기서 중요한 점은 게임의 모든 주요 구성 요소가 위임 및 팩토링의 목적을 무너 뜨리는 것과 동일한 책임을진다는 것입니다.

시스템 영향

설계의 주요 영향은 유지 보수성이 낮다는 것입니다. 전체 게임을 머리 속에 담을 수 있다는 사실은 훌륭한 프로그래머 일 가능성이 높습니다. 전체 프로젝트를 위해 머리 속에 담을 수 있도록 디자인 한 것들이 많이 있습니다. 하지만 시스템 엔지니어링의 핵심은 아닙니다. 요점은 한 사람 이 동시에 머리 둘 수있는 것보다 큰 일을 할 수있는 시스템을 만드는 것 입니다.

다른 프로그래머 또는 2 명 또는 8 명을 추가하면 시스템이 거의 즉시 분리됩니다.

  1. 신의 대상에 대한 학습 곡선은 평평하다 (즉, 대상에 능숙 해지는 데 시간이 오래 걸린다). 각 추가 프로그래머는 당신이 알고있는 모든 것을 배우고 그들의 머리 속에 두어야합니다. 당신은 거대한 신의 대상을 유지함으로써 고통을 겪을만큼 충분히 지불 할 수 있다고 가정 할 때, 당신보다 더 나은 프로그래머를 고용 할 수있을 것입니다.
  2. 테스트 프로비저닝은 설명에서 흰색 상자 일뿐입니다. 테스트를 설정하고, 실행하고, a) 옳은 일을했는지, b) 아무 것도하지 않았다는 것을 결정하기 위해서는 테스트중인 모듈뿐만 아니라 신 객체의 모든 세부 사항을 알아야합니다. 10,000 개의 잘못된 것. 확률은 당신에게 크게 쌓입니다.
  3. 새로운 기능을 추가하려면 a) 모든 하위 프로세스를 거치고 해당 기능이 코드에 영향을 미치는지 여부를 결정하고 그 반대의 경우도 마찬가지입니다. b) 글로벌 상태를 통해 추가를 디자인하고 c) 모든 단위 테스트를 거쳐 수정해야합니다. 테스트중인 장치가 새 기능에 부정적인 영향을 미치지 않았는지 확인합니다 .

드디어

  1. 변하기 쉬운 신의 대상은 내 프로그래밍 존재의 저주, 내 자신의 일, 내가 갇힌 일부의 저주였습니다.
  2. 상태 모나드는 확장되지 않습니다. 테스트와 운영에 대한 모든 함의와 함께 상태는 기하 급수적으로 증가합니다. 최신 시스템에서 상태를 제어하는 ​​방법은 위임 (책임 분할) 및 범위 지정 (상태의 하위 집합에 대한 액세스 제한)입니다. "모든 것이 맵"접근 방식은 상태 제어와 정반대입니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.