자바 문화를 고치기-왜 그렇게 무겁습니까? 무엇을 위해 최적화합니까? [닫은] 있습니다. 따라서 올바른 방향으로 설명이나 포인터를

나는 파이썬으로 많이 코딩했다. 이제 작업상의 이유로 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 프로젝트를 위해 준비된 상태 여야 함)이지만 매우 약한 추측입니다.



답변

자바 언어

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 로더를 작성했습니다. 그들은이 괴물들을 매일 구성하고 통합해야했습니다. 결과적으로 공동체 전체의 문화는 이러한 틀의 예를 따르고 과도하게 엔지니어링하는 경향이있었습니다.


답변

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

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객체 로 반환 될 수 있으며, 이는 위의 두 경우보다 훨씬 더 명확합니다.


답변

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;
  }
}


답변

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값 유형 인 적절한 후보입니다 . 일부 테스트에서는 구조체로 전환하면 런타임 성능 이점이 있는지 여부를 알 수 있습니다 (가능한 경우).


답변

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

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

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

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

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

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


답변

관행을 검색하지 마십시오. 모범 사례 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에서 제공하는 바로 가기를 사용합니다.


답변

일반적으로 Java 숙어 정보 :

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

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


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

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

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