자바 문화를 고치기-왜 그렇게 무겁습니까? 무엇을 위해 최적화합니까? [닫은]


288

나는 파이썬으로 많이 코딩했다. 이제 작업상의 이유로 Java로 코딩합니다. 내가하는 프로젝트는 다소 작으며 Python이 더 잘 작동하지만 Java를 사용해야하는 비 엔지니어링 이유가 있습니다 (자세한 내용은 볼 수 없습니다).

Java 구문은 문제가되지 않습니다. 다른 언어 일뿐입니다. 그러나 구문과는 별도로 Java에는 문화, 일련의 개발 방법 및 "올바른"것으로 간주되는 관행이 있습니다. 그리고 지금은 그 문화를 완전히 "탐색"하는데 실패하고 있습니다. 따라서 올바른 방향으로 설명이나 포인터를 정말 고맙게 생각합니다.

내가 시작한 스택 오버플로 질문에서 최소한의 완전한 예제를 사용할 수 있습니다 : https://stackoverflow.com/questions/43619566/returning-a-result-with-several-values-the-java-way/43620339

나는 하나의 문자열에서 파싱하고 세 개의 값 세트를 처리하는 작업을 가지고 있습니다. 파이썬에서는 파스칼 또는 C에서는 5 줄짜리 레코드 / 구조체로 1 줄짜리 (튜플)입니다.

대답에 따르면 구조체와 동등한 구문은 Java 구문으로 사용할 수 있으며 트리플은 널리 사용되는 Apache 라이브러리에서 사용할 수 있지만 실제로 올바른 방법은 값에 대한 별도의 클래스를 작성하는 것입니다. 게터와 세터. 어떤 사람은 완전한 모범을 보여 매우 친절했습니다. 47 줄의 코드였습니다 (이 라인 중 일부는 공백이었습니다).

나는 거대한 개발 커뮤니티가 "잘못"되지 않았 음을 이해합니다. 그래서 이것은 내 이해에 문제가 있습니다.

파이썬 관행은 가독성 (그 철학에서 유지 보수성을 이끌어 냄)과 그 이후 개발 속도를 최적화합니다. C 사례는 자원 사용을 최적화합니다. Java 사례는 무엇을 위해 최적화됩니까? 필자의 최선의 추측은 확장 성 (모두 수백만 LOC 프로젝트를 위해 준비된 상태 여야 함)이지만 매우 약한 추측입니다.


1
질문의 기술적 측면에 대답하지는 않지만 여전히 맥락에 있습니다 : softwareengineering.stackexchange.com/a/63145/13899
Newtopian

74


3
여기에 내가 정답이라고 생각하는 것이 있습니다. 프로그래머가 아닌 sysadmin과 같은 프로그래밍에 대해 생각하기 때문에 Java를 사용하지 않습니다. 귀하에게 소프트웨어는 가장 편리한 방식으로 특정 작업을 수행하는 데 사용하는 것입니다. 저는 20 년 동안 Java 코드를 작성해 왔으며, 제가 작업 한 일부 프로젝트에는 20, 2 년의 팀이 있습니다. Java는 Python을 대체하지 않으며 그 반대도 마찬가지입니다. 둘 다 일을하지만 일이 완전히 다릅니다.
Richard

1
Java가 사실상의 표준 인 이유는 단순히 옳기 때문입니다. 수염이 유행하기 전에 수염을 가진 진지한 사람들이 처음으로 올바르게 만들었습니다. 쉘 스크립트를 사용하여 파일을 조작하는 데 익숙하다면 Java는 견딜 수 없을 것 같습니다. 그러나 클러스터 된 redis 캐싱, 3 개의 청구 시스템 및 2 천만 파운드의 Oracle 데이터베이스 클러스터를 기반으로 하루에 5 천만 명의 사용자를 지원할 수있는 이중 중복 서버 클러스터를 만들려면 액세스하는 경우에도 파이썬을 사용하지 않을 것입니다. 파이썬의 데이터베이스는 코드가 25 줄 적습니다.
Richard

답변:


237

자바 언어

Java가 작동하는 방식에 대한 의도를 밝히려고하면이 모든 대답에 요점이 빠져 있다고 생각합니다. 파이썬과 다른 많은 언어는 아직 더 간결한 구문을 가지고 있기 때문에 Java의 장황함은 객체 지향적이라는 것이 아닙니다. Java의 자세한 정보는 액세스 수정 자 지원에서 나온 것이 아닙니다. 대신, 그것은 단순히 Java가 디자인되고 진화 된 방식입니다.

Java는 원래 OO를 통해 약간 개선 된 C로 작성되었습니다. 이러한 Java에는 70 년대의 구문이 있습니다. 또한 Java는 이전 버전과의 호환성을 유지하고 시간 테스트를 견딜 수 있도록 기능을 추가하는 데 매우 보수적입니다. Java가 2005 년에 XML 리터럴과 같은 최신 유행 기능을 추가 한 것은 XML이 10 년 후 아무도 신경 쓰지 않고 진화를 제한하는 유령 기능으로 인해 언어가 부풀려 졌을 때였습니다. 따라서 Java에는 개념을 간결하게 표현할 수있는 많은 현대 구문이 없습니다.

그러나 Java가 해당 구문을 채택하지 못하게하는 근본적인 것은 없습니다. 예를 들어, Java 8은 람다 및 메소드 참조를 추가하여 많은 상황에서 자세한 정보를 크게 줄였습니다. 자바는 비슷하게 스칼라의 케이스 클래스와 같은 컴팩트 한 데이터 타입 선언에 대한 지원을 추가 할 수있다. 그러나 Java는 단순히 그렇게하지 않았습니다. 사용자 정의 값 유형이 수평선에 있으며이 기능은이를 선언하기위한 새로운 구문을 도입 할 수 있습니다. 나는 우리가 볼 것이라고 생각합니다.


자바 문화

엔터프라이즈 Java 개발의 역사는 오늘날 우리가 보는 문화로 크게 이끌었습니다. 90 년대 후반 ~ 00 년대 초에 Java는 서버 측 비즈니스 응용 프로그램에 매우 인기있는 언어가되었습니다. 당시에는 이러한 응용 프로그램이 대부분 임시로 작성되었으며 HTTP API, 데이터베이스 및 XML 피드 처리와 같은 많은 복잡한 문제가 통합되었습니다.

00 년대에는 이러한 응용 프로그램 중 많은 부분이 Hibernate ORM, Xerces XML 파서, JSP 및 서블릿 API 및 EJB와 같이 이러한 문제를 관리 할 수있는 공통된 프레임 워크를 가지고 있음이 분명해졌습니다. 그러나 이러한 프레임 워크는 자동화하도록 설정 한 특정 도메인에서 작업하려는 노력을 줄 였지만 구성 및 조정이 필요했습니다. 당시 어떤 이유로 든 가장 복잡한 사용 사례를 제공하기 위해 프레임 워크를 작성하는 것이 인기가 있었으므로 이러한 라이브러리는 설정 및 통합이 복잡했습니다. 그리고 시간이 지남에 따라 기능이 누적되면서 점점 복잡해졌습니다. Java 엔터프라이즈 개발은 점차 써드 파티 라이브러리를 연결하는 것과 점점 알고리즘을 작성하는 것이 점점 더 어려워졌습니다.

결국 엔터프라이즈 툴의 지루한 구성 및 관리는 프레임 워크, 특히 Spring 프레임 워크가 관리를 관리하기 위해 따라 올 정도로 고통스러워졌습니다. 모든 구성을 한 곳에 배치 할 수 있고 이론이 진행된 다음 구성 도구가 조각을 구성하고 함께 연결합니다. 불행히도 이러한 "프레임 워크 프레임 워크" 는 왁스 전체에 더 많은 추상화와 복잡성더했습니다 .

지난 몇 년 동안 더 가벼운 라이브러리가 인기를 얻었습니다. 그럼에도 불구하고, 대기업 프레임 워크가 성장하는 동안 모든 세대의 Java 프로그래머가 시대를 맞이했습니다. 프레임 워크를 개발하는 역할 모델은 팩토리 팩토리 및 프록시 구성 Bean 로더를 작성했습니다. 그들은이 괴물들을 매일 구성하고 통합해야했습니다. 결과적으로 공동체 전체의 문화는 이러한 틀의 예를 따르고 과도하게 엔지니어링하는 경향이있었습니다.


15
재미있는 점은 다른 언어가 "자바주의"를 선택하고있는 것 같습니다. PHP OO 기능은 Java에서 많은 영향을 받았으며, 오늘날 JavaScript는 방대한 양의 프레임 워크와 모든 종속성을 수집하고 새로운 응용 프로그램을 시작하는 것이 얼마나 어려운지에 대한 비판을받습니다.
marcus

14
@marcus 아마 아마도 어떤 사람들은 "바퀴를 재발 명하지 마십시오"라는 것을 알게 되었습니까? 종속성은 결국 바퀴를 재발 명하지 않는 비용입니다
Walfrat

9
"Terse"는 종종 코드 골프 같은 한 줄짜리 비전, "영리한" 로 번역됩니다 .
Tulains Córdova

7
사려 깊고 잘 작성된 답변. 역사적 맥락. 상대적으로 분류되지 않은. 잘 했어요
Cheezmeister

10
@ TulainsCórdova 나는 우리가 "전염병과 같은 영리한 속임수"(Donald Knuth)를 피해야한다는 데 동의하지만, 이것이 더 적절하고 읽기 쉬운 for루프를 작성하는 것을 의미하지는 않습니다 map. 행복한 매체가 있습니다.
Brian McCutchon

73

나는 당신이 제기 한 포인트 중 하나에 대해 다른 사람들에 의해 제기되지 않은 답변을 가지고 있다고 생각합니다.

Java는 어떠한 가정도하지 않음으로써 컴파일 타임 프로그래머 오류 감지를 위해 최적화합니다

일반적으로 Java는 프로그래머가 자신의 의도를 명시 적으로 표현한 후에 만 ​​소스 코드에 대한 사실 만 유추하는 경향이 있습니다. Java 컴파일러는 코드에 대한 가정을하지 않으며 중복 코드를 줄이기 위해 추론 만 사용합니다.

이 철학의 이유는 프로그래머가 단지 인간이기 때문입니다. 우리가 쓰는 것이 항상 프로그램이 실제로 의도 한 것은 아닙니다. Java 언어는 개발자가 항상 명시 적으로 유형을 선언하도록하여 이러한 문제 중 일부를 완화하려고합니다. 그것은 작성된 코드가 실제로 의도 한 것을 수행하는지 다시 확인하는 방법입니다.

일부 다른 언어는 사전 조건, 사후 조건 및 불변성을 검사하여 해당 논리를 더욱 강화합니다 (컴파일 타임에 확실하지는 않지만). 프로그래머가 컴파일러가 자신의 작업을 다시 확인하도록하는 가장 극단적 인 방법입니다.

귀하의 경우, 이는 컴파일러가 실제로 반환한다고 생각하는 유형을 실제로 반환하도록 보장하려면 해당 정보를 컴파일러에 제공해야합니다.

Java에는 두 가지 방법이 있습니다.

  1. 사용 Triplet<A, B, C>정말로에 있어야하는 (반환 형식으로 java.util, 그렇지 않은 이유를 정말 설명 할 수 없다. JDK8 소개 특히 때문에 Function, BiFunction, Consumer, BiConsumer, 등 ... 그것은 그냥 보인다 PairTriplet이상 감각을 만들 것입니다.하지만 난 벗어나 다 )

  2. 각 필드의 이름과 유형이 올바르게 지정된 해당 목적에 맞는 고유 한 값 유형을 작성하십시오.

두 경우 모두 컴파일러는 함수가 선언 한 형식을 반환하고 호출자가 반환 된 각 필드의 형식을 인식하고 적절하게 사용하도록 보장 할 수 있습니다.

일부 언어는 정적 유형 검사와 유형 유추를 동시에 제공하지만 미묘한 유형의 불일치 문제에 대한 문을 열어 둡니다. 개발자가 특정 유형의 값을 반환하려고하지만 실제로 다른 유형을 반환하는 경우 컴파일러는 여전히 함수와 호출자 모두의 동시 발생에 의해 의도 된 두 가지 모두에 적용 할 수있는 메소드 만 사용하기 때문에 코드를 수락합니다 실제 유형.

명시 적 타이핑 대신 유형 유추가 사용되는 Typescript (또는 플로우 유형)에서 이와 같은 것을 고려하십시오.

function parseDurationInMillis(csvLine){
    // here the developer intends to return a Number, 
    // but actually it's a String
    return csv.firstField();
}

// Compiler infers that parseDurationInMillis is String, so it does
// string concatenation and infers that plusTwoSeconds is String
// Developer actually intended Number
var plusTwoSeconds = 2000 + parseDurationInMillis(csvLine);

물론 이것은 사소한 사소한 일이지만 코드 제대로 보이기 때문에 훨씬 더 미묘하고 문제를 디버깅하기가 어려울 수 있습니다. 이러한 종류의 문제는 Java에서 완전히 피할 수 있으며 이것이 전체 언어가 설계된 것입니다.


적절한 객체 지향 원칙과 도메인 중심 모델링에 따라 링크 된 질문의 기간 분석 사례도 java.time.Duration객체 로 반환 될 수 있으며, 이는 위의 두 경우보다 훨씬 더 명확합니다.


38
Java가 코드 정확성을 최적화한다고 말하는 것이 올바른 지적 일 수는 있지만 언어가 실패하거나 그렇게 비효율적이라고 생각합니다 (많은 언어, 최근에는 약간의 Java 프로그래밍). 물론 Java는 오래되었고 당시에는 많은 것들이 존재하지 않았지만 Haskell (그리고 Rust 또는 Go라고 감히 말한 것처럼) 더 나은 정확성 검사를 제공하는 현대적인 대안이 있습니다. 이 목표를 달성하기 위해 Java의 모든 조작이 불필요합니다. 그리고 게다가, 이것은 문화 를 전혀 설명 하지는 않지만 , 솔로 모노 프는 문화가 BS라고 밝혔습니다.
tomsmeding

8
이 샘플은 형식 유추가 동적 언어에서 암시 적 변환을 허용하려는 JavaScript의 결정이 바보라는 문제라는 것을 보여주지 않습니다. 제정 된 동적 언어 또는 유형 유추가있는 정적으로 선전 된 언어는이를 허용하지 않습니다. 두 종류의 예 : 파이썬은 런타임을 제외하고 Haskell은 컴파일하지 않습니다.
Voo

39
@tomsmeding Haskell이 Java보다 5 년 정도 더 오래 갔기 때문에이 주장은 특히 어리석은 일 입니다. Java에는 유형 시스템이있을 수 있지만 릴리스 될 때 최신 정확성 검증조차하지 않았습니다.
Alexis King

21
"컴파일 타임 오류 감지를 위해 Java를 최적화합니다"— 아니요. 제공 하지만 다른 사람들이 지적했듯이 단어의 의미에서이를 최적화하지는 않습니다. 또한,이 인수는 전혀 관계가없는 다른 언어 때문에 영업 이익의 질문에 않습니다 실제로 컴파일 타임 오류 검출을 위해 최적화 비슷한 시나리오에 대한 훨씬 더 가벼운 구문을 가지고있다. Java의 장황한 설명은 컴파일 타임 확인과는 아무런 관련이 없습니다 .
Konrad Rudolph

19
@KonradRudolph 네,이 답변은 감정적으로 호소력이 있다고 가정하지만 완전히 무의미합니다. 왜 그렇게 많은 표가 있는지 잘 모르겠습니다. Haskell (또는 훨씬 더 중요한 Idris)은 Java보다 정확성을 훨씬 더 많이 최적화하며 간단한 구문의 튜플이 있습니다. 또한 데이터 유형을 정의하는 것은 하나의 라이너이며 Java 버전이 얻는 모든 것을 얻습니다. 이 답변은 언어 디자인이 불충분하고 불필요한 자세한 표현에 대한 변명이지만 Java를 좋아하고 다른 언어에 익숙하지 않은 경우 좋은 소리 입니다.
Alexis King

50

Java와 Python이 가장 많이 사용하는 두 가지 언어이지만 다른 방향에서 왔습니다. 즉, 파이썬을 사용하기 전에 Java 세계에 깊이 빠져서 도움을 줄 수 있습니다. "무슨 일이 왜 그렇게 무거운가?"라는 더 큰 질문에 대한 답은 두 가지로 나옵니다.

  • 둘 사이의 개발 비용은 긴 풍선 동물 풍선의 공기와 같습니다. 풍선의 한 부분을 쥐고 다른 부분이 부풀어 오를 수 있습니다. 파이썬은 초기 부분을 짜내는 경향이 있습니다. 자바는 후반부를 압박한다.
  • 자바는 여전히이 무게의 일부를 제거 할 수있는 기능이 없다. Java 8은 이것에 큰 찌그러짐을 만들었지 만 문화는 그 변화를 완전히 소화하지 못했습니다. Java는와 같은 몇 가지를 더 사용할 수 있습니다 yield.

Java는 대규모 팀이 수년 동안 유지 관리 할 가치가 높은 소프트웨어를 '최적화'합니다. 나는 파이썬으로 물건을 쓰고 1 년 후에 그것을보고 내 자신의 코드에 당황한 경험이 있습니다. Java에서는 다른 사람들의 코드를 작은 조각으로보고 그 기능을 즉시 알 수 있습니다. 파이썬에서는 실제로 그렇게 할 수 없습니다. 그것은 당신이 깨닫는 것처럼 나아지는 것이 아니라 비용이 다릅니다.

언급 한 특정 경우에는 튜플이 없습니다. 쉬운 해결책은 공개 가치를 가진 클래스를 만드는 것입니다. Java가 처음 나왔을 때 사람들은 이것을 상당히 규칙적으로 수행했습니다. 그렇게하는 첫 번째 문제는 유지 관리 두통이라는 것입니다. 로직이나 스레드 안전성을 추가하거나 다형성을 사용하려면 최소한 'tuple-esque'객체와 상호 작용하는 모든 클래스를 터치해야합니다. 파이썬에는 이와 같은 솔루션이 __getattr__있으므로 그렇게 끔찍하지는 않습니다.

그럼에도 불구하고 나쁜 습관 (IMO)이 있습니다. 이 경우 튜플을 원한다면 왜 튜플을 변경 가능한 객체로 만들지 의문입니다. getter 만 필요합니다 (부수적으로 get / set 규칙은 싫어하지만 그것이 무엇인지). 베어 클래스 (변경 가능 여부)는 Java의 개인 또는 패키지 개인 컨텍스트에서 유용 할 수 있다고 생각합니다 . 즉, 프로젝트의 참조를 클래스로 제한함으로써 클래스의 공용 인터페이스를 변경하지 않고 나중에 필요에 따라 리팩터링 할 수 있습니다. 간단한 불변 객체를 만드는 방법의 예는 다음과 같습니다.

public class Blah 
{
  public static Blah blah(long number, boolean isInSeconds, boolean lessThanOneMillis)
  {
    return new Blah(number, isInSeconds, lessThanOneMillis);
  }

  private final long number;
  private final boolean isInSeconds;
  private final boolean lessThanOneMillis;

  public Blah(long number, boolean isInSeconds, boolean lessThanOneMillis)
  {
    this.number = number;
    this.isInSeconds = isInSeconds;
    this.lessThanOneMillis = lessThanOneMillis;
  }

  public long getNumber()
  {
    return number;
  }

  public boolean isInSeconds()
  {
    return isInSeconds;
  }

  public boolean isLessThanOneMillis()
  {
    return lessThanOneMillis;
  }
}

이것은 내가 사용하는 일종의 패턴입니다. IDE를 사용하지 않는 경우 시작해야합니다. 그것은 당신을 위해 게터 (그리고 필요한 경우 세터)를 생성하므로 그렇게 고통스럽지 않습니다.

여기에 대부분의 요구를 충족시키는 것으로 보이는 유형이 이미 있음을 지적하지 않으면 망설임을 느낄 것 입니다. 이를 제외하고는 사용중인 접근 방식이 Java가 강점이 아니라 약점에 영향을 미치기 때문에 적합하지 않습니다. 간단한 개선 사항은 다음과 같습니다.

public class Blah 
{
  public static Blah fromSeconds(long number)
  {
    return new Blah(number * 1000_000);
  }

  public static Blah fromMills(long number)
  {
    return new Blah(number * 1000);
  }

  public static Blah fromNanos(long number)
  {
    return new Blah(number);
  }

  private final long nanos;

  private Blah(long nanos)
  {
    this.nanos = nanos;
  }

  public long getNanos()
  {
    return nanos;
  }

  public long getMillis()
  {
    return getNanos() / 1000; // or round, whatever your logic is
  }

  public long getSeconds()
  {
    return getMillis() / 1000; // or round, whatever your logic is
  }

  /* I don't really know what this is about but I hope you get the idea */
  public boolean isLessThanOneMillis()
  {
    return getMillis() < 1;
  }
}

의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
maple_shaft

2
"더 현대적인"Java 기능과 관련하여 Scala도 살펴 보도록하겠습니다.
MikeW

1
@MikeW 나는 실제로 초기 릴리스에서 Scala 방식으로 시작하여 잠시 동안 scala 포럼에서 활동했습니다. 언어 디자인에서 큰 성과를 거두었다고 생각하지만 실제로는 내가 원하는 것이 아니며 해당 커뮤니티의 사각 지대라는 결론에 도달했습니다. 그때 이후로 크게 바뀌었기 때문에 다시 한 번 살펴 봐야합니다.
JimmyJames

이 답변은 OP가 제기 한 대략적인 가치 측면을 다루지 않습니다.
ErikE

@ErikE "근사값"이라는 단어가 질문 텍스트에 나타나지 않습니다. 내가 해결하지 않은 질문의 특정 부분을 알려 주시면 그렇게 할 수 있습니다.
JimmyJames 17

41

Java에 너무 화가 나기 전에 다른 게시물에서 내 답변을 읽으십시오 .

불만 사항 중 하나는 일부 값 집합을 답변으로 반환하기 위해 클래스를 만들어야한다는 것입니다. 이것은 귀하의 프로그래밍 직관이 올바르게 진행되고 있음을 보여주는 올바른 관심사입니다! 그러나 나는 당신이 저지른 원시적 강박 관념에 반하여 다른 답변에 마크가 빠져 있다고 생각합니다. 또한 Java는 여러 프리미티브로 작업하기가 쉽지 않습니다. 파이썬은 여러 프리미티브를 사용하여 여러 값을 기본적으로 반환하고 여러 변수에 쉽게 할당 할 수 있습니다.

그러나 ApproximateDuration타입이 당신을 위해 무엇을하는지 생각하기 시작 하면, "세 개의 값을 반환하기 위해 겉보기에 불필요한 클래스"로 범위가 좁지 않다는 것을 알게됩니다. 이 클래스가 나타내는 개념은 실제로 핵심 도메인 비즈니스 개념 중 하나입니다 . 대략적인 방식으로 시간을 표현하고 비교할 수 있어야합니다. 이는 테스트, 모듈 식, 재사용 및 유용성을 제공 할 수 있도록 우수한 객체 및 도메인 지원과 함께 애플리케이션 코어의 유비쿼터스 언어의 일부 여야합니다.

대략적인 지속 시간을 합산하는 코드 (또는 오류 한계가있는 지속 시간)는 전체 절차 적입니까? 아니면 객체 지향성이 있습니까? 대략적인 지속 시간을 합산하는 것에 대한 좋은 디자인은 테스트 할 수있는 클래스 내에서 소비 코드 외부에서 수행하는 것이 좋습니다. 이러한 종류의 도메인 객체를 사용하면 코드에서 긍정적 인 파급 효과가 발생하여 한 줄씩 절차 단계에서 벗어나 단일 책임 클래스를 통해 단일 책임 과제를 달성하는 데 도움이됩니다. 서로 다른 우려의 갈등이 없습니다.

예를 들어, 지속 시간 합산 및 비교가 올바르게 작동하는 데 실제로 필요한 정밀도 또는 스케일에 대해 더 자세히 알고 "약 32 밀리 초 오류"(사각형에 근접 함)를 나타내는 중간 플래그가 필요하다는 것을 알 수 있습니다 1000의 루트이므로 1과 1000 사이의 로그에 반정도). 프리미티브를 사용하여 이것을 나타내는 코드에 자신을 묶었다면 코드의 모든 위치를 찾아서 is_in_seconds,is_under_1ms변경해야합니다.is_in_seconds,is_about_32_ms,is_under_1ms. 모든 것이 모든 곳에서 바뀌어야 할 것입니다! 오류 마진을 기록하여 다른 곳에서 사용할 수 있도록 책임을지는 클래스를 만들면 소비자는 오류 마진 또는 결합 방법에 대한 세부 정보를 알 수 없으며 관련 오류 마진을 지정할 수 있습니다. 지금. 즉, 모든 오래된 오류 마진이 여전히 유효하기 때문에 클래스에서 새 오류 마진을 추가 할 때 오류 마진이 올바른 소비 코드는 변경되지 않습니다.

결산 명세서

그러면 Java의 무거움에 대한 불만은 SOLID 및 GRASP의 원칙과보다 진보 된 소프트웨어 엔지니어링에 가까워 질수록 사라지는 것 같습니다.

추가

C #의 자동 속성과 생성자에 get-only 속성을 할당하는 기능이 "자바 방식"에 필요한 다소 복잡한 코드를 더 명확하게 정리하는 데 도움이된다는 점을 완전하고도 불공평하게 추가 할 것입니다 (명시적인 개인 지원 필드 및 getter / setter 함수 사용) :

// Warning: C# code!
public sealed class ApproximateDuration {
   public ApproximateDuration(int lowMilliseconds, int highMilliseconds) {
      LowMilliseconds = lowMilliseconds;
      HighMilliseconds = highMilliseconds;
   }
   public int LowMilliseconds { get; }
   public int HighMilliseconds { get; }
}

위의 Java 구현은 다음과 같습니다.

public final class ApproximateDuration {
  private final int lowMilliseconds;
  private final int highMilliseconds;

  public ApproximateDuration(int lowMilliseconds, int highMilliseconds) {
    this.lowMilliseconds = lowMilliseconds;
    this.highMilliseconds = highMilliseconds;
  }

  public int getLowMilliseconds() {
    return lowMilliseconds;
  }

  public int getHighMilliseconds() {
    return highMilliseconds;
  }
}

이제는 꽤 대담합니다. 불변성을 매우 중요하고 의도적으로 사용하는 것에 주목하십시오. 이것은 특정 종류의 가치를 지니는 클래스에 중요합니다.

그 점 에서이 클래스는 또한 struct값 유형 인 적절한 후보입니다 . 일부 테스트에서는 구조체로 전환하면 런타임 성능 이점이 있는지 여부를 알 수 있습니다 (가능한 경우).


9
나는 이것이 내가 가장 좋아하는 대답이라고 생각합니다. 나는 이처럼 엉뚱한 버림받은 소지자 클래스를 어른으로 승진시킬 때 모든 관련 책임과 소란의 고향이된다는 것을 알게되었습니다! 모든 것이 깨끗해집니다! 그리고 나는 보통 문제 공간에 대해 흥미로운 것을 배웁니다. 과도하게 엔지니어링하는 것을 피하는 기술은 분명히 있지만 ... 고슬링은 GOTO와 마찬가지로 튜플 구문을 생략했을 때 어리석지 않았습니다. 귀하의 성명은 훌륭합니다. 감사!
SusanW

2
이 답변이 마음에 드시면 도메인 기반 디자인을 읽으십시오. 코드에서 도메인 개념을 나타내는이 주제에 대해 할 말이 많습니다.
neontapir

@neontapir 예, 감사합니다! 나는 그 이름이 있다는 것을 알고 있었다! :-) 도메인 개념이 영감을 얻은 리팩토링에서 유기적으로 나타날 때 선호한다고 말해야하지만 (이와 같이!) ... 해왕성을 발견하여 19 세기 중력 문제를 해결할 때와 조금 비슷합니다. .
SusanW

@SusanW 동의합니다. 원시 강박 관념을 제거하기위한 리팩토링은 도메인 개념을 발견하는 훌륭한 방법이 될 수 있습니다!
neontapir

논의한대로 예제의 Java 버전을 추가했습니다. 나는 C #의 모든 마술 구문 설탕에 올라 있지 않으므로 뭔가 빠진 것이 있으면 알려주십시오.
JimmyJames 17

24

Python과 Java는 모두 디자이너의 철학에 따라 유지 관리에 최적화되어 있지만이를 달성하는 방법에 대한 아이디어는 매우 다릅니다.

Python은 코드의 명확성단순성 (읽기 및 쓰기가 용이함)을 최적화하는 다중 패러다임 언어입니다 .

Java는 전통적으로 단일 패러다임 클래스 기반 OO 언어로 ,보다 장황한 코드를 사용하더라도 명료성일관성 을 최적화 합니다.

파이썬 튜플은 고정 된 수의 필드를 가진 데이터 구조입니다. 명시 적으로 선언 된 필드가있는 일반 클래스에서도 동일한 기능을 수행 할 수 있습니다. 파이썬에서는 특히 튜플에 대한 내장 구문 지원으로 인해 코드를 크게 단순화 할 수 있으므로 클래스 대신 튜플을 제공하는 것이 당연합니다.

그러나 명시 적으로 선언 된 클래스를 이미 사용할 수 있기 때문에 Java 문화와 일치하여 바로 가기를 제공하지 않습니다. 코드를 저장하고 선언을 피하기 위해 다른 종류의 데이터 구조를 도입 할 필요가 없습니다.

Java는 최소 특수 구문 구문 설탕을 일관되게 적용하는 단일 개념 (클래스)을 선호하는 반면 Python은 여러 도구와 많은 구문 설탕을 제공하여 특정 목적에 가장 편리한 것을 선택할 수 있습니다.


+1이지만 스칼라와 같은 클래스가있는 정적으로 유형이 지정된 언어와 어떻게 튜플을 도입해야 하는지를 알 수 있습니까? 클래스가있는 언어의 경우에도 튜플이 더 깔끔한 솔루션이라고 생각합니다.
Andres F.

@AndresF .: 메쉬의 의미는 무엇입니까? 스칼라는 Java와 다른 언어이며 다른 디자인 원칙과 관용구를 가지고 있습니다.
JacquesB

나는 당신의 5 번째 단락에 대한 답으로 "그러나 이것은 Java 문화와 실제로는 일치하지 않습니다 ...]. 사실, 나는 장황함이 Java 문화의 일부이지만, 튜플의 부족은 "이미 명시 적으로 선언 된 클래스를 사용하기"때문일 수 없다. 결국 스칼라는 명시 적 클래스를 가지고있다 (그리고 더 간결한 케이스 클래스를 도입한다 ). 튜플도 허용합니다! 결국, 자바가 튜플을 도입하지 않았을 가능성이 있다고 생각합니다. 클래스는 클래스를 동일하게 만들 수있을뿐만 아니라 구문이 C 프로그래머에게 친숙해야하며 C에는 튜플이 없었기 때문입니다.
Andres F.

@AndresF .: Scala는 여러 가지 방식으로 작업을 회피하지 않으며 기능적 패러다임과 클래식 OO의 기능을 결합합니다. 그런 식으로 파이썬 철학에 더 가깝습니다.
JacquesB

@AndresF .: 예 Java는 C / C ++에 가까운 구문으로 시작했지만 많이 단순화되었습니다. C #은 거의 정확한 Java 사본으로 시작했지만 몇 년 동안 튜플을 포함하여 많은 기능을 추가했습니다. 언어 디자인 철학의 차이입니다. (이후 버전의 Java는 독단적이지 않은 것으로 보인다. 같은 클래스를 수행 할 수 있기 때문에 고차 함수는 처음에 거부되었지만 이제는 소개되었으므로 철학에 변화가있을 수있다.)
JacquesB

16

관행을 검색하지 마십시오. 모범 사례 BAD 에서 말했듯이 일반적으로 나쁜 생각입니다 . . 모범 사례를 요구하지는 않지만 여전히 관련 요소를 찾을 수 있다고 생각합니다.

문제에 대한 해결책을 찾는 것이 연습보다 낫고 문제는 Java에서 세 가지 값을 빠르게 반환하는 튜플이 아닙니다.

  • 배열이 있습니다
  • 하나의 라이너로 배열을 목록으로 반환 할 수 있습니다. Arrays.asList (...)
  • 보일러 플레이트를 적게 사용하고 물체를 옆면으로 유지하려면 :

class MyTuple {
    public final long value_in_milliseconds;
    public final boolean is_in_seconds;
    public final boolean is_under_1ms;
    public MyTuple(long value_in_milliseconds,....){
        ...
    }
 }

여기에는 데이터 만 포함하는 불변의 객체가 있으며 공개이므로 getter가 필요하지 않습니다. 그러나 ORM과 같은 일부 직렬화 도구 또는 지속성 계층을 사용하는 경우 일반적으로 getter / setter를 사용합니다 (getter / setter 대신 필드를 사용하도록 매개 변수를 허용 할 수 있음). 이것이 바로 이러한 관행이 많이 사용되는 이유입니다. 따라서 관행에 대해 알고 싶다면 관행을 더 잘 활용하기 위해 왜 여기에 있는지 이해하는 것이 좋습니다.

마지막으로, 나는 많은 직렬화 도구를 사용하기 때문에 getter를 사용하지만 그것들도 쓰지 않는다. 나는 lombok을 사용합니다. IDE에서 제공하는 바로 가기를 사용합니다.


9
다양한 언어로 된 일반적인 관용구를 이해하는 데 여전히 가치가 있습니다. 사실상이 표준이 접근하기 쉽기 때문입니다. 이러한 관용구는 어떤 이유로 든 "모범 사례"라는 제목에 속합니다.
Berin Loritsch

3
이 문제에 대한 합리적인 해결책입니다. [gs] etters를 사용하여 과도하게 엔지니어링 된 OOP 솔루션 대신 소수의 공개 멤버로 클래스 (/ struct)를 작성하는 것이 상당히 합리적입니다.
tomsmeding

3
파이썬과 달리 검색이나 더 짧고 더 우아한 솔루션이 권장되지 않기 때문에 이것은 팽창으로 이어집니다. 그러나 단점은 Java 개발자에게 부풀림이 다소 비슷하다는 것입니다. 따라서 개발자가 더 상호 교환 가능하고 두 프로젝트가 합쳐 지거나 두 팀이 의도 치 않게 분기되어 유지 보수성이 향상되므로 스타일 차이가 줄어 듭니다.
Mikhail Ramendik

2
@MikhailRamendik 그것은 YAGNI와 OOP의 트레이드 오프입니다. Orthodox OOP는 모든 단일 객체를 교체 할 수 있어야한다고 말합니다. 중요한 것은 객체의 공용 인터페이스입니다. 이를 통해 콘크리트 객체와 인터페이스에 중점을 둔 코드를 작성할 수 있습니다. 필드는 인터페이스의 일부가 될 수 없으므로 노출되지 않습니다. 이렇게 하면 장기적으로 코드를 훨씬 쉽게 유지 관리 수 있습니다. 또한 유효하지 않은 상태를 방지 할 수 있습니다. 원본 샘플에서 상호 배타적 인 "<ms"및 "s"객체를 가질 수 있습니다. 그 나쁜.
Luaan

2
@PeterMortensen 그것은 상당히 관련이없는 것들을 많이 수행하는 Java 라이브러리입니다. 그래도 아주 좋습니다. 기능은 다음과 같습니다
Michael

11

일반적으로 Java 숙어 정보 :

Java에 모든 클래스가있는 여러 가지 이유가 있습니다. 내가 아는 한 주된 이유는 다음과 같습니다.

초보자는 Java를 쉽게 배울 수 있어야합니다. 더 분명한 것이 중요할수록 중요한 세부 사항을 놓치기가 더 어려워집니다. 초보자가 이해하기 어려운 마술이 덜 발생합니다.


구체적인 예를 들어, 별도의 클래스에 대한 논란은 다음과 같습니다. 만약이 세 가지가 서로 밀접하게 관련되어 있다면 하나의 값으로 반환되고, "사물"이라는 가치가 있습니다. 그리고 일반적인 방식으로 구조화 된 것들 그룹의 이름을 도입한다는 것은 클래스를 정의하는 것을 의미합니다.

Lombok과 같은 도구를 사용하여 상용구를 줄일 수 있습니다.

@Value
class MyTuple {
    long value_in_milliseconds;
    boolean is_in_seconds;
    boolean is_under_1ms;
}

5
Google의 AutoValue 프로젝트이기도합니다. github.com/google/auto/blob/master/value/userguide/index.md
Ivan

이 때문에 Java 프로그램을 비교적 쉽게 유지할
Thorbjørn Ravn Andersen

7

Java 문화에 관해 말할 수있는 많은 것들이 있지만, 지금 당면한 상황에서는 몇 가지 중요한 측면이 있다고 생각합니다.

  1. 라이브러리 코드는 한 번 작성되지만 훨씬 더 자주 사용됩니다. 라이브러리 작성의 오버 헤드를 최소화하는 것이 좋지만 장기적으로는 라이브러리 사용의 오버 헤드를 최소화하는 방식으로 작성하는 것이 더 가치가 있습니다.
  2. 즉, 자체 문서화 유형이 훌륭하다는 것을 의미합니다. 메소드 이름을 사용하면 발생하는 일과 객체에서 발생하는 일을 명확하게 알 수 있습니다.
  3. 정적 타이핑은 특정 클래스의 오류를 제거하는 데 매우 유용한 도구입니다. 그것은 모든 것을 고치지는 않습니다 (Haskell에 대한 농담을 좋아하는 사람들은 일단 유형 시스템이 코드를 받아들이게되면 아마 정확할 것입니다). 그러나 특정 종류의 잘못된 것을 불가능하게 만드는 것은 매우 쉽습니다.
  4. 라이브러리 코드 작성은 계약 지정에 관한 것입니다. 인수 및 결과 유형에 대한 인터페이스를 정의하면 계약의 경계가보다 명확하게 정의됩니다. 어떤 것이 튜플을 받아들이거나 생산한다면, 그것이 실제로 수신하거나 생산 해야하는 튜플인지 여부는 없으며, 일반적인 유형에 대한 제약 방식이 거의 없습니다 (적당한 수의 요소가 있습니까?) 그들은 당신이 기대 한 유형입니까?).

필드가있는 "구조"클래스

다른 답변에서 언급했듯이 공개 필드가있는 클래스를 사용할 수 있습니다. 이것을 최종적으로 만들면 불변 클래스를 얻고 생성자로 클래스를 초기화합니다.

   class ParseResult0 {
      public final long millis;
      public final boolean isSeconds;
      public final boolean isLessThanOneMilli;

      public ParseResult0(long millis, boolean isSeconds, boolean isLessThanOneMilli) {
         this.millis = millis;
         this.isSeconds = isSeconds;
         this.isLessThanOneMilli = isLessThanOneMilli;
      }
   }

물론 이것은 특정 클래스에 묶여 있고 구문 분석 결과를 생성하거나 소비 해야하는 모든 것이이 클래스를 사용해야한다는 것을 의미합니다. 일부 응용 프로그램의 경우 문제가 없습니다. 다른 사람들에게는 약간의 고통이 생길 수 있습니다. 많은 Java 코드는 계약 정의에 관한 것이며 일반적으로 인터페이스로 연결됩니다.

또 다른 함정은 클래스 기반 접근 방식을 사용하면 필드를 노출하고 해당 필드 모두에 값이 있어야한다는 것입니다. 예를 들어 isLessThanOneMilli가 true 인 경우에도 isSeconds 및 millis는 항상 일부 값을 가져야합니다. isLessThanOneMilli가 true 일 때 millis 필드 값의 해석은 무엇이어야합니까?

인터페이스로서의 "구조"

인터페이스에서 정적 메소드를 허용하면 구문상의 오버 헤드없이 불변 유형을 작성하는 것이 상대적으로 쉽습니다. 예를 들어, 다음과 같은 종류의 결과 구조를 구현할 수 있습니다.

   interface ParseResult {
      long getMillis();

      boolean isSeconds();

      boolean isLessThanOneMilli();

      static ParseResult from(long millis, boolean isSeconds, boolean isLessThanOneMill) {
         return new ParseResult() {
            @Override
            public boolean isSeconds() {
               return isSeconds;
            }

            @Override
            public boolean isLessThanOneMilli() {
               return isLessThanOneMill;
            }

            @Override
            public long getMillis() {
               return millis;
            }
         };
      }
   }

그것은 여전히 ​​많은 상용구이며, 나는 완전히 동의하지만, 몇 가지 이점도 있습니다. 그리고 나는 그들이 당신의 주요 질문에 대답하기 시작한다고 생각합니다.

이 구문 분석 결과와 같은 구조를 사용하면 구문 분석기 의 계약 이 매우 명확하게 정의됩니다. 파이썬에서 한 튜플은 다른 튜플과 실제로 구별되지 않습니다. Java에서는 정적 입력을 사용할 수 있으므로 이미 특정 클래스의 오류를 배제했습니다. 예를 들어 Python에서 튜플을 반환하고 튜플 (millis, isSeconds, isLessThanOneMilli)을 반환하려는 경우 실수로 다음을 수행 할 수 있습니다.

return (true, 500, false)

당신이 의미 할 때 :

return (500, true, false)

이러한 종류의 Java 인터페이스를 사용하면 컴파일 할 수 없습니다.

return ParseResult.from(true, 500, false);

조금도. 너가해야되는:

return ParseResult.from(500, true, false);

이는 정적으로 유형이 지정된 언어의 이점입니다.

이 방법은 또한 얻을 수있는 값을 제한 할 수있는 기능을 제공하기 시작합니다. 예를 들어, getMillis ()를 호출 할 때 isLessThanOneMilli ()가 true인지 여부를 확인할 수 있으며,이 경우 의미있는 값 (millis)이 없으므로 IllegalStateException (예 : IllegalStateException)을 처리하십시오.

잘못된 일을하기 어렵게 만들기

위의 인터페이스 예에서 isSeconds 및 isLessThanOneMilli 인수는 동일한 유형을 갖기 때문에 실수로 바꿀 수있는 문제가 있습니다.

실제로 TimeUnit과 Duration을 실제로 사용하여 다음과 같은 결과를 얻을 수 있습니다.

   interface Duration {
      TimeUnit getTimeUnit();

      long getDuration();

      static Duration from(TimeUnit unit, long duration) {
         return new Duration() {
            @Override
            public TimeUnit getTimeUnit() {
               return unit;
            }

            @Override
            public long getDuration() {
               return duration;
            }
         };
      }
   }

   interface ParseResult2 {

      boolean isLessThanOneMilli();

      Duration getDuration();

      static ParseResult2 from(TimeUnit unit, long duration) {
         Duration d = Duration.from(unit, duration);
         return new ParseResult2() {
            @Override
            public boolean isLessThanOneMilli() {
               return false;
            }

            @Override
            public Duration getDuration() {
               return d;
            }
         };
      }

      static ParseResult2 lessThanOneMilli() {
         return new ParseResult2() {
            @Override
            public boolean isLessThanOneMilli() {
               return true;
            }

            @Override
            public Duration getDuration() {
               throw new IllegalStateException();
            }
         };
      }
   }

그것은 훨씬 더 많은 코드 가 될 것입니다. 하지만 한 번만 작성하면되며 (문서를 올바르게 문서화했다고 가정하면) 코드 를 사용 하는 사람들 은 결과의 의미를 추측 할 필요가 없습니다. result[0]그들이 뜻할 때 실수로 일을 할 수 없습니다 result[1]. 여전히 간결하게 인스턴스 를 만들 수 있으며 인스턴스에서 데이터를 가져 오는 것도 그렇게 어려운 것은 아닙니다.

  ParseResult2 x = ParseResult2.from(TimeUnit.MILLISECONDS, 32);
  ParseResult2 y = ParseResult2.lessThanOneMilli();

실제로 클래스 기반 접근 방식으로 이와 같은 작업을 수행 할 수 있습니다. 다른 경우에 대해 생성자를 지정하십시오. 그래도 다른 필드를 초기화하는 것에 대한 문제가 있지만 필드에 대한 액세스를 막을 수는 없습니다.

또 다른 대답은 Java의 엔터프라이즈 유형 특성으로 인해 이미 존재하는 다른 라이브러리를 작성하거나 다른 사람들이 사용할 라이브러리를 작성한다는 의미입니다. 귀하의 공개 API는 그것을 피할 수있는 경우 결과 유형을 해독 문서를 컨설팅 많은 시간을 필요가 없습니다.

당신은 쓰기 한 번 이러한 구조를,하지만 당신은 만들 그들에게 여러 번, 그래서 당신은 여전히 (당신이 얻을)이 간결하게 만들 싶어. 정적 타이핑을 사용하면 데이터에서 얻는 데이터가 예상 한 것입니다.

이제는 간단한 튜플이나 목록이 많은 의미를 가질 수있는 곳이 여전히 있습니다. 무언가의 배열을 반환하는 데 오버 헤드가 적을 수 있으며 그 경우 (그리고 프로파일 링으로 결정한 오버 헤드가 중요한 경우) 내부적 으로 간단한 값 배열을 사용하면 많은 의미가 있습니다. 공개 API에는 여전히 명확하게 정의 된 유형이 있어야합니다.


마지막에 실제 시간 단위와 함께 UNDER1MS를 추가했기 때문에 마지막에 TimeUnit 대신 자체 열거 형을 사용했습니다. 이제 3 개가 아닌 2 개 필드 만 있습니다. (이론적으로 TimeUnit.NANOSECONDS를 오용 할 수 있지만 매우 혼란 스러울 수 있습니다.)
Mikhail Ramendik

나는 getter를 만들지 않았지만 이제는 originalTimeUnit=DurationTimeUnit.UNDER1MS호출자가 밀리 초 값을 읽으려고 할 때 getter가 어떻게 예외를 던질 수 있는지 알 수 있습니다.
Mikhail Ramendik

나는 당신이 그것을 언급했다는 것을 알고 있지만, 파이썬 튜플을 사용한 예제는 실제로 동적 대 정적 타입 튜플에 관한 것임을 기억하십시오. 실제로 튜플 자체에 대해서는별로 언급하지 않습니다. 알다시피, 정적 타입의 튜플은 컴파일에 실패 있습니다. :)
Andres F.

1
@ Veedrac 무슨 말인지 잘 모르겠습니다. 내 요점은 라이브러리 코드를 작성 하는 것보다 코드를 사용 하는 데 더 많은 시간이 걸리기 때문에 라이브러리 코드를 더 자세하게 작성 하는 것이 좋습니다 ( 시간이 더 걸리더라도) .
Joshua Taylor

1
@Veedrac 네, 맞습니다. 그러나 나는이 것을 유지하는 것 입니다 의미 다시 사용됩니다 코드와 코드 사이에 구별이되지 않습니다. 예를 들어, Java 패키지에서 일부 클래스는 공용이며 다른 위치에서 사용됩니다. 이러한 API는 잘 고려해야합니다. 내부적으로는 서로 대화해야하는 수업이 있습니다. 이러한 내부 커플 링은 빠르고 더러운 구조 (예 : 세 가지 요소가있는 Object [])가 좋은 곳입니다. 퍼블릭 API의 경우 일반적으로보다 의미 있고 명확한 것이 선호됩니다.
Joshua Taylor

7

문제는 사과를 오렌지와 비교한다는 것 입니다 . 타입이 지정되지 않은 튜플을 사용하여 신속하고 더러운 파이썬 예제를 제공하는 단일 값보다 더 많은 값을 반환하는 시뮬레이션 방법을 실제로 실제로 한 줄짜리 답변을 받았습니다 .

허용 된 답변은 올바른 비즈니스 솔루션을 제공합니다. 처음에는 버리고 올바르게 구현해야하는 빠른 임시 해결 방법이 없습니다. 반환 된 값으로 실제적인 작업을 수행해야하지만 지속성, 직렬화 / 직렬화, 계측 및 가능한 모든 것.

이것은 또한 오래 걸리지 않습니다. 작성해야 할 유일한 것은 필드 정의입니다. 세터, 게터, 해시 코드 및 등호를 생성 할 수 있습니다. 따라서 실제 질문은 왜 게터와 세터가 자동 생성되지는 않지만 문화적 문제가 아닌 구문 문제 (구문 설탕 문제, 일부는 말할 것입니다)입니다.

그리고 마지막으로, 당신이있어 지나친 전혀 중요하지 뭔가를 빠르게하려고합니다. DTO 클래스를 작성하는 데 소요되는 시간은 시스템을 유지 관리하고 디버깅하는 데 소요되는 시간과 비교하여 중요하지 않습니다. 따라서 아무도 덜 자세한 정보를 최적화하지 않습니다.


2
"아무도"는 사실 정확하지 않습니다. 덜 자세한 정보를 위해 최적화 된 많은 도구가 있으며 정규 표현식은 극단적 인 예입니다. 그러나 당신은 "주류 자바 세계에 아무도"를 의미했을 것입니다. 그리고 그 답을 읽는 것은 많은 의미가 있습니다. 감사합니다. 자세한 내용은 코드를 작성하는 데 걸리는 시간이 아니라 코드를 읽는 데 걸린 시간입니다. IDE는 읽기 시간을 절약하지 않습니다. 그러나 아이디어는 99 %의 시간 만 인터페이스 정의를 읽으므로 간결해야한다고 생각합니다.
Mikhail Ramendik

5

관찰하고있는 내용에 영향을주는 세 가지 요소가 있습니다.

튜플 대 명명 된 필드

아마도 가장 사소한 것입니다-다른 언어에서는 튜플을 사용했습니다. 튜플이 좋은 아이디어인지 논의하는 것은 실제로 요점이 아닙니다. 그러나 Java에서는 무거운 구조를 사용했기 때문에 약간 불공평 한 비교입니다. 객체 배열과 일부 유형 캐스팅을 사용할 수 있습니다.

언어 구문

클래스를 선언하는 것이 더 쉬울 수 있습니까? 나는 필드를 공개하거나 맵을 사용하는 것이 아니라 Scala의 사례 클래스와 같은 것에 대해 이야기하고 있습니다.이 클래스는 설명 한 설정의 모든 이점을 제공하지만 훨씬 간결합니다.

case class Foo(duration: Int, unit: String, tooShort: Boolean)

우리는 그럴 수 있지만 비용이 듭니다 : 구문이 더 복잡해집니다. 물론 향후 5 년간 어떤 경우 나 대부분의 경우, 또는 대부분의 경우에는 그만한 가치가 있지만 판단해야합니다. 그건 그렇고, 이것은 당신이 자신을 수정할 수있는 언어 (예 : lisp)와 함께 멋진 것들입니다. 구문의 단순성으로 인해 이것이 어떻게 가능해 지는지 주목하십시오. 실제로 언어를 수정하지 않더라도 간단한 구문으로보다 강력한 도구를 사용할 수 있습니다. 예를 들어 Java에서는 사용할 수 있지만 스칼라에서는 사용할 수없는 몇 가지 리팩토링 옵션이 누락되었습니다.

언어 철학

그러나 가장 중요한 요소는 언어가 특정 사고 방식을 가능하게해야한다는 것입니다. 때로는 억압 감을 느낄 수도 있지만 (특정 기능에 대한 지원을 원했습니다) 기능을 제거하는 것만 큼 중요합니다. 모든 것을 지원할 수 있습니까? 물론 모든 단일 언어를 컴파일하는 컴파일러를 작성하는 것도 가능합니다. 다시 말해, 언어가 없을 것입니다. 언어의 상위 집합이 있으며 모든 프로젝트는 기본적으로 하위 집합을 채택합니다.

물론 언어 철학에 위배되는 코드를 작성하는 것이 가능하며, 관찰 한 바와 같이 결과는 종종 추악합니다. Java에 몇 개의 필드가있는 클래스를 갖는 것은 스칼라의 var를 사용하여 프롤로그 조건자를 함수로 생성하고 haskell 등에서 unsafePerformIO를 수행하는 것과 유사합니다. Java 클래스는 가볍지 않습니다-데이터를 전달할 수는 없습니다 . 무언가가 어려워 보일 때 종종 물러서서 다른 방법이 있는지 확인하는 것이 유익합니다. 귀하의 예에서 :

왜 기간이 단위와 분리되어 있습니까? Duration (5, seconds) (구문은 다를 수 있음)과 같은 기간을 선언 할 수있는 많은 시간 라이브러리가 있으며 훨씬 더 강력한 방식으로 원하는 것을 수행 할 수 있습니다. 아마 당신은 그것을 변환하고 싶을 것입니다-왜 result [1] (또는 [2]?)가 'hour'이고 3600을 곱한 지 확인하십시오? 그리고 세 번째 논쟁은 목적이 무엇입니까? 어느 시점에서 "1ms 미만"또는 실제 시간을 인쇄해야한다고 생각합니다. 시간 데이터에 자연스럽게 속하는 논리입니다. 즉, 다음과 같은 수업이 있어야합니다.

class TimeResult {
    public TimeResult(duration, unit, tooShort)
    public String getResult() {
        if tooShort:
           return "too short"
        else:
           return format(duration)
}

}

또는 실제로 데이터와 관련하여 원하는 것을 로직으로 캡슐화합니다.

물론,이 방법으로 작동하지 않는 경우가있을 수 있습니다-이것이 튜플 결과를 관용적 Java 코드로 변환하는 마법의 알고리즘이라고 말하고 있지 않습니다! 그리고 매우 추악하고 나쁜 경우가있을 수 있으며 아마도 다른 언어를 사용해야했을 수도 있습니다. 그래서 결국 많은 사람들이 있습니다!

그러나 Java에서 클래스가 "무거운 구조"인 이유에 대한 나의 견해는 클래스를 데이터 컨테이너가 아니라 자체 포함 된 논리 셀로 사용하려는 것입니다.


내가 할 지속 기간으로하는 것은 다른 비교 한 후 그 합을 만들고 대해 사실입니다. 출력이 없습니다. 비교는 반올림을 고려해야하므로 비교시 원래 시간 단위를 알아야합니다.
Mikhail Ramendik

내 클래스의 인스턴스를 추가하고 비교하는 방법을 만드는 것이 가능할 수도 있지만 이는 상당히 무거울 수 있습니다. 특히 플로트를 나누고 곱해야하기 때문에 (UI에서 확인할 백분율이 있습니다). 그래서 지금은 최종 필드로 불변 클래스를 만들었습니다.
Mikhail Ramendik

이 특정 질문에 대한 더 중요한 부분은 더 일반적인 측면이며 모든 대답에서 그림이 함께 나오는 것 같습니다.
Mikhail Ramendik

@MikhailRamendik 어쩌면 어떤 종류의 어큐뮬레이터가 옵션 일 것입니다. 그러나 문제를 더 잘 알고 있습니다. 실제로이 특정 문제를 해결하는 것이 아닙니다. 조금만 추적하면 죄송합니다. 제 주요 요점은 언어가 "간단한"데이터의 사용을 방해한다는 것입니다. 때때로, 이것은 당신이 당신의 접근 방식을 다시 생각하도록 도와줍니다-다른 시간에 int는 단지 int입니다
Thanos Tintinidis

@MikhailRamendik 보일러 플레이트 방식에 대한 좋은 점은 일단 수업이 있으면 행동을 추가 할 수 있다는 것입니다. 그리고 / 또는 당신이했던 것처럼 불변하게하십시오 (이것은 대부분 좋은 생각입니다; 항상 새로운 객체를 합계로 반환 할 수 있습니다). 결국, "불필요한"클래스는 필요한 모든 행동을 캡슐화하고 게터 비용은 무시할 수 있습니다. 또한 필요하거나 필요하지 않을 수도 있습니다. lombok 또는 autovalue 사용을 고려하십시오 .
maaartinus

5

내 이해에는 핵심 이유는

  1. 인터페이스는 클래스를 추상화하는 기본 Java 방법입니다.
  2. Java는 메소드 또는 객체 또는 배열 또는 기본 값 (int / long / double / float / boolean)에서 단일 값만 리턴 할 수 있습니다.
  3. 인터페이스는 필드를 포함 할 수 없으며 메소드 만 포함 할 수 있습니다. 필드에 액세스하려면 getter 및 setter와 같은 메소드를 거쳐야합니다.
  4. 메소드가 인터페이스를 리턴하면 실제로 리턴 할 구현 클래스가 있어야합니다.

이렇게하면 "중요하지 않은 결과를 반환하는 클래스를 작성해야합니다"는 결과가 다소 무겁습니다. 인터페이스 대신 클래스를 사용한 경우 필드를 사용하여 직접 사용할 수 있지만 특정 구현에 연결됩니다.


이 추가하려면 : 경우에 당신이 단지 - 이상적으로 불변 (말하자면 클래스의 private 메소드를) 작은 맥락에서이 필요, 그것은 베어 레코드를 사용하기 괜찮습니다. 그러나 실제로는 실제로 클래스의 공개 계약으로 유출되어서는 안됩니다 (또는 더 나쁜 것은 라이브러리입니다!). 물론 향후 코드 변경시 이러한 상황이 발생하지 않도록하기 위해 단순히 레코드를 결정할 수도 있습니다. 종종 더 간단한 솔루션입니다.
루안

그리고 "Tuples는 Java에서 제대로 작동하지 않습니다"라는 견해에 동의합니다. 제네릭을 사용하면 매우 빨리 불쾌해질 것입니다.
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen 제네릭 튜플이 있는 정적으로 유형이 지정된 언어 인 스칼라에서 잘 작동 합니다. 아마도 이것이 Java의 잘못입니까?
Andres F.

@AndresF. "제네릭을 사용하는 경우"부분을 참고하십시오. 자바가 이것을하는 방식은 Haskell과는 달리 복잡한 구조에 적합하지 않다. 나는 비교를 할만 큼 스칼라를 잘 모르지만 스칼라가 여러 반환 값을 허용한다는 것이 사실입니까? 그것은 많은 도움이 될 것입니다.
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Scala 함수는 튜플 유형일 수있는 단일 반환 값을 갖습니다 (예 : 정수의 튜플이되는 값을 반환하는 def f(...): (Int, Int)함수 f). Java의 제네릭에 문제가 있는지 잘 모르겠습니다. 예를 들어 Haskell은 유형 삭제도 수행합니다. Java에 튜플이없는 기술적 인 이유 가 있다고 생각 하지 않습니다.
Andres F.

3

JacquesB의 답변에 동의합니다.

Java는 (전통적으로) 명시 성과 일관성을 최적화하는 단일 패러다임 클래스 기반 OO 언어입니다.

그러나 명시 성과 일관성은 최적화해야 할 최종 목표가 아닙니다. '파이썬이 가독성에 최적화되어있다'고 말하면 최종 목표는 '유지 보수성'과 '개발 속도'라고 즉시 언급 할 수 있습니다.

Java 방식으로 명시적이고 일관성이 있으면 무엇을 얻을 수 있습니까? 필자는 소프트웨어 문제를 해결하기 위해 예측 가능하고 일관되고 균일 한 방법을 제공한다고 주장하는 언어로 발전했다는 점입니다.

즉, Java 문화는 관리자가 소프트웨어 개발을 이해한다고 믿도록 최적화되었습니다.

또는, 같은 하나의 현명한 사람은 아주 오래 전에 넣어 ,

언어를 판단하는 가장 좋은 방법은 제안자가 작성한 코드를 보는 것입니다. "Radix enim omnium malorum est cupiditas"-Java는 분명히 돈 지향 프로그래밍 (MOP)의 예입니다. SGI에서 자바의 주요 지지자는 나에게 말했다 : "알렉스, 당신은 돈이있는 곳으로 가야한다." 그러나 나는 돈이있는 곳으로 가고 싶지 않습니다. 보통 좋은 냄새가 나지 않습니다.


3

(이 답변은 특히 Java에 대한 설명이 아니라“[무거운] 관행이 무엇을 최적화 할 수 있습니까?”에 대한 일반적인 질문을 다룹니다.)

다음 두 가지 원칙을 고려하십시오.

  1. 프로그램이 옳은 일을 할 때 좋습니다. 올바른 일을하는 프로그램을 쉽게 작성할 수 있도록해야합니다.
  2. 프로그램이 잘못된 일을하면 나쁘다. 우리는 잘못된 일을하는 프로그램을 작성하기 어렵게 만들어야합니다.

이러한 목표 중 하나를 최적화하려고하면 때로는 다른 목표를 방해 할 수 있습니다 (즉 , 잘못된 일을하기 어렵게 만들면 올바른 일을하기가 어려워 질 수도 있습니다) 지거나 그 반대 수도 있습니다).

특정 경우에 어떤 상충 관계가 발생하는지는 응용 프로그램, 해당 프로그래머 또는 팀의 결정 및 문화 (조직 또는 언어 커뮤니티의)에 따라 다릅니다.

예를 들어, 프로그램에서 버그 나 몇 시간의 가동 중단으로 인명 손실 (의료 시스템, 항공) 또는 심지어 돈 (Google 광고 시스템에서 수백만 달러와 같은)까지 손실이 발생할 경우 다른 상충 관계 ( 일회성 스크립트를 사용하는 것보다 사용자 언어로, 엔지니어링 문화의 다른 측면에서) : "무거운"쪽으로 기울어 질 수 있습니다.

시스템을 "무겁게"만드는 다른 예 :

  • 수년에 걸쳐 많은 팀이 대규모 코드베이스를 작업했을 때 큰 관심사는 누군가 다른 사람의 API를 잘못 사용할 수 있다는 것입니다. 잘못된 순서로 인수를 사용하여 호출되거나 예상되는 일부 전제 조건 / 제약을 보장하지 않고 호출되는 함수는 치명적일 수 있습니다.
  • 특별한 경우, 팀이 특정 API 또는 라이브러리를 유지 관리하고 변경하거나 리팩토링하려고한다고 가정하십시오. 사용자가 코드를 사용할 수있는 방법이 "제약적"일수록 코드를 쉽게 변경할 수 있습니다. (여기서 아무도 특별한 방법으로 그것을 사용할 수 없다는 것을 실제로 보증하는 것이 바람직합니다.)
  • 개발이 여러 사람 또는 팀으로 분할 된 경우 한 사람 또는 팀이 인터페이스를 "지정"하고 다른 사람이 실제로 구현하도록하는 것이 좋습니다. 그것이 작동하기 위해서는 구현이 실제로 사양과 일치한다는 확신을 얻을 수 있어야합니다.

이것들은 단지 "무거운"것을 만들고 (그리고 코드를 빨리 작성하기가 더 어려워지는) 실제 의도적 인 경우에 대한 아이디어를 제공하는 몇 가지 예일뿐입니다. (코드를 작성하는 데 많은 노력이 필요한 경우 코드를 작성하기 전에 더 신중하게 생각해야 할 수도 있습니다! 물론이 논란의 여지가 빨리 말도 안됩니다.)

예를 들어, 구글의 내부 파이썬 시스템은 단순히 다른 사람의 코드를 가져올 수 없도록 "무거운"것을 만드는 경향이 있습니다. BUILD 파일에 의존성을 선언 해야합니다. 임포트하려는 코드를 가진 팀은 라이브러리를 다음과 같이 선언해야합니다. 코드 등에 표시됩니다.


참고 : 위의 모든 것은 상황이 "무거운"경향이있을 때입니다. 저는 Java 나 Python (언어 자체 또는 문화)이 특정 상황에 대해 최적의 균형을 이루고 있다고 절대 주장 하지 않습니다 . 그것은 당신이 생각하기위한 것입니다. 그러한 트레이드 오프에 대한 두 가지 관련 링크 :


1
코드 를 읽는 것은 어떻습니까? 거기에 또 다른 트레이드 오프가 있습니다. 기괴한 사건들과 풍부한 의식들에 의해 가중 된 코드는 읽기가 더 어렵다. IDE에 상용구와 불필요한 텍스트 (및 클래스 계층 구조)가 있으면 코드가 실제로 무엇을하는지 알기가 더 어려워집니다. 코드를 작성하기가 쉽지는 않다는 주장을 볼 수 있지만 (동의하는지 확실하지 않지만 장점이 있음) 확실히 읽을 수 있어야합니다 . 그것은 그러나이었다 - 물론 복잡한 코드는 수와 자바로 구축하고있다 - 다른 주장 어리석은 것 감사 의 상세에, 또는 에도 불구하고 그것의?
Andres F.

@AndresF. 나는 자바에 관한 것이 아니라는 것을 분명히하기 위해 답변을 편집했습니다 (나는 그중의 팬이 아닙니다). 그러나 그렇습니다. 코드를 읽을 때의 장단점 : 일단 말하면“코드가 실제로 무엇을하고 있는지”쉽게 읽을 수 있기를 원합니다. 다른 쪽에서는 다음과 같은 질문에 대한 답변을 쉽게 볼 수 있기를 원합니다.이 코드는 다른 코드와 어떤 관련이 있습니까? 이 코드가 할 수있는 최악의 상황 : 다른 상태에 미치는 영향, 부작용은 무엇입니까? 이 코드는 어떤 외부 요소에 의존 할 수 있습니까? (물론 두 질문에 대한 빠른 답변을 원할 것입니다.)
ShreevatsaR

2

Java 문화는 오픈 소스와 엔터프라이즈 소프트웨어 배경 모두에서 큰 영향을 받아 시간이 지남에 따라 진화했습니다. 실제로 생각하면 이상한 혼합입니다. 엔터프라이즈 솔루션에는 많은 도구가 필요하고 오픈 소스에는 단순성이 필요합니다. 결과적으로 Java는 중간에 있습니다.

권장 사항에 영향을 미치는 것은 파이썬에서 읽을 수 있고 유지 관리 가능한 것으로 간주되는 부분과 Java가 매우 다릅니다.

  • 파이썬에서 튜플은 언어 기능입니다.
  • Java와 C # 모두에서 튜플은 라이브러리 기능입니다.

표준 라이브러리에는 Tuple <A, B, C, .. n> 클래스 세트가 있기 때문에 C #에 대해서만 언급하고 언어가 직접 지원하지 않는 경우 다루기 힘든 튜플의 완벽한 예입니다. 거의 모든 경우에서 문제를 처리 할 클래스를 잘 선택하면 코드를보다 읽기 쉽고 유지 관리 할 수있게됩니다. 연결된 스택 오버플로 질문의 특정 예에서 다른 값은 반환 객체에서 계산 된 게터로 쉽게 표현됩니다.

C # 플랫폼이 행복한 중간 단계를 제공하는 흥미로운 솔루션은 이 가려움증을 긁는 익명의 객체 ( C # 3.0 에서 릴리스)에 대한 아이디어입니다 . 불행히도 Java에는 아직 동등한 기능이 없습니다.

Java의 언어 기능이 수정 될 때까지 가장 읽기 쉽고 유지 관리 가능한 솔루션은 전용 객체를 갖는 것입니다. 그 이유는 1995 년 초로 거슬러 올라가는 언어의 제약 때문입니다. 원저자에게는 더 많은 언어 기능이 계획되어 있지 않았으며, 이전 버전과의 호환성은 시간이 지남에 따라 Java의 진화를 둘러싼 주요 제약 중 하나입니다.


4
최신 버전의 c #에서 새 튜플은 라이브러리 클래스가 아닌 일등 시민입니다. 거기에 도착하기에는 너무 많은 버전이 필요했습니다.
Igor Soloydenko

마지막 3 개의 문단은 "Language X에 Java가 제공하지 않는 기능이 있습니다."라고 요약 할 수 있으며, 그 내용에 대한 답변이 무엇인지 알 수 없습니다. Python to Java 주제에서 C #을 언급하는 이유는 무엇입니까? 아뇨, 요점은 보이지 않습니다. 20k 이상의 담당자가 조금 이상합니다.
Olivier Grégoire

1
내가 말했듯이, "Tuples는 라이브러리 기능이기 때문에 C # 만 언급합니다"는 주 라이브러리에 Tuples가있는 경우 Java에서 작동하는 방식과 유사합니다. 사용하기가 매우 어려우며 더 좋은 대답은 거의 항상 전용 수업을받는 것입니다. 그러나 익명의 객체는 튜플이 설계된 것과 유사한 가려움증을 긁습니다. 더 나은 것에 대한 언어 지원이 없으면 본질적으로 가장 유지 보수가 쉬운 작업을 수행해야합니다.
Berin Loritsch

@IgorSoloydenko, Java 9에 대한 희망이 있습니까? 이것은 람다의 논리적 다음 단계 인 기능 중 하나 일뿐입니다.
Berin Loritsch

1
@IgorSoloydenko 나는 그것이 사실인지 확신하지 못합니다 (퍼스트 클래스 시민). 라이브러리에서 클래스의 인스턴스를 만들고 풀기위한 구문 확장 인 것 같습니다. 클래스, 구조체, 열거 형으로 CLR에서 첫 번째 클래스 지원을하는 것입니다.
피트 Kirkham

0

이 경우 클래스 사용에 대한 핵심 사항 중 하나는 함께 진행되는 것이 함께 있어야한다는 것입니다.

메소드 인수에 대한 다른 방법 으로이 토론을했습니다 .BMI를 계산하는 간단한 메소드를 고려하십시오.

CalculateBMI(weight,height)
{
  System.out.println("BMI: " + (( weight / height ) x 703));
}

이 경우 무게와 키가 관련되어 있기 때문에이 스타일에 반대합니다. 이 방법은 그렇지 않은 경우 두 가지 개별 값을 "통신"합니다. 한 사람의 체중과 다른 사람의 신장으로 BMI를 언제 계산합니까? 말이되지 않습니다.

CalculateBMI(Person)
{
  System.out.println("BMI: " + (( Person.weight / Person.height ) x 703));
}

이제 키와 몸무게가 동일한 출처에서 나온다는 점을 명확하게 전달하기 때문에 훨씬 더 의미가 있습니다.

여러 값을 반환하는 것도 마찬가지입니다. 그것들이 명확하게 연결되면 깔끔한 작은 패키지를 반환하고 여러 값을 반환하지 않으면 객체를 사용하십시오.


4
그러나 그래프를 표시하는 (가설적인) 작업이 있다고 가정하십시오. BMI가 특정 고정 높이의 무게에 어떻게 의존하는지. 그런 다음 각 데이터 포인트에 대해 Person을 작성하지 않고는이를 수행 할 수 없습니다. 그리고 그것을 극단으로 가져 가면 유효한 생년월일과 여권 번호가없는 Person 클래스의 인스턴스를 만들 수 없습니다. 이제 뭐? 나는 btw를 downvote하지 않았으며, 어떤 클래스에서 속성을 함께 묶어야 하는 유효한 이유 가 있습니다 .
artem

1
@artem 인터페이스가 작동하는 다음 단계입니다. 따라서 personweightheight 인터페이스는 이와 같은 것일 수 있습니다. 내가 한 예에서 당신은 함께가는 것이 함께되어야한다는 점을 알게되었습니다.
Pieter B

0

솔직히 말해서, 문화는 Java 프로그래머가 원래 객체 지향 원칙과 지속 가능한 소프트웨어 디자인 원칙을 가르치는 대학에서 나온 경향이 있다는 것입니다.

ErikE가 그의 대답에서 더 많은 단어로 말했듯이 지속 가능한 코드를 작성하지 않는 것 같습니다. 내가 당신의 모범에서 본 것은 매우 어색한 염려가 있다는 것입니다.

Java 문화권에서는 어떤 라이브러리를 사용할 수 있는지 알고 경향이있어 커프스 프로그래밍보다 훨씬 많은 것을 달성 할 수 있습니다. 그래서 당신은 하드 코어 산업 환경에서 시도되고 테스트 된 디자인 패턴과 스타일에 대한 특질을 교환 할 것입니다.

그러나 당신이 말했듯이, 이것이 단점이 아닙니다. 오늘, 10 년 넘게 Java를 사용해 왔지만, 더 빠른 개발을 허용하고 마이크로 서비스 스타일 아키텍처를 사용하기 때문에 Node / Javascript 또는 Go를 새 프로젝트에 사용하는 경향이 있습니다. 종종 충분합니다. 구글이 처음으로 Java를 많이 사용했지만 Go의 창시자라는 사실로 판단하면, 그들이 똑같은 일을하고 있다고 생각합니다. 그러나 지금은 Go와 Javascript를 사용하고 있지만 수년간 Java를 사용하고 이해하면서 얻은 많은 디자인 기술을 사용하고 있습니다.


1
특히 Google이 사용하는 foobar 모집 도구를 사용하면 솔루션에 Java와 python의 두 가지 언어를 사용할 수 있습니다. Go가 옵션이 아니라는 것이 흥미 롭습니다. Go 에서이 프리젠 테이션을 했는데 Java와 Python (및 다른 언어)에 대한 흥미로운 점이 있습니다. 예를 들어, "전문가가 아닌 프로그래머는 결과적으로 "해석 및 동적 타이핑과 함께 사용하십시오." 읽을만한 가치가 있습니다.
JimmyJames

@JimmyJames는 방금 '전문가의 손에, 훌륭합니다'라는 질문을 슬라이드에 넣었습니다. 문제의 문제에 대한 좋은 요약
Tom
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.