클래스 메서드보다 문자열이 느슨해지지 않습니까?


18

Swing을 사용하여 Java로 학교 그룹 프로젝트를 시작하고 있습니다. 데이터베이스 데스크톱 앱의 간단한 GUI입니다.

교수는 작년 프로젝트에서 코드를 제공하여 그가 어떻게 작동하는지 확인할 수있었습니다. 저의 첫 인상은 코드가 생각보다 훨씬 복잡하다는 것입니다. 그러나 프로그래머는 단지 그들이 작성하지 않은 코드를 볼 때 이것을 생각한다고 생각합니다.

그의 시스템이 좋은지 나쁜지를 찾는 이유가 있습니다. (교수에게 물었고 나중에 더 좋은 이유에 대해서는 나중에 보겠다고 말했습니다.)

기본적으로 지속 가능한 객체, 모델 (비즈니스 로직) 및 뷰 간의 연결을 피하기 위해 모든 것은 문자열로 수행됩니다. 데이터베이스에 저장되는 지속 가능한 객체는 문자열의 해시 테이블이며 모델과 뷰는 서로 "구독"하여 가입 한 "이벤트"에 대한 문자열 키를 제공합니다.

이벤트가 트리거되면 뷰 또는 모델은 해당 이벤트에 대해 수행 할 작업을 결정하는 모든 구독자에게 문자열을 보냅니다. 예를 들어 뷰 액션 리스너 메소드 중 하나에서 (이것은 지속 가능한 객체에 bicycleMakeField를 설정한다고 생각합니다.)

    else if(evt.getSource() == bicycleMakeField)
    {
        myRegistry.updateSubscribers("BicycleMake", bicycleMakeField.getText());
    }

이 호출은 결국 Vehicle 모델에서이 메소드에 도달합니다.

public void stateChangeRequest(String key, Object value) {

            ... bunch of else ifs ...

    else
    if (key.equals("BicycleMake") == true)
    {
                ... do stuff ...

교수는 이러한 방법으로 비즈니스 로직 객체에 대한 메소드를 호출하는 것보다 확장 가능하고 유지 관리가 가능하다고 말합니다. 그는 뷰와 모델이 서로의 존재를 알지 못하기 때문에 커플 링이 없다고 말합니다.

뷰와 모델이 작동하기 위해 동일한 문자열을 사용해야하기 때문에 이것이 더 나쁜 종류의 커플 링이라고 생각합니다. 뷰 또는 모델을 제거하거나 문자열에 오타를 작성하면 컴파일 오류가 발생하지 않습니다. 또한 필요한 것보다 코드를 훨씬 더 길게 만듭니다.

나는 이것과 그에 대해 논의하고 싶지만, 경험이없는 학생 인 내가 할 수있는 모든 주장에 반대하기 위해 그의 산업 경험을 사용합니다. 내가 놓친 그의 접근 방식에 이점이 있습니까?


명확히하기 위해 위의 접근법을 모델과 분명히 결합시키는 관점을 비교하고 싶습니다. 예를 들어, 차량 모델 객체를 뷰에 전달한 다음 차량의 "make"를 변경하려면 다음과 같이하십시오.

vehicle.make = bicycleMakeField.getText();

이것은 현재 차량 제조를 한 곳에서 하나의 판독 가능한 코드 줄로 설정하는 데 사용되는 15 줄의 코드를 줄입니다. (이러한 종류의 작업은 앱 전체에서 수백 번 수행되므로 가독성과 안전성 측면에서 큰 승리라고 생각합니다.)


최신 정보

팀 리더와 저는 정적 타이핑을 사용하여 원하는 방식으로 프레임 워크를 재구성하고 교수에게 정보를 제공하고 결국 데모를 제공했습니다. 그는 도움을 요청하지 않는 한 프레임 워크를 사용할 수있을만큼 충분히 관대하며 팀의 나머지 부분을 신속하게 유지할 수 있는지 여부는 공평합니다.


12
OT. 교수님은 자신을 업데이트하고 오래된 코드를 사용하지 않아야한다고 생각합니다. 학계에서는 2012 년에 2006 년부터 JAVA 버전을 사용할 이유가 없습니다.
Farmor

3
당신이 결국 그의 답변을 얻을 때 우리를 게시하십시오.
JeffO

4
코드의 품질이나 기술의 지혜에 관계없이 식별자로 사용되는 문자열에 대한 설명에서 "magic"이라는 단어를 삭제해야합니다. "매직 스트링"은 시스템이 논리적이지 않다는 것을 암시하며, 강사와의 대화에 대한 견해를 밝히고 독이됩니다. "키", "이름"또는 "식별자"와 같은 단어는 좀 더 중립적이고 설명 적이며 도움이되는 대화로 이어질 가능성이 높습니다.
Caleb

1
진실. 나는 그와 대화하는 동안 그것들을 마법의 줄이라고 부르지 않았지만, 당신이 옳습니다. 소리를 더 나쁘게 만들 필요는 없습니다.
Philip

2
교수가 설명하는 접근 방식 (문자열로 명명 된 이벤트를 통해 청취자에게 알림)이 좋습니다. 실제로 열거 형이 더 의미가 있지만 이것이 가장 큰 문제는 아닙니다. 여기서 볼 수있는 실제 문제는 알림을받는 객체가 모델 자체이므로 이벤트 종류에 따라 수동으로 파견해야한다는 것입니다. 함수가 일류 인 언어에서는 이벤트에 객체가 아니라 함수를 등록합니다 . 자바에서 나는 하나 하나의 함수 포장 개체의 어떤 종류를 사용해야 추측
안드레아

답변:


32

교수가 제안하는 접근 방식은 문자열 형식으로 설명하는 것이 가장 좋으며 거의 모든 수준에서 잘못되었습니다.

커플 링은 종속성 인 버젼 (dependency inversion )에 의해 가장 잘 감소되는데 ,이 경우 여러 경우를 배선하는 것이 아니라 다형성 디스패치 (polymorphic dispatch)에 의해 수행됩니다.


3
당신은 "거의 모든 수준"을 쓰지만,이 예제에서 왜 그것이 적절한 경우가 아닌지는 쓰지 않았습니다. 알고 흥미로울 것입니다.
hakre

1
@hakre : 적어도 Java에는없는 적절한 경우가 없습니다. 나는 그것이 "거의 모든 수준에서 잘못되었다"고 말했다. 왜냐하면 전혀 간접적 인 명령을 사용하지 않고 모든 코드를 한 곳에 던지는 것보다 낫기 때문이다. 그러나 수동 디스패치의 기초로 (전역) 매직 문자열 상수를 사용하는 것은 결국 전역 객체를 사용하는 복잡한 방법입니다.
back2dos

따라서 당신의 주장은 다음과 같습니다. 적절한 경우는 없습니다. 그래서 이것도 그렇지 않습니다? 그것은 거의 논쟁이 아니며 실제로별로 도움이되지 않습니다.
hakre 2019

1
@hakre : 내 주장은이 접근법은 여러 가지 이유로 나쁘다는 것입니다 (1 단락-이것을 피할 수있는 충분한 이유는 문자열 유형의 설명에 나와 있습니다). 더 나은 방법이 있기 때문에 사용해서는 안됩니다 (2 단락) ).
back2dos

3
@hakre 나는 연쇄 살인범이 당신을 의자에 두드리고 전기 톱을 머리 위로 들고, 정신적으로 웃으며, 파견 논리를 위해 모든 곳에서 문자열 리터럴을 사용하도록 명령 한 경우가 될 것이라고 생각할 수 있습니다. 그 중에서도 이런 종류의 작업을 수행하려면 언어 기능에 의존하는 것이 좋습니다. 실제로 악어와 닌자가 관련된 다른 사례를 생각할 수 있지만 조금 더 관련이 있습니다.
Rob

19

교수님 께서 잘못하고 계십니다 . 그는 커뮤니케이션이 문자열을 통해 양방향으로 이동하도록하여 뷰와 모델 을 완전히 분리 하려고합니다 (뷰는 모델에 의존하지 않고 모델은 뷰에 의존하지 않음) . 이는 객체 지향 디자인 및 MVC의 목적을 완전히 상실합니다. 클래스를 작성하고 OO 기반 아키텍처를 디자인하는 대신 텍스트 기반 메시지에 간단히 응답하는 개별 모듈 을 작성 하는 것 같습니다.

교수에게 다소 공평하게하기 위해, 그는라는 매우 일반적인 .NET 인터페이스와 매우 유사한 모양을 Java로 작성하려고합니다 INotifyPropertyChanged. .NET에서 최소한이 인터페이스는 주로 데이터 모델과 통신하여 개체 및 GUI 요소 (바인딩)를 보는 데 사용됩니다.

올바르게 수행하면 이 방법을 사용하면몇 가지 이점을 얻을 있습니다 ¹ :

  1. 뷰는 모델에 따라 다르지만 모델이 뷰에 의존 할 필요는 없습니다. 이것은 적절한 제어 반전입니다 .
  2. View 코드에는 모델의 변경 알림에 대한 응답을 관리하는 단일 장소가 있고 구독 / 구독 해제 할 장소는 한 곳뿐이므로 중단 된 참조 메모리 누수 가능성이 줄어 듭니다.
  3. 이벤트 게시자에서 이벤트를 추가하거나 제거하려는 경우 코딩 오버 헤드가 줄어 듭니다. .NET에는 내장 된 발행 / 구독 메커니즘이 events있지만 Java에서는 클래스 또는 오브젝트를 작성하고 각 이벤트에 대해 구독 / 구독 취소 코드를 작성해야합니다.
  4. 이 방법의 가장 큰 단점은 구독 코드가 게시 코드와 동기화되지 않을 수 있다는 것입니다. 그러나 이는 일부 개발 환경에서도 이점입니다. 모델에서 새 이벤트가 개발되고 뷰를 구독하도록 업데이트되지 않은 경우 뷰가 여전히 작동합니다. 마찬가지로 이벤트가 제거되면 사용 불능 코드가 있지만 여전히 컴파일 및 실행할 수 있습니다.
  5. .NET에서는 Reflection이 구독자에게 전달 된 문자열에 따라 getter 및 setter를 자동으로 호출하는 데 매우 효과적으로 사용됩니다.

간단히 말해서 (그리고 당신의 질문에 대답하기 위해), 할 수는 있지만 교수가 취하는 접근법은 뷰와 모델 사이에 적어도 일방적 인 의존성을 허용하지 않는다는 잘못입니다. 실용적이지 않고 지나치게 학문적이며 도구를 사용하는 방법에 대한 좋은 예는 아닙니다.


¹ Google에서 검색을 통해 INotifyPropertyChanged사람들이 마법의 끈을 사용하지 않는 방법을 찾을 수있는 방법을 찾으 십시오.


여기에 SOAP를 설명한 것처럼 들립니다. ... D (하지만 그래, 난 당신의 요점을 파악하고있는 거 오른쪽)
콘라드 루돌프

11

enumpublic static final String매직 스트링 대신 s (또는 s)를 사용해야합니다.

교수님이 OO를 이해하지 못합니다 . 예를 들어 구체적인 Vehicle 클래스가 있습니까?
그리고이 수업에서 자전거의 제조사를 이해하게하려면?

차량은 추상적이어야하며 자전거라는 구체적인 서브 클래스가 있어야합니다. 나는 그의 규칙에 따라 좋은 성적을 받고 그의 가르침을 잊어 버릴 것입니다.


3
이 연습의 요점이 더 나은 OO 원칙을 사용하여 코드를 개선하는 것이 아니라면. 이 경우 리팩터링하십시오.
joshin4colours

2
@ joshin4colours StackExchange가 등급을 매겼다면 맞을 것입니다. 그러나 채점자는 아이디어를 생각 해낸 사람과 동일하기 때문에 구현이 더 낫다는 것을 알기 때문에 우리 모두에게 나쁜 점을 줄 것입니다.
Dan Neely

7

나는 당신이 좋은 지적을 가지고 있다고 생각합니다, 마술 줄은 나쁩니다. 우리 팀의 누군가가 몇 주 전에 마법의 끈으로 인한 문제를 디버깅해야했습니다 (그러나 그들이 작성한 코드는 입을 다물었습니다.) 그리고 그것은 업계에서 일어났습니다 !

그의 접근 방식의 장점은 특히 소규모 데모 프로젝트의 경우 코딩 속도가 더 빠를 수 있다는 것입니다. 단점은 문자열이 자주 사용 될수록 오타가 ​​발생하기 쉬우므로 오타 코드가 잘못 될 가능성이 높다는 것입니다. 당신이 이제까지 싶다면 변경 마법의 문자열을 문자열을 리팩토링하면 문자열을 만지지 수있는 리팩토링 도구 있기 때문에 변수를 리팩토링보다 어렵습니다 포함 (문자열로) 마법의 문자열을하지만 있습니다 하지 스스로에게 마법의 문자열을해야하고 하지 만지는. 또한, 새로운 개발자가 동일한 목적으로 새로운 매직 문자열을 발명하지 않고 기존 매직 문자열을 찾는 데 시간을 낭비하지 않도록 매직 문자열의 사전 또는 색인을 유지해야 할 수도 있습니다.

이러한 맥락에서 Enum이 마술 문자열이나 전역 문자열 상수보다 낫습니다. 또한 IDE는 상수 문자열이나 열거 형을 사용할 때 일종의 코드 지원 (자동 완성, 리팩터링, 모든 참조 찾기 등)을 제공합니다. 마법의 문자열을 사용하면 IDE는 큰 도움이되지 않습니다.


열거 형이 문자열 리터럴보다 낫다는 데 동의합니다. 그러나 열거 형 키로 "stateChangeRequest"를 호출하거나 모델에서 직접 메소드를 호출하는 것이 더 낫습니까? 어느 쪽이든, 모델과 뷰는 그 한 지점에서 결합됩니다.
Philip

@Philip : 그렇다면, 모델을 분리하고 그 시점에서 볼 수있을 것입니다. 그렇게하려면 일종의 컨트롤러 클래스가 필요할 수 있습니다.
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner-예. MVC 아키텍처는 가장 분리되어 있습니다
cdeszaq

다른 층이 그것들을 분리하는 데 도움이된다면, 나는 그것에 반대하지 않을 것입니다. 그러나 해당 계층은 여전히 ​​정적으로 유형을 지정할 수 있으며 문자열 또는 열거 메시지 대신 실제 메소드를 사용하여 연결할 수 있습니다.
Philip

6

'산업 경험'을 사용하는 것은 잘못된 주장입니다. 교수님은 그보다 더 나은 주장을 제시해야합니다. :-).

다른 사람들이 언급했듯이, 마법 문자열의 사용은 확실히 눈살을 찌푸리고 열거 형을 사용하는 것이 훨씬 안전하다고 간주됩니다. 열거 형에는 의미범위가 있으며 마술 문자열은 그렇지 않습니다. 그 외에도 교수가 관심사를 분리하는 방식은 오늘날 사용되는 것보다 더 오래되고 깨지기 쉬운 기술로 간주됩니다.

나는 @backtodos가 이것을 다루는 동안 이것을 다루었 음을 알았습니다. 따라서 Inversion of Control (종속성 주입이 어떤 형태인지)을 사용하면 오랫동안 업계의 Spring 프레임 워크를 실행 하게 될 것이라는 의견을 간단히 추가 할 것입니다. ..)는 이러한 유형의 분리를 처리하는보다 현대적인 방법으로 간주됩니다.


2
학계가 업계보다 훨씬 높은 요구 사항을 가져야한다는 데 전적으로 동의합니다. 학계 == 최고의 이론적 방법; 업계 == 가장 실용적인 방법
Farmer

3
불행하게도, 학계에서 이런 것들을 가르치는 사람들 중 일부는 산업계에서 그것을 삭감 할 수 없기 때문에 그렇게합니다. 긍정적 인 측면에서, 학계에있는 사람들 중 일부는 훌륭하고 일부 회사 사무실에 숨겨져 있어서는 안됩니다.
FrustratedWithFormsDesigner

4

아주 명확하게합시다. 거기에는 필연적으로 약간의 커플 링이 있습니다. 가입 할 수있는 주제가 있다는 사실은 메시지의 나머지 특성 (예 :“단일 문자열”)과 마찬가지로 결합 점입니다. 그렇다면 문자열이나 유형을 통해 수행 할 때 커플 링이 더 큰지 여부 중 하나가됩니다. 이에 대한 대답은 시간이나 공간에 따라 분배되는 커플 링에 대해 걱정하는지 여부에 따라 다릅니다. 메시지를 나중에 읽기 전에 파일에 보존하거나 다른 프로세스로 보낼 경우 (특히 문자열을 사용하면 일이 훨씬 간단 해집니다. 단점은 형식 검사가 없다는 것입니다. 실수는 런타임까지 (때로는 감지되지 않을 때까지) 감지되지 않습니다.

Java에 대한 완화 / 타협 전략은 유형이 지정된 인터페이스를 사용하는 것이지만 해당 유형의 인터페이스가 별도의 패키지에있는 경우입니다. Java interface및 기본 값 유형 (예 : 열거, 예외)으로 구성되어야합니다. 이 없어야 에는 해당 패키지의 인터페이스의 구현. 그런 다음 모든 사람이 이에 대해 구현하고 복잡한 방식으로 메시지를 전달해야하는 경우 Java 대리자의 특정 구현에서 필요에 따라 마법 문자열을 사용할 수 있습니다. 또한 코드를 JEE 또는 OSGi와 같은보다 전문적인 환경으로 마이그레이션하는 것이 훨씬 쉬워지고 테스트와 같은 작은 것들을 크게 지원합니다.


4

교수님이 제안하는 것은 "마술 현"을 사용하는 것입니다. 클래스를 서로 "느슨하게 결합"하여 변경을 쉽게 할 수 있지만 반 패턴입니다. 문자열은 매우 길어야하며, 매우 드물게 코드 명령어를 포함하거나 제어하는 ​​데 거의 사용되지 않아야하며, 절대적으로 발생해야하는 경우 논리를 제어하는 ​​데 사용하는 문자열 값은 중앙에서 지속적으로 정의되어야합니다. 특정 문자열.

그 이유는 매우 간단합니다. 문자열은 다른 값과의 구문 또는 일관성에 대해 컴파일러에서 확인되지 않습니다 (최상의 문자열에서 이스케이프 문자 및 기타 언어 별 형식과 관련하여 올바른 형식이 있는지 확인). 문자열에 원하는 것을 넣을 수 있습니다. 따라서, 컴파일 할 알고리즘 / 프로그램을 절망적으로 깨뜨릴 수 있으며 오류가 발생하기 전까지는 그렇지 않습니다. 다음 코드 (C #)를 고려하십시오.

private Dictionary<string, Func<string>> methods;

private void InitDictionary() //called elsewhere
{
   methods = new Dictionary<string, Func<string>> 
      {{"Method1", ()=>doSomething()},
       {"MEthod2", ()=>doSomethingElse()}} //oops, fat-fingered it
}

public string RunMethod(string methodName)
{
   //very naive, but even a check for the key would generate a runtime error, 
   //not a compile-time one.
   return methods[methodName](); 
}

...

//this is what we thought we entered back in InitDictionary for this method...
var result = RunMethod("Method2"); //error; no such key

...이 코드는 모두 컴파일하고 먼저 시도하지만 오류는 두 번째로 분명합니다. 이런 종류의 프로그래밍에 대한 많은 예가 있으며, 이러한 종류의 오류가 모두 발생합니다. 문자열을 상수로 정의하더라도 .NET의 상수는 사용되는 각 라이브러리의 매니페스트에 쓰여지기 때문에 정의 된 값이 "전역 적으로"변경되도록 정의 된 값이 변경되면 모두 다시 컴파일됩니다. 컴파일 타임에 오류가 발생하지 않기 때문에 런타임 테스트 중에 오류가 발생해야하며, 적절한 코드 연습을 통해 단위 테스트에서 100 % 코드 적용 범위에서만 보장됩니다. 이는 일반적으로 가장 절대적인 안전 장치에서만 발견됩니다. 실시간 시스템.


2

실제로 이러한 클래스는 여전히 밀접하게 연결되어 있습니다. 이제는 컴파일러가 문제가 발생했을 때 알 수없는 방식으로 밀접하게 결합되어 있습니다!

누군가 "BicycleMake"를 "BicyclesMake"로 변경하면 아무도 런타임까지 문제가 있음을 알 수 없습니다.

런타임 오류는 컴파일 시간 오류보다 수정하는 데 비용이 많이 듭니다. 이것은 모든 종류의 악입니다.

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