2024년에 우리는 풍부한 프로젝트를 분석하고 발견한 내용을 블로그에 공유했습니다. 이제 새해 전야입니다. 축제 이야기를 들려줄 시간입니다! 오픈 소스 프로젝트에서 발견된 가장 흥미로운 Java 오류를 수집하여 이제 여러분께 제공합니다!
저희는 PVS-Studio에서 발견된 가장 흥미로운 버그를 게시하는 전통을 오랫동안 지켜왔지만 2020년 이후로 Java 관련 탑이 없습니다! 이번에는 빈티지 루브릭을 부활시켜 보았습니다. 제가 여러분을 위해 10가지가 넘는 재미있는 벌레를 골랐으니 여러분의 손에 포근한 담요와 따뜻한 차 한 잔이 있기를 바랍니다. 순위는 다음과 같습니다.
자신만의 프로그래밍 지혜가 담긴 10가지 재미있는 이야기를 준비하세요. 곧 새해가 다가옵니다 :)
열 번째로 첫 번째 코드 조각이 두 팔 벌려 우리를 환영합니다.
public Builder setPersonalisation(Date date, ....) { .... final OutputStreamWriter out = new OutputStreamWriter(bout, "UTF-8"); final DateFormat format = new SimpleDateFormat("YYYYMMdd"); out.write(format.format(date)); .... }
이 코드의 버그로 인해 다음 해로 더 빠르게 이동할 수 있기 때문에 새해 전야 톱에 포함하지 않을 수 없었습니다. :) 버그가 어디서 유래했는지 추측해 보세요.
SimpleDateFormat 생성자에 전달된 인수를 살펴보겠습니다. 유효한 것 같나요? 이 기사를 작성한 날짜(2024년 10월 12일)와 같은 거의 모든 날짜를 전달하면 코드는 올바른 값인 20241210을 반환합니다.
그러나 2024년 12월 29일이 지나면 대신 20251229가 반환되므로 기발하게 새해를 일찍 맞이하게 됩니다. 그런데 시간여행도 가능해요.
이는 SimpleDateFormat 인수의 Y 문자가 주 번호를 기준으로 연도를 나타내기 때문에 발생합니다. 간단히 말해서, 새해의 최소 4일을 포함하는 한 주가 첫 번째 주로 간주됩니다. 그래서 한 주가 일요일로 시작된다면 3일 일찍 새해를 맞이할 수 있습니다.
이 문제를 해결하려면 대문자 Y를 소문자 y로 바꾸세요. 더 자세히 알고 싶으십니까? 우리는 이 주제에 대해 전체 게시물을 할애했습니다.
이 오류에 대한 PVS-Studio 경고는 다음과 같습니다.
V6122 'Y'(연도) 패턴 사용이 감지되었습니다. 아마도 'y'(연도)를 사용하려는 의도였던 것 같습니다. SkeinParameters.java 246
주 번호의 특성으로 인해 테스트는 이 오류를 찾는 가장 좋은 친구가 아닙니다. 그렇다면 왜 그러한 시사적인 버그가 맨 마지막에 나오는 걸까요? 그 이유는 경고가 Bouncy Castle의 실제 버전이 아니라 테스트 기반에서 나온 것이기 때문입니다. 오래된 소스가 아직 남아 있어서 이 버그가 수정된 지 오래되었습니다. 이것은 과거의 경례이며 다시 시간 여행을 하는 것입니다 :)
아홉 번째에는 GeoServer 분석에서 다음과 같은 경고가 있습니다.
@Test public void testStore() { Properties newProps = dao.toProperties(); // .... Assert.assertEquals(newProps.size(), props.size()); for (Object key : newProps.keySet()) { Object newValue = newProps.get(key); Object oldValue = newProps.get(key); // <= Assert.assertEquals(newValue, oldValue); } }
PVS-Studio 경고는 다음과 같습니다.
V6027 변수 'newValue', 'oldValue'는 동일한 함수 호출을 통해 초기화됩니다. 오류이거나 최적화되지 않은 코드일 수 있습니다. DataAccessRuleDAOTest.java 110, DataAccessRuleDAOTest.java 111
이러한 오류의 흥미로운 점은 무엇입니까? 네 개의 점 뒤에 무엇이 숨겨져 있는지 공개하겠습니다:
public Builder setPersonalisation(Date date, ....) { .... final OutputStreamWriter out = new OutputStreamWriter(bout, "UTF-8"); final DateFormat format = new SimpleDateFormat("YYYYMMdd"); out.write(format.format(date)); .... }
어떤 이유로 코드가 작동하지 않는다는 댓글이 있습니다. 솔직히 처음 봤을 때 웃음이 나더군요.
그런데 댓글이 좀 모호하네요. 비교가 중단되는 동안 실패를 방지하기 위해 의도적으로 테스트를 그런 식으로 작성했을 가능성이 높습니다. 그러나 코드가 10년 넘게 이 상태로 유지되었기 때문에 몇 가지 의문이 제기됩니다. 그 모호함 때문에 더 높게 평가하지 않습니다.
JBullet의 버그를 발에 맞은 버그라고 부를 수 없다면 어떤 버그를 그렇게 부를 수 있는지 모르겠습니다. 기사의 오류는 다음과 같습니다.
@Test public void testStore() { Properties newProps = dao.toProperties(); // .... Assert.assertEquals(newProps.size(), props.size()); for (Object key : newProps.keySet()) { Object newValue = newProps.get(key); Object oldValue = newProps.get(key); // <= Assert.assertEquals(newValue, oldValue); } }
오류가 발생한 위치를 파악하기 위해 PVS-Studio 경고도 필요하지 않다고 생각합니다. 어쨌든 혹시 모르니 다음과 같이 하세요.
V6026 이 값은 이미 'proxy1' 변수에 할당되어 있습니다. HashedOverlappingPairCache.java 233
네, 당황스러울 정도로 단순한 오류입니다. 하지만 그 단순함은 이 영화를 더욱 재미있게 만듭니다. 그럼에도 불구하고 그 나름의 사연이 있습니다.
JBullet 라이브러리는 C/C Bullet 라이브러리의 포트이며 비슷한 기능이 있습니다.
@Test public void testStore() { Properties newProps = dao.toProperties(); // properties equality does not seem to work... Assert.assertEquals(newProps.size(), props.size()); for (Object key : newProps.keySet()) { Object newValue = newProps.get(key); Object oldValue = newProps.get(key); Assert.assertEquals(newValue, oldValue); } }
이 코드가 올바르게 작성되었음을 쉽게 알 수 있습니다. git 비난으로 판단하면 원래 올바르게 작성되었습니다. 코드를 한 언어에서 다른 언어로 포팅할 때 오류가 발생한 것으로 나타났습니다.
풍부한 역사와 결합된 눈에 띄지 않는 소박함으로 인해 저는 이 경고에 8위를 부여합니다. 발에 박힌 버그가 C와 관련된 것으로 밝혀져 재미있게 보셨기를 바랍니다.
물론 다음 경고는 여러 가지 이유로 마음이 따뜻해졌습니다. 다음은 GeoGebra 검사의 코드 조각입니다:
@Override public BroadphasePair findPair(BroadphaseProxy proxy0, BroadphaseProxy proxy1) { BulletStats.gFindPairs++; if (proxy0.getUid() > proxy1.getUid()) { BroadphaseProxy tmp = proxy0; proxy0 = proxy1; proxy1 = proxy0; } .... }
오류를 직접 찾아보세요! 여러분이 엿보지 못하도록 경고와 설명을 스포일러에 숨겼습니다.
V6107 상수 0.7071067811865를 활용하고 있습니다. 결과 값이 정확하지 않을 수 있습니다. Math.sqrt(0.5) 사용을 고려해보세요. DrawAngle.java 303 실제로 0.7071067811865는 마법의 숫자가 아닙니다. 단순히 0.5의 제곱근을 취한 반올림 결과입니다. 하지만 이러한 정밀도 손실이 얼마나 중요한가요? GeoGebra는 수학자들을 위해 맞춤 제작된 소프트웨어로, 추가적인 정밀도가 문제가 되지 않는 것 같습니다. 저는 왜 이 버그를 좋아할까요? 먼저 컨퍼런스에서 참석자들에게 다른 코드 조각에서 유사한 오류를 찾아보라고 요청하는 경우가 많습니다. 버그가 상수에 숨겨져 있을 때 주의 깊게 코드를 분석하는 모습을 지켜보는 것은 언제나 재미있습니다. 둘째, 이것은 Java 분석기에 구현된 첫 번째 진단 규칙입니다. 그래서 상위권에 포함하지 않을 수가 없었어요. 편견을 인지하면서도 7위에 올렸어요 :)답변
먼저 PVS-Studio 경고를 살펴보겠습니다.
DBeaver 검사를 기반으로 한 첫 번째 기사에서 가져온 다음 경고는 즉시 관심을 끌지 못할 수도 있습니다. 다음은 코드 조각입니다.
public Builder setPersonalisation(Date date, ....) { .... final OutputStreamWriter out = new OutputStreamWriter(bout, "UTF-8"); final DateFormat format = new SimpleDateFormat("YYYYMMdd"); out.write(format.format(date)); .... }
PVS-Studio 분석기가 감지한 내용은 다음과 같습니다.
V6082 안전하지 않은 이중 확인 잠금. 필드는 휘발성으로 선언되어야 합니다. TaskImpl.java 59, TaskImpl.java317
이 특별한 경고에는 특별한 내용이 없지만 여전히 매우 흥미롭습니다. 요점은 적용된 Double-Checked Locking 패턴이 작동하지 않는다는 것입니다. 비결은 무엇입니까? 이것은 20년 전에도 관련이 있었습니다 :)
주제에 대해 더 자세히 알아보고 싶다면 전체 기사를 읽어 보시기 바랍니다. 하지만 지금은 간단한 요약을 해드리겠습니다.
이중 확인 잠금 패턴은 다중 스레드 환경에서 지연된 초기화를 구현하는 데 사용됩니다. "무거운" 검사 전에 "경량" 검사는 동기화 블록 없이 실행됩니다. 두 검사를 모두 통과한 경우에만 리소스가 생성됩니다.
그러나 이 접근 방식에서는 객체 생성이 비원자적이며 프로세서와 컴파일러가 작업 순서를 변경할 수 있습니다. 따라서 다른 스레드가 실수로 부분적으로 생성된 개체를 받아 작업을 시작할 수 있으며 이로 인해 잘못된 동작이 발생할 수 있습니다. 이 오류는 거의 발생하지 않으므로 디버깅은 개발자에게 큰 어려움이 될 것입니다.
여기에 반전이 있습니다. 이 패턴은 JDK 5까지 작동하지 않았습니다. JDK 5부터 이전 발생 원칙 덕분에 작업 재정렬의 잠재적인 문제를 해결하기 위해 휘발성 키워드가 도입되었습니다. 분석기는 이 키워드를 추가해야 한다고 경고합니다.
그러나 어쨌든 이 패턴은 피하는 것이 좋습니다. 그 이후로 하드웨어와 JVM 성능은 크게 발전했으며, 동기화 작업은 더 이상 그렇게 느리지 않습니다. 그러나 DCL 패턴을 잘못 구현하는 것은 위에서 설명한 심각한 결과를 초래할 수 있는 일반적인 함정으로 남아 있습니다. 이는 우리 분석기가 여전히 오래된 프로젝트에서 이러한 부주의한 오류를 발견한다는 사실을 확인시켜 줍니다.
5위는 우리가 기사를 쓴 또 다른 DBeaver 경고입니다. 살펴보겠습니다:
@Test public void testStore() { Properties newProps = dao.toProperties(); // .... Assert.assertEquals(newProps.size(), props.size()); for (Object key : newProps.keySet()) { Object newValue = newProps.get(key); Object oldValue = newProps.get(key); // <= Assert.assertEquals(newValue, oldValue); } }
설명은 다음과 같습니다.
V6030 '&' 연산자 오른쪽에 위치한 메소드는 왼쪽 피연산자의 값에 관계없이 호출됩니다. 아마도 '&&'를 사용하는 것이 더 나을 것입니다. ExasolTableColumnManager.java 79, DB2TableColumnManager.java 77
개발자가 논리 &&를 비트 &와 혼동했습니다. 동작은 서로 다릅니다. 표현식의 조건은 비트 AND 이후에 종료되지 않습니다. 단락 평가는 비트 AND에서는 작동하지 않습니다. 따라서 exasolTableBase != null이 false를 반환하더라도 실행 스레드는 exasolTableBase.getClass()에 도달하여 NPE로 이어집니다.
알겠습니다. 오타일 뿐입니다. 다음으로 넘어갈까요? DBeaver에는 이러한 경고가 많이 있습니다. 많이. 대부분은 상대적으로 무해하지만 호기심이 많은 독자를 위해 아래에 몇 가지 예를 남겨두었습니다.
ExasolConnectionManager.java: ExasolDataSource.java:오류가 발생하지 않는 비트 연산 사용
ExasolSecurityPolicy.java:
public Builder setPersonalisation(Date date, ....) {
....
final OutputStreamWriter
out = new OutputStreamWriter(bout, "UTF-8");
final DateFormat
format = new SimpleDateFormat("YYYYMMdd");
out.write(format.format(date));
....
}
@Test
public void testStore() {
Properties newProps = dao.toProperties();
// ....
Assert.assertEquals(newProps.size(), props.size());
for (Object key : newProps.keySet()) {
Object newValue = newProps.get(key);
Object oldValue = newProps.get(key); // <=
Assert.assertEquals(newValue, oldValue);
}
}
@Test
public void testStore() {
Properties newProps = dao.toProperties();
// properties equality does not seem to work...
Assert.assertEquals(newProps.size(), props.size());
for (Object key : newProps.keySet()) {
Object newValue = newProps.get(key);
Object oldValue = newProps.get(key);
Assert.assertEquals(newValue, oldValue);
}
}
저희 팀은 더 자세히 조사한 결과 개발자가 성능을 세밀하게 최적화하려고 시도했을 수도 있다고 가정했습니다. 전체 내용을 보려면 기사를 확인하세요. 여기에 요약을 소개합니다.
핵심은 비트 연산이 분기 예측에 의존하지 않으므로 잠재적으로 논리 연산에 비해 더 빠른 실행이 가능하다는 것입니다.
놀랍게도 자체 벤치마크가 이 주장을 뒷받침합니다.
차트는 각 작업 유형에 필요한 시간을 보여줍니다. 신뢰한다면 비트 연산이 논리 연산보다 40% 더 빠른 것으로 나타납니다.
내가 왜 이 주제를 제기하는 걸까요? 잠재적인 미세 최적화 비용을 강조합니다.
첫째, 개발자가 분기 예측을 발명한 데에는 이유가 있습니다. 포기하기에는 비용이 너무 많이 들기 때문입니다. 따라서 값이 실제 사례에서 관찰될 가능성이 없는 정규 분포를 가지므로 벤치마크가 더 빠르게 작동할 수 있습니다.
둘째, 단락 평가 메커니즘을 포기하면 비용이 상당히 높아질 수 있습니다. 위 스포일러의 세 번째 예를 보면 가장 빠르지 않은 포함 작업이 즉시 중지되지 않고 항상 실행된다는 것을 알 수 있습니다.
셋째, 챕터 시작부터 이러한 오류에 대해 전권을 부여합니다.
전반적으로 미시 최적화 가격에 대한 경고 이야기는 상위 5위권을 열 만큼 매력적이라고 생각합니다.
자동화된 테스트는 다양한 오류에 대한 궁극적인 보호 수단으로 간주되는 경우가 많습니다. 그러나 나는 종종 "누가 테스트를 스스로 테스트하는가?"라고 묻고 싶은 유혹을 받습니다. GeoServer 검사의 또 다른 경고는 이를 다시 한 번 보여줍니다. 다음은 코드 조각입니다.
@Override public BroadphasePair findPair(BroadphaseProxy proxy0, BroadphaseProxy proxy1) { BulletStats.gFindPairs++; if (proxy0.getUid() > proxy1.getUid()) { BroadphaseProxy tmp = proxy0; proxy0 = proxy1; proxy1 = proxy0; } .... }
PVS-Studio 경고:
V6060 'e' 참조는 null에 대해 확인되기 전에 활용되었습니다. ResourceAccessManagerWCSTest.java 186, ResourceAccessManagerWCSTest.java 193
V6060은 종종 중복 코드에 대해 발행되기 때문에 언뜻 보면 이 경고가 분석기에서 가장 흥미로운 경고처럼 보이지 않을 수 있습니다. 그러나 나는 그들의 호소력을 바탕으로 후보를 선택하겠다고 약속했습니다. 그래서 이 사건은 보이는 것보다 훨씬 더 흥미롭습니다.
처음에는 e 변수가 catch 연산자에서 얻어지고 더 이상 변경되지 않고 그대로 유지되므로 결코 null이 아니기 때문에 테스트 논리가 잘못된 것처럼 보일 수 있습니다. 우리는 잘못된 편집을 하고 if(e == nul) 조건의 then 분기를 도달할 수 없는 것으로 제거할 수 있습니다. 그러나 그것은 근본적으로 잘못된 것입니다. 아직 캐치를 파악하셨나요?
핵심은 예외 개체가 포함된 코드의 또 다른 변수인 se에 있습니다. 루프 본문 내에서 변경되는 값이 있습니다. 따라서 조건에 e 대신 se 변수가 있어야 함을 쉽게 짐작할 수 있습니다.
이 오류로 인해 then 분기가 실행되지 않으므로 예외가 없다는 것을 알 수 없습니다. 더 나쁜 것은 변수 이름이 너무 비슷하기 때문에 코드 검토에서 이러한 오류를 발견하는 것이 오히려 어렵습니다.
이 이야기에서 얻을 수 있는 두 가지 지혜는 다음과 같습니다.
이렇게 귀중한 교훈을 전해 준 공로로 이 경고문을 4위로 수여합니다.
상위 3명의 우승자는 NetBeans 점검의 경고에 속합니다. 이전 코드 조각은 상대적으로 작았으므로 긴 코드 조각을 살펴보겠습니다.
public Builder setPersonalisation(Date date, ....) { .... final OutputStreamWriter out = new OutputStreamWriter(bout, "UTF-8"); final DateFormat format = new SimpleDateFormat("YYYYMMdd"); out.write(format.format(date)); .... }
마지막으로 버그를 직접 찾아보세요. 기다리겠습니다...
검색 중이신가요?
좋아요! iDesc.neighbor != null || 표현식에서만 오류를 발견하신 분 iDesc.index == iDesc.index, 안타깝지만 졌네요 :)
물론 문제가 있지만 상위권에 비해 그다지 흥미롭지는 않습니다. 예, 여기에는 두 가지 오류가 있습니다. 제가 당신을 조금 속였습니다. 그런데 조금의 장난도 없는 명절이 어디 있겠습니까? :)
분석기가 여기 i^i 표현식에서 오류를 감지하고 다음 경고를 표시했습니다.
V6001 '^' 연산자의 왼쪽과 오른쪽에 동일한 하위 표현식 'i'가 있습니다. LayoutFeeder.java 3897
두 개의 동일한 값을 갖는 배타적 OR은 항상 0이기 때문에 XOR 연산은 의미가 없습니다. 빠르게 복습하기 위해 XOR의 진리표는 다음과 같습니다.
a | b | a^b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
즉, 피연산자가 다른 경우에만 연산이 true가 됩니다. 값이 동일하므로 모든 비트가 동일합니다.
나는 왜 이 버그를 그토록 좋아했을까? i^i와 거의 동일해 보이는 i^1 작업이 있습니다. 따라서 위에서 올바른 i^1을 이미 확인했기 때문에 코드 검토에서 이 오류를 놓치기가 매우 쉽습니다.
나는 당신에 대해 모르지만, 그 유명한 것을 생각나게 합니다:
public Builder setPersonalisation(Date date, ....) { .... final OutputStreamWriter out = new OutputStreamWriter(bout, "UTF-8"); final DateFormat format = new SimpleDateFormat("YYYYMMdd"); out.write(format.format(date)); .... }
그렇지 않으면 간단한 오타로 지루한 버전을 무시하지 않는 한 코드에 어떻게 추가되었는지 설명하기 어렵습니다. 버그를 발견하셨다면, 격려해 주시거나 댓글로 여러분의 추리 능력을 공유해 주세요 :)
이미 첫 번째 및 세 번째 DBeaver 기사의 오류를 표시했으며 두 번째 기사는 건너뛰었습니다. 정정합니다. 다음은 두 번째 기사의 내용입니다.
PVS-Studio 분석기는 isBinaryContents가 하위 클래스에서 재정의되는 TextWithOpen 클래스의 생성자에서 호출되는 것을 좋아하지 않습니다.
@Test public void testStore() { Properties newProps = dao.toProperties(); // .... Assert.assertEquals(newProps.size(), props.size()); for (Object key : newProps.keySet()) { Object newValue = newProps.get(key); Object oldValue = newProps.get(key); // <= Assert.assertEquals(newValue, oldValue); } }
그럼 어쩌죠? 재정의되었지만 큰 문제는 아닙니다. 코드 냄새처럼 보이지만 중요하지 않습니다. 적어도 나는 예전에는 그렇게 생각했습니다. 저는 이 버그로 인한 어려움을 다룬 기사를 작성했습니다.
TextWithOpen에는 많은 하위 클래스가 있으며 그 중 하나가 TextWithOpenFile입니다. 여기서 메소드는 실제로 재정의되고 false 대신 슈퍼클래스에 없는 필드를 반환합니다.
@Test public void testStore() { Properties newProps = dao.toProperties(); // properties equality does not seem to work... Assert.assertEquals(newProps.size(), props.size()); for (Object key : newProps.keySet()) { Object newValue = newProps.get(key); Object oldValue = newProps.get(key); Assert.assertEquals(newValue, oldValue); } }
아직 의심되시나요? 이 클래스의 생성자는 어떻게 생겼나요?
@Override public BroadphasePair findPair(BroadphaseProxy proxy0, BroadphaseProxy proxy1) { BulletStats.gFindPairs++; if (proxy0.getUid() > proxy1.getUid()) { BroadphaseProxy tmp = proxy0; proxy0 = proxy1; proxy1 = proxy0; } .... }
알았나요? 바이너리 필드는 슈퍼클래스 생성자가 호출된 후에 초기화됩니다. 그러나 하위 클래스 필드를 참조하는 isBinaryContents 메소드에 대한 호출이 있습니다!
PVS-Studio 경고는 다음과 같습니다.
V6052 'TextWithOpen' 상위 클래스 생성자에서 재정의된 'isBinaryContents' 메서드를 호출하면 초기화되지 않은 데이터가 사용될 수 있습니다. 검사 필드: 바이너리. TextWithOpenFile.java(77), TextWithOpen.java 59
꽤 재미있는 사진이네요. 언뜻 보기에 개발자는 유지 관리가 불가능한 코드 스파게티를 피하고 템플릿 메서드 패턴을 통해 표준 OOP를 구현하려고 시도하는 모범 사례를 따르는 것처럼 보였습니다. 하지만 이렇게 단순한 패턴을 구현하더라도 실수를 할 수 있는데, 그런 일이 일어났습니다. 제 생각엔 이런 (잘못된) 단순함의 아름다움이 2위라고 생각합니다.
기뻐하세요! 무대 1위! 경쟁은 치열했지만 선택은 내려져야 했습니다. 많은 고민 끝에 NetBeans 검사에서 경고를 받기로 결정했습니다. 최종 코드 조각을 소개하겠습니다.
public Builder setPersonalisation(Date date, ....) { .... final OutputStreamWriter out = new OutputStreamWriter(bout, "UTF-8"); final DateFormat format = new SimpleDateFormat("YYYYMMdd"); out.write(format.format(date)); .... }
물론 여러분이 직접 실수를 저지르지 않는 한, 그러한 버그를 한 눈에 발견하는 것은 불가능하다고 확신합니다. 기다리게 하지 않겠습니다. PVS-Studio 경고는 다음과 같습니다.
V6009 버퍼 용량은 char 값을 사용하여 '47'로 설정됩니다. 아마도 '/' 기호는 버퍼에 배치되어야 할 것입니다. IgnoreUnignoreCommand.java 107
사실 이 오류는 터무니없이 초보적입니다. StringBuilder 생성자에는 char을 허용하는 오버로드가 없습니다. 그러면 어떤 생성자가 호출됩니까? 개발자는 String을 허용하는 오버로드가 호출될 것이라고 생각한 것 같으며, StringBuilder의 초기 값은 이 슬래시가 될 것입니다.
그러나 암시적 유형 변환이 발생하고 int를 허용하는 유형 생성자가 호출됩니다. 우리의 경우에는 StringBuilder의 초기 크기를 나타냅니다. char을 인수로 전달하면 최종 문자열에 포함되지 않으므로 기능적으로 아무 영향도 미치지 않습니다. 초기 크기를 초과하면 예외나 기타 부작용이 발생하지 않고 저절로 커지기만 합니다.
그런데 잠깐만요, 제가 두 가지 오류를 언급했지요? 두 번째는 어디에 있고, 어떻게 연결되어 있나요? 이를 발견하려면 메소드 본문을 읽고 이 코드의 기능을 파악해야 합니다.
파일이나 디렉터리에 대한 절대 경로를 생성합니다. 코드에 따르면 결과 경로는 다음과 같아야 합니다.
코드가 꽤 정확한 것 같습니다. 그게 문제입니다. 코드는 실제로 예상대로 작동합니다. :) 그러나 문자를 문자열로 바꾸어 오류를 수정하면 올바른 결과 대신 다음과 같은 결과를 얻게 됩니다.
즉, 문자열 끝에 추가 슬래시가 추가됩니다. 위의 코드는 매번 줄의 시작 부분에 새 텍스트를 추가하기 때문에 끝에 있게 됩니다.
그러므로 두 번째 오류는 이 슬래시가 생성자에 전혀 인수로 전달되었다는 것입니다. 그러나 누군가가 확인하지 않고 문자를 문자열로 바꾸기로 결정하면 문제가 발생할 수 있으므로 이러한 오류를 과소평가하지는 않습니다.
이렇게 오류의 첫 번째 위치는 올바르게 작동하는 코드가 됩니다. 새해의 기적, 무엇을 기대하셨나요? :)
제 버그 스토리를 재미있게 읽으셨기를 바랍니다. 특별히 기억에 남는 스토리가 있거나 순위 조정에 대한 제안 사항이 있다면 댓글로 여러분의 생각을 자유롭게 공유해 주세요. 다음에 참고하도록 하겠습니다 :)
다른 언어에 관심이 있다면 여기에서 2024년 주요 C# 버그를 확인하시기 바랍니다. 새로운 내용을 계속 지켜봐 주시기 바랍니다!
이 모든 오류는 PVS-Studio 분석기로 감지되었으며 최신 버전(7.34)이 방금 출시되었습니다! 이 링크를 통해 체험해 보실 수 있습니다.
코드 품질에 대한 새로운 기사를 계속해서 받아보시려면 구독해 주시기 바랍니다.
새해 복 많이 받으세요!
위 내용은 4에서 가장 흥미로운 Java 오류의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!