260616_15_장종민#62
Conversation
자바 상속 학습 과제 - PR 검토 요약📋 과제 개요이 PR은 자바의 상속(Inheritance) 개념을 실습하는 교육용 과제입니다. ✅ 강점1. 상속 개념의 올바른 이해와 적용
2. 메서드 오버라이드의 정확한 구현
3. 매직 넘버를 상수로 추출
4. 캡슐화 원칙 준수
5. 포괄적인 테스트 커버리지
6. 일관된 설계 패턴
🤔 성장을 위한 고민점1. 부모 클래스의 초기화 방식에 대한 질문
2. 메서드 오버라이드 시 다른 매개변수는 어떻게 처리해야 할까요?
3. 상수 정의 위치에 대한 고려
4. 패키지-프라이빗 메서드의 오버라이드
5. super 호출의 타이밍
📝 학습 체크리스트학생의 자기평가에 따르면 다음을 학습했다고 기록했습니다:
다음 학습 제안:
🎯 종합 평가모든 요구사항이 충족되었으며, 코드 품질이 높습니다. 특히 테스트 커버리지의 완성도가 우수합니다. 다음 단계에서는 상속 설계의 우아함과 한계를 더 깊이 이해하는 것을 권장합니다. Walkthrough
ChangesSlime 상속 계층 구현
Wizard 상속 계층 구현
설계 문서 및 학습 자료
Sequence Diagram(s)sequenceDiagram
participant Test as 테스트 코드
participant PoisonSlime
participant Slime
participant Hero
rect rgba(70, 130, 180, 0.5)
Note over Test,Hero: PoisonSlime 공격 흐름
Test->>PoisonSlime: attack(hero)
PoisonSlime->>Slime: super.attack(hero) — ATTACK_DAMAGE(10) 차감
Slime->>Hero: setHp(getHp() - 10)
PoisonSlime->>PoisonSlime: poisonCount > 0 확인
PoisonSlime->>Hero: setHp(getHp() - getHp()/DIVISOR) — 독 피해
PoisonSlime->>PoisonSlime: poisonCount -= 1
end
rect rgba(60, 179, 113, 0.5)
Note over Test,Hero: GreatWizard superHeal 흐름
Test->>PoisonSlime: superHeal(hero)
PoisonSlime->>PoisonSlime: mp < SUPER_HEAL_MP_COST(50) 확인
alt MP 부족
PoisonSlime-->>Test: "마나가 부족합니다" 출력
else MP 충분
PoisonSlime->>Hero: setHp(Hero.MAX_HP)
PoisonSlime->>PoisonSlime: mp -= 50
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
game/src/test/java/com/survivalcoding/WizardTest.java (1)
156-163: ⚡ Quick win
System.out복구는finally로 보장하는 방식이 안전합니다.현재는 중간 예외/실패 시
System.out원복이 누락될 수 있어 테스트 간 간섭 위험이 있습니다. (game/src/test/java/com/survivalcoding/GreatWizardTest.java의 출력 캡처 테스트도 동일 패턴)수정 예시
PrintStream originalOut = System.out; -System.setOut(new PrintStream(outputStream)); - -wizard.heal(hero); - -System.setOut(originalOut); +try { + System.setOut(new PrintStream(outputStream)); + wizard.heal(hero); +} finally { + System.setOut(originalOut); +}Also applies to: 178-185
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@game/src/test/java/com/survivalcoding/WizardTest.java` around lines 156 - 163, The System.out restoration is not guaranteed when exceptions occur because the System.setOut(originalOut) call is not wrapped in a finally block. Refactor the code by creating a try-finally block around the System.out redirection logic in the wizard.heal(hero) test: place the System.setOut(new PrintStream(outputStream)) before the try block, move wizard.heal(hero) into the try block, and move System.setOut(originalOut) into the finally block to ensure restoration regardless of exceptions. Apply this same fix pattern to all other output capture tests in the file that have the same structure, as well as in GreatWizardTest.java which has the same issue.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@game/src/main/java/com/survivalcoding/Slime.java`:
- Around line 26-31: The attack method in the Slime class can reduce the Hero's
HP to negative values, which causes PoisonSlime.attack's poison damage
calculation to potentially increase HP instead of decreasing it. In the attack
method, ensure the Hero's HP never goes below 0 by using Math.max() to enforce a
lower bound of 0 when setting the hero's HP after subtracting ATTACK_DAMAGE.
This guarantees the HP lower limit is maintained at the point of basic attack.
In `@game/src/main/java/com/survivalcoding/Wizard.java`:
- Around line 13-23: The heal method in the Wizard class lacks null validation
for the hero parameter, which causes a NullPointerException when heal is called
with a null argument. Add a null check at the beginning of the heal method to
validate that the hero parameter is not null, and return early with an
appropriate error message if it is. The same null validation pattern must also
be applied to the heal and superHeal methods in the GreatWizard class to prevent
the identical NPE vulnerability across all these public methods.
In `@TIL/2026-06-16-상속.md`:
- Around line 240-244: The superHeal method implementation uses Hero.MAX_HP as
the reference constant, but this inconsistency with how the method contract is
documented elsewhere creates confusion. Review the documentation and other
related code in the PR to determine the correct ownership of the max HP constant
being used in the superHeal method, then update either the constant reference in
the superHeal method implementation or the documentation to ensure both use the
same constant (either Hero.MAX_HP or GreatWizard's max HP constant) consistently
throughout.
- Around line 115-245: The code block contains four public classes (Slime,
PoisonSlime, Wizard, and GreatWizard) in a single file, which violates Java's
compilation rule that allows only one public class per file. Either separate
each public class into its own individual file (Slime.java, PoisonSlime.java,
Wizard.java, GreatWizard.java) or modify the code block to make all but one of
the classes package-private (remove the public modifier) and add a note
clarifying this is pseudocode for reference purposes only.
---
Nitpick comments:
In `@game/src/test/java/com/survivalcoding/WizardTest.java`:
- Around line 156-163: The System.out restoration is not guaranteed when
exceptions occur because the System.setOut(originalOut) call is not wrapped in a
finally block. Refactor the code by creating a try-finally block around the
System.out redirection logic in the wizard.heal(hero) test: place the
System.setOut(new PrintStream(outputStream)) before the try block, move
wizard.heal(hero) into the try block, and move System.setOut(originalOut) into
the finally block to ensure restoration regardless of exceptions. Apply this
same fix pattern to all other output capture tests in the file that have the
same structure, as well as in GreatWizardTest.java which has the same issue.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f6d1cc3a-fa7f-42a8-a747-d769bd96fe63
📒 Files selected for processing (14)
TIL/2026-06-16-상속.mdgame/puml/260616_연습문제3.pumlgame/puml/260616_연습문제4-5.pumlgame/src/main/java/com/survivalcoding/260616_연습문제1game/src/main/java/com/survivalcoding/260616_연습문제2game/src/main/java/com/survivalcoding/GreatWizard.javagame/src/main/java/com/survivalcoding/Hero.javagame/src/main/java/com/survivalcoding/PoisonSlime.javagame/src/main/java/com/survivalcoding/Slime.javagame/src/main/java/com/survivalcoding/Wizard.javagame/src/test/java/com/survivalcoding/GreatWizardTest.javagame/src/test/java/com/survivalcoding/PoisonSlimeTest.javagame/src/test/java/com/survivalcoding/SlimeTest.javagame/src/test/java/com/survivalcoding/WizardTest.java
| void attack(Hero hero) { | ||
| System.out.println("슬라임 " + suffix + "이/가 공격했다"); | ||
| System.out.println(ATTACK_DAMAGE + "의 데미지"); | ||
|
|
||
| hero.setHp(hero.getHp() - ATTACK_DAMAGE); | ||
| } |
There was a problem hiding this comment.
HP 하한 미보장으로 독 추가타가 역으로 HP를 올릴 수 있습니다.
기본 공격에서 HP를 음수로 내릴 수 있어, 이후 PoisonSlime.attack의 독 데미지 계산이 음수가 되는 경로가 생깁니다. 기본 공격 시점에서 HP 하한(0)을 보장해 주세요.
수정 제안
void attack(Hero hero) {
System.out.println("슬라임 " + suffix + "이/가 공격했다");
System.out.println(ATTACK_DAMAGE + "의 데미지");
- hero.setHp(hero.getHp() - ATTACK_DAMAGE);
+ int nextHp = Math.max(0, hero.getHp() - ATTACK_DAMAGE);
+ hero.setHp(nextHp);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| void attack(Hero hero) { | |
| System.out.println("슬라임 " + suffix + "이/가 공격했다"); | |
| System.out.println(ATTACK_DAMAGE + "의 데미지"); | |
| hero.setHp(hero.getHp() - ATTACK_DAMAGE); | |
| } | |
| void attack(Hero hero) { | |
| System.out.println("슬라임 " + suffix + "이/가 공격했다"); | |
| System.out.println(ATTACK_DAMAGE + "의 데미지"); | |
| int nextHp = Math.max(0, hero.getHp() - ATTACK_DAMAGE); | |
| hero.setHp(nextHp); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@game/src/main/java/com/survivalcoding/Slime.java` around lines 26 - 31, The
attack method in the Slime class can reduce the Hero's HP to negative values,
which causes PoisonSlime.attack's poison damage calculation to potentially
increase HP instead of decreasing it. In the attack method, ensure the Hero's HP
never goes below 0 by using Math.max() to enforce a lower bound of 0 when
setting the hero's HP after subtracting ATTACK_DAMAGE. This guarantees the HP
lower limit is maintained at the point of basic attack.
| public void heal(Hero hero) { | ||
| int basePoint = 10; | ||
| int recovPoint = (int) (basePoint * this.wand.getPower()); | ||
| hero.setHp(hero.getHp() + recovPoint); | ||
| if (mp < HEAL_MP_COST) { | ||
| System.out.println("마나가 부족합니다"); | ||
| return; | ||
| } | ||
|
|
||
| hero.setHp(hero.getHp() + HEAL_POINT); | ||
| mp -= HEAL_MP_COST; | ||
|
|
||
| System.out.println("힐을 시전했습니다. 대상 HP: " + hero.getHp()); | ||
| } |
There was a problem hiding this comment.
heal 공개 API에 null 입력 방어가 없어 NPE가 발생할 수 있습니다.
Line 19/22에서 hero를 바로 역참조하므로 heal(null) 호출 시 즉시 실패합니다. 같은 패턴이 game/src/main/java/com/survivalcoding/GreatWizard.java의 heal/superHeal에도 반복됩니다. 메서드 초입에서 명시적으로 검증해 크래시를 예방하는 편이 안전합니다.
수정 예시
public void heal(Hero hero) {
+ if (hero == null) {
+ throw new IllegalArgumentException("대상 hero는 null일 수 없습니다.");
+ }
if (mp < HEAL_MP_COST) {
System.out.println("마나가 부족합니다");
return;
}
hero.setHp(hero.getHp() + HEAL_POINT);
mp -= HEAL_MP_COST;
System.out.println("힐을 시전했습니다. 대상 HP: " + hero.getHp());
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public void heal(Hero hero) { | |
| int basePoint = 10; | |
| int recovPoint = (int) (basePoint * this.wand.getPower()); | |
| hero.setHp(hero.getHp() + recovPoint); | |
| if (mp < HEAL_MP_COST) { | |
| System.out.println("마나가 부족합니다"); | |
| return; | |
| } | |
| hero.setHp(hero.getHp() + HEAL_POINT); | |
| mp -= HEAL_MP_COST; | |
| System.out.println("힐을 시전했습니다. 대상 HP: " + hero.getHp()); | |
| } | |
| public void heal(Hero hero) { | |
| if (hero == null) { | |
| throw new IllegalArgumentException("대상 hero는 null일 수 없습니다."); | |
| } | |
| if (mp < HEAL_MP_COST) { | |
| System.out.println("마나가 부족합니다"); | |
| return; | |
| } | |
| hero.setHp(hero.getHp() + HEAL_POINT); | |
| mp -= HEAL_MP_COST; | |
| System.out.println("힐을 시전했습니다. 대상 HP: " + hero.getHp()); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@game/src/main/java/com/survivalcoding/Wizard.java` around lines 13 - 23, The
heal method in the Wizard class lacks null validation for the hero parameter,
which causes a NullPointerException when heal is called with a null argument.
Add a null check at the beginning of the heal method to validate that the hero
parameter is not null, and return early with an appropriate error message if it
is. The same null validation pattern must also be applied to the heal and
superHeal methods in the GreatWizard class to prevent the identical NPE
vulnerability across all these public methods.
| ```java | ||
| package com.survivalcoding; | ||
|
|
||
| public class Slime { | ||
| private static final int DEFAULT_HP = 50; | ||
| private static final int ATTACK_DAMAGE = 10; | ||
|
|
||
| final String suffix; | ||
| int hp = DEFAULT_HP; | ||
|
|
||
| public Slime(String suffix) { | ||
| this.suffix = suffix; | ||
| } | ||
|
|
||
| public String getSuffix() { | ||
| return suffix; | ||
| } | ||
|
|
||
| public int getHp() { | ||
| return hp; | ||
| } | ||
|
|
||
| public void setHp(int hp) { | ||
| this.hp = hp; | ||
| } | ||
|
|
||
| void attack(Hero hero) { | ||
| System.out.println("슬라임 " + suffix + "이/가 공격했다"); | ||
| System.out.println(ATTACK_DAMAGE + "의 데미지"); | ||
|
|
||
| hero.setHp(hero.getHp() - ATTACK_DAMAGE); | ||
| } | ||
| } | ||
|
|
||
| public class PoisonSlime extends Slime { | ||
| private static final int INITIAL_POISON_COUNT = 5; | ||
| private static final int POISON_DAMAGE_DIVISOR = 5; | ||
|
|
||
| private int poisonCount = INITIAL_POISON_COUNT; | ||
|
|
||
| public PoisonSlime(String suffix) { | ||
| super(suffix); | ||
| } | ||
|
|
||
| @Override | ||
| void attack(Hero hero) { | ||
| super.attack(hero); | ||
| if (poisonCount > 0) { | ||
| System.out.println("추가로, 독 포자를 살포했다!"); | ||
|
|
||
| int poisonDamage = hero.getHp() / POISON_DAMAGE_DIVISOR; | ||
| hero.setHp(hero.getHp() - poisonDamage); | ||
|
|
||
| System.out.println(poisonDamage + "포인트 데미지"); | ||
|
|
||
| poisonCount--; | ||
| } | ||
| } | ||
|
|
||
| public int getPoisonCount() { | ||
| return poisonCount; | ||
| } | ||
| } | ||
| public class Wizard { | ||
| private static final int INITIAL_MP = 100; | ||
| private static final int HEAL_POINT = 20; | ||
| private static final int HEAL_MP_COST = 10; | ||
|
|
||
| private int hp; | ||
| private int mp = INITIAL_MP; | ||
| private String name; | ||
| private Wand wand; | ||
|
|
||
| public void heal(Hero hero) { | ||
| if (mp < HEAL_MP_COST) { | ||
| System.out.println("마나가 부족합니다"); | ||
| return; | ||
| } | ||
|
|
||
| hero.setHp(hero.getHp() + HEAL_POINT); | ||
| mp -= HEAL_MP_COST; | ||
|
|
||
| System.out.println("힐을 시전했습니다. 대상 HP: " + hero.getHp()); | ||
| } | ||
|
|
||
| public int getMp() { | ||
| return mp; | ||
| } | ||
| public void setMp(int mp) { | ||
| if (mp < 0) { | ||
| throw new IllegalArgumentException("마법사의 MP는 0 이상이어야 한다."); | ||
| } | ||
| this.mp = mp; | ||
| } | ||
| } | ||
|
|
||
| public class GreatWizard extends Wizard { | ||
| private static final int INITIAL_MP = 150; | ||
| private static final int HEAL_POINT = 25; | ||
| private static final int HEAL_MP_COST = 5; | ||
| private static final int SUPER_HEAL_MP_COST = 50; | ||
|
|
||
| public GreatWizard() { | ||
| setMp(INITIAL_MP); | ||
| } | ||
|
|
||
| @Override | ||
| public void heal(Hero hero) { | ||
| if (getMp() < HEAL_MP_COST) { | ||
| System.out.println("마나가 부족합니다"); | ||
| return; | ||
| } | ||
|
|
||
| hero.setHp(hero.getHp() + HEAL_POINT); | ||
| setMp(getMp() - HEAL_MP_COST); | ||
|
|
||
| System.out.println("힐을 시전했습니다. 대상 HP: " + hero.getHp()); | ||
| } | ||
|
|
||
| public void superHeal(Hero hero) { | ||
| if (getMp() < SUPER_HEAL_MP_COST) { | ||
| System.out.println("마나가 부족합니다"); | ||
| return; | ||
| } | ||
|
|
||
| hero.setHp(Hero.MAX_HP); | ||
| setMp(getMp() - SUPER_HEAL_MP_COST); | ||
|
|
||
| System.out.println("슈퍼 힐을 시전했습니다. 대상 HP: " + hero.getHp()); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if the file exists and view the specified lines
fd -type f "2026-06-16-상속.md" 2>/dev/null || echo "File not found with fd"
# Also try a direct path check
if [ -f "TIL/2026-06-16-상속.md" ]; then
echo "File found at TIL/2026-06-16-상속.md"
wc -l TIL/2026-06-16-상속.md
echo "---"
sed -n '115,245p' TIL/2026-06-16-상속.md
else
echo "File not found at expected path"
fiRepository: SurvivalCodingCampus/polytech_java2
Length of output: 3036
🌐 Web query:
Java multiple public classes in single file compilation rule
💡 Result:
In Java, a single source file (.java file) can contain multiple top-level class or interface declarations, but it is subject to specific rules regarding the public modifier [1][2][3]. The primary rule is that a compilation unit (a single.java file) can contain at most one public top-level class or interface [1][2]. If a file contains a public class, that class must have the same name as the file (e.g., a public class named MyClass must be defined in a file named MyClass.java) [3][4]. Additional details regarding this rule include: 1. Package-Private Classes: Other classes or interfaces defined in the same file that are not marked as public (i.e., package-private classes) do not have this naming restriction [1][3]. They are accessible only to other code within the same package [3]. 2. Rationale: While the Java Language Specification (JLS) allows host systems to enforce this restriction to simplify the process of locating class files within a file system, it is effectively mandatory in the standard Oracle Java compiler [5][6][7]. 3. Compilation: When multiple classes are defined in one file, compiling that file will generate separate.class files for each class defined [6]. 4. Alternatives: If you need to group multiple classes together, consider using nested classes (inner or static nested classes) instead of multiple top-level classes in a single file, as this is often considered a cleaner design [1][2]. Note: When running Java source code directly (without explicit compilation, available in newer versions of Java), the restriction may behave differently depending on the execution environment, but it remains a standard practice for maintainable, professional Java development [1].
Citations:
- 1: https://stackoverflow.com/questions/2336692/java-multiple-class-declarations-in-one-file
- 2: https://www.delftstack.com/howto/java/multiple-classes-in-a-single-file-java/
- 3: https://www.baeldung.com/java-error-class-x-is-public-declared
- 4: https://stackoverflow.com/questions/13811020/error-class-x-is-public-should-be-declared-in-a-file-named-x-java
- 5: https://docs.oracle.com/en/java/javase/26/docs/specs/jls/jls-7.html
- 6: https://stackoverflow.com/questions/4826685/source-file-cant-have-more-than-one-public-class
- 7: https://stackoverflow.com/questions/1318712/why-is-each-public-class-in-a-separate-file
실습 코드 블록을 파일 단위로 분리해 주세요.
이 예제는 Slime, PoisonSlime, Wizard, GreatWizard 네 개의 public class를 한 코드 블록에 작성했는데, Java 컴파일 규칙상 하나의 파일에는 최대 하나의 public class만 포함될 수 있습니다. 현재 코드를 그대로 붙여넣으면 컴파일 오류가 발생합니다. 각 클래스를 별도 파일로 분리한 예제로 변경하거나, 발췌용 의사 코드임을 명시하기 바랍니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@TIL/2026-06-16-상속.md` around lines 115 - 245, The code block contains four
public classes (Slime, PoisonSlime, Wizard, and GreatWizard) in a single file,
which violates Java's compilation rule that allows only one public class per
file. Either separate each public class into its own individual file
(Slime.java, PoisonSlime.java, Wizard.java, GreatWizard.java) or modify the code
block to make all but one of the classes package-private (remove the public
modifier) and add a note clarifying this is pseudocode for reference purposes
only.
| hero.setHp(Hero.MAX_HP); | ||
| setMp(getMp() - SUPER_HEAL_MP_COST); | ||
|
|
||
| System.out.println("슈퍼 힐을 시전했습니다. 대상 HP: " + hero.getHp()); | ||
| } |
There was a problem hiding this comment.
Hero.MAX_HP 기준을 문서와 구현에서 통일해 주세요.
Line 240에서 Hero.MAX_HP를 참조하지만, 같은 PR의 다른 산출물은 GreatWizard 쪽 최대 체력 상수를 기준으로 설명하고 있습니다. 상수 소유 위치를 하나로 맞춰야 superHeal 계약이 헷갈리지 않습니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@TIL/2026-06-16-상속.md` around lines 240 - 244, The superHeal method
implementation uses Hero.MAX_HP as the reference constant, but this
inconsistency with how the method contract is documented elsewhere creates
confusion. Review the documentation and other related code in the PR to
determine the correct ownership of the max HP constant being used in the
superHeal method, then update either the constant reference in the superHeal
method implementation or the documentation to ensure both use the same constant
(either Hero.MAX_HP or GreatWizard's max HP constant) consistently throughout.
260616_15_장종민
📝 과제 정보
📋 체크리스트
📷 실행 결과 (또는 테스트 결과)
❓질문 및 어려웠던 점
🔄 자체 평가 & 회고