Java에서 매우 반복적 인 코드 및 문서 관리
매우 반복적 인 코드는 일반적으로 나쁜 일이며이를 최소화하는 데 도움이되는 디자인 패턴이 있습니다. 그러나 때로는 언어 자체의 제약으로 인해 단순히 피할 수없는 경우도 있습니다. 에서 다음 예를 가져옵니다 java.util.Arrays
.
/**
* Assigns the specified long value to each element of the specified
* range of the specified array of longs. The range to be filled
* extends from index <tt>fromIndex</tt>, inclusive, to index
* <tt>toIndex</tt>, exclusive. (If <tt>fromIndex==toIndex</tt>, the
* range to be filled is empty.)
*
* @param a the array to be filled
* @param fromIndex the index of the first element (inclusive) to be
* filled with the specified value
* @param toIndex the index of the last element (exclusive) to be
* filled with the specified value
* @param val the value to be stored in all elements of the array
* @throws IllegalArgumentException if <tt>fromIndex > toIndex</tt>
* @throws ArrayIndexOutOfBoundsException if <tt>fromIndex < 0</tt> or
* <tt>toIndex > a.length</tt>
*/
public static void fill(long[] a, int fromIndex, int toIndex, long val) {
rangeCheck(a.length, fromIndex, toIndex);
for (int i=fromIndex; i<toIndex; i++)
a[i] = val;
}
위의 코드 조각은 소스 코드에 8 문서 / 메소드 서명에 아주 작은 변화에 시간 만 나타납니다 정확히 같은 방법 본체 , 루트 배열 유형 각각에 대해 하나를 int[]
, short[]
, char[]
, byte[]
, boolean[]
, double[]
, float[]
,와 Object[]
.
나는 누군가가 (그 자체로 완전히 다른 주제 인) 성찰에 의지하지 않는 한,이 반복은 불가피하다고 믿는다. 나는 유틸리티 클래스로서 이러한 반복적 인 자바 코드의 집중이 매우 비정형 적이라는 것을 알고 있지만, 모범 사례에서도 반복이 발생합니다 ! 리팩토링이 항상 가능한 것은 아니기 때문에 항상 작동하는 것은 아닙니다 (반복이 문서에있는 경우).
분명히이 소스 코드를 유지하는 것은 악몽입니다. 문서의 약간의 오타 또는 구현의 사소한 버그가 여러 번 반복 되었더라도 곱해집니다. 실제로 가장 좋은 예는 다음과 같은 정확한 클래스를 포함하는 것입니다.
Google Research 블로그-Extra, Extra-모든 정보 읽기 : 거의 모든 바이너리 검색 및 병합이 깨졌습니다 (소프트웨어 엔지니어 Joshua Bloch 작성).
이 버그는 놀랍도록 미묘한 것으로, 많은 사람들이 단순하고 간단한 알고리즘이라고 생각하는 것에서 발생합니다.
// int mid =(low + high) / 2; // the bug
int mid = (low + high) >>> 1; // the fix
위의 줄 은 소스 코드에서 11 번 나타납니다 !
그래서 내 질문은 다음과 같습니다.
- 이러한 종류의 반복적 인 Java 코드 / 문서는 실제로 어떻게 처리됩니까? 어떻게 개발, 유지 및 테스트됩니까?
- "원본"으로 시작하여 최대한 성숙하게 만든 다음 필요에 따라 복사하여 붙여넣고 실수하지 않았 으면하나요?
- 원본에서 실수를했다면 복사본을 삭제하고 전체 복제 프로세스를 반복하는 데 익숙하지 않은 한 모든 곳에서 수정하면됩니다.
- 그리고 테스트 코드에도 동일한 프로세스를 적용합니까?
- Java가 이러한 종류의 제한된 사용 소스 코드 전처리의 이점을 누릴 수 있습니까?
- 아마도 Sun은 이러한 종류의 반복적 인 라이브러리 코드를 작성, 유지, 문서화 및 테스트하는 데 도움이되는 자체 전처리기를 보유하고 있습니까?
댓글이 다른 예를 요청했기 때문에 Google Collections에서 com.google.common.base.Predicates 라인 276-310 ( AndPredicate
) 대 라인 312-346 ( OrPredicate
)을 가져 왔습니다 .
이 두 클래스의 소스는 다음을 제외하고 동일합니다.
AndPredicate
vsOrPredicate
(각 클래스에서 5 번 나타남)"And("
vsOr("
(각각의toString()
방법에서)#and
vs#or
(@see
Javadoc 주석에서)true
vsfalse
(inapply
;!
표현식에서 다시 쓸 수 있음)-1 /* all bits on */
대0 /* all bits off */
에서hashCode()
&=
대|=
에서hashCode()
절대적으로 성능을 필요로하는 사람들을 위해, 박싱과 언 박싱, 그리고 생성 된 컬렉션과 그다지 큰 것은 아닙니다.
동일한 문제가 float 및 double 모두에 대해 작업하기 위해 동일한 컴플렉스가 필요한 성능 컴퓨팅에서도 발생합니다 (Goldberd의 " 모든 컴퓨터 과학자가 부동 소수점 숫자에 대해 알아야 할 사항 " 문서에 표시된 방법 중 일부 ).
이유있다 Trove를 의 TIntIntHashMap
자바 주위 실행 원이있어 HashMap<Integer,Integer>
데이터의 비슷한 양의 작업.
이제 Trove 컬렉션의 소스 코드는 어떻게 작성됩니까?
물론 소스 코드 계측을 사용하여 :)
반복되는 소스 코드를 생성하기 위해 코드 생성기를 사용하는 더 높은 성능 (기본 Java 라이브러리보다 훨씬 높음)을위한 여러 Java 라이브러리가 있습니다.
우리 모두는 "소스 코드 계측"이 악하고 코드 생성이 쓰레기라는 것을 알고 있지만, 여전히 자신이하는 일을 정말로 아는 사람들 (즉, Trove와 같은 것을 작성하는 사람들)이하는 방식입니다. :)
다음과 같은 큰 경고가 포함 된 소스 코드를 생성 할 가치가 있습니다.
/*
* This .java source file has been auto-generated from the template xxxxx
*
* DO NOT MODIFY THIS FILE FOR IT SHALL GET OVERWRITTEN
*
*/
코드를 반드시 복제해야하는 경우 제공 한 훌륭한 예제를 따르고 변경해야 할 때 쉽게 찾고 수정할 수있는 한 곳에서 해당 코드를 모두 그룹화하십시오. 복제를 문서화하고 더 중요한 것은 복제 이유를 문서화하여 뒤 따르는 모든 사람이 두 가지를 모두 알 수 있도록하십시오.
에서 위키 백과 음주 자신 (DRY)를 반복하지 않거나 복제가 악 (DIE)
In some contexts, the effort required to enforce the DRY philosophy may be greater than the effort to maintain separate copies of the data. In some other contexts, duplicated information is immutable or kept under a control tight enough to make DRY not required.
There is probably no answer or technique to prevent problems like that.
Even fancy pants languages like Haskell have repetitive code (see my post on haskell and serialization)
It seems there are three choices to this problem:
- Use reflection and lose performance
- Use preprocessing like Template Haskell or Caml4p equivalent for your language and live with nastiness
- Or my personal favorite use macros if your language supports it (scheme, and lisp)
I consider the macros different than preprocessing because the macros are usually in the same language that the target is where as preprocessing is a different language.
I think Lisp/Scheme macros would solve many of these problems.
I get that Sun has to document like this for the Java SE library code and maybe other 3rd party library writers do as well.
However, I think it is an utter waste to copy and paste documentation throughout a file like this in code that is only used in house. I know many people will disagree because it will make their in house JavaDocs look less clean. However, the trade off is that is makes their code more clean which, in my opinion, is more important.
Java primitive types screw you, especially when it comes to arrays. If you're specifically asking about code involving primitive types, then I would say just try to avoid them. The Object[] method is sufficient if you use the boxed types.
In general, you need lots of unit tests and there really isn't anything else to be done, other than resorting to reflection. Like you said, it's another subject entirely, but don't be too afraid of reflection. Write the DRYest code you can first, then profile it and determine if the reflection performance hit is really bad enough to warrant writing out and maintaining the extra code.
You could use a code generator to construct variations of the code using a template. In that case, the java source is a product of the generator and the real code is the template.
Given two code fragments that are claimed to be similar, most languages have limited facilities for constructing abstractions that unify the code fragments into a monolith. To abstract when your language can't do it, you have to step outside the language :-{
The most general "abstraction" mechanism is a full macro processor which can apply arbitrary computations to the "macro body" while instantiating it (think Post or string-rewriting system, which is Turing capable). M4 and GPM are quintessential examples. The C preprocessor isn't one of these.
If you have such a macro processor, you can construct an "abstraction" as a macro, and run the macro processor on your "abstracted" source text to produce the actual source code you compile and run.
You can also use more limited versions of the ideas, often called "code generators". These are usually not Turing capable, but in many cases they work well enough. It depends on how sophisticated your "macro instantiation" needs to be. (The reason people are enamored with the C++ template mechanism is ths despite its ugliness, it is Turing capable and so people can do truly ugly but astonishing code generation tasks with it). Another answer here mentions Trove, which is apparantly in the more limited but still very useful category.
Really general macro processors (like M4) manipulate just text; that makes them powerful but they don't handle the structure of programming language well, and it is really awkward to write a generaor in such a mcaro processor that can not only produce code, but optimize the generated result. Most code generators that I encounter are "plug this string into this string template" and so cannot do any optimization of a generated result. If you want generation of arbitrary code and high performance to boot, you need something that is Turing capable but understands the structure of the generated code so it can easily manipulate (e.g., optimize) it).
Such a tool is called a Program Transformation System. Such a tool parses the source text just like a compiler does,and then carries analyses/transformations on it to achieve a desired effect. If you can put markers in the source text of your program (e.g, structured comments or annotations in langauges that have them) directing the program transformaiton tool what to do, then you can use it to carry out such abstraction instantiation, code generation, and/or code optimization. (One poster's suggestion of hooking into the Java compiler is a variation on this idea). Using a general puprose transformation system (such as DMS Software Reengineering Tookit means you can do this for essentially any language.
A lot of this kind of repetition can now be avoided thanks to generics. They're a godsend when writing the same code where only the types change.
Sadly though, I think generic arrays are still not very well supported. For now at least, use containers that allow you to take advantage of generics. Polymorphism is also a useful tool to reduce this kind of code duplication.
To answer your question about how to handle code that absolutely must be duplicated... Tag each instance with easily searchable comments. There are some java preprocessors out there, that add C-style macros. I think I remember netbeans having one.
'Program Tip' 카테고리의 다른 글
Django 및 Bootstrap : 어떤 앱을 권장합니까? (0) | 2020.11.02 |
---|---|
Maven이 아티팩트를 검색하기 위해 원격 저장소로 이동하지 않고 로컬 저장소를 사용하도록 강제하는 방법은 무엇입니까? (0) | 2020.11.02 |
WeakReference로 청취자의 장단점 (0) | 2020.11.01 |
브라우저 간 (피어 투 피어) 연결을 어떻게 만들 수 있습니까? (0) | 2020.11.01 |
구성 / 메이크주기에 include 및 lib 경로를 추가하는 방법은 무엇입니까? (0) | 2020.11.01 |