programing tip

코드에서 이벤트 핸들러를 호출하는 것은 왜 나쁜 습관입니까?

itbloger 2020. 10. 24. 09:42
반응형

코드에서 이벤트 핸들러를 호출하는 것은 왜 나쁜 습관입니까?


동일한 작업을 수행하는 메뉴 항목과 버튼이 있다고 가정 해 보겠습니다. 작업에 대한 코드를 한 컨트롤의 작업 이벤트에 넣은 다음 다른 컨트롤에서 해당 이벤트를 호출하는 것은 왜 나쁜 습관입니까? 델파이는 vb6처럼 이것을 허용하지만 realbasic은 허용하지 않으며 메뉴와 버튼 모두에서 호출되는 메소드에 코드를 넣어야한다고 말합니다.


프로그램이 어떻게 구성되어 있는지에 대한 질문입니다. 설명한 시나리오에서 메뉴 항목의 동작은 버튼의 관점에서 정의됩니다.

procedure TJbForm.MenuItem1Click(Sender: TObject);
begin
  // Three different ways to write this, with subtly different
  // ways to interpret it:

  Button1Click(Sender);
  // 1. "Call some other function. The name suggests it's the
  //    function that also handles button clicks."

  Button1.OnClick(Sender);
  // 2. "Call whatever method we call when the button gets clicked."
  //    (And hope the property isn't nil!)

  Button1.Click;
  // 3. "Pretend the button was clicked."
end;

이 세 가지 구현 중 하나가 작동하지만 메뉴 항목이 버튼에 그렇게 의존해야하는 이유는 무엇입니까? 메뉴 항목을 정의해야하는 버튼의 특별한 점은 무엇입니까? 새로운 UI 디자인에서 버튼이 사라 졌다면 메뉴는 어떻게 되나요? 더 좋은 방법은 이벤트 처리기의 작업을 제외하여 연결된 컨트롤과 독립적 인 것입니다. 이를 수행하는 몇 가지 방법이 있습니다.

  1. 하나는 MenuItem1Click메서드를 모두 제거 하고 Button1Click메서드를 MenuItem1.OnClick이벤트 속성에 할당하는 입니다. 메뉴 항목의 이벤트에 할당 된 버튼에 대해 이름이 지정된 메소드를 갖는 것은 혼란 스럽기 때문에 이벤트 핸들러의 이름을 바꾸고 싶을 것입니다.하지만 VB와 달리 Delphi의 메소드 이름은 처리 할 이벤트를 정의 하지 않기 때문에 괜찮습니다 . 서명이 일치하는 한 모든 이벤트 처리기에 모든 메서드를 할당 할 수 있습니다. 두 구성 요소의 OnClick이벤트는 유형 TNotifyEvent이므로 단일 구현을 공유 할 수 있습니다. 그들이 속한 일이 아니라 그들이하는 일에 대한 이름 방법.

  2. 또 다른 방법은 버튼의 이벤트 처리기 코드를 별도의 메서드로 이동 한 다음 두 구성 요소의 이벤트 처리기에서 해당 메서드를 호출하는 것입니다.

    procedure HandleClick;
    begin
      // Do something.
    end;
    
    procedure TJbForm.Button1Click(Sender: TObject);
    begin
      HandleClick;
    end;
    
    procedure TJbForm.MenuItem1Click(Sender: TObject);
    begin
      HandleClick;
    end;
    

    이렇게하면 실제로 작업을 수행하는 코드가 두 구성 요소에 직접 연결되지 않고 이름을 바꾸거나 다른 컨트롤로 바꾸는 등 컨트롤을보다 쉽게 ​​변경할 수 있습니다 . 컴포넌트에서 코드를 분리하면 세 번째 방법으로 이어집니다.

  3. TAction델파이 4에 도입 된 구성 요소는, 특히 같은 명령에 여러 UI 경로가 당신이 설명한 상황을 위해 설계되었습니다. (다른 언어와 개발 환경은 유사한 개념을 제공합니다. Delphi에만있는 것은 아닙니다.) 이벤트 처리 코드를 TActionOnExecute이벤트 처리기에 넣은 다음 해당 작업을 Action버튼과 메뉴 항목 속성에 할당합니다 .

    procedure TJbForm.Action1Click(Sender: TObject);
    begin
      // Do something
      // (Depending on how closely this event's behavior is tied to
      // manipulating the rest of the UI controls, it might make
      // sense to keep the HandleClick function I mentioned above.)
    end;
    

    버튼처럼 작동하는 다른 UI 요소를 추가하고 싶으십니까? 문제 없어요. 추가하고 Action속성을 설정 하면 완료됩니다. 새 컨트롤을 이전 컨트롤처럼 보이게하고 작동하도록 더 많은 코드를 작성할 필요가 없습니다. 이미 해당 코드를 한 번 작성했습니다.

    TAction goes beyond just event handlers. It lets you ensure that your UI controls have uniform property settings, including captions, hints, visibility, enabledness, and icons. When a command isn't valid at the time, set the action's Enabled property accordingly, and any linked controls will automatically get disabled. No need to worry about a command being disabled through the tool bar, but still enabled through the menu, for example. You can even use the action's OnUpdate event so that the action can update itself based on current conditions, instead of you needing to know whenever something happens that might require you to set the Enabled property right away.


Because you should separate internal logic to some other function and call this function...

  1. from both event handlers
  2. separately from code if you need to

This is a more elegant solution and is much easier to maintain.


This is an extension answer, as promised. In 2000 we have started to write an application using Delphi. This was one EXE and few DLL’s containing logic. This was movie industry, so there was customers DLL, booking DLL, box office DLL and billing DLL. When user wanted to do billing, he opened appropriate form, selected customer from a list, then OnSelectItem logic loaded customers theaters to next combo box, then after selecting theater next OnSelectItem event filled third combo box with information about the movies, that has not been billed yet. Last part of the process was pushing the button “Do Invoice”. Everything was done as an event procedures.

Then someone decided we should have extensive keyboard support. We have added calling event handlers from another even handlers.. The workflow of event handlers begun to complicate.

After two years someone decided to implement another feature – so that user working with customer data in another module (customers module) should be presented with a button titled “Invoice this customer”. This button should fire the invoice form and present it in such a state, like it was user who have been manually selecting all the data (the user was to be able to look at, make some adjustments, and press magic “Do Invoice” button). Since customer data was one DLL and billing was another, it was EXE that was passing messages. So the obvious idea was that customer data developer will have single routine with single ID as a parameter, and that all this logic will be inside billing module.
Imagine what happened. Since ALL logic was inside event handlers, we spent huge amount of time, trying actually not implement logic, but trying to mimic user activity – like choosing items, suspending Application.MessageBox inside event handlers using GLOBAL variables, and so one. Imagine – if we had even simple logic procedures called inside event handlers, we would have been able to introduce DoShowMessageBoxInsideProc Boolean variable to the procedure signature. Such a procedure could have been called with true parameter if called from event handler, and with FALSE parameters when called from external place.

So this is what have taught me not to put logic directly inside GUI event handlers, with a possible exception of small projects.


Separation of concerns. A private event for a class should be encapsulated within that class and not called from external classes. This makes your project easier to change down the road if you have strong interfaces between objects and minimize the occurences of multiple entry points.


Suppose at some point you decide that the menu item no longer makes sense, and you want to get rid of the menu item. If you just have one other control pointing to the menu item's event handler, that might not be a big problem, you can just copy the code into the button's event handler. But if you have several different ways the code can be invoked, you'll have to do a lot of changing.

Personally I like the way Qt handles this. There is a QAction class with it's own event handler that can be hooked, and then the QAction is associated with any UI elements that need to perform that task.


Another big reason is for testability. When event handling code is buried in the UI, the only way to test this is via either manual testing or automated testing that is heavily tied to the UI. (e.g. Open menu A, Click button B). Any change in the UI naturally can then break dozens of tests.

If the code is refactored into a module that deals exclusively with the job it needs to perform, then testing become a whole lot easier.


It is neater obviously. But ease of use and productivity is of course also always important.

In Delphi I generally refrain from it in serious apps, but I call eventhandlers in small stuff. If small stuff somehow morphes into something bigger, I clean it up, and usually at the same time increase logic-UI separation.

I do know though that it won't matter in Lazarus/Delphi. Other languages might have more special behaviour attached to eventhandlers.


Why is it bad practice? Because it is much easier to reuse code when it is not embedded into UI controls.

Why can't you do it in REALbasic? I doubt there's any technical reason; it's likely just a design decision they made. It certainly does enforce better coding practices.


Suppose at some time you decided that the menu should do something slightly differently. Perhaps this new change only happens under some specific circumstances. You forget about the button, but now you have changed its behaviour as well.

On the other hand if you call a function, you are less likely to change what it does, since you (or the next guy) knows that this will have bad consequences.

참고URL : https://stackoverflow.com/questions/956255/why-is-it-bad-practice-to-call-an-eventhandler-from-code

반응형