이벤트 중심 코드를 쉽게 유지 관리하는 방법은 무엇입니까?


16

이벤트 기반 구성 요소를 사용할 때 종종 유지 보수 단계에서 약간의 고통을 느낍니다.

실행 된 코드가 모두 분리되어 있기 때문에 런타임에 포함될 모든 코드 부분이 무엇인지 파악하기가 매우 어려울 수 있습니다.

이로 인해 누군가 새로운 이벤트 핸들러를 추가 할 때 미묘하고 디버그하기 어려운 문제가 발생할 수 있습니다.

주석에서 편집 : 응용 프로그램 전체 이벤트 버스 및 응용 프로그램의 다른 부분으로 비즈니스를 위임하는 처리기를 사용하는 등의 모범 사례가 있더라도 코드가 많기 때문에 코드를 읽기가 어려워지는 순간이 있습니다. 여러 장소에서 등록 된 핸들러 (특히 버스가있는 경우에 해당)

그런 다음 시퀀스 다이어그램이 복잡해지기 시작하고, 발생하는 상황을 파악하는 데 소요되는 시간이 길어지고 디버깅 세션이 지저분 해집니다 (핸들러에서 반복하는 동안 핸들러 관리자의 중단 점, 특히 비동기 핸들러 및 그 위에 일부 필터링 사용).

///////////////

서버에서 일부 데이터를 검색하는 서비스가 있습니다. 클라이언트에는 콜백을 사용하여이 서비스를 호출하는 기본 구성 요소가 있습니다. 구성 요소 사용자에게 확장 점을 제공하고 다른 구성 요소 간의 연결을 피하기 위해 쿼리가 전송되기 전에, 응답이 다시 올 때, 실패 할 때로 다른 이벤트가 발생합니다. 컴포넌트의 기본 동작을 제공하는 사전 등록 된 기본 핸들러 세트가 있습니다.

이제 구성 요소 사용자 (및 구성 요소 사용자도 있음)는 처리기 (조회, 로그, 데이터 분석, 데이터 필터링, 데이터 마사지, UI 고급 애니메이션, 여러 순차 쿼리 수정)를 수행하기 위해 처리기를 추가 할 수 있습니다. ) 따라서 일부 처리기는 다른 처리기 전후에 실행해야하며 응용 프로그램의 다양한 진입 점에서 등록됩니다.

잠시 후 12 개 이상의 핸들러가 등록 될 수 있으며이 작업을 수행하는 것은 지루하고 위험 할 수 있습니다.

상속을 사용하는 것이 완전히 혼란스러워지기 시작했기 때문에이 디자인이 등장했습니다. 이벤트 시스템은 합성물이 무엇인지 아직 모르는 일종의 구성으로 사용됩니다.

예제 끝
//////////////

그래서 다른 사람들이 어떻게 이런 종류의 코드를 다루는 지 궁금합니다. 읽고 쓸 때.

큰 어려움없이 그러한 코드를 작성하고 유지할 수있는 방법이나 도구가 있습니까?


당신은 의미 외에 이벤트 핸들러에서 논리에서 리팩토링?
Telastyn

진행 상황을 기록하십시오.

@Telastyn, 나는 '이벤트 핸들러에서 논리를 리팩토링하는 것 외에'라는 것이 무엇을 의미하는지 완전히 이해하지 못했습니다.
기 illa

@Thorbjoern : 내 업데이트를 참조하십시오.
기 illa

3
작업에 올바른 도구를 사용하지 않는 것 같습니까? 주문이 중요하다면, 처음부터 응용 프로그램의 해당 부분에 대해 일반 이벤트를 사용해서는 안됩니다. 기본적으로 일반적인 버스 시스템에서 이벤트 순서에 제한이있는 경우 더 이상 일반적인 버스 시스템은 아니지만 여전히 그렇게 사용하고 있습니다. 즉, 주문을 처리하기 위해 추가 로직을 추가하여 해당 문제를 해결해야합니다. 적어도 당신의 문제를 올바르게 이해한다면 ..
stijn

답변:


7

내부 이벤트 스택을 사용하여 이벤트를 처리하면 (특히, 임의 제거 기능이있는 LIFO 대기열) 이벤트 구동 프로그래밍이 크게 단순화됩니다. "외부 이벤트"처리를 여러 개의 작은 "내부 이벤트"로 나눌 수 있으며, 그 사이에 상태가 잘 정의되어 있습니다. 자세한 내용은 이 질문에 대한 답변을 참조하십시오 .

여기에이 패턴으로 해결되는 간단한 예가 나와 있습니다.

서비스 A를 수행하기 위해 객체 A를 사용하고 있고 완료되면이를 알리기 위해 콜백을 제공한다고 가정합니다. 그러나 A는 콜백을 호출 한 후 더 많은 작업을 수행해야 할 수도 있습니다. 콜백 내에서 A가 더 이상 필요하지 않다고 결정하고 어떤 식 으로든 파괴하면 위험이 발생합니다. 그러나 콜백이 반환 된 후 A가 콜백이 파괴되었음을 안전하게 알 수 없으면 나머지 작업을 수행하려고 할 때 충돌이 발생할 수 있습니다.

참고 : 참조 횟수 감소와 같은 다른 방법으로 "파괴"를 수행 할 수 있지만 이는 중간 상태 및 추가 코드 및 버그로 인해 처리되지 않습니다. 중간 상태를 유지하는 것 외에는 더 이상 필요하지 않은 경우 A가 완전히 작동을 멈추는 것이 좋습니다.

필자의 패턴에서 A는 내부 이벤트 (작업)를 이벤트 루프의 LIFO 대기열로 푸시하여 콜백을 호출 한 후 즉시 이벤트 루프로 돌아가서 필요한 추가 작업을 예약 합니다 . 이 코드는 A가 반환하기 때문에 더 이상 위험하지 않습니다. 콜백이 A를 파괴하지 않으면, 푸시 된 작업은 결국 추가 작업을 수행하기 위해 이벤트 루프에 의해 실행됩니다 (콜백이 완료된 후 모든 푸시 된 작업은 재귀 적으로 수행됩니다). 반면에 콜백이 A를 파괴하면 A의 소멸자 또는 deinit 함수는 푸시 된 작업을 이벤트 스택에서 제거하여 푸시 된 작업의 실행을 암시 적으로 막을 수 있습니다.


7

적절한 로깅이 상당히 큰 도움이 될 수 있다고 생각합니다. 처리 / 처리 된 모든 이벤트가 어딘가에 기록되는지 확인하십시오 (이에 로깅 프레임 워크를 사용할 수 있음). 디버깅 할 때 버그가 발생했을 때 정확한 코드 실행 순서 를 확인 하기 위해 로그를 참조 할 수 있습니다 . 이것은 종종 문제의 원인을 좁히는 데 도움이 될 것입니다.


네, 도움이되는 조언입니다. 생산 된 통나무의 양이 무서울 수 있습니다 (매우 복잡한 형태의 이벤트 처리에서주기의 원인을 찾기 위해 어려움을 겪었던 것을 기억할 수 있습니다)
Guillaume

한 가지 해결책은 흥미로운 정보를 별도의 로그 파일에 기록하는 것입니다. 또한 각 이벤트에 UUID를 할당하여 각 이벤트를보다 쉽게 ​​추적 할 수 있습니다. 로그 파일에서 특정 정보를 추출 할 수있는 일부보고 도구를 작성할 수도 있습니다. 또는 코드의 스위치를 사용하여 다른 정보를 다른 로그 파일에 기록합니다.
Giorgio

3

그래서 다른 사람들이 어떻게 이런 종류의 코드를 다루는 지 궁금합니다. 읽고 쓸 때.

이벤트 중심 프로그래밍 모델은 코딩을 어느 정도 단순화합니다. 그것은 아마도 오래된 언어에서 사용되는 큰 Select (또는 case) 문을 대체하여 발전했으며 VB 3과 같은 초기 시각적 개발 환경에서 인기를 얻었습니다 (역사에 대해 인용하지 말고 확인하지 마십시오)!

이벤트 순서가 중요하고 1 개의 비즈니스 조치가 여러 이벤트에 분할되면 모델은 고통이됩니다. 이 방식의 프로세스는이 방식의 이점을 위반합니다. 모든 비용으로, 해당 이벤트에서 조치 코드를 캡슐화하고 이벤트 내에서 이벤트를 발생시키지 마십시오. 그러면 GoTo로 인한 스파게티보다 훨씬 나빠집니다.

때때로 개발자들은 그러한 이벤트 의존성을 요구하는 GUI 기능을 제공하기를 간절히 원하지만 실제로는 훨씬 더 간단한 대안이 없습니다.

결론은이 기술을 현명하게 사용하면 나쁘지 않다는 것입니다.


'스파게티보다 더 나쁘다'를 피하기 위해 대체 디자인이 있습니까?
기 illa

예상되는 GUI 동작을 단순화하지 않고 쉬운 방법이 있는지 확실하지 않지만 모든 사용자 상호 작용을 창 / 페이지와 함께 나열하고 각 프로그램이 정확히 무엇을 할 것인지 결정하면 코드를 한곳으로 그룹화하여 직접 시작할 수 있습니다 요청의 실제 처리를 담당하는 서브 / 메소드 그룹에 대한 여러 이벤트. 또한 백엔드 처리를 수행하는 코드에서 GUI를 제공하는 코드를 분리하면 도움이 될 수 있습니다.
NoChance

이 메시지를 게시해야 할 필요성은 상속 지옥에서 이벤트 기반으로 이동할 때 리팩토링에서 비롯된 것입니다. 어떤 점에서는 실제로 더 낫지 만 다른 것에서는 꽤 나쁩니다 ... 일부 GUI에서 '사건이 사라졌습니다'에 문제가 있었기 때문에 그러한 유지 관리를 개선하기 위해 무엇을 할 수 있는지 궁금합니다. 이벤트 슬라이스 코드.
기 illa

이벤트 중심 프로그래밍은 VB보다 훨씬 오래되었습니다. SunTools GUI에 있었고 그 전에는 Simula 언어로 작성된 것을 기억합니다.
케빈 클라인

@Guillaume, 나는 당신이 서비스를 엔지니어링하는 것 이상이라고 생각합니다. 위의 설명은 실제로 GUI 이벤트를 기반으로했습니다. 이 유형의 처리가 (실제로) 필요합니까?
NoChance

3

"평 평화"및 "평 평화"제어 흐름 이후 몇 가지 유레카 순간을 얻었고 주제에 대한 새로운 생각을 공식화했기 때문에이 답변을 업데이트하고 싶었습니다.

복잡한 부작용 대 복잡한 제어 흐름

내가 찾은 것은 일반적으로 이벤트 처리에서 볼 수 있지만 두 가지의 결합이 아닌 복잡한 부작용 이나 복잡한 그래프와 같은 제어 흐름을 견딜 수 있다는 것 입니다.

순차 for루프 의 제어 흐름과 같이 매우 간단한 제어 흐름으로 적용되는 4 가지 부작용을 일으키는 코드를 쉽게 추론 할 수 있습니다 . 내 두뇌는 요소의 크기를 조정하고 위치를 변경하고, 애니메이션을 만들고, 다시 그리고, 일종의 보조 상태를 업데이트하는 순차적 루프를 허용 할 수 있습니다. 이해하기 쉽습니다.

캐스 케이 딩 이벤트 나 복잡한 그래프와 같은 데이터 구조를 순회 할 때처럼 복잡한 제어 흐름을 이해할 수 있습니다. 간단한 순차 루프에서 지연된 방식으로 처리됩니다.

내가 길을 잃고 혼란스럽고 압도당하는 곳은 복잡한 부작용을 일으키는 복잡한 제어 흐름이있을 때입니다. 이 경우 복잡한 제어 흐름으로 인해 최종적으로 어디로 향하게 될지를 미리 예측하기 어렵고 복잡한 부작용으로 인해 결과로 어떤 일이 일어날 지 정확히 예측하기가 어렵습니다. 코드가 완벽하게 작동하더라도 원하지 않는 부작용을 일으킬 염려없이 코드를 변경하는 것은 매우 무섭습니다.

복잡한 제어 흐름은 상황이 언제 어디서 발생할지 추론하기 어렵게 만드는 경향이 있습니다. 이러한 복잡한 제어 흐름으로 인해 발생하는 상황과시기를 이해하는 것이 중요한 부작용의 복잡한 조합을 트리거하는 경우에만 두통을 유발할 수 있습니다.

제어 흐름 또는 부작용 단순화

따라서 이해하기 어려운 위의 시나리오가 발생하면 어떻게해야합니까? 전략은 제어 흐름 또는 부작용을 단순화하는 것입니다.

부작용을 단순화하는 데 널리 적용되는 전략은 지연 처리를 선호하는 것입니다. GUI 크기 조정 이벤트를 예로 사용하면 일반적인 유혹은 GUI 레이아웃을 다시 적용하고 자식 위젯의 위치를 ​​조정하고 크기를 조정하여 다른 일련의 레이아웃 응용 프로그램을 트리거하고 계층 구조의 크기를 조정하고 위치를 조정하며 컨트롤을 다시 페인트하여 일부를 트리거 할 수 있습니다. 더 많은 이벤트를 유발하는 사용자 지정 크기 조정 동작이있는 위젯의 고유 한 이벤트 등을 누가 알 수 있는지 등을 유도합니다. 한 번에 모든 작업을 수행하거나 이벤트 큐를 스팸 처리하는 대신 위젯 계층 구조를 내릴 수 있습니다. 레이아웃을 업데이트해야하는 위젯을 표시하십시오. 그런 다음 간단한 순차적 제어 흐름이있는 지연된 패스에서 필요한 위젯의 모든 레이아웃을 다시 적용하십시오. 그런 다음 다시 칠해야 할 위젯을 표시 할 수 있습니다. 제어 흐름이 간단하면서 순차적으로 지연된 패스에서 다시 그려야하는 것으로 표시된 위젯을 다시 그립니다.

이는 그래프 순회 동안 반복되는 이벤트가 아니므로 제어 플로우가 단순화되므로 제어 플로우와 부작용을 단순화하는 효과가 있습니다. 대신 캐스케이드는 지연된 순차 루프에서 발생하고 다른 지연된 순차 루프에서 처리 될 수 있습니다. 더 복잡한 그래프와 같은 제어 흐름 중에는 부작용이 단순 해지기 때문에 더 복잡한 부작용을 유발하는 지연된 순차 루프로 처리해야 할 작업을 표시하기 만하면됩니다.

여기에는 약간의 처리 오버 헤드가 있지만 이러한 지연된 패스를 병렬로 수행 할 수있는 문을 열어 성능이 문제가 될 경우 시작한 것보다 훨씬 효율적인 솔루션을 얻을 수 있습니다. 대부분의 경우 성능은 큰 문제가되지 않습니다. 가장 중요한 것은 이것이 차이가있는 것처럼 보일 수 있지만 추론하기가 훨씬 쉽다는 것을 알았습니다. 현재 상황과시기를 훨씬 쉽게 예측할 수 있으며 현재 상황을보다 쉽게 ​​이해할 수있는 가치를 과대 평가할 수 없습니다.


2

나를 위해 일한 것은 다른 이벤트를 참조하지 않고 각 이벤트를 자체적으로 세우는 것입니다. 그들이 asynchroniously에서 오는 경우, 당신은하지 않습니다 너무없는 것 외에, 무의미 어떤 순서로 어떻게되는지 알아 내려고 노력하고, 순서를.

당신이 끝내는 것은 특별한 순서없이 12 개의 스레드에 의해 읽고 수정되고 생성되고 제거되는 많은 데이터 구조입니다. 당신은 매우 적절한 멀티 스레드 프로그래밍을해야합니다. 쉽지 않습니다. "이 이벤트를 사용하면 변경 내용에 관계없이 마이크로 초 전의 데이터에 관계없이 특정 순간에 보유한 데이터를 살펴볼 것입니다. 잠금 해제를 기다리는 100 개의 스레드가 무엇을 할 것인지에 관계없이이를 수행 할 것입니다. 그런 다음이 짝수와 내가 본 내용에 따라 변경을 수행 할 것입니다. "

내가하고있는 한 가지는 특정 컬렉션을 스캔하고 참조 및 컬렉션 자체 (스레드 안전하지 않은 경우)가 올바르게 잠겨 있고 다른 데이터와 올바르게 동기화되어 있는지 확인하는 것입니다. 더 많은 이벤트가 추가되면이 집안일이 커집니다. 그러나 이벤트 사이의 관계를 추적한다면 일이 훨씬 더 빨리 커질 것입니다. 또한 때로는 많은 잠금이 자체 방법으로 분리되어 실제로 코드를 단순화 할 수 있습니다.

각 스레드를 완전히 독립적 인 엔티티로 취급하는 것은 어렵지만 (하드 코어 멀티 스레딩 때문에) 가능합니다. "확장 가능"은 내가 찾고있는 단어 일 수 있습니다. 두 배나 많은 이벤트가 두 배나 많은 작업을 수행하고 어쩌면 1.5 배만 수행합니다. 보다 비동기적인 이벤트를 조정하려고하면 빠르게 파 묻힐 수 있습니다.


내 질문을 업데이트했습니다. 시퀀스와 비동기식에 대해 완전히 옳습니다. 다른 모든 이벤트가 처리 된 후 이벤트 X를 실행 해야하는 경우를 어떻게 처리합니까? 더 복잡한 처리기 관리자를 만들어야하는 것처럼 보이지만 사용자에게 너무 많은 복잡성을 노출시키지 않으려 고합니다.
기 illa

데이터 세트에 이벤트 X를 읽을 때 설정되는 스위치 및 일부 필드가 있습니다. 다른 모든 것이 처리되면 스위치를 확인하고 X를 처리해야하고 데이터가 있음을 알고 있습니다. 스위치와 데이터는 실제로 자체적으로 있어야합니다. 설정되면 "X를 처리해야합니다"가 아니라 "이 작업을 수행해야합니다"라고 생각해야합니다. 다음 문제 : 이벤트가 완료되었음을 어떻게 알 수 있습니까? 그리고 2 개 이상의 이벤트 X를 받으면 어떻게해야합니까? 최악의 경우, 상황을 확인하고 자체 이니셔티브로 작동 할 수있는 루핑 유지 보수 스레드를 실행할 수 있습니다. (3 초 동안 입력이 없습니까? X 스위치가 설정되어 있습니까? 그런 다음 종료 코드를 실행하십시오.
RalphChapin

2

State Machines & Event Driven Activities를 찾고있는 것 같습니다 .

그러나 State Machine Markup Workflow Sample 을보고 싶을 수도 있습니다 .

다음은 상태 머신 구현에 대한 간략한 개요입니다. 상태 시스템 워크 플로우 상태로 구성되어 있습니다. 각 상태는 하나 이상의 이벤트 핸들러로 구성됩니다. 각 이벤트 핸들러는 첫 번째 활동으로 지연 또는 IEventActivity를 포함해야합니다. 각 이벤트 핸들러에는 한 상태에서 다른 상태로 전환하는 데 사용되는 SetStateActivity 활동이 포함될 수도 있습니다.

각 상태 시스템 워크 플로에는 InitialStateName 및 CompletedStateName이라는 두 가지 속성이 있습니다. 상태 머신 워크 플로의 인스턴스가 생성되면 InitialStateName 속성에 저장됩니다. 상태 시스템이 CompletedStateName 속성에 도달하면 실행이 완료됩니다.


2
이 이론적으로 질문에 대답 할 수 있습니다 동안, 바람직 할 것이다 여기에 대한 대답의 본질적인 부분을 포함하고 참조 할 수 있도록 링크를 제공합니다.
Thomas Owens

그러나 각 상태에 수십 개의 처리기가 연결되어 있다면 무슨 일이 일어나고 있는지 이해하려고 할 때 그리 좋지 않을 것입니다 ... 그리고 모든 이벤트 기반 구성 요소는 상태 머신으로 설명되지 않을 수 있습니다.
기 illa

1

이벤트 중심 코드는 실제 문제가 아닙니다. 실제로 콜백이 명시 적으로 정의되거나 인라인 콜백이 사용되는 구동 코드에서도 논리를 따르는 데 아무런 문제가 없습니다. 예를 들어 토네이도의 생성기 스타일 콜백 은 매우 쉽게 따라갈 수 있습니다.

디버깅하기 어려운 것은 동적으로 생성 된 함수 호출입니다. 지옥에서 콜백 팩토리라고 부르는 (안티?) 패턴. 그러나 이런 종류의 함수 팩토리는 전통적인 흐름에서 디버깅하기가 어렵습니다.

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