Java의 템플릿 "메타 프로그래밍"이 좋은 생각입니까?


29

상당히 큰 프로젝트에는 성능에 매우 민감한 여러 기능을 가진 소스 파일이 있습니다 (초당 수백만 번이라고 함). 실제로, 이전 관리자는 하나의 함수에서 조건을 확인하는 데 소요되는 시간을 절약하기 위해 각각 약간 씩 다른 함수의 12 개 사본을 작성하기로 결정했습니다.

불행히도 이것은 코드가 유지해야 할 PITA라는 것을 의미합니다. 중복 코드를 모두 제거하고 하나의 템플릿 만 작성하고 싶습니다. 그러나 Java 언어는 템플릿을 지원하지 않으므로 제네릭이 이것에 적합한 지 잘 모르겠습니다.

현재 계획은 12 개의 함수 사본 (실제로는 일회용 전용 템플릿 확장기)을 생성하는 파일을 작성하는 것입니다. 물론 파일을 프로그래밍 방식으로 생성해야하는 이유에 대한 충분한 설명을 제공하고자합니다.

내 관심사는 이것이 미래 관리자의 혼란을 초래하고 파일을 수정 한 후 파일을 재생성하는 것을 잊어 버렸거나 프로그램 적으로 생성 된 파일을 수정하면 더 나쁜 버그를 초래할 수 있다는 것입니다. 불행히도 C ++로 모든 것을 다시 작성하지 않으면이 문제를 해결할 방법이 없습니다.

이 방법의 장점이 단점보다 더 중요합니까? 대신에 나는 :

  • 성능을 높이고 유지 관리 가능한 단일 기능을 사용하십시오.
  • 기능을 12 번 복제해야하는 이유에 대한 설명을 추가하고 유지 보수 부담을 감수하십시오.
  • 제네릭을 템플릿으로 사용하려고 시도합니다 (아마도 그렇게 작동하지 않을 수 있습니다).
  • 단일 기능에서 성능에 따라 코드를 작성하는 데 도움이 된 이전 관리자에게 소리를 지 릅니다.
  • 성능 및 유지 관리 성을 유지하는 다른 방법은 무엇입니까?

추신 : 프로젝트의 디자인이 잘못되어 기능을 프로파일 링하는 것은 다소 까다 롭습니다. 나는 이것이 5 % 이상을 의미한다고 가정하지만, 그것은 내 부분에 대한 완전한 추측입니다.


아마도 좀 정교하게 만들어야 할 것입니다. 12 개의 사본은 매우 유사한 작업을 수행하지만 약간의 차이가 있습니다. 차이점은 함수 전체의 여러 위치에 있으므로 불행히도 많은 조건문이 있습니다. 실제로는 6 개의 "작동 모드"와 2 개의 "패러다임"이 있습니다 (자신이 작성한 단어). 이 기능을 사용하기 위해 "모드"와 "패러다임"을 지정합니다. 이것은 결코 역동적이지 않습니다. 각 코드는 정확히 하나의 모드와 패러다임을 사용합니다. 12 개의 모드-패러다임 쌍이 응용 프로그램 어딘가에 사용됩니다. 함수는 적절하게 func1에서 func12로 명명되며 짝수는 두 번째 패러다임을 나타내고 홀수는 첫 번째 패러다임을 나타냅니다.

유지 관리가 목표라면 이것이 최악의 디자인에 불과하다는 것을 알고 있습니다. 그러나 "충분히 빠른"것처럼 보이며이 코드는 잠시 동안 변경이 필요하지 않았습니다 ... 원래 함수가 삭제되지 않았다는 점도 주목할 가치가 있습니다. (알 수있는 한 죽은 코드 임에도 불구하고) 리팩토링은 간단합니다.


12 가지 버전의 기능을 보증 할만큼 성능 저하가 심각하면 기능에 문제가있을 수 있습니다. 단일 기능으로 리팩토링하거나 제네릭을 사용하는 경우 성능 저하가 고객과 비즈니스를 잃을 정도로 나빠질 것입니까?
FrustratedWithFormsDesigner

12
그런 다음 자신의 테스트를 수행해야한다고 생각합니다 (프로파일 링이 까다 롭다는 것을 알고 있지만 시스템 전체에 대해 대략적인 "블랙 박스"성능 테스트를 수행 할 수 있습니다). 입니다. 그리고 눈에 띄는 경우 코드 생성기에 갇혀있을 수 있습니다.
FrustratedWithFormsDesigner

1
이것은 각 함수를 다른 이름으로 호출하는 대신 일부 파라 메트릭 다형성 (또는 어쩌면 제네릭)의 이점을 얻을 수있는 것처럼 들릴 수 있습니다.
Robert Harvey

2
func1 ... func12 함수의 이름을 지정하는 것은 미친 것 같습니다. 적어도 mode1Par2 등 또는 myFuncM3P2로 이름을 지정하십시오.
user949300

2
"프로그래밍 방식으로 생성 된 파일을 수정하는 경우"... " Makefile"(또는 사용하는 시스템) 에서 빌드 할 때만 파일을 작성하고 올바른 후 처리 컴파일을 제거하십시오 . 이런 식으로 그들은 단순히 잘못된 소스 파일을 수정할 기회 가 없습니다 .
Bakuriu

답변:


23

이것은 매우 나쁜 상황입니다.이 최대한 빨리 리팩토링해야합니다. 최악의 경우 기술 부채입니다. 코드가 실제로 얼마나 중요한지조차 모릅니다 . 중요하다고 추측하십시오.

가능한 빨리 솔루션에 대해 :
사용자 정의 컴파일 단계를 추가하는 것이 가능합니다. 실제로 매우 간단한 Maven을 사용하면 다른 자동화 된 빌드 시스템도 이에 대응할 수 있습니다. .java와 다른 확장자를 가진 파일을 작성하고 소스와 같은 파일을 검색하고 실제 .java를 재생성하는 사용자 정의 단계를 추가하십시오. 또한 추가 할 수 있습니다 거대한 수정하지 설명하는 자동 생성 된 파일에 면책 조항을.

한 번 생성 된 파일 사용의 장점 : 개발자는 .java 작업에 대한 변경 사항을 얻지 못합니다. 커밋하기 전에 실제로 컴퓨터에서 코드를 실행하면 변경 사항이 아무런 영향을 미치지 않습니다 (ha). 그리고 나서 그들은 면책 조항을 읽을 것입니다. 이 특정 파일을 다른 방식으로 변경해야한다는 점을 기억하여 팀원과 미래의 자기 자신을 믿지 않아도됩니다. JUnit은 테스트를 실행하기 전에 프로그램을 컴파일하고 파일을 재생성하므로 자동 테스트도 가능합니다.

편집하다

의견으로 판단하면이 작업을 무기한으로 수행하고 프로젝트의 다른 성능 중요한 부분에 배포하는 것이 좋은 방법 인 것처럼 대답이 나왔습니다.

간단히 말해 : 그렇지 않습니다.

자신의 미니 언어를 만들고, 코드 생성기를 작성하고, 유지 관리해야 할 추가 부담은 향후 유지 보수 담당자에게 가르치는 것은 말할 것도없이 장기적으로 어려울 것입니다. 위의 내용 은 장기적인 해결책을 찾고있는 동안 문제 보다 안전하게 처리 할 수있는 방법 입니다. 내가 취할 것은 저의 것입니다.


19
죄송하지만 동의하지 않습니다. 당신은 그를 위해 결정을 내릴 OP의 코드에 대해 충분하지 않습니다. 코드 생성은 원래 솔루션보다 더 많은 기술적 부채가 포함 된 것처럼 보입니다.
Robert Harvey

6
Robert의 의견은 과장 될 수 없습니다. "상어"라는 별명을 가진 불법 부리가 운영하는 수표 현금 인출기와 동등한 "기술 부채"인 "파일을 수정하지 말라고하는 면책 조항"이있을 때.
corsiKa

8
자동 생성 된 파일은 물론 소스 리포지토리에 속하지 않으며 (결국 소스 코드가 아니며 컴파일 된 코드 임) ant clean해당 파일이나 그에 상응하는 파일 로 제거해야합니다 . 거기에없는 파일에 고지 사항을 넣을 필요가 없습니다!
Jörg W Mittag 2016 년

2
@RobertHarvey OP에 대한 결정을 내리지 않습니다. OP는 그러한 템플릿을 자신이 가지고있는 방식으로 사용하는 것이 좋은지 물었고 나는이를 유지하는 (더 나은) 방법을 제안했습니다. 그것은 이상적인 해결책이 아니며 실제로 첫 번째 문장에서 언급했듯이 전체 상황이 좋지 않으며 앞으로 나아가는 훨씬 더 좋은 방법은 문제를 해결하고 흔들리는 해결책을 만드는 것이 아닙니다. 여기에는이 코드가 얼마나 중요한지, 성능 저하가 얼마나 나쁜지, 많은 나쁜 코드를 작성하지 않고 작동하게하는 방법이 있는지를 올바르게 평가하는 것이 포함됩니다.
Ordous

2
@ corsiKa 나는 이것이 지속적인 해결책이라고 말한 적이 없습니다. 이것은 원래 상황만큼 많은 부채가 될 것입니다. 그것이하는 유일한 방법은 체계적인 솔루션을 찾을 때까지 변동성을 줄이는 것입니다. Java에서 이와 같은 문제가 발생하면 매우 복잡하거나 잘못 된 일이 있기 때문에 완전히 새로운 플랫폼 / 프레임 워크 / 언어로 옮기는 것이 포함될 수 있습니다.
Ordous

16

유지 보수가 실제로 발생합니까, 아니면 귀찮게합니까? 그것이 당신을 귀찮게한다면, 혼자 두십시오.

성능 문제가 실제입니까, 아니면 이전 개발자 만 해당 문제를 생각 했습니까? 프로파일 러를 사용하더라도 성능 문제는 종종 생각되는 위치가 아닙니다. ( 이 기술 을 사용 하여 안정적으로 찾을 수 있습니다.)

따라서 문제가 아닌 것에 대한 추악한 해결책이있을 수도 있지만 실제 문제에 대한 추악한 해결책 일 수도 있습니다. 의심스러운 경우 혼자 두십시오.


10

실제 프로덕션 환경 (가비지 콜렉션을 실제로 트리거하는 실제 메모리 소비 등)보다 실제로는 별도의 모든 메소드가 실제로 한 가지 방법이 아닌 성능 차이를 만듭니다. 이 과정은 며칠이 걸리지 만 지금부터 작업하는 코드를 단순화하여 몇 주를 절약 할 수 있습니다.

당신이 경우에도 모든 기능이 필요하다는 것을 발견, 당신은 사용 할 수 있습니다 와 Javassist를 프로그래밍 방식으로 코드를 생성 할 수 있습니다. 다른 사람들이 지적했듯이 Maven으로 자동화 할 수 있습니다 .


2
또한 성능 문제가 실제 인 것으로 밝혀 지더라도이를 연구하는 연습을 통해 코드로 가능한 것이 무엇인지 더 잘 알 수 있습니다.
Carl Manaster 2018 년

6

이 시스템에 일종의 템플릿 프리 프로세서 / 코드 생성을 포함시키지 않는 이유는 무엇입니까? 나머지 코드를 컴파일하기 전에 추가 Java 소스 파일을 실행하고 생성하는 사용자 정의 추가 빌드 단계입니다. 이것이 wsdl 및 xsd 파일에서 웹 클라이언트를 포함시키는 것이 종종 작동하는 방식입니다.

물론 전 처리기 / 코드 생성기를 유지 보수해야하지만 걱정할 중복 된 코어 코드 유지 보수는 없습니다.

컴파일 시간 Java 코드가 생성되므로 추가 코드에 대한 성능 저하가 없습니다. 그러나 모든 중복 코드 대신 템플릿을 사용하면 유지 관리가 간단 해집니다.

Java 제네릭은 언어에서 유형이 지워 지므로 성능상의 이점이 없으며 간단한 캐스팅이 대신 사용됩니다.

C ++ 템플릿에 대한 이해를 바탕으로 각 템플릿 호출마다 여러 함수로 컴파일됩니다. 예를 들어 std :: vector에 저장하는 각 유형에 대해 중복 된 미드 컴파일 타임 코드가 표시됩니다.


2

정상적인 Java 메소드는 12 개의 변형을 가질만큼 길 수 없으며 JITC는 긴 메소드를 싫어합니다. 단순히 메소드를 올바르게 최적화하지 않습니다. 방법을 두 개의 더 짧은 방법으로 분리하여 속도가 2 배가되는 것을 보았습니다. 어쩌면 이것이 갈 길입니다.

사본이 여러 개인 OTOH는 동일하더라도 의미가있을 수 있습니다. 각기 다른 장소에서 사용되면서 서로 다른 사례에 맞게 최적화됩니다 (JITC가이를 프로파일 링 한 다음 드문 사례를 예외적 인 경로에 배치합니다.

좋은 이유가 있다고 가정하면 코드 생성이 중요하지 않다고 말하고 싶습니다. 오버 헤드는 다소 적으며 이름이 올바르게 지정된 파일은 즉시 소스로 연결됩니다. 얼마 전에 소스 코드를 생성 // DO NOT EDIT할 때 모든 줄을 썼습니다.


1

언급 한 '문제'에는 아무런 문제가 없습니다. 내가 아는 바로는 DB 서버가 우수한 성능을 발휘하기 위해 사용하는 정확한 종류의 디자인과 접근 방식입니다.

특정 조건이 적용될 때 결합, 선택, 집계 등 모든 종류의 작업에 대한 성능을 최대화 할 수있는 많은 특수 방법이 있습니다.

요컨대, 당신이 생각하는 것과 같은 코드 생성은 나쁜 생각입니다. 아마도이 다이어그램을 보면 DB가 자신과 비슷한 문제를 어떻게 해결하는지 확인할 수 있습니다.여기에 이미지 설명을 입력하십시오


1

여전히 합리적인 추상화를 사용할 수 있는지 확인하고 템플릿 메소드 패턴 을 사용 하여 공통 기능에 대한 이해할 수있는 코드를 작성하고 메소드 간의 차이점을 12의 "기본 작업"(패턴 설명에 따라)으로 옮길 수 있습니다. 서브 클래스. 이는 유지 보수성 및 테스트 가능성을 크게 향상 시키며, JVM이 잠시 후에 기본 오퍼레이션에 대한 메소드 호출을 인라인 할 수 있기 때문에 실제로 현재 코드와 동일한 성능을 가질 수 있습니다. 물론 성능 테스트에서이를 확인해야합니다.


0

스칼라 언어를 사용하여 특수한 방법의 문제를 해결할 수 있습니다.

스칼라는 메서드를 인라인 할 수 있으며,이 기능을 사용하면 (고차원 함수를 쉽게 사용할 수 있음) 무료로 코드 중복을 피할 수 있습니다. 이는 답변에서 언급 된 주요 문제처럼 보입니다.

또한 스칼라에는 구문 매크로가있어 컴파일 타임에 형식 안전 방식으로 코드로 많은 작업을 수행 할 수 있습니다.

그리고 제네릭에 사용될 때 복싱 프리미티브 유형의 일반적인 문제는 스칼라에서도 해결할 수 있습니다. @specialized주석 을 사용하여 자동으로 복싱을 피하기 위해 프리미티브에 대한 일반 전문화를 수행 할 수 있습니다. 이것은 언어 자체에 내장되어 있습니다. 따라서 기본적으로 스칼라에서 하나의 일반적인 방법을 작성하고 특수한 방법의 속도로 실행됩니다. 빠른 일반 산술도 필요한 경우 다른 숫자 유형의 연산 및 값을 주입하기 위해 Typeclass "pattern"을 사용하면 쉽게 수행 할 수 있습니다.

또한 스칼라는 다른 많은 방법으로 훌륭합니다. 그리고 Scala <-> Java 상호 운용성이 우수하기 때문에 모든 코드를 Scala로 다시 작성할 필요는 없습니다. 프로젝트를 빌드 할 때 SBT (scala build tool) 를 사용하십시오 .


-1

문제의 함수가 크면 "모드 / 패러다임"비트를 인터페이스로 전환 한 다음 해당 인터페이스를 매개 변수로 구현하는 객체를 함수에 전달할 수 있습니다. GoF는이를 "전략"패턴, iirc라고합니다. 기능이 작 으면 오버 헤드가 증가 할 수 있습니다. 누군가 프로파일 링에 대해 언급 한 적이 있습니까? ... 더 많은 사람들이 프로파일 링에 대해 언급해야합니다.


이것은 답변보다 주석처럼 읽습니다
gnat

-2

프로그램은 몇 살입니까? 아마도 최신 하드웨어가 병목 현상을 제거하고 유지 관리하기 쉬운 버전으로 전환 할 수 있습니다. 그러나 유지 보수를해야하는 이유가 있어야하므로 코드베이스를 개선하는 것이 아니라면 작업 상태 그대로 두십시오.


1
이것은 단지 몇 시간 전에 게시 된 이전 답변 에서 언급되고 설명 된 포인트를 반복하는 것 같습니다
gnat

1
병목 현상이 실제로 발생하는 위치를 결정하는 유일한 방법은 다른 답변에서 언급했듯이 병목 현상을 프로파일 링하는 것입니다. 이 핵심 정보가 없으면 성능에 대한 제안 된 수정은 순수한 추측입니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.